dynamicke ACL na clancich

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Vitek Jezek
hledá kolegy | 285
+
0
-

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
+
0
-

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
+
0
-

A proč prostě nevyselektuješ rovnou jenom články ke kterým má uživatel oprávnění?

pmg
Člen | 372
+
0
-

V6ak to ten select přesně dělá…

pmg
Člen | 372
+
0
-

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
pmg
Člen | 372
+
0
-

Pěkné řešení!