8768 sujets

Développement web côté serveur, CMS

Bonsoir,

Je réfléchi à une manière de concevoir des rôles utilisateurs pour un site web. Ma question comporte trois points :

1/ J'ai opté pour une colonne integer dans le compte utilisateur, avec un chiffre de 1 à 9 pour cibler les rôles, chiffre dont la valeur serait classée dans un ordre hiérarchique. Par exemple : "1" pour un administrateur, "9" pour un simple utilisateur inscrit au site. Plus tard, dans l'application, lorsque je testerai les rôles, je me disais que je pourrais juste faire un test du genre :
if (role < 3) {}

Plutôt que d'utiliser ceci :
if (role === 'administrator' || role === 'moderator') {}

Ou cela :
if (['administrator', 'moderator'].includes(role)) {}

Déjà, avant d'aller plus avant, que pensez-vous de cette solution ?

2/ Maintenant, voici les rôles prévus, si vous avez des remarques à faire à ce sujet (manque un rôle, ou autre) je suis preneur là aussi :
INSERT INTO __role (_id, _name, _description)
VALUES
  (1, 'administrator', 'Les administrateurs ont un accès complet au site. Ils peuvent effectuer toutes les tâches de gestion, y compris la modification des paramètres du site, l''ajout et la suppression de contenu, la gestion des utilisateurs.'),
  (2, 'moderator', 'Les modérateurs ont le pouvoir de publier, modifier et supprimer tout le contenu sur le site. Ils ne peuvent pas accéder aux paramètres du site ni effectuer des actions administratives.'),
  (4, 'author', 'Les auteurs peuvent publier et gérer leur propre contenu. Ils ne peuvent pas modifier ou supprimer le contenu des autres utilisateurs.'),
  (5, 'contributor', 'Les contributeurs peuvent soumettre leur contenu pour examen, mais ils ne peuvent pas le publier eux-mêmes. Les modérateurs ou les administrateurs doivent approuver leur contenu pour qu''il soit publié.'),
  (8, 'commentator', 'Les commentateurs peuvent publier des commentaires sous un contenu déjà créé.'),
  (9, 'subscriber', 'Les utilisateurs inscrits peuvent lire certains contenus non accessibles sans connexion au site.');


3/ Notez que j'imagine aussi ajouter une vérification de groupes. Par exemple, avec cela on pourrait être modérateur que pour une partie du site. Et j'imaginais notamment placer les bannis dans un groupe (alors que dans un premier temps j'avais fais une colonne booléenne "is_banned" dans le compte utilisateur). Là encore que pensez-vous de ce choix ?
Modifié par Olivier C (29 Feb 2024 - 15:50)
Modérateur
Bonjour,

1) Si tu utilises une valeur numérique pour représenter les rôles, utilise les "bits" des nombres plutôt qu'une comparaison des entiers.

En binaire, et en supposant qu'on se limite à un octet :
1 vaut 00000001
2 vaut 00000010
4 vaut 00000100

Et ainsi de suite. Sur un octet, tu auras 8 droits "fondamentaux" codables (tu peux aussi coder tes droits sur 2 octets auquel cas tu pourras coder 16 droits fondamentaux, etc.). Supposons que 1 soit les droits d'admin, 2 les droits de modérations, etc ..., pour tester si un role a les droits d'admin, tu feras :
if(role&1) ...
Pour tester les droits de modérations, tu feras :
if(role&2) ...
Et pour tester les droits soit d'admin soit de modération, tu feras :
if(role&3) ...

Ça simplifiera pas mal tes tests, et tu n'auras pas à mettre en place une hiérarchie entre les rôles du plus "fort" au plus "faible" (parce que parfois c'est compliqué).

2) Ne mets pas trop de rôles différents en place au départ, et fais la distinction entre rôle et droit. D'un côté tu feras une liste de droits fondamentaux, et de l'autres une liste de rôles qui auront chacun un ou plusieurs de ces droits.

Amicalement,
parsimonhi a écrit :
2) Ne mets pas trop de rôles différents en place au départ, et fais la distinction entre rôle et droit. D'un côté tu feras une liste de droits fondamentaux, et de l'autres une liste de rôles qui auront chacun un ou plusieurs de ces droits.

Ah oui, un peu comme WordPress. Je ne pensais pas aller jusque là. Merci pour ce retour, je vais continuer à creuser la question avant de tout implémenter tête baissée.

De mon côté je vois qu'il me manque peut-être un rôle, "editor", qui serait comme le "moderator" plus haut. Et le modérateur, lui, aurait en plus la charge de pouvoir s'occuper des comptes utilisateurs...

Et pour les bits, c'est la première fois que je vois ça. Tu as un exemple d'utilisation en ligne ? Un lien vers un compte GitHub par exemple ?
Salut,

