[2009-01-21] Šikovnější Permission
- David Grudl
- Nette Core | 8218
Mějme jednoduché ACL scéma:
$acl = new Permission;
$acl->addRole('member');
$acl->addResource('article');
Nastavit, že role member
má přístup ke zdroji
article
, je snadné:
$acl->allow('member', 'article');
$acl->isAllowed('member', 'article'); // -> vrací TRUE
Pokud by mělo rozhodování fungovat dynamicky, tedy že member
bude mít přístup jen mimo pracovní dobu, lze využít
IPermissionAssertion:
class TimeAssertion implements IPermissionAssertion
{
public function assert(Permission $acl, $role, $resource, $privilege)
{
return !Time::isWorkingTime(); // fiktivní metoda
}
}
$acl->allow('member', 'article', NULL, new TimeAssertion);
$acl->isAllowed('member', 'article'); // o výsledku rozhoduje TimeAssertion::assert()
Jak ale řešit případ, když role member
má mít přístup
ke zdroji article
jen tehdy, pokud je jeho
vlastníkem?
Od revize 193 je k tomu možné použít rozhraní IRole
a
IResource
:
class Member implements IRole
{
public $ownerId;
public function getRoleId()
{
return 'member';
}
}
class Article implements IResource
{
public $ownerId;
public function getResourceId()
{
return 'article';
}
}
$member = new Member;
$article = new Article;
$member->ownerId = $article->ownerId = 123; // vymyslíme nějaké hodnoty ownerId
$acl->allow('member', 'article', NULL, new MyAssertion);
// místo $acl->isAllowed('member', 'article') zavoláme
$acl->isAllowed($member, $article); // skutečný název role a resource vracejí metody getRoleId resp. getResourceId
Zbývá vytvořit nový MyAssertion, který bude porovnávat vlastnosti
ownerId
předané role a zdroje.
class MyAssertion implements IPermissionAssertion
{
public function assert(Permission $acl, $role, $resource, $privilege)
{
return $acl->getQueriedRole()->ownerId === $acl->getQueriedResource()->ownerId;
}
}
Proč v metodě není přímo
return $role->ownerId === $resource->ownerId
? Parametry
$role
a $resource
jsou řetezce, zde konkrétně
'member'
a 'article'
a to z toho důvodu, že celá
struktura ACL může být složitější:
$acl = new Permission;
$acl->addRole('registered');
$acl->addRole('member', 'registered');
$acl->addResource('item');
$acl->addResource('article', 'item');
$acl->allow('registered', 'item', NULL, new MyAssertion); // povolení pro member & article vyplývá z dědičnosti
Pokud nyní zavoláme $acl->isAllowed($member, $article);
,
bude metodě MyAssertion::assert()
jako $role
&
$resource
předány hodnoty 'registered'
&
'item'
, tedy právě ty hodnoty, s jakými byl volán
allow(...)
. Skutečné argumenty, které jsme předali metodě
isAllowed(...)
se proto získají pomocí metod
getQueriedRole()
a getQueriedResource()
.
Třída Permission doznala řady úprav, nicméně by měla zůstat zpětně kompatibilní podle současné dokumentace.
- krajaac
- Člen | 45
Super, moc dík Davide.
Ještě by se mi hodilo propojení s User.
Nevím, jak to mám implementovat, ale chtěl bych něco takového:
/* Prihlaseny uzivatel, role: admin, v identite: user_id = 123
* @var $user User
*/
$user;
$article = new Article();
$article->ownerId = 123;
$user->isAllowed($article, 'edit'); // melo by vratit TRUE
ACL schéma vypadá nějak takto:
$acl = new Permission;
$acl->addRole('admin');
$acl->addResource('article');
$acl->allow('admin', 'article', 'edit', new MyAssertion);
Pak v MyAssertion asi něco jako toto:
class MyAssertion implements IPermissionAssertion
{
public function assert(Permission $acl, $role, $resource, $privilege)
{
return $acl->getQueriedUser()->getIdentity()->user_id === $acl->getQueriedResource()->ownerId;
}
}
Může někdo poradit, jak na to?
Editoval krajaac (23. 1. 2009 14:49)
- Ondrej
- Člen | 110
krajaac napsal(a):
public function assert(Permission $acl, $role, $resource, $privilege)
{
return $acl->getQueriedUser()->getIdentity()->user_id === $acl->getQueriedResource()->ownerId;
}
}
A co to udelat takto?
<?php
public function assert(Permission $acl, $role, $resource, $privilege)
{
return Environment::getUser()->getIdentity()->user_id === $acl->getQueriedResource()->ownerId;
}
?>
- krajaac
- Člen | 45
Ondrej napsal(a):
A co to udelat takto?
<?php public function assert(Permission $acl, $role, $resource, $privilege) { return Environment::getUser()->getIdentity()->user_id === $acl->getQueriedResource()->ownerId; } ?>
Dík, to by šlo.. Pořád jsem hledal složité řešení, až jsem si neuvědomil, že to jde tak jednoduše :)
- Jod
- Člen | 701
Ako môžem takéto assertion aplikovať na zoznamy? Pretože aj keď môžem riešiť prístup k jednému prvku, ale jemu zvykne predchádzať celý zoznam a tam mi nejak nenapadá ako nato.
actionDefault → Zoznam článkov. Podľa práv vidí všetky, alebo len
svoje. To je najlepšie ošetriť where podmienkov už v sql. Ako zistiť, že
tam mám hodiť tú podmienku?
actionDetail → Detail článku. Tu je to jasné z príkladu.
Vie niekto? Ešte si začnem myslieť, že nette neni dokonalé =D
- viktorc
- Člen | 21
Ked implementujem vlastné Member a Article a vrátim po autentizácii
v Identity objekt Member, konstructor Identity ho prevedie na array. Mohol by
zostat naďalej ako objekt Member (IRole)?
Dal by sa potom elegantnejšie overovať prístup. Bolo by to také
čistejšie.
// MyAuthenticator po uspesnom overeni uzivatel vrati Role
function MyAuthenticator::authenticate (..) {
...
return new Identity($login, new Member($ownerId), $userdata);
}
function MyAssertion::assert(Permission $acl, $role, $resource, $privilege) {
$acl->getQueriedRole() ....
...
}
// nasledne overenie autorizacie v Presenteri, vyvola MyAssertion::assert,
// ale k objektu Member sa cez getQueriedRole() nedostanem
Environment::getUser()->isAllowed(new Article($ownerId))
// iste, da sa to nahradit, komplikovanejsim
$identity=Environment::getUser()->getIdentity();
$member=new Member($identity->role, $identity->ownerId);
$acl=Environment::getUser()->getAuthorizationHandler();
$acl->isAllowed($member, new Article($ownerId))
Editoval viktorc (14. 8. 2009 15:44)
- vlki
- Člen | 218
Konstruktor Identity
totiž počítá s tím, když má
nějaký uživatel více rolí. Mělo by tedy stačit změnit to vytváření
identity na:
// MyAuthenticator po uspesnem overeni uzivatel vrati Role
MyAuthenticator::authenticate (..) {
...
return new Identity($login, array(new Member($ownerId)), $userdata);
}
Zkus;)
Editoval vlki (14. 8. 2009 15:32)
- David Grudl
- Nette Core | 8218
phx napsal(a):
Otazka: Proc Assertion musi byt objekt? Osobne se mi jevi hezci a univerzalnejsi callback.
Odpověď je trošku rozsáhlejší. Ale ano, tady by callback nejspíš pasoval lépe.
- Roman Ožana
- Člen | 52
Dneska jsem experimentoval s tímhle vypadá to aj celkem použitelně.
Mám obecnou třídu implementující IResource
<?php
class Resource implements IResource {
public $id = null;
public $name = null;
function __construct($name , $id = null) {
$this->name = $name;
$this->id = $id;
}
public function getResourceId() {
return $this->name;
}
}
?>
Pak mám obecnou třídu implementující IRole:
<?php
class Role implements IRole {
public $id = null;
const guest = 'guest';
const member = 'member';
const admin = 'admin';
public $role = null;
function __construct() {
$args = func_get_args(); // get input args
// instance of User or Identity
if (func_num_args() == 1) {
if ($args[0] instanceof User) {
$this->role = $args[0]->getIdentity()->role;
$this->id = $args[0]->getIdentity()->id;
} elseif ($args[0] instanceof Identity) {
$this->role = $args[0]->role;
$this->id = $args[0]->id;
} else {
throw new Exception('Wrong input object.');
}
}
// role / id
if (func_num_args() >= 2) {
$this->role = $args[0];
$this->id = $args[1];
}
}
public function getRoleId() {
return $this->role;
}
}
?>
a samozřejmě taky
<?php
class PermissionAssertion implements IPermissionAssertion {
public function assert(Permission $acl, $role, $resource, $privilege) {
if ($role === Role::admin) return true; // admin can everything
// if ($role == Member::role) return Environment::getUser()->getIdentity()->id === $acl->getQueriedResource()->id; // member can only self account
return $acl->getQueriedRole()->id === $acl->getQueriedResource()->id;
//return false;
}
}
?>
No a nakonec zhruba takovéhle Permission
<?php
class ACL extends Permission {
public function __construct() {
$this->addRole(Role::guest); // guest
$this->addRole(Role::member, Role::guest); // member
$this->addRole(Role::admin, Role::member); // admin
$this->addResource('account');
$this->deny(Role::guest);
$this->allow(Role::member, 'account', null, new PermissionAssertion());
$this->allow(Role::admin, 'account', null, new PermissionAssertion());
}
}
?>
Potom stačí napsat:
<?php
$acl = new ACL();
Debug::dump($acl->isAllowed(new Role(Environment::getUser()), new Resource('account', 71))); // zalezi na id uzivatele
Debug::dump($acl->isAllowed(new Role('admin', 71), new Resource('account', 71))); // vrací vzdy true
Debug::dump($acl->isAllowed(new Role('guest', 71), new Resource('account', 71))); // vrací false
// atd.
?>
Editoval Roman Ožana (19. 1. 2010 15:28)
- hrach
- Člen | 1838
je tam, jen misto obejktu predavas callbacky, viz https://github.com/…a74f1468935c
- Teyras
- Člen | 81
hrach napsal(a):
je tam, jen misto obejktu predavas callbacky, viz https://github.com/…a74f1468935c
OK, to je určitě o něco intuitivnější…
- bojovyletoun
- Člen | 667
Ahoj, chystám se použít Permission a to tak, aby měl k editaci
článku(a jeho potomkům- článek je i kategorie) přístup ten, kdo ho
vytvořil. Jedna cesta by byla, že co uživatel, to role. To se mi zdá
nečisté. Proto bych vytvořil jen roli editor a množství resource
[„article12“,„article732“].
Je dobré pro to využít asserce? Nebo nějakou jednodušší třídu (jenže
budu chtít Authorizator využít i na jiné věci)
V aserci by se kouklo, zda id aktuálního uživatele je id autora
resource.
Dál se chci zeptat na další informace ohledě Assercí- téma je staré, v dokumentaci jsem toho moc nenašel a ve fóru 2 topicy.
Jinak chápu to správně: queriedrole/resource se používá kvůli tomu, že interně se pracuje se stringy (v případě že role/zdroje jsou objekty)?
- Filip Procházka
- Moderator | 4668
A proč by uživatel nemohl být role? Co takhle mít role, ale do toho ještě uživatele jako role. Tím můžeš mít skupiny oprávnění (role) a navíc specifické oprávnění (uživatelé).
- bojovyletoun
- Člen | 667
Vlastně, když to rozmýšlím, tak je to asi jediná cesta a není ani nečisté. Prostě mě by se hodilo, aby Permission nebral role, ale rovnou uživatele. A právě tím, že definuji roli za každého uživatele, tak tím docílím takového chování. Samozřejmě klasické role (generic, skupiny) s tím lze míchat.
gmvasek: chtěl bych se o tom dozvědět víc. Původně jsem sem chtěl napsat, že jediné, co jsem se dočetl, je, že místo class Assertion jsou callbacky.
Jinak ještě jsem našel další pohled na Asserce (i když do jsou funkce už, tak se mi tenhle název líbí): defatko umožňuje seskupovat množiny zdrojů – místo, abych definoval pro každý article jeden resource, tak definuji 1 article a v asserci rozhoduji, zda je user jeho vlastníkem. Šikovná věc.
- Podbor
- Člen | 19
Zdravím,
já bych měl k tomuto tématu otázku na odmítnutí či povolení přístupu
k jednotlivým částem aplikace. Práva mám definována v databázi dle
schématu ACL (resource, privilege, role, acl) a takto je načítám přes svůj
Autorizátor, který dědí třídu „Permission“.
Chtěl bych vyřešit to, že když uživatel přes URL zadá požadavek na
nějaký presenter a akci, ke které nemá přístup, aby byl přesměrován na
hlavní stránku. V současné době jsem to vyřešil takhle:
<?php
abstract class BasePresenter extends Nette\Application\Presenter
{
protected function startup()
{
parent::startup();
if(!$this->getUser()->isAllowed($this->getResource(),'show')) $this->redirect('Homepage:');//pokud uživatel nemá právo prohlížet daný Presenter, bude automaticky přesměrován
}//public function startup()
/**
* Chráněná abstraktní metoda "getResource", kteoru mís všichni potomci Základního preseneru překrýt. Má vracet řetězec obsahující název "zdroje"
*/
protected abstract function getResource();
?>
Čili předek všech Presenterů kontroluje přes „isAllowed“, jestli uživatel má právo na daný zdroj. (Zatím to zkouším pouze na úrovni toho, že beru zdroj = presenter). V každém potomkovi pak dojde k implementaci metody „getResource()“, která vrací řetězec se jménem presenteru.
<?php
class RegistrationPresenter extends BasePresenter
{
private $resource = "registration";
protected function getResource()
{
return $this->resource;
}//protected function getResource()
?>
A přesně tady bych se chtěl zeptat, jestli to jde vyřešit nějak lépe
než prostě staticky uloženou hodnotou ve vnitřní proměnné daného
presenteru?
Předem díky za rady.
- bojovyletoun
- Člen | 667
využít` $this->name nebo $this->action `.. jinak doporučuji pročíst toto , je to ale dlouhé, ale pořád jde o jeden z nejlepších tutoriálů
Editoval bojovyletoun (23. 3. 2011 15:02)
- Podbor
- Člen | 19
bojovyletoun napsal(a):
využít` $this->name nebo $this->action `.. jinak doporučuji pročíst toto , je to ale dlouhé, ale pořád jde o jeden z nejlepších tutoriálů
Díky Ti, to první je přesně to, co jsem hledal, ale nějak jsem se k těmto vlastnostem nemohl dopátrat. Na ten zmíněný odkaz se ještě určitě podívám, už jsem si ho jednou četl, ale ještě to jednou zopakuji, ať se konečně dostanu k tomu „jádru pudla“.
Editoval Podbor (24. 3. 2011 9:25)
- OndrejMalacka
- Člen | 2
Chtěl bych se zeptat, jakým způsobem je možné šikovnější permissions použít v šabloně (kvůli zobrazení/nezobrazení ikon). Pokusil sem se (neúspěšně) to udělat následovně:
Šablona:
{if $user->isAllowed(new UsersResource($u['id']),'edit')}
<a href="{plink edit,$u->id}">
<img src="{$baseUri}/images/editIcon.gif" alt=""/>
</a>
{/if}
Třída resource:
class UsersResource implements Nette\Security\IResource {
public $ownerId;
function __construct($id) {
$this->ownerId = $id;
}
public function getResourceId() {
return 'Front:Users';
}
}
co se tyce ACL:
$this->allow('user', 'Front:Users', 'edit', function($permission, $role, $resource, $privilege) {
return Nette\Environment::getUser()->getIdentity()->id === $permission->getQueriedResource()->ownerId;
});
Když se to pokusím udělat v presenteru, tak to funguje jak má, ovšem v šabloně už nikoliv …
- OndrejMalacka
- Člen | 2
Tak potřebuji projít v šabloně přes všechny položky a editační tlačítko u té položky, která je daného uživatele. Sice by to šlo obejít, ale chtěl sem to mít hezky právě pomocí šikovnějších permissions
- llook
- Člen | 407
Trochu se vracím k programování a občas se zasekávám na základních věcech. Teď přemýšlím, jak nejelegantněji řešit velmi obvyklý úkol: Přihlášený uživatel může upravovat svůj profil, ale ne ostatní profily.
Takže mám uživatele $user
a nějaký uživatelský profil
$profile
. Jak má vypadat ta omáčka, aby se dalo
napsat toto?:
$user->isAllowed($profile, 'edit');
Autorizér pro rozhodnutí potřebuje znát $user->id
a
$profile->user_id
. Přečetl jsem si toto vlákno a zatím
nejpoužitelnější, co mi z toho vychází, je zhruba tohle:
class User extends Nette\Security\User implements Nette\Security\IRole
{
public function getRoleId()
{
return __CLASS__;
}
public function __construct()
{
$this->addRole($this);
}
}
class Profile implements Nette\Security\IResource
{
// ...
}
$acl->allow(null, null, 'edit', function ($permission, $role, $resource, $privilege) {
$queRole = $permission->getQueriedRole();
$queResource = $permission->getQueriedResource();
if (($queRole instanceof User) && ($queResource instanceof Profile)) {
return $queRole->getId() === $queResource->getUserId();
} else {
return false;
}
});
Zkrátka z uživatele udělat jeho vlastní roli a jeho ID potom získávat přes getQueriedRole.
Ale moc se mi to nelíbí. Kdybych to tímto způsobem psal v C#, musel bych
přetypovávat a to obvykle spolehlivě značí špatný návrh. Krom toho si
nejsem moc jistý tím, co mám vracet v getRoleId
a v
getResourceId
– spíš nějaký identifikátor typu
(„profile“, „user“ apod.) nebo spíš identifikátor konkrétního
objektu („profile/123“, „user/321“ apod.)?
Budu rád za každou myšlenku.
- awsickness
- Člen | 98
mzues to mit udelane takhle
mas objekt user nad kterym udelas neco jako isEditable nebo tak neco a tam se
podivas zda sedi tento uzivatel s uzivatelem ktery si na zaklade id vytahl
z db.
prijde mi to nejrozumnejsi reseni, ktery jde udelat.
$user->canEdit($user->identity)
....
- llook
- Člen | 407
Profilem jsem nemyslel systémové údaje o uživateli, ale spíš něco jako profil na Facebooku nebo na Lidech. Prostě stránka, kam si uživatel může nahrát fotku a nějaký kecy, ale jinak je to samostatná entita.
Uznávám, že jsem možná nevybral nejlepší příklad, ale můžeme si místo profilu představit třeba článek v blogu: Uživatel může upravovat svoje články, ale ne cizí.
class User extends Nette\Security\User implements Nette\Security\IRole
{
function __construct()
{
$this->addRole($this);
}
...
}
class Article implements Nette\Security\IResource
{ ... }
$acl->allow(null, null, 'edit', function ($permission, $role, $resource, $privilege) {
$queRole = $permission->getQueriedRole();
$queResource = $permission->getQueriedResource();
if (($queRole instanceof User) && ($queResource instanceof Article)) {
return $queRole->getId() === $queResource->getAuthorId();
} else {
return false;
}
});
Příklad použití:
{if $user->isAllowed($article, 'edit')}
<a n:href="Article:edit $article->id">Překopat</a>
{/if}
Je tohle ten nejlepší způsob?
- Jan Jakeš
- Člen | 177
Ehm, četl si první první
příspěvek? Zdá se mi to nebo popisuje přesně to, co potřebuješ?
Nejčistším řešením asi bude použití Assertion
.
- llook
- Člen | 407
Juan napsal(a):
Ehm, četl si první první příspěvek? Zdá se mi to nebo popisuje přesně to, co potřebuješ? Nejčistším řešením asi bude použití
Assertion
.
Ehm, četl. Ano, první příspěvek zřejmě navrhuje velmi podobné
řešení a mě to připadá jako špinavé řešení. Další příspěvky ve
vlákně navrhují buďto totéž, nebo ještě horší
(Environment::getUser()
). Vzhledem k tomu, že je to 3 roky
starý materiál, tak jsem doufal, že někdo od té doby třeba přišel na
nějaké čistší řešení.
- Jan Tvrdík
- Nette guru | 2595
Jestli ti to připadá špinavé, tak máš pravdu. Snažil jsem to HosipLanovi na Nette Brain Cloud vysvětlit, ale pak se to nějak zakecalo. Související vlákno: https://forum.nette.org/…ou-indentitu
Editoval Jan Tvrdík (17. 1. 2012 13:42)