Onde as verificações de permissão do usuário devem ocorrer no MVC e por quem?

26

As verificações de permissão do usuário devem ocorrer no modelo ou no controlador? E quem deve lidar com as verificações de permissão, o objeto Usuário ou algum auxiliar do UserManagement?

Onde isso deveria acontecer?

Verificando no controlador:

class MyController {
  void performSomeAction() {
    if (user.hasRightPermissions()) {
      model.someAction();
    }
  }
  ...

A realização de verificações no Controlador ajuda a simplificar as ações dos Modelos, para que possamos manter toda a lógica para os Controladores.

Verificando o modelo:

class MyModel {
  void someAction() {
    if (user.hasRightPermissions()) {
      ...
    }
  }
  ...

Ao colocar as verificações no Modelo, complicamos o Modelo, mas também garantimos que não permitimos acidentalmente que os usuários façam coisas que não deveriam no Controlador.

E por quem?

Depois de nos estabelecermos no local, quem deve fazer as verificações? O usuário?

Class User {
  bool hasPermissions(int permissionMask) {
    ...
  }
  ...

Mas, na verdade, não é responsabilidade do usuário saber o que ele pode acessar, talvez uma aula auxiliar?

Class UserManagement {
  bool hasPermissions(User user, int permissionMask) {
    ...
  }
  ...

Eu sei que é comum fazer apenas uma pergunta, bem, uma pergunta, mas acho que elas podem ser respondidas muito bem juntas.

kba
fonte

Respostas:

20

Como sempre, "depende"

  • as verificações de permissão funcionarão funcionalmente em qualquer lugar que seja conveniente colocá-las,
  • mas se você estiver fazendo uma pergunta técnica, a resposta pode ser 'coloque as verificações no objeto que possui os dados necessários para executar a verificação' (que provavelmente é o controlador).
  • mas se você estiver fazendo uma pergunta filosófica, sugiro uma resposta alternativa: não mostre aos usuários ações que eles não têm permissão para executar .

Portanto, no último caso, você pode ter a permissão de verificação no controlador implementada como uma propriedade booleana e vincular essa propriedade à propriedade Visible do botão ou painel na interface do usuário que controla a ação

como usuário, é frustrante ver botões de ações que não consigo executar; parece que estou sendo deixado de fora da diversão;)

Steven A. Lowe
fonte
Nosso aplicativo implementa o terceiro cenário, com a exceção de que não ocultamos os controles, os desabilitamos. Infelizmente, tudo é feito no code-behind do Winforms, por isso não é realmente relevante para a questão do OP.
Dave Nay
11
"É frustrante ver botões para ações que não posso executar" -> Tente upvote o seu próprio posto :)
Rowan Freeman
5
Não é suficiente simplesmente ocultar botões para ações que o usuário não pode executar; o servidor deve verificar todas as solicitações de permissões. O terceiro item de marcador não é "uma resposta alternativa", é algo a ser feito, além de verificar as permissões no servidor.
Flimm
@Flimm concordou, se as solicitações forem tratadas por um servidor; a pergunta específica era sobre a classe Controller
Steven A. Lowe
7

A segurança é uma preocupação transversal, portanto, precisa ser implementada em várias camadas. A seguir, é apresentado um exemplo para o MVC, mas o conceito se aplica a outras arquiteturas e / ou padrões, basta identificar os pontos de aplicação.

Onde isso deveria acontecer?

As visualizações podem conter elementos da interface do usuário (widgets, botões, menus etc.) que precisam ser exibidos ou não para alguns usuários, com base em suas permissões. Isso pode ser uma responsabilidade do mecanismo de exibição , pois você não deseja que cada exibição lide com isso por conta própria. Dependendo do tipo de elementos que você está autorizando, mova essa responsabilidade para outro local. Por exemplo, pense em um menu no qual alguns itens precisam ser exibidos e outros não. Os itens podem ser implementados como uma lista em algum lugar e filtrar essa lista com base nas permissões e encaminhá-la para a exibição.

Os controladores respondem às solicitações; portanto, se um usuário não tiver permissão para executar uma ação, ele deverá ser verificado antes que a ação seja invocada, movendo a responsabilidade para o invocador da ação, em vez de mantê-la no controlador. Isso tem a vantagem de manter seu controlador limpo e, se algo mudar nas permissões, você não precisará examinar seus controladores para aplicar essas alterações.

Os recursos são exibidos com base nas permissões. Isso normalmente é feito no nível do banco de dados , pois você não deseja extrair tudo do banco de dados e aplicar permissões.

Como você pode ver, dependendo do que você deseja autorizar, existem diferentes locais onde isso deve ser feito. O objetivo é ser o mais discreto possível, para que, quando sua política de segurança for alterada, você possa aplicá-la facilmente, de preferência sem alterar o código do aplicativo. Isso pode não ser válido para aplicativos pequenos, onde o conjunto de permissões é bastante pequeno e não muda com muita frequência. Em aplicativos corporativos, porém, a história é bem diferente.

Quem deve fazer isso?

Claramente não é o modelo. Cada camada deve ter um ponto de aplicação que lida com a autorização. O texto em itálico acima destaca o possível ponto de execução para cada nível.

Dê uma olhada no XACML . Você não precisa implementá-lo como está, mas ele fornecerá algumas orientações que você poderá seguir.

devnull
fonte
Esta é a melhor resposta. Por alguma razão, a primeira e as outras lidam com as diferenças entre controlador e visão, ou visão e modelo, que não é o que o OP está pedindo. Obrigado!
redFur
1

Eu uso o seguinte esquema. Vale dizer que a maioria das verificações de permissões de usuário pode ser dividida em dois casos gerais:

