Come posso fare il controllo di accesso tramite una tabella SQL?
-
03-07-2019 - |
Domanda
Sto cercando di creare un sistema di controllo degli accessi.
Ecco un esempio ridotto di come appare la tabella a cui sto cercando di controllare l'accesso:
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
E la tabella di controllo degli accessi è simile alla seguente:
access table:
user_id type object_id access
1 group 1 50
1 thing 1 10
1 thing 2 100
L'accesso può essere concesso specificando direttamente l'id della "cosa" oppure concedendo un intero gruppo di cose specificando un ID del gruppo. Nell'esempio sopra, all'utente 1 è stato concesso un livello di accesso di 50 al gruppo 1, che dovrebbe applicarsi a meno che non ci siano altre regole che garantiscano un accesso più specifico a una singola cosa.
Ho bisogno di una query che ritorni un elenco di cose (solo ids va bene) insieme al livello di accesso per un utente specifico. Quindi, usando l'esempio sopra, vorrei qualcosa di simile per l'id utente 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)
Il più vicino che posso trovare è questo:
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)
)
Ma questo restituisce più righe, quando ne voglio solo una per ogni riga nella tabella "cose". Non sono sicuro di come passare a una singola riga per ogni 'cosa', o come dare priorità alle regole 'cosa' rispetto alle regole 'gruppo'.
Se aiuta, il database che sto usando è PostgreSQL.
Non esitate a lasciare un commento se ci sono informazioni che mi sono perso.
Grazie in anticipo!
Soluzione
Non conosco il dialetto SQL di Postgres, ma forse qualcosa del tipo:
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
Per inciso, non mi piace il design. Preferirei che la tabella di accesso fosse divisa in due:
thing_access (user_id, thing_id, access)
group_access (user_id, group_id, access)
La mia query diventa quindi:
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
Preferisco questo perché ora le chiavi esterne possono essere utilizzate nelle tabelle di accesso.
Altri suggerimenti
Ho appena letto un articolo ieri sera su questo. Ha alcune idee su come farlo. Se non riesci a utilizzare il link sul titolo, prova a utilizzare Google Scholar su Limitare la divulgazione in Ippocratico Basi di dati.
Mentre ci sono molte buone risposte, la più efficace sarebbe probabilmente qualcosa del genere:
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
Che utilizza semplicemente il riepilogo aggiunto alla tua query per ottenere ciò che stai cercando.
Tony:
Non è una cattiva soluzione, mi piace, sembra funzionare. Ecco la tua domanda dopo una piccola modifica:
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;
E i risultati sembrano corretti:
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 |
Faccio completamente il fatto che non sia uno schema ideale. Tuttavia, ne sono bloccato in una certa misura.
Josef:
La tua soluzione è molto simile alle cose con cui stavo giocando, e i miei istinti (come sono) mi dicono che dovrebbe essere possibile farlo in quel modo. Purtroppo non produce risultati completamente corretti:
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 |
Il livello di accesso per 'cosa 1' ha preso il valore di accesso 'gruppo' più alto, piuttosto che il valore di accesso 'cosa' più specifico di 10, che è quello che sto cercando. Non credo che ci sia un modo per risolverlo all'interno di un GROUP BY
, ma se qualcuno ha qualche suggerimento sono più che felice di essermi dimostrato errato su quel punto.