Stockage des éléments de menu avec des autorisations utilisateur

11

Je crée un système de menus en PHP et MySQL. J'aurai plusieurs menus différents et chaque menu aura un ensemble d'éléments de menu qui lui seront connectés.

Sur le site, j'ai également différentes autorisations d'utilisateur, certains utilisateurs peuvent voir tous les éléments de menu et certains éléments sont cachés à certains utilisateurs. Je suis curieux de savoir comment je pourrais gérer les autorisations d'une manière propre qui permettra d'ajouter plus de types d'utilisateurs à l'avenir.

Ce que j'ai jusqu'à présent est quelque chose comme ceci:

-------------------
|Menus
-------------------
|id| |display name|
-------------------

----------------------------------------------------------
|Menu items
----------------------------------------------------------
|id| |menu_id| |label| |link| |parent| |sort| |permission|
----------------------------------------------------------

Je pense que la permissioncolonne peut être une chaîne séparée par des virgules que je peux comparer à l'ID d'autorisation de l'utilisateur actuel. Il peut également s'agir d'une référence à un autre tableau qui définit toutes les combinaisons possibles des autorisations existantes.

Une solution pourrait également être de simplement stocker plusieurs éléments de menu où la seule différence est la permission, bien que cela conduirait à un stockage en double et peut-être une douleur à administrer.

J'aimerais entendre une réflexion sur la façon de structurer cela et ce qui pourrait être considéré comme propre, dynamique et merdique.

Merci.

envergure
la source
Les utilisateurs ont-ils quelque chose en commun? En règle générale, vous regroupez les éléments de menu en groupes fonctionnels et affectez des utilisateurs à ces groupes (par exemple - utilisateurs Admin, utilisateurs DB, commerçants, etc.). Ensuite, vous gérez simplement les regroupements, en fonction du choix technologique - cela peut être géré en utilisant quelque chose comme Active Directory.
Michael
1
Vous obtenez beaucoup de bonnes réponses ici, mais ce que vous voudrez peut-être lire, c'est ACL. en.wikipedia.org/wiki/Access_control_list
Reactgular

Réponses:

17

Je vais le modéliser à l'aide d'un diagramme ER.

  • A PERMISSIONest l'accès accordé à un ROLEsur un donné MENU_ITEM.
  • Un RÔLE est un ensemble d'autorisations prédéfinies auxquelles on donne un nom
  • Un USERpeut avoir plusieurs ROLE accordés.
  • Le fait d'avoir des autorisations attribuées à des rôles plutôt qu'à des utilisateurs facilite grandement l'administration des autorisations.

entrez la description de l'image ici

Ensuite, vous pouvez créer une vue pour ne pas avoir à écrire les jointures à chaque fois:

create or replace view v_user_permissions
select
    distinct mi.id, mi.menu_id. mi.label. mi.link, mi.parent, mi.sort, u.user_id
from
    user u
    join user_role ur on (u.user_id = ur.user_id)
    join role ro on (ur.role_id = role.role_id)
    join permission per on (ro.role_id = per.role_id)
    join menu_item mi on (per.metu_item_id = mi.metu_item_id)

Ensuite, chaque fois que vous voulez savoir à quels éléments de menu un utilisateur a accès, vous pouvez l'interroger:

select * from v_user_permissions up where up.user_id = 12736 order by up.sort

ÉDITER:

Puisqu'un utilisateur peut se voir attribuer plusieurs rôles, les autorisations des rôles peuvent se chevaucher, c'est-à-dire que deux rôles distincts peuvent avoir accès au même élément de menu. Lorsque vous définissez un rôle, vous ne savez pas au préalable s'il aura des autorisations en commun avec d'autres rôles. Mais puisqu'il s'agit de l'union d'ensembles, il importe seulement qu'une autorisation donnée fasse partie de l'ensemble, pas le nombre de fois où elle apparaît, d'où la distinctclause dans la vue.

Tulains Córdova
la source
Super merci. Je ne vois pas pourquoi je n'ai pas pu rédiger ça moi-même. Je suppose que j'étais bloqué et non expérimenté :)
span
Ack, je pensais avoir compris mais visiblement pas. Comment puis-je avoir plusieurs autorisations pour un seul élément de menu?
span
1
@span Parce qu'un utilisateur peut se voir attribuer plusieurs rôles et que les autorisations des rôles peuvent se chevaucher, c'est-à-dire que deux rôles distincts peuvent avoir accès au même élément de menu. Lorsque vous définissez un rôle, vous ne savez pas au préalable si le rôle sera accordé avec d'autres qui ont des autorisations en commun avec lui. Mais comme ce problème concerne l'union des ensembles, il importe seulement qu'une autorisation donnée fasse partie de l'ensemble ou non, pas le nombre de fois où elle apparaît.
Tulains Córdova
Merci, je continuerai à lire votre réponse jusqu'à ce que je l'obtienne;). Je pense que mon erreur a été de penser qu'une seule autorisation pouvait être utilisée avec le rôle pour séparer les éléments du menu. Il semble que j'ai besoin d'une autorisation pour chaque «type» d'élément de menu. Encore une fois, merci pour ton aide! Je vais dessiner des diagrammes de Venn et voir si je peux m'en sortir correctement \ o /
span
5

