2008-10-14 4 views
4

J'essaie de créer un système de contrôle d'accès.Comment puis-je effectuer un contrôle d'accès via une table SQL?

est ici un dépouillé exemple de ce que la table, je suis en train de contrôler l'accès à ressemble:

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 de choses en spécifiant un identifiant de groupe. Dans l'exemple ci-dessus, l'utilisateur 1 s'est vu accorder un niveau d'accès de 50 au groupe 1, qui devrait s'appliquer à moins qu'il n'y ait d'autres règles accordant un accès plus spécifique à une chose individuelle.

J'ai besoin d'une requête qui renvoie une liste de choses (ids seulement est correct) avec le niveau d'accès pour un utilisateur spécifique. Donc, en utilisant l'exemple ci-dessus je veux quelque chose comme ça 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 peux trouver est la suivante:

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 qui retourne plusieurs lignes, quand je ne Je veux un pour chaque rangée dans le tableau des «choses». Je ne suis pas sûr de savoir comment obtenir une seule ligne pour chaque 'chose', ou comment hiérarchiser les règles 'chose' plutôt que les règles 'groupe'. Si cela peut vous aider, la base de données que j'utilise est PostgreSQL.

S'il vous plaît n'hésitez pas à laisser un commentaire s'il y a des informations que j'ai manqué.

Merci d'avance!

Répondre

1

Je ne sais pas le dialecte Postgres SQL, 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 

Soit dit en passant, je n'aime pas la conception. Je préférerais la table d'accès à diviser 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 parce que les clés étrangères peuvent désormais être utilisées dans les tableaux d'accès.

+0

Ooh c'est une solution intéressante. Semble travailler sur mes données d'exemple limitées, je vais essayer certaines données réelles et voir comment ça marche. Pour l'anecdote, je suis d'accord que le design de la table est un peu méchant. Je suis malheureusement coincé avec. – Dan

+0

Merci, Tony. Cela fonctionne parfaitement. – Dan

1

Bien qu'il existe plusieurs bonnes réponses, le serait probablement plus efficace quelque chose comme ceci:

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 vous a ajouté à summarization requête pour obtenir ce que vous cherchez.

0

Tony:

Pas une solution mauvaise, je l'aime, semble fonctionner.Voici votre requête après peaufinage mineur:

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 complètement prendre le point à ce sujet ne pas être un schéma idéal. Cependant, je suis coincé dans une certaine mesure.


Josef:

Votre solution est très similaire à des choses que je jouais avec, et mes instincts (tels qu'ils sont) me dire qu'il devrait être possible de le faire de cette façon. Malheureusement, il ne produit pas tout à fait des résultats 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érieur, plutôt que plus la valeur d'accès spécifique « chose » de 10, ce qui est ce que je suis après. Je ne pense pas qu'il existe un moyen de corriger cela dans un GROUP BY, mais si quelqu'un a des suggestions, je suis plus qu'heureux d'être prouvé incorrect sur ce point.

Questions connexes