Où les vérifications des autorisations des utilisateurs doivent-elles avoir lieu dans MVC et par qui?

26

Les vérifications des autorisations des utilisateurs doivent-elles avoir lieu dans le modèle ou le contrôleur? Et qui devrait gérer les vérifications des autorisations, l'objet utilisateur ou un assistant de gestion des utilisateurs?

Où cela devrait-il arriver?

Vérification dans le contrôleur:

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

Avoir les vérifications dans le contrôleur aide à rendre les modèles des actions simples, afin que nous puissions garder toute la logique pour les contrôleurs.

Vérification du modèle:

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

En mettant les vérifications dans le modèle, nous compliquons le modèle, mais nous veillons également à ne pas autoriser accidentellement les utilisateurs à faire des choses qu'ils ne sont pas censés faire dans le contrôleur.

Et par qui?

Une fois que nous nous sommes installés, qui devrait faire les vérifications? L'utilisateur?

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

Mais ce n'est pas vraiment la responsabilité de l'utilisateur de savoir ce qu'il peut accéder, alors peut-être une classe d'aide?

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

Je sais qu'il est courant de poser juste une seule question dans, eh bien, une question, mais je pense qu'on peut y répondre bien ensemble.

kba
la source

Réponses:

20

Comme d'habitude, "ça dépend"

  • les contrôles d'autorisation seront fonctionnellement travailler partout où il est commode de les mettre,
  • mais si vous posez une question technique, la réponse peut être «mettez les vérifications dans l'objet qui possède les données requises pour effectuer la vérification» (qui est probablement le contrôleur).
  • mais si vous posez une question philosophique, je suggère une autre réponse: ne montrez pas aux utilisateurs les actions qu'ils ne sont pas autorisés à effectuer .

Ainsi, dans ce dernier cas, la vérification des autorisations dans le contrôleur peut être implémentée en tant que propriété booléenne et lier cette propriété à la propriété Visible du bouton ou du panneau dans l'interface utilisateur qui contrôle l'action.

en tant qu'utilisateur, il est frustrant de voir des boutons pour des actions que je ne peux pas effectuer; j'ai l'impression d'être laissé de côté;)

Steven A. Lowe
la source
Notre application implémente le troisième scénario à l'exception que nous ne masquons pas les contrôles, nous les désactivons. Malheureusement, tout est fait dans le code-code de Winforms, donc ce n'est pas vraiment pertinent pour la question OP.
Dave Nay
11
"c'est frustrant de voir des boutons pour des actions que je ne peux pas effectuer" -> Essayez de voter pour votre propre article :)
Rowan Freeman
5
Il ne suffit pas de masquer simplement les boutons pour les actions que l'utilisateur ne peut pas effectuer, le serveur doit vérifier chaque demande d'autorisation. Le troisième élément de puce n'est pas "une autre réponse", c'est quelque chose à faire en plus de vérifier les autorisations côté serveur.
Flimm
@Flimm a accepté, si les demandes sont traitées par un serveur; la question spécifique concernait la classe Controller
Steven A. Lowe
7

La sécurité est une préoccupation transversale, elle doit donc être mise en œuvre en plusieurs couches. Ce qui suit est un exemple pour MVC mais le concept s'applique à d'autres architectures et / ou modèles, il vous suffit d'identifier les points d'application.

Où cela devrait-il arriver?

Les vues peuvent contenir des éléments d'interface utilisateur (widgets, boutons, menus, etc.) qui doivent être affichés ou non pour certains utilisateurs, en fonction de leurs autorisations. Cela pourrait être une responsabilité du moteur de vue , car vous ne voulez pas que chaque vue gère cela seule. Selon le type d'éléments que vous accordez, vous déplacez à froid cette responsabilité dans un autre endroit. Par exemple, pensez à un menu dans lequel certains éléments doivent être affichés et d'autres non. Les éléments peuvent être implémentés sous forme de liste quelque part et filtrer cette liste en fonction des autorisations, puis la transmettre à la vue.

Les contrôleurs répondent aux demandes, donc si un utilisateur n'a pas la permission d'exécuter une action, elle doit être vérifiée avant que l'action soit invoquée, déplaçant la responsabilité vers le demandeur de l' action au lieu de la conserver dans le contrôleur. Cela a l'avantage de garder votre contrôleur propre et si quelque chose change dans les autorisations, vous n'avez pas à passer au crible vos contrôleurs pour appliquer ces changements.

Les ressources sont affichées en fonction des autorisations. Cela se fait normalement au niveau de la base de données , car vous ne voulez pas tout extraire de la base de données, puis appliquer des autorisations.

Comme vous pouvez le voir, selon ce que vous souhaitez autoriser, il existe différents endroits où cela doit être fait. Le but est d'être aussi discret que possible, de sorte que lorsque votre politique de sécurité change, vous pouvez facilement l'appliquer, de préférence sans altérer le code de votre application. Cela peut ne pas être valable pour les petites applications, où l'ensemble d'autorisations est assez petit et ne change pas très souvent. Cependant, dans les applications d'entreprise, l'histoire est assez différente.

Qui devrait le faire?

Clairement pas le modèle. Chaque couche doit avoir un point d'application qui gère l'autorisation. Le texte en italique ci-dessus met en évidence le point d'application possible pour chaque niveau.

