Il existe de nombreuses façons de résoudre un problème, et c'est le cas avec l'administration des rôles et des statuts d'utilisateur dans les systèmes logiciels. Dans cet article, vous trouverez une évolution simple de cette idée ainsi que des conseils utiles et des exemples de code.
Idée de base
Dans la plupart des systèmes, il est généralement nécessaire d'avoir des rôles et les statuts des utilisateurs .
Les rôles sont liés aux droits que les utilisateurs ont lorsqu'ils utilisent un système après s'être connectés avec succès. Des exemples de rôles sont « employé du centre d'appels », « responsable du centre d'appels », « employé du back-office », « responsable du back-office » ou « gestionnaire ». Généralement, cela signifie qu'un utilisateur aura accès à certaines fonctionnalités s'il a le rôle approprié. Il est sage de supposer qu'un utilisateur peut avoir plusieurs rôles en même temps.
Les statuts sont beaucoup plus stricts et déterminent si l'utilisateur a le droit de se connecter au système ou non. Un utilisateur ne peut avoir qu'un seul statut à la fois. Des exemples de statuts seraient :« en activité », « en vacances », « en arrêt maladie », « fin de contrat ».
Lorsque nous modifions le statut d'un utilisateur, nous pouvons toujours conserver inchangés tous les rôles liés à cet utilisateur. C'est très utile car la plupart du temps, nous ne voulons modifier que le statut de l'utilisateur. Si un utilisateur qui travaille comme employé d'un centre d'appels part en vacances, nous pouvons simplement changer son statut en "en vacances" et le remettre en statut "travailler" à son retour.
Tester les rôles et les statuts lors de la connexion nous permet de décider de ce qui se passera. Par exemple, nous voulons peut-être interdire la connexion même si le nom d'utilisateur et le mot de passe sont corrects. Nous pourrions le faire si le statut actuel de l'utilisateur n'implique pas qu'il travaille ou si l'utilisateur n'a aucun rôle dans le système.
Dans tous les modèles ci-dessous, les tableaux status
et role
sont les mêmes.
Tableau status
a les champs id
et status_name
et l'attribut is_active
. Si l'attribut is_active
est défini sur "True", cela signifie que l'utilisateur qui a ce statut travaille actuellement. Par exemple, le statut "working" aurait l'attribut is_active
avec une valeur Vrai, tandis que d'autres (« en vacances », « en arrêt maladie », « fin de contrat ») auraient une valeur Faux.
La table des rôles n'a que deux champs :id
et role_name
.
Le user_account
table est identique au user_account
tableau présenté dans cet article. Ce n'est que dans le premier modèle que le user_account
table contient deux attributs supplémentaires (role_id
et status_id
).
Quelques modèles seront présentés. Tous fonctionnent et peuvent être utilisés mais ont leurs avantages et leurs inconvénients.
Modèle simplifié
La première idée pourrait être que nous ajoutions simplement des relations de clé étrangère au user_account
table, référencement sur les tables status
et role
. Les deux role_id
et status_id
sont obligatoires.
C'est assez simple à concevoir et aussi à gérer les données avec des requêtes mais présente quelques inconvénients :
-
Nous ne conservons aucune donnée historique (ou future).
Lorsque nous modifions le statut ou le rôle, nous mettons simplement à jour
status_id
etrole_id
dans leuser_account
table. Cela fonctionnera bien pour le moment, donc lorsque nous apporterons un changement, cela se reflétera dans le système. Ce n'est pas grave si nous n'avons pas besoin de savoir comment les statuts et les rôles ont changé historiquement. Il y a aussi un problème dans le sens où nous ne pouvons pas ajouter futur rôle ou statut sans ajouter de tables supplémentaires à ce modèle. Une situation où nous aimerions probablement avoir cette option, c'est lorsque nous savons que quelqu'un sera en vacances à partir de lundi prochain. Un autre exemple est quand nous avons un nouvel employé; peut-être voulons-nous entrer dans son statut et son rôle maintenant et qu'ils deviennent valides à un moment donné dans le futur.Il y a aussi une complication au cas où nous aurions des événements programmés qui utilisent des rôles et des statuts. Les événements qui préparent les données pour le jour ouvrable suivant s'exécutent généralement lorsque la plupart des utilisateurs n'utilisent pas le système (par exemple pendant la nuit). Donc, si quelqu'un ne travaille pas demain, nous devrons attendre la fin de la journée en cours, puis modifier ses rôles et son statut, le cas échéant. Par exemple, si nous avons des employés qui travaillent actuellement et qui ont le rôle "employé du centre d'appels", ils obtiendront une liste de clients qu'ils doivent appeler. Si quelqu'un par erreur avait ce statut et ce rôle, il obtiendra également ses clients et nous devrons passer du temps à le corriger.
-
L'utilisateur ne peut avoir qu'un seul rôle à la fois.
En général, les utilisateurs doivent pouvoir avoir plusieurs rôles dans le système. Peut-être qu'au moment où vous concevez la base de données, il n'y a pas besoin de quelque chose comme ça. Gardez à l'esprit que des changements dans le flux de travail/processus peuvent survenir. Par exemple, à un moment donné, le client pourrait décider de fusionner deux rôles en un seul. Une solution possible consiste à créer un nouveau rôle et à lui attribuer toutes les fonctionnalités des rôles précédents. L'autre solution (si les utilisateurs peuvent avoir plus d'un rôle) serait que le client attribue simplement les deux rôles aux utilisateurs qui en ont besoin. Bien sûr, cette deuxième solution est plus pratique et donne au client la possibilité d'adapter plus rapidement le système à ses besoins (ce qui n'est pas pris en charge par ce modèle).
D'un autre côté, ce modèle a aussi un gros avantage sur les autres. C'est simple et donc les requêtes pour changer les statuts et les rôles seraient également simples. De plus, une requête qui vérifie si l'utilisateur a le droit de se connecter au système est beaucoup plus simple que dans d'autres cas :
select user_account.id, user_account.role_id from user_account left join status on user_account.status_id = status.id where status.is_user_working = True and user_account.user_name = @user_name and user_account.password_hash_algorithm = @password;
@user_name et @password sont des variables d'un formulaire de saisie tandis que la requête renvoie l'identifiant de l'utilisateur et le role_id qu'il possède. Dans les cas où le nom d'utilisateur ou le mot de passe ne sont pas valides, si la paire nom d'utilisateur et mot de passe n'existe pas, ou si l'utilisateur a un statut attribué qui n'est pas actif, la requête ne renverra aucun résultat. De cette façon, nous pouvons interdire la connexion.
Ce modèle pourrait être utilisé dans les cas où :
- nous sommes sûrs qu'il n'y aura aucun changement dans le processus qui obligerait les utilisateurs à avoir plus d'un rôle
- nous n'avons pas besoin de suivre les changements de rôle/statut dans l'historique
- nous ne nous attendons pas à avoir beaucoup d'administration de rôle/statut.
Composant de temps ajouté
Si nous devons suivre le rôle et l'historique des statuts d'un utilisateur, nous devons ajouter plusieurs relations entre le user_account
et role
et le user_account
et status
. Bien sûr, nous supprimerons role_id
et status_id
depuis le user_account
table. Les nouvelles tables du modèle sont user_has_role
et user_has_status
et tous les champs qu'ils contiennent, à l'exception des heures de fin, sont obligatoires.
Le tableau user_has_role
contient des données sur tous les rôles que les utilisateurs ont eus dans le système. La clé alternative est (user_account_id
, role_id
, role_start_time
) car il est inutile d'attribuer plusieurs fois le même rôle à un utilisateur en même temps.
Le tableau user_has_status
contient des données sur tous les statuts que les utilisateurs ont jamais eus dans le système. La clé alternative ici est (user_account_id
, status_start_time
) car un utilisateur ne peut pas avoir deux statuts qui commencent exactement au même moment.
L'heure de début ne peut pas être nulle car lorsque nous insérons un nouveau rôle/statut, nous connaissons le moment à partir duquel il commencera. L'heure de fin peut être nulle si nous ne savons pas quand le rôle/statut se terminera (par exemple, le rôle est valide à partir de demain jusqu'à ce que quelque chose se produise dans le futur).
En plus d'avoir un historique complet, nous pouvons maintenant ajouter des statuts et des rôles à l'avenir. Mais cela crée des complications car nous devons vérifier les chevauchements lorsque nous faisons une insertion ou une mise à jour.
Par exemple, un utilisateur ne peut avoir qu'un seul statut à la fois. Avant d'insérer un nouveau statut, nous devons comparer l'heure de début et l'heure de fin d'un nouveau statut avec tous les statuts existants pour cet utilisateur dans la base de données. Nous pouvons utiliser une requête comme celle-ci :
select * from user_has_status where user_has_status.user_account_id = @user_account_id and ( # test if @start_time included in interval of some previous status (user_has_status.status_start_time <= @start_time and ifnull(user_has_status.status_end_time, "2200-01-01") >= @start_time) or # test if @end_time included in interval of some previous status (user_has_status.status_start_time <= @end_time and ifnull(user_has_status.status_end_time, "2200-01-01") >= ifnull(@end_time, "2199-12-31")) or # if @end_time is null we cannot have any statuses after @start_time (@end_time is null and user_has_status.status_start_time >= @start_time) or # new status "includes" old satus (@start_time <= user_has_status.status_start_time <= @end_time) (user_has_status.status_start_time >= @start_time and user_has_status.status_start_time <= ifnull(@end_time, "2199-12-31")) )
@start_time
et @end_time
sont des variables contenant l'heure de début et l'heure de fin d'un statut que nous voulons insérer et @user_account_id
est l'identifiant de l'utilisateur pour lequel nous l'insérons. @end_time
peut être nul et nous devons le gérer dans la requête. Pour cela, les valeurs nulles sont testées avec le ifnull()
une fonction. Si la valeur est nulle, une valeur de date élevée est attribuée (suffisamment élevée pour que, lorsque quelqu'un remarque une erreur dans la requête, nous soyons partis depuis longtemps :). La requête vérifie toutes les combinaisons d'heure de début et d'heure de fin pour un nouveau statut par rapport à l'heure de début et à l'heure de fin des statuts existants. Si la requête renvoie des enregistrements, nous avons un chevauchement avec les statuts existants et nous devons interdire l'insertion du nouveau statut. De plus, il serait bien de générer une erreur personnalisée.
Si nous voulons vérifier la liste des rôles et statuts actuels (droits d'utilisateur), nous testons simplement en utilisant l'heure de début et l'heure de fin.
select user_account.id, user_has_role.id from user_account left join user_has_role on user_has_role.user_account_id = user_account.id left join user_has_status on user_account.id = user_has_status.user_account_id left join status on user_has_status.status_id = status.id where user_account.user_name = @user_name and user_account.password_hash_algorithm = @password and user_has_role.role_start_time <= @time and ifnull(user_has_role.role_end_time,"2200-01-01") >= @time and user_has_status.status_start_time <= @time and ifnull(user_has_status.status_end_time,"2200-01-01") >= @time and status.is_user_working = True
@user_name
et @password
sont des variables du formulaire de saisie tandis que @time
peut être défini sur Now(). Lorsqu'un utilisateur essaie de se connecter, nous voulons vérifier ses droits à ce moment-là. Le résultat est une liste de tous les rôles qu'un utilisateur a dans le système au cas où le nom d'utilisateur et le mot de passe correspondent et l'utilisateur a actuellement un statut actif. Si l'utilisateur a un statut actif mais qu'aucun rôle n'est attribué, la requête ne renverra rien.
Cette requête est plus simple que celle de la section 3 et ce modèle nous permet d'avoir un historique des statuts et des rôles. De plus, nous pouvons gérer les statuts et les rôles pour le futur et tout fonctionnera bien.
Modèle final
Ce n'est qu'une idée de la façon dont le modèle précédent pourrait être modifié si nous voulions améliorer les performances. Puisqu'un utilisateur ne peut avoir qu'un seul statut actif à la fois, nous pourrions ajouter status_id
dans le user_account
table (current_status_id
). De cette façon, nous pouvons tester la valeur de cet attribut et n'aurons pas à rejoindre le user_has_status
table. La requête modifiée ressemblerait à ceci :
select user_account.id, user_has_role.id from user_account left join user_has_role on user_has_role.user_account_id = user_account.id left join status on user_account.current_status_id = status.id where user_account.user_name = @user_name and user_account.password_hash_algorithm = @password and user_has_role.role_start_time <= @time and ifnull(user_has_role.role_end_time,"2200-01-01") >= @time and status.is_user_working = True
Évidemment, cela simplifie la requête et conduit à de meilleures performances, mais il y a un problème plus important qui devrait être résolu. Le current_status_id
dans le user_account
table doit être vérifiée et modifiée si nécessaire dans les situations suivantes :
- à chaque insertion/mise à jour/suppression dans
user_has_status
tableau - Chaque jour d'un événement planifié, nous devons vérifier si le statut de quelqu'un a changé (le statut actuellement actif a expiré ou/et un statut futur est devenu actif) et le mettre à jour en conséquence
Il serait judicieux de sauvegarder les valeurs que les requêtes utiliseront fréquemment. De cette façon, nous éviterons de refaire les mêmes vérifications encore et encore et de diviser le travail. Ici, nous éviterons de rejoindre le user_has_status
table et nous apporterons des modifications sur current_status_id
uniquement lorsqu'ils se produisent (insertion/mise à jour/suppression) ou lorsque le système n'est pas trop utilisé (les événements planifiés s'exécutent généralement lorsque la plupart des utilisateurs n'utilisent pas le système). Peut-être que dans ce cas, nous ne gagnerions pas grand-chose avec current_status_id
mais considérez cela comme une idée qui peut aider dans des situations similaires.