dynamicke ACL na clancich
- Vitek Jezek
- hledá kolegy | 285
ze SQL bychom radi pomoci modelu tahali data – napr. clanky. Na strance
bychom jich meli zobrazit prave 20. Jenze na nektere z nich nemusime mit
opravneni pro cteni, coz zjistime az po zminenem SQL selectu.
Pokud mezi vybranymi bude tedy radek, na ktery nemam opravneni, tak jej
nezobrazim a budu mit zobrazeno jen radku 19. Nyni muzu zjistit, ze nemam vsech
20 polozek a zazadat si o dalsi polozku. Jenze opet na ni nemusim mit
opravneni a kruh se uzavira…
(Maximalne me napadla mini-optimalizace v podobe vybrani vice radku
najednou – brat treba nasobky 40, ale i tak to aplikaci muze zabit, pokud
budeme mit treba 1000 clanku a ja nestastnik budu mit vsehovsudy pristupnych
jen 10).
Nemate nejaky pekny napad, jak z toho ven? Pripojinam, ze sice ACL v SQL bude, ale rozhodne nebude dostatecne trivialni na to, abych jej dokazal vyselektovat na jediny SQL dotaz (problem bude s dedenim, allow/deny etc). Navic to porusuje myslenku OOP – ACL by mel byt ‚black box‘.
- pmg
- Člen | 372
Pokud se požadovaná role ukládá pro každý článek zvlášť, pak je
potřeba si k němu uložit požadovanou roli. Takže stačí provést SQL
obdobu funkce isInRole
(na tom nevidím nic nekoncepčního).
Doporučuju si pro role vytvořit přídavnou tabulku, kam si pro každou
dvojici rolí uložíš, jestli je jedna potomkem druhé. Pak je možné
použít jediný dotaz.
$roles = $user->getRoles();
SELECT * FROM articles
WHERE required_role IN (
SELECT parent FROM role_parents
WHERE role IN ($roles)
)
Pokud by metoda getRoles
zohledňovala i zděděné role (jak
jsem dříve navrhoval), obejdeš se dokonce i bez přídavné tabulky a
vnořeného dotazu.
- Patrik Votoček
- Člen | 2221
A proč prostě nevyselektuješ rovnou jenom články ke kterým má uživatel oprávnění?
- pmg
- Člen | 372
Zkusím to lépe. Tou zmatenou první větou v předchozím příspěvku
jsem chtěl říct, že ke každému z článků bude nutné si uložit
nějakou informaci regulující přístup. To je odlišnost od přístupu, kdy
je v databázi jediný zdroj article
– společný pro všechny
články.
Z koncepčního hlediska není správné ukládat přímo požadovanou roli.
Lepší je vytvořit tabulku article_resources
, která umožní
přiřadit každému článku několik zdrojů, kterých je součástí. Aby
uživatel mohl s článkem pracovat, musí mít v tabulce acl
povolenu práci s některým ze zdrojů, kterých je daný článek
součástí.
Pro snazší práci s dědičností rolí a zdrojů vytvoříme tabulky
role_members
a resource_members
, které budou pro
každou položku obsahovat ji samotnou a všechny její předky.
Tabulku acl
bych koncipoval tak, že přítomnost nějaké
trojice (role, resource, permission) by značila její povolení. Myslím, že
v praxi to s vhodným použitím dědičnosti stačí – a obecně je
lepší říct, co je dovoleno, než povolit hodně a pak ubírat.
Doporučuji udělat si vlastní implementaci IAuthorizator
, a to
nejlépe jeho začleněním do modelu. Na začátku se pravidla pro uživatele
mohou načíst a kešovat. Je možné přidat i metody allow
a
deny
, které upraví pravidlo v databázi. Podobně je dobré
implementovat také IAuthenticator
.
Oproti schématu
v dokumentaci bych jako primární klíč nastavil rovnou název role
(pravidla); v tabulce acl
by měl primární klíč obsahovat
všechny tři položky. Pole id
v dokumentaci slouží
částečně i jako řadící číslo, ale pokud jen povolujeme, na pořadí
nám nezáleží.
Na začátku z databáze zjistíme všechny role, které uživatel má. Podle nich pak vybereme zdroje, ke kterým má přístup.
// přidělené role poprvé přečte metoda authenticate
$roles = $user->getRoles();
// k nim se nám budou hodit i jejich předci
$roles = SELECT member FROM role_members WHERE role IN ($roles)
// k rolím si načteme přístupné zdroje
SELECT DISTINCT rm.member resource, acl.privilege privilege FROM acl
JOIN resource_members rm ON rm.resource = acl.resource WHERE acl.role IN ($roles)
// takto potom vybereme články
SELECT * FROM articles a
JOIN article_resources ar ON ar.article = a.id
JOIN resource_members rm ON rm.resource = ar.resource
WHERE rm.member IN ($resources)
GROUP BY a.id
LIMIT 20