Ukládání Permission do databáze

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
morousej
Člen | 18
+
0
-

Chtěl bych umožňovat dynamickou úpravu rolí, zdrojů a přístupu k nim z administrace, takže by bylo vhodné ukládat tyto parametry databáze, jaký používáte přístup? Napadá mě ukládání serializovaného objektu Permission do databáze, ale zrovna pohodlné a čisté to nebude…

David Grudl
Nette Core | 8172
+
0
-

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

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

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.

phx
Člen | 651
+
0
-

Bodlo by napsat co je cilem aby logika opravneni zvladala. Takhle se v tom moc nevyznam. Napr chci stromovou strukturu roli s dedenim opravenini…

Panda
Člen | 569
+
0
-

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 sloupce allowed) 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.

phx
Člen | 651
+
0
-

Krasne receno;)

Jen je nutne dat si pozor pri dedeni prav od vice rodicu. Reseni bude asi pravidlo ze povoleni vyhrava. (myslim tim kdyz se sejdou od rodicu prava, kde jeden tvrdi, ze muze a druhy ze nee ⇒ potomek muze)

David Grudl
Nette Core | 8172
+
0
-

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

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

Pěkně si to sepsal Dave, doporučoval bych udělat example do balíčku na toto téma.

romansklenar
Člen | 655
+
0
-

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ě.

Jod
Člen | 701
+
0
-

Škoda, že si to tam nedal skôr než som si to spravil sám, ušetrilo by mi to kopu času )