Primeira pergunta
Por favor, você poderia me explicar como o ACL mais simples pode ser implementado no MVC.
Aqui está a primeira abordagem de uso de Acl no controlador ...
<?php
class MyController extends Controller {
public function myMethod() {
//It is just abstract code
$acl = new Acl();
$acl->setController('MyController');
$acl->setMethod('myMethod');
$acl->getRole();
if (!$acl->allowed()) die("You're not allowed to do it!");
...
}
}
?>
É uma abordagem muito ruim, e o menos é que temos que adicionar parte do código Acl em cada método do controlador, mas não precisamos de nenhuma dependência adicional!
A próxima abordagem é criar todos os métodos do controlador private
e adicionar o código ACL ao __call
método do controlador .
<?php
class MyController extends Controller {
private function myMethod() {
...
}
public function __call($name, $params) {
//It is just abstract code
$acl = new Acl();
$acl->setController(__CLASS__);
$acl->setMethod($name);
$acl->getRole();
if (!$acl->allowed()) die("You're not allowed to do it!");
...
}
}
?>
É melhor do que o código anterior, mas os principais pontos negativos são ...
- Todos os métodos do controlador devem ser privados
- Temos que adicionar o código ACL no método __call de cada controlador.
A próxima abordagem é colocar o código Acl no controlador pai, mas ainda precisamos manter todos os métodos do controlador filho privados.
Qual é a solução? E qual é a melhor prática? Onde devo chamar funções Acl para decidir permitir ou proibir o método a ser executado.
Segunda questão
A segunda pergunta é sobre como obter o papel usando Acl. Vamos imaginar que temos convidados, usuários e amigos de usuários. O usuário restringiu o acesso à visualização de seu perfil, de forma que apenas amigos podem visualizá-lo. Todos os convidados não podem ver o perfil deste usuário. Então, aqui está a lógica ..
- temos que garantir que o método que está sendo chamado é o perfil
- temos que detectar o dono deste perfil
- temos que detectar se o visualizador é o proprietário deste perfil ou não
- temos que ler as regras de restrição sobre este perfil
- temos que decidir executar ou não executar o método de perfil
A principal questão é sobre como detectar o proprietário do perfil. Podemos detectar quem é o dono do perfil apenas executando o método $ model-> getOwner () do modelo, mas Acl não tem acesso ao modelo. Como podemos implementar isso?
Espero que meus pensamentos estejam claros. Desculpe pelo meu Inglês.
Obrigado.
fonte
if($user->hasFriend($other_user) || $other_user->profileIsPublic()) $other_user->renderProfile()
(senão, exibir "Você não tem acesso ao perfil deste usuário" ou algo parecido? Não entendi.Respostas:
Primeira parte / resposta (implementação ACL)
Na minha humilde opinião, a melhor maneira de abordar isso seria usar o padrão decorator . Basicamente, isso significa que você pega seu objeto e o coloca dentro de outro objeto, que funcionará como uma concha de proteção. Isso NÃO exigiria que você estendesse a aula original. Aqui está um exemplo:
E é assim que você usa esse tipo de estrutura:
Como você pode notar, esta solução tem várias vantagens:
Controller
Mas , há um grande problema com esse método também - você não pode verificar nativamente se o objeto seguro implementa e interface (o que também se aplica à pesquisa de métodos existentes) ou faz parte de alguma cadeia de herança.
Segunda parte / resposta (RBAC para objetos)
Nesse caso, a principal diferença que você deve reconhecer é que seus Objetos de Domínio (por exemplo
Profile
:) contêm detalhes sobre o proprietário. Isso significa que para você verificar se (e em que nível) o usuário tem acesso a ele, será necessário alterar esta linha:Essencialmente, você tem duas opções:
Forneça à ACL o objeto em questão. Mas você deve ter cuidado para não violar a Lei de Deméter :
Solicite todos os detalhes relevantes e forneça à ACL apenas o que ela precisa, o que também a tornará um pouco mais amigável para testes de unidade:
Alguns vídeos que podem ajudá-lo a criar sua própria implementação:
Notas laterais
Você parece ter um entendimento bastante comum (e completamente errado) do que é o Modelo em MVC. O modelo não é uma classe . Se você tem uma classe nomeada
FooBarModel
ou algo que herdaAbstractModel
, você está fazendo isso errado.No MVC adequado, o modelo é uma camada, que contém muitas classes. Grande parte das turmas podem ser separadas em dois grupos, com base na responsabilidade:
- Domínio Business Logic
( leia mais : aqui e aqui ):
As instâncias desse grupo de classes tratam do cálculo de valores, verificam as diferentes condições, implementam regras de vendas e fazem todo o resto o que você chamaria de "lógica de negócios". Eles não têm ideia de como os dados são armazenados, onde estão armazenados ou mesmo se existe armazenamento em primeiro lugar.
O objeto Domain Business não depende do banco de dados. Quando você está criando uma fatura, não importa de onde vêm os dados. Pode ser de SQL ou de uma API REST remota, ou até mesmo uma captura de tela de um documento MSWord. A lógica de negócios não muda.
- Acesso e armazenamento de dados
As instâncias feitas a partir desse grupo de classes às vezes são chamadas de Objetos de Acesso a Dados. Normalmente estruturas que implementam o padrão Data Mapper (não confunda com ORMs de mesmo nome .. sem relação). É aqui que estariam suas instruções SQL (ou talvez seu DomDocument, porque você o armazena em XML).
Ao lado das duas partes principais, existe mais um grupo de instâncias / classes, que deve ser mencionado:
- Serviços
É aqui que entram em jogo seus componentes e de terceiros. Por exemplo, você pode pensar em "autenticação" como um serviço, que pode ser fornecido por você ou algum código externo. Além disso, "remetente de correio" seria um serviço, que poderia unir algum objeto de domínio com um PHPMailer ou SwiftMailer, ou seu próprio componente de remetente de correio.
Outra fonte de serviços é a abstração em camadas de domínio e acesso a dados. Eles são criados para simplificar o código usado pelos controladores. Por exemplo: a criação de uma nova conta de usuário pode exigir o trabalho com vários objetos de domínio e mapeadores . Mas, ao usar um serviço, será necessário apenas uma ou duas linhas no controlador.
O que você precisa lembrar ao fazer serviços é que toda a camada deve ser fina . Não há lógica de negócios em serviços. Eles estão lá apenas para manipular objetos de domínio, componentes e mapeadores.
Uma das coisas que todos eles têm em comum é que os serviços não afetam a camada View de nenhuma maneira direta e são autônomos a tal ponto que podem ser (e encerrar com frequência - são) usados fora da própria estrutura MVC. Além disso, essas estruturas autossustentáveis tornam a migração para uma estrutura / arquitetura diferente muito mais fácil, devido ao acoplamento extremamente baixo entre o serviço e o restante do aplicativo.
fonte
Request
instância (ou algum análogo dela). O controlador apenas extrai dados daRequest
instância e passa a maior parte deles para os serviços adequados (alguns deles também vão para visualização). Os serviços executam operações que você os comandou a fazer. Então, quando o view está gerando a resposta, ele solicita dados dos serviços e, com base nessas informações, gera a resposta. A referida resposta pode ser HTML feita a partir de vários modelos ou apenas um cabeçalho de localização HTTP. Depende do estado definido pelo controlador.ACL e controladores
Em primeiro lugar: na maioria das vezes, trata-se de coisas / camadas diferentes. Conforme você critica o código do controlador exemplar, ele coloca os dois juntos - obviamente muito apertados.
tereško já esboçou uma maneira de desacoplar isso mais com o padrão do decorador.
Eu daria um passo para trás primeiro para procurar o problema original que você está enfrentando e discutiria um pouco depois.
Por um lado, você quer ter controladores que apenas façam o trabalho para o qual são comandados (comando ou ação, vamos chamá-lo de comando).
Por outro lado, você deseja poder colocar ACL em seu aplicativo. O campo de trabalho dessas ACLs deve ser - se bem entendi sua pergunta - controlar o acesso a determinados comandos de seus aplicativos.
Esse tipo de controle de acesso, portanto, precisa de algo mais que reúna esses dois. Com base no contexto em que um comando é executado, o ACL entra em ação e decisões precisam ser tomadas se um comando específico pode ou não ser executado por um sujeito específico (por exemplo, o usuário).
Vamos resumir até este ponto o que temos:
O componente ACL é central aqui: ele precisa saber pelo menos algo sobre o comando (para identificar o comando para ser preciso) e precisa ser capaz de identificar o usuário. Os usuários normalmente são facilmente identificados por um ID exclusivo. Mas muitas vezes em aplicativos da web há usuários que não são identificados de forma alguma, geralmente chamados de convidados, anônimos, todos, etc. Para este exemplo, assumimos que a ACL pode consumir um objeto de usuário e encapsular esses detalhes. O objeto de usuário está vinculado ao objeto de solicitação do aplicativo e a ACL pode consumi-lo.
Que tal identificar um comando? Sua interpretação do padrão MVC sugere que um comando é composto de um nome de classe e um nome de método. Se olharmos mais de perto, existem até argumentos (parâmetros) para um comando. Portanto, é válido perguntar o que exatamente identifica um comando? O nome da classe, o nome do método, o número ou nomes dos argumentos, até mesmo os dados dentro de qualquer um dos argumentos ou uma mistura de tudo isso?
Dependendo do nível de detalhe que você precisa para identificar um comando em seu ACL, isso pode variar muito. Para o exemplo, vamos mantê-lo simples e especificar que um comando seja identificado pelo nome da classe e pelo nome do método.
Portanto, o contexto de como essas três partes (ACL, Comando e Usuário) pertencem umas às outras está agora mais claro.
Poderíamos dizer, com um componente ACL imaginário, já podemos fazer o seguinte:
Veja o que está acontecendo aqui: ao tornar o comando e o usuário identificáveis, a ACL pode fazer seu trabalho. O trabalho da ACL não está relacionado ao trabalho do objeto de usuário e do comando concreto.
Só falta uma parte, isso não pode viver no ar. E isso não acontece. Portanto, você precisa localizar o local onde o controle de acesso precisa ser acionado. Vamos dar uma olhada no que acontece em um aplicativo da web padrão:
Para localizar esse lugar, sabemos que deve ser antes que o comando concreto seja executado, portanto, podemos reduzir essa lista e só precisamos olhar para os seguintes locais (potenciais):
Em algum ponto de seu aplicativo, você sabe que um usuário específico solicitou a execução de um comando concreto. Você já faz algum tipo de ACL aqui: Se um usuário solicitar um comando que não existe, você não permite que esse comando seja executado. Então, onde quer que isso aconteça em seu aplicativo, pode ser um bom lugar para adicionar as verificações de ACL "reais":
O comando foi localizado e podemos criar a identificação dele para que a ACL possa lidar com ele. Caso o comando não seja permitido para um usuário, o comando não será executado (ação). Talvez um, em
CommandNotAllowedResponse
vez deCommandNotFoundResponse
para o caso, uma solicitação não pudesse ser resolvida em um comando concreto.O local onde o mapeamento de um HTTPRequest concreto é mapeado em um comando costuma ser chamado de Roteamento . Como o Routing já tem o trabalho de localizar um comando, por que não estendê-lo para verificar se o comando é realmente permitido por ACL? Por exemplo, alargando o
Router
a um roteador ciente ACL:RouterACL
. Se o seu roteador ainda não conhece oUser
, entãoRouter
não é o lugar certo, pois para o ACL funcionar não só o comando, mas também o usuário deve estar identificado. Portanto, este local pode variar, mas tenho certeza de que você pode localizar facilmente o local que precisa estender, porque é o local que atende aos requisitos de usuário e comando:O usuário está disponível desde o início, Comando primeiro com
Request(Command)
.Portanto, em vez de colocar suas verificações de ACL dentro da implementação concreta de cada comando, você o coloca antes dele. Você não precisa de nenhum padrão pesado, mágica ou qualquer outra coisa, o ACL faz o seu trabalho, o usuário faz o seu trabalho e especialmente o comando faz o seu trabalho: apenas o comando, nada mais. O comando não tem interesse em saber se as funções se aplicam ou não a ele, se está guardado em algum lugar ou não.
Portanto, mantenha separadas as coisas que não pertencem uma à outra. Use uma pequena reformulação do Princípio de Responsabilidade Única (SRP) : Deve haver apenas um motivo para alterar um comando - porque o comando foi alterado. Não porque agora você introduz ACL'ing em seu aplicativo. Não porque você troca o objeto Usuário. Não porque você migra de uma interface HTTP / HTML para um SOAP ou interface de linha de comando.
A ACL no seu caso controla o acesso a um comando, não o comando em si.
fonte
Uma possibilidade é agrupar todos os seus controladores em outra classe que estenda Controller e fazer com que ele delegue todas as chamadas de função à instância agrupada após verificar a autorização.
Você também pode fazer mais upstream, no dispatcher (se seu aplicativo realmente tiver um) e consultar as permissões com base nas URLs, em vez de métodos de controle.
editar : se você precisa acessar um banco de dados, um servidor LDAP, etc. é ortogonal à questão. Meu ponto é que você pode implementar uma autorização baseada em URLs em vez de métodos de controlador. Eles são mais robustos porque você normalmente não mudará seus URLs (URLs como área de interface pública), mas você também pode mudar as implementações de seus controladores.
Normalmente, você tem um ou vários arquivos de configuração nos quais mapeia padrões de URL específicos para métodos de autenticação e diretivas de autorização específicos. O despachante, antes de despachar a solicitação para os controladores, determina se o usuário está autorizado e aborta o despacho se não estiver.
fonte