  • acesso do usuário à ação do controlador com base na função do usuário sem verificar os parâmetros, a ação é chamada com,
  • acesso do usuário ao modelo com base em qualquer lógica ou relação entre usuário específico e modelo específico.

O acesso à ação do controlador sem verificação de atributos geralmente é implementado nas estruturas MVC. Isso é simples: você define regras, seus usuários têm papel. Você simplesmente verifica se o usuário tem permissão para procurar ações em suas regras.

O acesso do usuário a determinado modelo deve ser definido no modelo. (O ator é a classe de usuário base. Suponha que possa ser cliente, vendedor ou convidado.)

interface ICheckAccess
{
    public function checkAccess(Actor $actor, $role);
}

class SomeModel implements ICheckAccess
{
    public function checkAccess(Actor $actor, $role)
    {
        // Your permissions logic can be as sophisticated as you want.
    }
}

Colocar essa lógica no modelo traz algum lucro. O método de verificação de acesso pode ser herdado, você não precisa criar nenhuma classe extra, pode usar vantagens gerais de OOP.

Em seguida, para simplificar a verificação de acesso, adotamos algumas premissas que quase sempre são implementadas já por simplicidade e bom estilo:

  • geralmente os controladores estão relacionados a alguma classe de modelo;
  • as ações que são verificadas quanto ao acesso tomam o ID do modelo único como parâmetro;
  • este parâmetro sempre pode ser acessado uniformemente a partir do método da classe do controlador base;
  • A ação é colocada no controlador correspondente ao modelo que a ação do id executa.

Com essas suposições, as ações que usam o ID do modelo podem ser associadas a uma instância específica do modelo. De fato, a maioria das ações pode ser facilmente transformada e movida para se ajustar às suposições mencionadas acima.

Então, alguma classe de controlador abstrata de base deve ser definida e herdada.

abstract class ModelController
{
    // Retrieve model from database using id from action parameter.
    public abstract function loadModel($id);

    // Returns rules for user role to pass to SomeModel::checkAccess()
    // Something like array('view' => 'viewer', 'delete' => 'owner', 'update' => 'owner')
    public abstract function modelRules();

    public abstract fucntion getIdParameter();

    public function filterModelAccess()
    {
        $id = $this->getIdParameter();
        if(!$this->checkModelAccess($id))
            throw new HttpException(403);
    }

    public function checkModelAccess($id)
    {
        $model = $this->loadModel($id);
        $actor = My::app()->getActor();
        $rules = $this->modelRules();
        $role = $rules[My::app()->getActionName()];
        return $model->chechAccess($actor, $role);
    }
}

Você pode chamar o método SomeController :: checkModelAccess ($ id) quando criar seus menus e decidir se deseja mostrar algum link.

George Sovetov
fonte
Sinto muito pelo PHP.
George Sovetov 22/02
1

No modelo e na vista

Na exibição - porque a interface do usuário não deve mostrar os elementos da interface do usuário restritos ao usuário atual

(por exemplo, o botão "Excluir" deve ser exibido para pessoas com permissões apropriadas)

No modelo - porque seu aplicativo provavelmente tem algum tipo de API, certo? A API também deve verificar as permissões e provavelmente reutiliza o modelo.

(por exemplo, você tem o botão "Excluir" na interface do usuário e o método da API "http: / server / API / DeleteEntry / 123" ao mesmo tempo

jitbit
fonte
Por que você escolheu o modelo em vez do controlador?
Flimm
não sei por que visualizar, modelar e não no controlador, onde na maioria das vezes isso é feito.
VP.
@VP o controlador não tem poder para mostrar / ocultar elementos da interface (que não passar um bool-var para a exibição)
Jitbit
Eu não sei, em todos os lugares normalmente é feito na camada do controlador, por isso fiquei curioso.
VP.
0

MVC é um padrão de apresentação. Como tal, a visão e o controlador devem ter apenas responsabilidades em relação à apresentação. Algumas permissões se aplicam à apresentação, como um modo especialista, recursos experimentais da interface do usuário ou designs diferentes. Esses podem ser manipulados pelo controlador MVC.

Muitos outros tipos de permissões são relevantes em várias camadas do aplicativo. Por exemplo, se você deseja ter usuários que podem apenas visualizar dados e não alterar coisas:

  • a camada de apresentação precisa ocultar os recursos de edição
  • Se um recurso de edição é chamado de qualquer maneira, isso pode / deve ser detectado (pelas partes específicas do aplicativo da camada de negócios, não pela parte específica do domínio - TrainEditor, não Train) e provavelmente causa uma exceção
  • A camada de acesso a dados também pode verificar se há gravações, mas se há tipos mais complexos de permissões que exigem rapidamente muito conhecimento da camada de negócios para ser uma boa idéia.

Há alguma duplicação nessa abordagem. Porém, como a apresentação é geralmente volátil, pode-se defender a verificação de permissão na parte geralmente mais estável do aplicativo, mesmo que isso signifique algumas verificações redundantes, caso a camada de apresentação funcione como pretendido.

Patrick
fonte