Avoir une liste séparée par des virgules signifie faire une comparaison de sous-chaîne chaque fois que vous effectuez une requête sur le menu. C'est loin d'être idéal.

Vous devez normaliser le tableau:

---------------------------------------------
|Menu items
---------------------------------------------
|id| |menu_id| |label| |link| |parent| |sort|
---------------------------------------------

+---------------------------+
| menu_permission           |
|---------------------------|
| |menu_permission_id| (pk) |
| |menu_id| (fk)            |
| |permission|              |
+---------------------------+

Si vous voulez toujours la liste séparée par des virgules (pour une raison quelconque), vous pouvez la retirer avec des choses comme group_concatdans mysql wm_concat dans oracle ou des fonctions similaires dans d'autres langues.

L'avantage est multiple.

Tout d'abord, il y a l'aspect pratique de l'appel. Faire une sous-chaîne par rapport à une chaîne arbitrairement grande (si vous corrigez la taille, vous pouvez avoir des problèmes plus tard avec le remplissage de la chaîne afin que vous commenciez à obtenir des autorisations comme aau lieu de another_permission) signifie analyser la chaîne sur chaque ligne. Ce n'est pas quelque chose pour lequel les bases de données sont optimisées.

Deuxièmement, la requête que vous écrivez devient beaucoup plus simple. Pour déterminer si l'autorisation «foo» existe dans une liste séparée par des virgules, vous devez vérifier «foo».

... permission like "%foo%" ...

Cependant, cela donnera un faux positif si vous avez également l'autorisation «foobar». Alors maintenant, vous devez avoir un test comme

... permission like "%,foo,%" ...

mais cela donnera un faux négatif si 'foo' est au début ou à la fin de la chaîne. Cela mène à quelque chose comme

... (permission like "foo,%" 
  or permission like "%,foo,%" 
  or permission like "%,foo" ) ...

Vous noterez qu'il est très probable que vous devrez effectuer plusieurs analyses de la chaîne. Cette voie mène à la folie.

Notez qu'avec tout cela, vous n'avez pas la capacité pratique de faire la liaison de paramètres (toujours possible, devient encore plus laid).

La normalisation du champ vous donne beaucoup plus de flexibilité et de lisibilité dans votre base de données. Vous ne le regretterez pas.


la source
Merci pour votre excellente réponse, elle m'a donné plus de connaissances et je suis reconnaissant pour cela, même si je pense que la solution user61852 conviendra le mieux pour l'instant.
span
3

Cette approche classique de ceci est User -> UserGrouppuis associée a Menu -> MenuItem -> UserGroup. Utilisation d'une valeur entière pour peser le niveau d'autorisation.

-------------------
|Menu
-------------------
|id| |display name|
-------------------

-------------------------------------------------------------
|Menu Item
-------------------------------------------------------------
|id| |menu_id| |label| |link| |parent| |sort| | min_option1
-------------------------------------------------------------

-------------------------------------------
| User
-------------------------------------------
|id| | username | password | user_group_id
-------------------------------------------

-------------------------------------------
| User Group
-------------------------------------------
|id| option1 | option2 | option2
-------------------------------------------

Lorsque vous devez afficher un menu pour l'utilisateur actuel. Vous pouvez interroger la base de données comme ceci.

SELECT * FROM `MenuItem`
    LEFT JOIN `UserGroup` ON (`MenuItem`.`user_group_id` = `UserGroup`.`id`)
    LEFT JOIN `User` ON (`UserGroup`.`id` = `User`.`user_group_id` AND `User`.`id` = $current_user_id)
        WHERE `MenuItem`.`menu_id` = $menu_id AND `UserGroup`.`option1` >= `MenuItem`.`min_option1`;

Cela ne sélectionnera que les menus visibles par l'utilisateur actuel en fonction de la condition pour option1.

Sinon, si vous stockez les détails du groupe de l'utilisateur actuel dans la session en cours, aucune jointure n'est requise.

SELECT * FROM `MenuItem` WHERE `MenuItem`.`menu_id` = $menu_id AND `MenuItem`.`min_option1` >= $user_group_option1;

Lorsque vous parlez de vouloir stocker plusieurs autorisations par élément de menu. Je ferais attention de ne pas confondre les rôles utilisateur et la logique métier.

Reactgular
la source
2
Cette conception permettrait uniquement à chaque MenuItem d'être lié à un seul UserGroup, ce qui signifie soit des menus limités, soit une duplication des données. En réalité, vous voulez une table de liens, idéalement. De plus, votre choix de nommer la table DB comme pluriel me rend triste;)
Ed James
@EdWoodcock oh très bon point. J'aurais dû choisir un niveau d'autorisation (int), puis le comparer au niveau du groupe de l'utilisateur. Je vais changer ça. Remarque, habitude des noms pluriels causée par mon utilisation de CakePHP. Ce qui est étrange, car le framework utilise des alias singuliers pour les tables dans les requêtes.
Reactgular
@MatthewFoscarini Pas de soucis, je ne suis pas vraiment gêné tant qu'une base de code est cohérente;)
Ed James
1
Réponse géniale. Je garderai cela à l'esprit la prochaine fois que je ferai quelque chose comme ça. Pour l'instant, je pense que la solution user61852 conviendra le mieux car elle ne nécessite pas autant de modifications du code existant. Merci!
span