Jetez un oeil à XACML . Vous n'êtes pas obligé de l'implémenter tel quel, mais cela vous donnera quelques indications que vous pourriez suivre.

devnull
la source
C'est la meilleure réponse. Pour une raison quelconque, celui du haut et d'autres traitent des différences entre le contrôleur et la vue, ou la vue et le modèle, ce qui n'est pas ce que OP demande. Merci!
redFur
1

J'utilise le schéma suivant. Il convient de dire que la plupart des vérifications des autorisations des utilisateurs peuvent être divisées en deux cas généraux:

  • l'accès de l'utilisateur à l'action du contrôleur basé sur le rôle de l'utilisateur sans vérifier l'action des paramètres est appelé avec,
  • accès utilisateur au modèle basé sur une logique ou des relations entre un utilisateur particulier et un modèle particulier.

L'accès à l'action du contrôleur sans vérification des attributs est généralement implémenté dans les frameworks MVC. C'est simple du tout: vous définissez des règles, vos utilisateurs ont un rôle. Vous vérifiez simplement que l'utilisateur est autorisé à effectuer une recherche de son rôle dans les règles.

L'accès des utilisateurs à un modèle particulier doit être défini dans le modèle. (L'acteur est une classe d'utilisateurs de base. Supposons qu'il puisse s'agir d'un client, d'un vendeur ou d'un invité.)

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.
    }
}

Placer cette logique dans le modèle apporte un certain profit. La méthode de contrôle d'accès peut être héritée, vous n'avez pas besoin de créer de classes supplémentaires, vous pouvez utiliser les avantages généraux de la POO.

Ensuite, pour simplifier la vérification d'accès, nous prenons certaines hypothèses qui sont presque toujours déjà implémentées pour la simplicité et le bon style:

  • les contrôleurs sont généralement liés à une classe de modèle;
  • les actions dont l'accès est vérifié prennent un identifiant de modèle unique comme paramètre;
  • ce paramètre est toujours accessible de manière uniforme à partir de la méthode de la classe de contrôleur de base;
  • l'action est placée dans le contrôleur correspondant au modèle que l'action id prend.

Avec ces hypothèses, les actions qui utilisent l'ID de modèle peuvent être associées à une instance de modèle particulière. En fait, la plupart des actions peuvent facilement être transformées et déplacées pour correspondre aux hypothèses énoncées ci-dessus.

Ensuite, une classe de contrôleur abstrait de base doit être définie et héritée.

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);
    }
}

Vous pouvez appeler la méthode SomeController :: checkModelAccess ($ id) lorsque vous construisez vos menus et décidez d'afficher ou non un lien.

George Sovetov
la source
Je suis désolé pour PHP.
George Sovetov
1

Dans le modèle et la vue

Dans la vue - car l'interface utilisateur ne doit pas afficher les éléments d'interface utilisateur qui sont limités à l'utilisateur actuel

(comme, par exemple, le bouton "Supprimer" devrait être affiché aux personnes disposant des autorisations appropriées)

Dans le modèle - parce que votre application a probablement une sorte d'API, non? L'API doit également vérifier les autorisations et probablement réutiliser le modèle.

(comme, disons, vous avez le bouton "Supprimer" dans l'interface utilisateur et la méthode API "http: / server / API / DeleteEntry / 123" en même temps

jitbit
la source
Pourquoi avez-vous choisi le modèle plutôt que le contrôleur?
Flimm
Je ne sais pas pourquoi voir, modéliser et non dans le contrôleur, où la plupart du temps cela se fait.
VP.
@VP, le contrôleur n'a pas le pouvoir d'afficher / masquer les éléments de l'interface utilisateur (autre que passer un bool-var À LA VUE)
jitbit
Je ne sais pas, partout se fait normalement dans la couche contrôleur, c'est pourquoi j'étais curieux.
VP.
0

MVC est un modèle de présentation. En tant que tel, la vue et le contrôleur ne devraient avoir que des responsabilités concernant la présentation. Certaines autorisations s'appliquent à la présentation, comme un mode expert, des fonctionnalités d'interface utilisateur expérimentales ou différentes conceptions. Celles-ci peuvent être gérées par le contrôleur MVC.

De nombreux autres types d'autorisations s'appliquent à plusieurs couches de l'application. Par exemple, si vous souhaitez avoir des utilisateurs qui ne peuvent voir que les données et ne pas changer les choses:

  • la couche de présentation doit masquer les fonctionnalités d'édition
  • Si une fonctionnalité d'édition est appelée de toute façon, cela pourrait / devrait être détecté (par les parties spécifiques à l'application de la couche métier, pas par la partie spécifique au domaine - TrainEditor, pas Train) et provoquer probablement une exception
  • La couche d'accès aux données peut également vérifier les écritures, mais pour les types d'autorisations plus complexes qui nécessitent rapidement trop de connaissances de la couche métier pour être une bonne idée.

Il y a une certaine duplication dans cette approche. Mais comme la présentation est généralement volatile, on peut faire un bon argument pour vérifier l'autorisation dans la partie généralement plus stable de l'application, même si cela signifie des vérifications redondantes au cas où la couche de présentation fonctionne comme prévu.

Patrick
la source