1 : perso je ne suis pas vraiment convaincu par ton idée de "hiérarchie" des droits (on a souvent besoin de donner certains droits et pas d'autres mais c'est souvent des cas distincts)
Et si tu conserves cette idée de hiérarchie, n'utilise pas directement la valeur dans ton code ( 3 dans ton exemple) mais crée des "constantes".
Cela permettra de facilement ajouter/supprimer des éléments dans ta hiérarchie et de garder un code valide (du genre : if role <= author , peut importe qu'il soit 4e ou 10e dans ta hiérarchie ça restera la même condition)

2 : J'ai pas trop compris pourquoi tu as mis des trous ( 3 6 7 ) dans ta liste

3 : J'ai pas compris ta notion de groupe Smiley sweatdrop

Pour les droits avec des bits, c'est le fonctionnement des droits de fichiers unix. Et pour le coup l'idée ce n'est pas de faire une hiérarchie mais de facilité toutes les combinaisons de droits possible via des additions :
1 -> read
2 -> write
3 = 1 +2 > read + write
4 -> execute
5 = 1+4 -> read + execute
6 = 2+4 -> write+execute
7 = 1 + 2 + 4 -> read + write + execute


Pour la gestion des droits j'aime bien celle mis en place par moodle qui permet d'associer les rôles (groupe de droits) à des "contextes". En gros tu peux obtenir les droits d'un enseignant dans "un contexte" (i.e dans ton cours) et le rôle d'étudiant dans un autre cours, et rien dans tous les autres cours.
Modérateur
Bonjour,

Olivier C a écrit :

Ah oui, un peu comme WordPress. Je ne pensais pas aller jusque là. Merci pour ce retour, je vais continuer à creuser la question avant de tout implémenter tête baissée.

Ce n'est pas seulement dans Wordpress que c'est comme ça. C'est assez général. Ça s'appelle "Le contrôle d'accès basé sur les rôles" (ou RBAC (Role Based Access Control). Et dans un RBAC, on attribue aux utilisateurs des rôles qui eux-mêmes sont en gros des listes d'autorisations gérées par un administrateur. Et il y a une montagne de références sur ce principe.

Tu pourrais bien sûr définir (informellement) la liste des autorisations (ou droits) de chaque rôle dès le départ, et te contenter dans ton code de faire référence à des rôles. Cependant, tu vas vite voir que ça manque de souplesse.

Amicalement,
Meilleure solution
Mathieuu a écrit :
Pour les droits avec des bits, c'est le fonctionnement des droits de fichiers unix.

parsimonhi a écrit :
Ce n'est pas seulement dans Wordpress que c'est comme ça. C'est assez général. Ça s'appelle "Le contrôle d'accès basé sur les rôles" (ou RBAC (Role Based Access Control). Et dans un RBAC, on attribue aux utilisateurs des rôles qui eux-mêmes sont en gros des listes d'autorisations gérées par un administrateur. Et il y a une montagne de références sur ce principe.

Merci à vous deux.

Je vais prendre le temps de me renseigner sur les sujets que vous évoquez avant de me lancer. Mais je vais partir sur quelque chose de cet ordre (peut-être pas pour les bits, mais pour une définition fine des capacités).

Sujet résolu (pour l'instant).
Modérateur
Salut

@parsimonhi!: +1 (et merci pour ces détails)

@Olivier: Commence juste avec le groupe "administrateur" et fait en sorte aussi que l'user soit dans le staff ou pas. Il peut être dans l'équipe mais pas admin. Ça pourra lui donner certains droits par la suite. Je rejoins aussi ce qu'a dit Mathieuu, mais d'une autre manière.

Sur chaque action, tu pourras donner des droits spécifiques à l'utilisateur ou au groupe d'utilisateurs. Exemple, tu crées un blog. En faisant simple, qui peut:
- accéder à l'espace admin
- créer un post
- mettre à jour un post
- supprimer un post
- lire un post
- créer une categorie
- mettre à jour une catégorie
- supprimer une categorie
- lire une categorie
- créer un commentaire
- mettre à jour un commentaire
- supprimer un commentaire
- lire un commentaire
- etc.
Modifié par niuxe (29 Feb 2024 - 19:33)
Oui c'est ça. Du coup je me suis dit que j'allais faire comme wordpress au final, pour partir de quelque chose ("qui peut"/les capacités).

Par contre, dans WordPress, j'ai vu qu'ils stockaient toutes les capacités dans une collection d'objets, au niveau de PHP. Je me souviens très bien de tout cela, lorsque je manipulais les rôles il y a 10 ans sous WordPres.

Pour une fois que WordPress ne met pas toute la logique du site en base de données...

Mais alors au final, ces capacités : il vaut mieux les stocker côté SQL ou côté application ? A priori je dirais que c'est mieux de les mettre en SQL.. du coup j'ai un doute...
Modifié par Olivier C (10 Mar 2024 - 00:11)
Modérateur
Olivier C a écrit :
A priori je dirais que c'est mieux de les mettre en SQL.. du coup j'ai un doute...


En SQL, tu n'auras pas de problème d'intégrité des données
C'est ce que je me suis dit aussi mais je n'étais pas sûr. Merci du coup.
Si je partais avec un (pseudo-)équivalent WordPress ça pourrait ressembler à quelque chose comme ceci :
CREATE TABLE capabilities (
    name VARCHAR(13) PRIMARY KEY,
    create_users BOOLEAN,
    update_users BOOLEAN,
    delete_users BOOLEAN,
    create_posts BOOLEAN,
    update_posts BOOLEAN,
    delete_posts BOOLEAN,
    update_others_posts BOOLEAN,
    delete_others_posts BOOLEAN,
    create_tags BOOLEAN,
    update_tags BOOLEAN,
    delete_tags BOOLEAN,
    create_comments BOOLEAN,
    update_comments BOOLEAN,
    delete_comments BOOLEAN,
    publish_posts BOOLEAN,
    read BOOLEAN,
    upload_files BOOLEAN
);

Et du coup ça vaudrait le coup de geler la table pour que personne n'y touche ? :
REVOKE INSERT, UPDATE, DELETE ON roles FROM PUBLIC;

Modifié par Olivier C (01 Mar 2024 - 01:47)
Mathieuu a écrit :
2 : J'ai pas trop compris pourquoi tu as mis des trous ( 3 6 7 ) dans ta liste

Et bien... étant donnée que le modèle que je m'apprêtait à suivre manquait de souplesse (comme l'a souligné Parsimonhi) j'avais pris les devants et prévu de l'espace pour ajouter des rôles... tout en ne voulant pas dépasser un chiffre (de 0 à 9)... Oui, oui : c'est un peu puéril.

Mathieuu a écrit :
3 : J'ai pas compris ta notion de groupe Smiley sweatdrop

Ça je l'ai encore en tête et j'aimerais bien savoir ce que vous en pensez. Par exemple : un modérateur ne pourrait modérer qu'une partie du site (la partie forum par exemple, s'il fait partie d'un groupe forum), au-delà, ses capacités de modérateurs seraient inopérantes. Selon son affiliation à tel ou tel groupe il pourrait même être restreint qu'à une partie du forum.

Ça va un peu loin peut-être. Mais j'ai eu ce cas à gérer une fois, avec les élèves de ma femme sur son site, et je m'en étais tiré ainsi.
Bonjour,

LE système purement hiérarchique avec 1 le plus fort et 9 le plus faible, c'est vite limité.
Il arrivera forcément un moment où tu voudras avoir des rôles indépendants de la hiérarchie initiale, et où tu seras du coup bloqué.
OU bien tu voudras intercaler un niveau intermédiaire, et malgré tous les trous que tu as pu prévoir, tu finiras par être obligé de tout décaler.
Bref ne fais pas ça.

Le système du champ de bits / bitfield / bitset est un classique. Par contre avec plein de langages et de SGBD, il existe des moyens de les manipuler plus aisément.
Par exemple les set a.k.a. enum set de MySQL/MariaDB, ou les EnumSet de Java.
Dans tous les cas, déclarer une enum ou des constantes plutôt que de manipuler les nombres directement, et si possible, se baser sur les noms plutôt que les valeurs numériques.
C'est plus robuste au cas où tu ajoutes/supprimes des rôles, et c'est moins sujet aux erreurs. Ca n'a l'air de rien, mais on fait vite des erreurs stupides avec les opérateurs binaires utilisés tels quels, et il faut connaître la logique de fonctionnement.

Si tu veux séparer les rôles et les droits, tu remarqueras que souvent, les droits, c'est du CRUD (create read update delete).
OU au pire ça se résume à une autre opération très très simple sur un seul type d'entité.
Ca veut dire que des droits, potentiellement tu en as beaucoup, beaucoup, beaucoup, peut-être même carrément un droit différent par opération possible dans ton application / un par point d'entrée.

L'avantage de séparer les rôles et les droits, et de stocker les relations rôles/droits en DB, c'est que tu peux décider dynamiquement d'ajouter ou de retirer des droits à tous les utilisateurs ayant un tel rôle en un seul coup.
C'est pratique. Par contre ça peut vite devenir une usine à gaz à gérer, donc si tu peux t'en passer et te limiter à une liste de rôles par utilisateur et oublier les listes des droits complètements, ça rendra ton système beaucoup plus simple.
A moins que tu ne délègues toute cette gestion à des outils comme ActiveDirectory.
Merci pour ton point de vue.

Pour les systèmes avec BITs hérités d'UNIX, Mathieuu et Parsimonhi m'en ont déjà parlé : je suis allé voir un peu, mais j'avoue que j'ai du mal à appréhender cette solution. Je pense donc rester sur un système de capacités stockées dans une table :
CREATE TABLE __role (
  _name                 VARCHAR(13)       UNIQUE NOT NULL,
  _access_admin         BOOLEAN           DEFAULT false,
  _create_contents      BOOLEAN           DEFAULT false,
  _edit_contents        BOOLEAN           DEFAULT false,
  _delete_contents      BOOLEAN           DEFAULT false,
  _publish_contents     BOOLEAN           DEFAULT false,
  _create_comments      BOOLEAN           DEFAULT false,
  _edit_comments        BOOLEAN           DEFAULT false,
  _delete_comments      BOOLEAN           DEFAULT false,
  _manage_users         BOOLEAN           DEFAULT false,
  _manage_groups        BOOLEAN           DEFAULT false,
  _manage_contents      BOOLEAN           DEFAULT false,
  _manage_tags          BOOLEAN           DEFAULT false,
  _manage_menus         BOOLEAN           DEFAULT false,
  _upload_files         BOOLEAN           DEFAULT false,
  _read                 BOOLEAN           DEFAULT false,
  PRIMARY KEY (_name)
);

REVOKE INSERT, UPDATE, DELETE ON __role FROM PUBLIC;

INSERT INTO __role (_name, _access_admin, _create_contents, _edit_contents, _delete_contents, _publish_contents, _create_comments, _edit_comments, _delete_comments, _manage_users, _manage_groups, _manage_contents, _manage_tags, _manage_menus, _upload_files, _read) 
VALUES
  ('administrator', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE),
  ('moderator', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, TRUE),
  ('editor', FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE),
  ('author', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE),
  ('contributor', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE),
  ('commentator', FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE),
  ('subscriber', FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE);

Ma question du moment est de savoir si je garde un VARCHAR plutôt qu'un ENUM. Le dernier me semble plus approprié mais ne fais pas partie de la norme SQL. Après... je ne compte pas (jamais) changer de base de données et il y a des fonctions postgres intéressantes pour la manipulations des données avec ENUM, alors je me tâte...

PS : la vérité c'est que je procrastine ; en effet les rôles ne sont pas ma préoccupation du moment, l'élément sérieux auquel je vais devoir m'attaquer est l'authentification. Je poserais sans doute une question en ce sens sur le forum, mais il faut d'abord que je trouve un peu de temps pour développer cette fonctionnalité.

Modifié par Olivier C (01 Apr 2024 - 00:31)
Bonsoir,

Dans l'exemple que tu donnes, changer le nom en enum ne fait pas de sens, tu t'auto-bloques pour ajouter de nouveaux rôles.

Par contre ce serait préférable d'avoir un identifiant unique en plus, ça améliorera grandement les jointures que tu ne manqueras pas de faire sur ta table de rôles.
Surtout si tu n'adoptes pas la technique des champs de bits.
QuentinC a écrit :
Par contre ce serait préférable d'avoir un identifiant unique en plus, ça améliorera grandement les jointures que tu ne manqueras pas de faire sur ta table de rôles.

Je ne vois pas trop... tu peux me donner un scénario ?

En attendant je te donne un exemple de ce que je peux faire comme requête avec jointures pour une page visualisant un compte utilisateur :
SELECT
  ...
FROM __account a
LEFT JOIN __media m
  ON m._id = a._media_id
LEFT JOIN __person p
  ON p._id = a._person_id
LEFT JOIN __role r
  ON r._name = a._role_name
...;

Comme on le voit, la comparaison "_name" détonne avec les "_id" qui précèdent, mais bon...
Modifié par Olivier C (02 Apr 2024 - 21:51)
a écrit :
Comme on le voit, la comparaison "_name" détonne avec les "_id" qui précèdent, mais bon...


C'est justement à ça que je pensais. C'est plus performant de comparer des nombres plutôt que des chaînes.
Vu que les contrôles sur les rôles sont presque partout, ça peut avoir sa petite influence.

Encore plus si tu fais une table de liaison n:m utilisateurs/rôles à la place d'un champ de bits.

Il y a quand même des inconvénients à utiliser un champ de bits. Typiquement lister les rôles de l'utilisateur en faisant une jointure sur une table contenant les noms des rôles, c'est pas très pratique par exemple.
Je crois que tu vas m'influencer. En effet, cette histoire de ne pas garder les ID ça ne me plaisait pas bien au final. Du coup je pense que je vais remettre des identifiants chiffrés, en plus la colonne des rôles "_name" en UNIQUE bien sûr.