Ukládání Permission do databáze
- David Grudl
- Nette Core | 8228
Nejlépe si vytvořit tabulku se sloupečky id, role, resource, privilege, allowed. Z něj vygenerovat objekt Permission a ten kešovat
- phx
- Člen | 651
Otazka: dynamicka uprava roli a zdroju = napr dnes role moderator muze mazat clanky a zitra mu tuto moznost seberu?
Co je mysleno zdrojem? Roli chapu jako nazev role (admin, moderator, uzivatel, host, …)
Osobne bych vse resil nejaky seznamem roli (kazda role ma nejaka opravneni) a uzivatelim jen ty role prideloval, ale to jsem asi vedle co? (vedle zminovaneho problemu).
Davide muzes mi okomentovat vyznam sloupecku? Neco chapu, ale…
- id = PK
- role = nazev role (admin, moderator,…)
- resource = zdroj, ale ceho, na co?
- privillege = opravneni???
- allowed = povoleni???
Osobne jsem si to predstavoval, kdyz zaznam v DB je prava mam kdyz neni nema.
Diky.
Ptam se proto, ze neco podobneho budu take resit…
- vlki
- Člen | 218
Uz jsem tento problem resil. Mam tri tabulky – resources, roles a rules. Pokud bude zajem, muzu zde zverejnit cele reseni.
Kazdopadne bych to nedelal stylem „je v tabulce = allow, neni = deny“, protoze pokud mas stromovou strukturu resources, tak pak nemuzes vyuzivat dedeni pravidel.
Zdrojem je myslen napr. presenter. Zalezi na tve implementaci.
- Panda
- Člen | 569
Já to chápu takto:
- role je skutečně role uživatele – např. moderátor, redaktor, návštěvník, zaregistrovaný uživatel, správce…
- zdrojem (sloupec
resource
) se rozumí nějaký logický prvek webu/aplikace – článek, stránka, uživatel, položka v menu, anketa… – při dobrém návrhu aplikace bude jistě možné použít presenter = resource - oprávněním (sloupec
privilege
) se rozumí nějaká konkrétní činnost, kterou uživatel může (nebo naopak nemůže – podle sloupceallowed
) s prvkem dělat – například stránka půjde smazat, upravit, vytvořit, s článkem půjde dělat totéž + komentovat, s anketou také totéž, navíc půjde hlasovat, falšovat výsledky k potěšení vlastního ega… - sloupec
allowed
bude indikovat, zda uživateli dané role bude daná činnost se ‚zdrojem‘ povolena, či odmítnuta (pokud objekt Permission nenajde pravidlo pro danou roli, resource a oprávnění, tak bude automaticky odmítnuta)
Resources pravděpodobně budou dány ‚natvrdo‘ logikou aplikace – třeba již zmiňované presenter = resource. Je možné použít také tabulku v databázi, nicméně asi to nebude věc, která se bude měnit každý den (a zpravidla se bude měnit s úpravou aplikace či přidáním modulu).
K tomu by se pravděpodobně hodila tabulka obsahující seznam rolí
(třeba id
– PK, name
– jméno role)
s vedlejší tabulkou, kde bude uložena stromová struktura
(parent
, child
) – to kvůli tomu, aby jedna role
mohla dědit od víc rodičů. Pokud tato vlastnost není potřeba, je možné
sloupec parent
fouknout rovnou k rolím.
Celé řešení je možné udělat jako třídu, která bude buď dědit od
Permission
(a bude mít tak přístup k vnitřní logice
Permission
, což může být v některých případech
užitečné), nebo bude sloužit jen jako jeho ‚obal‘ a bude obstarávat
jeho sestavování z databáze a cachování.
Jak řekl David, vyplatí se celý objekt kešovat – při změně pravidel bude potřeba keš invalidovat a sestavit z databáze nový objekt.
- David Grudl
- Nette Core | 8228
Přihodím pár informačních zdrojů pro inspiraci:
Když jsem dělal před čtyřmi lety první koncepci modulu Permission, vycházel jsem z těchto myšlenek http://www.jantichy.cz/…y/autorizace a http://www.jantichy.cz/…hpbase/prava, později jsem se přiblížil k Zend_Acl, ze kterého kód třídy Permission z velké části vychází. Narozdíl od Zend_Acl používám pro zdroje a role textové identifikátory (namísto objektů Zend_Acl_Role nebo Zend_Acl_Resource). Použití objektů mi připadalo jako zbytečný overhead, navíc ne příliš praktický.
- David Grudl
- Nette Core | 8228
Panda to popsal naprosto přesně. Jednu z možných implementací nabízí třída Nette\Web\User (kterou jsem dnes ráno zkusil updatovat, budu rád za feedback).
- každý uživatel může mít v jednu chvíli přiřazeno více rolí
- nepřihlášený uživatel má automaticky roli
$user->guestRole
(výchozí hodnota'guest'
) - autentizovaný (tj. přihlášený) uživatel bez identity
má automaticky roli
$user->authenticatedRole
(výchozí hodnota'authenticated'
) - autentizovaný uživatel s identitou (
$user->getIdentity()
) vychozí roli nezíská (TODO: upravit chování?), o role se stará$user->getIdentity()->getRoles()
Identita je objekt implementující rozhraní
Nette\Security\IIdentity
. Nette\Web\User jej udržuje v session.
Jeho výchozí implementace Nette\Security\Identity
udržuje
informaci o jméně, rolích a libovolných dalších datech. Nicméně
(zatím) jako ValueObject, tedy nastaví se v konstruktoru a poté nelze
data měnit.
Pozor: ačkoliv má uživatel identitu, nemusí být
přihlášený! Identita se sice obvykle získá při přihlášení,
ale i když vás systém po nějaké době odhlásí
($user->signOut()
), stále si ji pamatuje (jméno, obsah
košíku na eshopu, …). Pokud se to hodí, můžeme při odhlášení identitu
vymazat: $user->signOut(TRUE)
.
Autentizace
Autentizace = přihlášení. Zajišťuje ji
$user->authenticationHandler
, tj. objekt implementující
rozhraní Nette\Security\IAuthenticator
. Jeho triviální
implementací je třída Nette\Security\SimpleAuthenticator
, která
dostane v konstruktoru seznam uživatelů a hesel jakožto asociativní pole.
Úkolem handleru je v metodě authenticate(array $credentials)
ověřit, zda uživatelské jméno a heslo odpovídá a v případě úspěchu
vrátit identitu (TODO: udělat to volitelně?). Neúspěch indikuje vyhozením
výjimky Nette\Security\AuthenticationException
s popisem důvodu.
Lze využít připravené konstanty IDENTITY_NOT_FOUND
nebo
INVALID_CREDENTIAL
třídy výjimky.
Autorizace
Autorizace = zjištění, zda má uživatel právo to či ono udělat. Rozhoduje se na základě rolí (kde se berou jsem popsal výše) a toho, zda je uživatel přihlášen. V nejjednodušších případech si vystačíme s indikátorem přihlášení:
if ($user->isAuthenticated()) ..
Silnější mechanismus je rozhodování na základě rolí:
if ($user->isInRole('editor')) ..
S tím si u většiny Běžných Aplikací™ vystačíte.
Nejsilnější mechanismus poskytuje autorizační handler
($user->authorizationHandler
), tj. objekt implementující
rozhraní Nette\Security\IAuthorizator
s metodou
isAllowed()
. Jeho implementací je právě třída
Permission
, do hry tak kromě rolí vstupují i parametery
resource & privilege, které vysvětlil Panda.
if ($user->isAllowed($resource, $privilege)) ..
Protože uživatel může mít více rolí, povolení dostane, pokud alespoň jedna role má povolení. Oba parametry jsou volitelné, výchozí hodnota nese význam všechny. Takže pokud např. parametr $privilege nevyužijeme, můžeme ho vynechat.
Ještě upozornění: pokud uživateli po odhlášení zůstane identita, tak
i včetně všech rolí – získatelných přes
$user->getIdentity()->getRoles()
. Nicméně metoda
$user->getRoles()
stav přihlášení zohledňuje. Stejně tak
i dotazy isInRole() a isAllowed() – proto je není nutné kombinovat
s dotazem isAuthenticated().
Odkud se objekty berou?
Žádná má knihovny nepoužívá singletony, Nette není výjimkou. Globální úložiště pro služby (services) poskytuje Nette\Environment::getServiceLocator(), zkratkou pro získání objektu User je Environment::getUser(). Objekt Nette\Web\User má settery pro autorizační a autentizační handlery, ale aby to celé pracovalo hezky líně (tj. objekty se vytvářejí, až když jsou skutečně potřeba), pokouší se Nette\Web\User získat handlery opět přes metodu Nette\Environment::getService(), kde identifikátorem je název interface.
Principiálně tedy stačí nastavit:
Environment::getServiceLocator()->addService($authenHandler, 'Nette\Security\IAuthenticator');
Environment::getServiceLocator()->addService($authorHandler, 'Nette\Security\IAuthorizator');
// kde handler je buď hotový objekt (pak to ale není lazy), jméno třídy nebo callback na továrnu
Službu lze nastavit i skrze config.ini:
service> Nette\Security\IAuthenticator = MyAuthenticator
- ViliamKopecky
- Nette hipster | 230
Pěkně si to sepsal Dave, doporučoval bych udělat example do balíčku na toto téma.
- romansklenar
- Člen | 655
Implementaci na toto téma jsem sepsal do dokumentace: Dynamická
správa rolí a zdrojů.
Nechtěl jsem to dávat do Code Snippets, protože ja tam vazba na databázi a
taky protože už jsem něco na tohle téma nakousl v dokumentaci Nette\Security\Permission,
ale konkrétně zde se to moc nehodilo
tak jsem se to rozhodl jen přesunout, protože to už i lidi začali používat a
není to zrovna ukázkový (rozuměj jednoduchý) příklad, jaký by měl být
v dokumentaci k dané třídě.