Première question
S'il vous plaît, pourriez-vous m'expliquer comment l'ACL la plus simple pourrait être implémentée dans MVC.
Voici la première approche d'utilisation d'Acl dans Controller ...
<?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!");
...
}
}
?>
C'est une très mauvaise approche, et c'est moins que nous devons ajouter un morceau de code Acl dans la méthode de chaque contrôleur, mais nous n'avons pas besoin de dépendances supplémentaires!
L'approche suivante consiste à créer toutes les méthodes du contrôleur private
et à ajouter du code ACL dans la __call
méthode du contrôleur .
<?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!");
...
}
}
?>
C'est mieux que le code précédent, mais les principaux inconvénients sont ...
- Toutes les méthodes du contrôleur doivent être privées
- Nous devons ajouter du code ACL dans la méthode __call de chaque contrôleur.
L'approche suivante consiste à mettre du code Acl dans le contrôleur parent, mais nous devons toujours garder privées les méthodes du contrôleur enfant.
Quelle est la solution? Et quelle est la meilleure pratique? Où dois-je appeler les fonctions Acl pour décider d'autoriser ou d'interdire l'exécution de la méthode.
Deuxième question
La deuxième question concerne l'obtention du rôle en utilisant Acl. Imaginons que nous ayons des invités, des utilisateurs et des amis des utilisateurs. L'utilisateur a un accès restreint à la visualisation de son profil que seuls les amis peuvent voir. Tous les invités ne peuvent pas voir le profil de cet utilisateur. Alors, voici la logique.
- nous devons nous assurer que la méthode appelée est profil
- nous devons détecter le propriétaire de ce profil
- nous devons détecter si le spectateur est propriétaire de ce profil ou non
- nous devons lire les règles de restriction sur ce profil
- nous devons décider d'exécuter ou de ne pas exécuter la méthode de profil
La question principale est de détecter le propriétaire du profil. Nous pouvons détecter qui est propriétaire du profil uniquement en exécutant la méthode du modèle $ model-> getOwner (), mais Acl n'a pas accès au modèle. Comment pouvons-nous mettre cela en œuvre?
J'espère que mes pensées sont claires. Désolé pour mon anglais.
Je vous remercie.
la source
if($user->hasFriend($other_user) || $other_user->profileIsPublic()) $other_user->renderProfile()
(sinon, affichez "Vous n'avez pas accès au profil de cet utilisateur" ou quelque chose comme ça? Je ne comprends pas.Réponses:
Première partie / réponse (implémentation ACL)
À mon humble avis, la meilleure façon d'aborder cela serait d'utiliser un motif décorateur.En gros, cela signifie que vous prenez votre objet et le placez à l' intérieur d' un autre objet, qui agira comme une coque protectrice. Cela ne vous obligerait PAS à étendre la classe d'origine. Voici un exemple:
Et voici comment vous utilisez ce type de structure:
Comme vous le remarquerez peut-être, cette solution présente plusieurs avantages:
Controller
Mais , il y a aussi un problème majeur avec cette méthode - vous ne pouvez pas vérifier nativement si l'objet sécurisé implémente et l'interface (qui s'applique également à la recherche de méthodes existantes) ou fait partie d'une chaîne d'héritage.
Deuxième partie / réponse (RBAC pour les objets)
Dans ce cas, la principale différence que vous devez reconnaître est que vos objets de domaine (dans l'exemple
Profile
:) contiennent eux-mêmes des détails sur le propriétaire. Cela signifie que pour que vous puissiez vérifier si (et à quel niveau) l'utilisateur y a accès, il vous faudra changer cette ligne:Essentiellement, vous avez deux options:
Fournissez l'ACL avec l'objet en question. Mais vous devez faire attention à ne pas violer la loi de Déméter :
Demandez tous les détails pertinents et ne fournissez à l'ACL que ce dont elle a besoin, ce qui la rendra également un peu plus conviviale pour les tests unitaires:
Quelques vidéos qui pourraient vous aider à créer votre propre implémentation:
Notes d'accompagnement
Vous semblez avoir la compréhension assez courante (et complètement fausse) de ce qu'est le modèle dans MVC. Le modèle n'est pas une classe . Si vous avez une classe nommée
FooBarModel
ou quelque chose qui hérite,AbstractModel
vous le faites mal.Dans MVC approprié, le modèle est une couche, qui contient de nombreuses classes. Une grande partie des classes peut être séparée en deux groupes, en fonction de la responsabilité:
- Logique métier de domaine
(en savoir plus : ici et ici ):
Les instances de ce groupe de classes traitent du calcul des valeurs, vérifient les différentes conditions, implémentent des règles de vente et font tout le reste ce que vous appelleriez la «logique métier». Ils n'ont aucune idée de la manière dont les données sont stockées, où elles sont stockées ou même si le stockage existe en premier lieu.
Les objets métier de domaine ne dépendent pas de la base de données. Lorsque vous créez une facture, la provenance des données n'a pas d'importance. Cela peut provenir de SQL ou d'une API REST distante, ou même d'une capture d'écran d'un document MSWord. La logique métier ne change pas.
- Accès et stockage des données
Les instances créées à partir de ce groupe de classes sont parfois appelées objets d'accès aux données. Structures généralement qui implémentent Data Mapper modèle (ne pas confondre avec les ORM du même nom .. pas de relation). C'est là que se trouveraient vos instructions SQL (ou peut-être votre DomDocument, car vous le stockez en XML).
À côté des deux parties principales, il y a un autre groupe d'instances / classes, qui devrait être mentionné:
- les services
C'est là que vos composants tiers et vos composants tiers entrent en jeu. Par exemple, vous pouvez considérer "l'authentification" comme un service, qui peut être fourni par vous-même ou par un code externe. Un "expéditeur de courrier" serait également un service, qui pourrait relier un objet de domaine à un PHPMailer ou SwiftMailer, ou à votre propre composant d'envoi de courrier.
Une autre source de services est l'abstraction sur les couches d'accès aux domaines et aux données. Ils sont créés pour simplifier le code utilisé par les contrôleurs. Par exemple: la création d'un nouveau compte utilisateur peut nécessiter de travailler avec plusieurs objets de domaine et mappeurs . Mais, en utilisant un service, il n'aura besoin que d'une ou deux lignes dans le contrôleur.
Ce dont vous devez vous souvenir lors de la création de services, c'est que toute la couche est censée être mince . Il n'y a pas de logique métier dans les services. Ils ne sont là que pour jongler avec les objets de domaine, les composants et les mappeurs.
L'une des choses qu'ils ont tous en commun est que les services n'affectent pas la couche View de manière directe et sont tellement autonomes qu'ils peuvent être (et quittent souvent - sont) utilisés en dehors de la structure MVC elle-même. De plus, de telles structures auto-entretenues facilitent beaucoup la migration vers un autre cadre / architecture, en raison du couplage extrêmement faible entre le service et le reste de l'application.
la source
Request
instance (ou un analogue de celle-ci). Le contrôleur extrait uniquement les données de l'Request
instance et les transmet en grande partie aux services appropriés (une partie est également affichée). Les services exécutent les opérations que vous leur avez commandées. Ensuite, lorsque view génère la réponse, il demande des données aux services et, sur la base de ces informations, génère la réponse. Ladite réponse peut être soit du HTML créé à partir de plusieurs modèles, soit simplement un en-tête d'emplacement HTTP. Dépend de l'état défini par le contrôleur.ACL et contrôleurs
Tout d'abord: il s'agit le plus souvent de choses / couches différentes. Lorsque vous critiquez le code de contrôleur exemplaire, il met les deux ensemble - de toute évidence trop serré.
tereško a déjà décrit un moyen de dissocier cela davantage avec le motif de décoration .
Je ferais un pas en arrière pour rechercher le problème initial auquel vous êtes confronté et en discuter un peu ensuite.
D'une part, vous voulez avoir des contrôleurs qui ne font que le travail auquel ils sont commandés (commande ou action, appelons-le commande).
D'autre part, vous voulez pouvoir mettre ACL dans votre application. Le domaine de travail de ces ACL devrait être - si j'ai bien compris votre question - de contrôler l'accès à certaines commandes de vos applications.
Ce type de contrôle d'accès a donc besoin de quelque chose d'autre qui rapproche les deux. Sur la base du contexte dans lequel une commande est exécutée, l'ACL entre en action et des décisions doivent être prises pour savoir si une commande spécifique peut être exécutée ou non par un sujet spécifique (par exemple l'utilisateur).
Résumons à ce point ce que nous avons:
Le composant ACL est ici central: il a besoin de savoir au moins quelque chose sur la commande (pour identifier la commande pour être précis) et il doit être capable d'identifier l'utilisateur. Les utilisateurs sont normalement facilement identifiés par un identifiant unique. Mais souvent dans les applications Web, il y a des utilisateurs qui ne sont pas du tout identifiés, souvent appelés invités, anonymes, tout le monde etc. Pour cet exemple, nous supposons que l'ACL peut consommer un objet utilisateur et encapsuler ces détails. L'objet utilisateur est lié à l'objet de demande d'application et l'ACL peut le consommer.
Qu'en est-il de l'identification d'une commande? Votre interprétation du modèle MVC suggère qu'une commande est composée d'un nom de classe et d'un nom de méthode. Si nous regardons de plus près, il existe même des arguments (paramètres) pour une commande. Il est donc valable de se demander ce qui identifie exactement une commande? Le nom de la classe, le nom de la méthode, le nombre ou les noms d'arguments, même les données à l'intérieur de l'un des arguments ou un mélange de tout cela?
Selon le niveau de détail dont vous avez besoin pour identifier une commande dans votre ACL, cela peut beaucoup varier. Pour l'exemple, gardons-le simplement et spécifions qu'une commande est identifiée par le nom de la classe et le nom de la méthode.
Ainsi, le contexte de l'appartenance de ces trois parties (ACL, Commande et Utilisateur) est maintenant plus clair.
Nous pourrions dire qu'avec un composant ACL imaginaire, nous pouvons déjà faire ce qui suit:
Voyez simplement ce qui se passe ici: en rendant la commande et l'utilisateur identifiables, l'ACL peut faire son travail. Le travail de l'ACL n'est pas lié au travail de l'objet utilisateur et de la commande concrète.
Il ne manque qu'une partie, celle-ci ne peut pas vivre dans l'air. Et ce n'est pas le cas. Vous devez donc localiser l'endroit où le contrôle d'accès doit intervenir. Voyons ce qui se passe dans une application Web standard:
Pour localiser cet endroit, nous savons qu'il doit l'être avant que la commande concrète ne soit exécutée, nous pouvons donc réduire cette liste et n'avons besoin de regarder que dans les endroits (potentiels) suivants:
À un moment donné de votre application, vous savez qu'un utilisateur spécifique a demandé à exécuter une commande concrète. Vous faites déjà une sorte de ACL ici: Si un utilisateur demande une commande qui n'existe pas, vous ne permettez pas à cette commande de s'exécuter. Donc, partout où cela se produit dans votre application, cela peut être un bon endroit pour ajouter les «vrais» contrôles ACL:
La commande a été localisée et nous pouvons créer son identification afin que l'ACL puisse la traiter. Dans le cas où la commande n'est pas autorisée pour un utilisateur, la commande ne sera pas exécutée (action). Peut-être qu'un
CommandNotAllowedResponse
au lieu duCommandNotFoundResponse
pour le cas une demande ne pourrait pas être résolue sur une commande concrète.L'endroit où le mappage d'une HTTPRequest concrète est mappé sur une commande est souvent appelé Routage . Comme le routage a déjà le travail de localiser une commande, pourquoi ne pas l'étendre pour vérifier si la commande est réellement autorisée par ACL? Par exemple , en étendant le
Router
à un routeur en ACL:RouterACL
. Si votre routeur ne connaît pas encore leUser
, alors leRouter
n'est pas le bon endroit, car pour que l'ACL fonctionne non seulement la commande mais aussi l'utilisateur doit être identifié. Donc, cet endroit peut varier, mais je suis sûr que vous pouvez facilement localiser l'endroit que vous devez étendre, car c'est l'endroit qui remplit les exigences de l'utilisateur et de la commande:L'utilisateur est disponible depuis le début, Commandez d'abord avec
Request(Command)
.Donc, au lieu de placer vos vérifications ACL dans l'implémentation concrète de chaque commande, vous la placez avant. Vous n'avez pas besoin de motifs lourds, de magie ou autre, l'ACL fait son travail, l'utilisateur fait son travail et surtout la commande fait son travail: juste la commande, rien d'autre. La commande n'a aucun intérêt à savoir si des rôles lui s'appliquent ou non, si elle est gardée quelque part ou non.
Alors gardez simplement les choses séparées qui n'appartiennent pas les unes aux autres. Utilisez une légère reformulation du principe de responsabilité unique (SRP) : Il ne devrait y avoir qu'une seule raison de changer une commande - parce que la commande a changé. Pas parce que vous introduisez maintenant ACL dans votre application. Pas parce que vous changez l'objet utilisateur. Pas parce que vous migrez d'une interface HTTP / HTML vers une interface SOAP ou de ligne de commande.
L'ACL dans votre cas contrôle l'accès à une commande, pas la commande elle-même.
la source
Une possibilité est d'encapsuler tous vos contrôleurs dans une autre classe qui étend Controller et de lui faire déléguer tous les appels de fonction à l'instance encapsulée après avoir vérifié l'autorisation.
Vous pouvez également le faire plus en amont, dans le répartiteur (si votre application en a effectivement un) et rechercher les autorisations en fonction des URL, au lieu des méthodes de contrôle.
edit : Que vous ayez besoin d'accéder à une base de données, à un serveur LDAP, etc. est orthogonal à la question. Mon point était que vous pouviez implémenter une autorisation basée sur des URL au lieu de méthodes de contrôleur. Celles-ci sont plus robustes car vous ne modifierez généralement pas vos URL (zone URL de type interface publique), mais vous pouvez tout aussi bien modifier les implémentations de vos contrôleurs.
En règle générale, vous disposez d'un ou plusieurs fichiers de configuration dans lesquels vous mappez des modèles d'URL spécifiques à des méthodes d'authentification et des directives d'autorisation spécifiques. Le répartiteur, avant d'envoyer la demande aux contrôleurs, détermine si l'utilisateur est autorisé et abandonne l'envoi s'il ne l'est pas.
la source