Comment puis-je contrôler l'accès via une table SQL?
-
03-07-2019 - |
Question
J'essaie de créer un système de contrôle d'accès.
Voici un exemple dépouillé de la table sur laquelle je tente de contrôler l'accès:
things table:
id group_id name
1 1 thing 1
2 1 thing 2
3 1 thing 3
4 1 thing 4
5 2 thing 5
Et la table de contrôle d'accès ressemble à ceci:
access table:
user_id type object_id access
1 group 1 50
1 thing 1 10
1 thing 2 100
L'accès peut être accordé soit en spécifiant directement l'identifiant de la 'chose', soit accordé pour un groupe entier d'objets en spécifiant un identifiant de groupe. Dans l'exemple ci-dessus, l'utilisateur 1 a reçu un niveau d'accès de 50 pour le groupe 1, qui devrait s'appliquer sauf s'il existe d'autres règles accordant un accès plus spécifique à un élément individuel.
J'ai besoin d'une requête qui renvoie une liste d'éléments (les identifiants uniquement sont acceptables) ainsi que le niveau d'accès d'un utilisateur spécifique. Donc, en utilisant l'exemple ci-dessus, je voudrais quelque chose comme ceci pour l'ID utilisateur 1:
desired result:
thing_id access
1 10
2 100
3 50 (things 3 and 4 have no specific access rule,
4 50 so this '50' is from the group rule)
5 (thing 5 has no rules at all, so although I
still want it in the output, there's no access
level for it)
Le plus proche que je puisse trouver est le suivant:
SELECT *
FROM things
LEFT JOIN access ON
user_id = 1
AND (
(access.type = 'group' AND access.object_id = things.group_id)
OR (access.type = 'thing' AND access.object_id = things.id)
)
Mais cela retourne plusieurs lignes, alors que je n'en veux qu'une pour chaque ligne du tableau "objets". Je ne suis pas sûr de savoir comment arriver à une seule ligne pour chaque "chose", ou comment donner la priorité aux règles "de chose" par rapport aux règles de "groupe".
Si cela peut vous aider, la base de données que j'utilise est PostgreSQL.
N'hésitez pas à laisser un commentaire si des informations me manquent.
Merci d'avance!
La solution
Je ne connais pas le dialecte SQL de Postgres, mais peut-être quelque chose comme:
select thing.*, coalesce ( ( select access
from access
where userid = 1
and type = 'thing'
and object_id = thing.id
),
( select access
from access
where userid = 1
and type = 'group'
and object_id = thing.group_id
)
)
from things
Incidemment, je n'aime pas le design. Je préférerais que la table d'accès soit divisée en deux:
thing_access (user_id, thing_id, access)
group_access (user_id, group_id, access)
Ma requête devient alors:
select thing.*, coalesce ( ( select access
from thing_access
where userid = 1
and thing_id = thing.id
),
( select access
from group_access
where userid = 1
and group_id = thing.group_id
)
)
from things
Je préfère cela car les clés étrangères peuvent maintenant être utilisées dans les tables d'accès.
Autres conseils
Je viens de lire un article hier soir à ce sujet. Il a quelques idées sur la façon de faire cela. Si vous ne pouvez pas utiliser le lien figurant dans le titre, essayez d'utiliser Google Scholar sur Limiter la divulgation d'informations à Hippocratic. Bases de données.
Bien qu'il existe plusieurs bonnes réponses, la plus efficace serait probablement la suivante:
SELECT things.id, things.group_id, things.name, max(access)
FROM things
LEFT JOIN access ON
user_id = 1
AND (
(access.type = 'group' AND access.object_id = things.group_id)
OR (access.type = 'thing' AND access.object_id = things.id)
)
group by things.id, things.group_id, things.name
Qui utilise simplement le résumé ajouté à votre requête pour obtenir ce que vous recherchez.
Tony:
Ce n’est pas une mauvaise solution, je l’aime bien, semble fonctionner. Voici votre requête après quelques modifications mineures:
SELECT
things.*,
coalesce (
( SELECT access
FROM access
WHERE user_id = 1
AND type = 'thing'
AND object_id = things.id
),
( SELECT access
FROM access
WHERE user_id = 1
AND type = 'group'
AND object_id = things.group_id
)
) AS access
FROM things;
Et les résultats semblent corrects:
id | group_id | name | access
----+----------+---------+--------
1 | 1 | thing 1 | 10
2 | 1 | thing 2 | 100
3 | 1 | thing 3 | 50
4 | 1 | thing 4 | 50
5 | 2 | thing 5 |
Je prends complètement le fait que ce n’est pas un schéma idéal. Cependant, je suis coincé dans une certaine mesure.
Josef:
Votre solution est très similaire à celle avec laquelle je jouais, et mon instinct (tel qu’il est) me dit qu’il devrait être possible de le faire de cette façon. Malheureusement, cela ne donne pas des résultats complètement corrects:
id | group_id | name | max
----+----------+---------+-----
1 | 1 | thing 1 | 50
2 | 1 | thing 2 | 100
3 | 1 | thing 3 | 50
4 | 1 | thing 4 | 50
5 | 2 | thing 5 |
Le niveau d'accès pour 'chose 1' a pris la valeur d'accès 'groupe' supérieure, au lieu de la valeur d'accès plus spécifique 'chose' de 10, ce que je recherche après. Je ne pense pas qu'il soit possible de résoudre ce problème au sein d'un GROUP BY
, mais si quelqu'un a des suggestions, je suis plus qu'heureux d'être prouvé qu'il est incorrect sur ce point.