Nalezena chyba v autorizátoru – ACL
- Bulldog
- Člen | 110
Ahoj, pro testování mám následující autorizátor:
<?php declare(strict_types=1);
namespace App\Model\Security;
use Nette\Security\Permission;
class AuthorizatorFactory
{
public static function create(): Permission
{
$acl = new Permission();
$acl->addRole('guest');
$acl->addRole('client', 'guest');
$acl->addRole('admin', 'client');
$acl->addResource('Address');
$acl->deny('guest');
$acl->allow('client', 'Address', 'add');
$acl->deny('admin');
$acl->allow('admin', Permission::ALL, ['view', 'detail']);
bdump($acl->isAllowed('admin', 'Address', 'add')); // Vypíše true...
return $acl;
}
}
Očividně tedy zavolání funkce deny
bez dalších parametrů
nefunguje jak by měla. Podle prototypu funkce deny
bych
očekával, že bude zakazovat všechny privileges nad všemi resources, což se
očividně neděje.
EDIT
"php": ">=8.1",
"nette/application": "^3.1",
"nette/bootstrap": "^3.1",
"nette/caching": "^3.1",
"nette/database": "^3.1",
"nette/di": "^3.0",
"nette/finder": "^2.5",
"nette/forms": "^3.1",
"nette/http": "^3.1",
"nette/mail": "^3.1",
"nette/robot-loader": "^3.3",
"nette/security": "^3.1",
"nette/utils": "^3.2",
"latte/latte": "^2.9",
"tracy/tracy": "^2.8",
Editoval Bulldog (2. 11. 2022 20:45)
- Bulldog
- Člen | 110
Tak ještě update, pokud udělám toto:
$acl->allow('client', 'Address', 'add');
$acl->deny('admin', 'Address'); // Přidána resource
$acl->allow('admin', Permission::ALL, ['view', 'detail']);
bdump($acl->isAllowed('admin', 'Address', 'add')); // Už vypíše false.
Tak už se add
nepodědí a opravdu se přístup k
Address
zakáže.
Pokud se ale zeptám na to view
, nebo detail
, který
následně povoluji všem:
$acl->allow('client', 'Address', 'add');
$acl->deny('admin', 'Address'); // Přidána resource
$acl->allow('admin', Permission::ALL, ['view', 'detail']);
bdump($acl->isAllowed('admin', 'Address', 'view')); // Vypíše false.
bdump($acl->isAllowed('admin', 'Address', 'detail')); // Vypíše false.
Tak vypíše false, i když jsem jej povolil na všechny resources, tedy
i na Address
Editoval Bulldog (2. 11. 2022 20:50)
- Šaman
- Člen | 2659
Chápu to tak, že konkrétní oprávnění má přednost před konstantou
::ALL
.
Takže třeba v tom posledním příkladu má admin práva view
a
detail
na cokoliv, ale zároveň má zakázané všechno nad
Address
a toto pravidlo má vyšší prioritu.
Není to sekvenční, práva na ::ALL
se vztahují i na zdroje
a oprávnění přidané později. Takže ve chvíli, kdy voláš metodu
allow
, tak se mu nenastaví přístup ke konkrétním zdrojům, ale
uloží se u něho informace, že jedno z pravidel je „přístup na ALL“.
A teprve až se zavolá isAllowed
, tak se projdou všechna
pravidla a vyhodnotí se. Skoro bych i řekl, že je jedno v jakém pořadí
se práva přidělují/zakazují.
Editoval Šaman (2. 11. 2022 22:34)
- Bulldog
- Člen | 110
Kdyby to tak bylo, tak bych to chápal taky.
Ovšem v dokumentaci toto chování není popsáno, jediné, co tam je jen
ohledně rolí.
Role může dědit od jiné role či od více rolí. Co se ale stane, pokud má jeden předek akci zakázanou a druhý povolenou? Jaké budou práva potomka? Určuje se to podle váhy role – poslední uvedená role v seznamu předků má největší váhu, první uvedená role tu nejmenší.
Bohužel to, že by konkrétní pravidlo mělo přednost před obecným
::ALL
nesplňuje tento fakt:
$acl->allow('client', 'Address', 'add');
$acl->deny('client', 'Address', Permission::ALL);
bdump($acl->isAllowed('client', 'Address', 'add'));
Vypíše podle očekávání false.
Přijde mi divné, aby u resources měly konkrétní resources větší váhu,
než obecné all a u privileges měly zase váhu podle pořadí.
Editoval Bulldog (3. 11. 2022 9:11)
- David Grudl
- Nette Core | 8218
Na pořadí volání allow() / deny() myslím nezáleží. To že má největší váhu poslední role/resource v seznamu předků se týká pole rolí/resources, které jsou předané jako druhý parametr do addRole/addResource.
- Bulldog
- Člen | 110
Dobře děkuju.
předělal jsem to, aby to snad fungovalo.
Nebo jak by jste prosím zapsali, že ten, kdo je role
supervisor
může dělat vše, co ten, kdo je role
user
, ale nesmí nic editovat?
Původně jsem to měl takto:
$acl->addRole('user');
$acl->addRole('supervisor', 'user');
$acl->deny('user'); // zakážu vše, abych nezapomněl na zákaz čehokoliv.
// pak povoluju jen to, co user může
$acl->allow('user', 'Article', 'add');
$acl->allow('user', 'Article', 'view');
$acl->allow('user', 'Article', 'edit');
$acl->allow('user', 'Article', 'remove');
// další definice pro usera
// a teď nadefinuju supervisora, který může dělat všechno, co user, ale nesmí nic editovat.
$acl->deny('supervisor', Permission::ALL, 'edit');
To ale podle tohoto vlákna nefunguje, tak jsem to musel předělat tak, že
supervisor
od user
a nedědí, ale explicitně pomocí
CopyPasty definuju ty pravidla znovu, což je redundance jako blázen. Dá se to
nějak zkrátit?
EDIT
Vím, že místo role můžu předat pole rolí, ale mám ve zvyku definovat
pravidla pro jednotlivé role zvlášť. Je to kvůli přehlednosti, že vím
hned, že definice všeho pro usera mám pohromadě a nemusím to hledat. Tedy
pokud bych dělal průniky a dělil to podle nich, tak by musely vznikat další
dělení, kde při více než 3 rolích bych se z toho zbláznil, jelikož pro
4 role by bylo až 15 různých částí definic, kde bych stejně musel
procházet jednotlivé definice a hledat, co vlastně user může. bych se
zbláznil.
Editoval Bulldog (3. 11. 2022 18:58)
- David Grudl
- Nette Core | 8218
Permission je založeno na Zend_Acl a vnitřně funguje úplně stejně. Takže můžeš jako informační zdroj brát i dokumentaci Zend potažmo Laminas. Jen mám pocit, že ty dokumentace jsou sice obsáhlé, ale úplně plytké.
Každopádně jestli najdeš lepší vysvětlení, jak to přesně funguje, bude skvělé, když to sem odkážeš nebo rovnou přidáš do dokumentace.
- Bulldog
- Člen | 110
Tak jsem se na to díval a nechovají se stejně.
Viz příklady:
private function laminas(): void
{
$acl = new \Laminas\Permissions\Acl\Acl();
$acl->addRole('client');
$acl->addRole('admin', 'client');
$acl->addResource('Address');
$acl->allow('client', 'Address', 'add');
$acl->deny('admin');
bdump($acl->isAllowed('admin', 'Address', 'add'), 'Laminas'); // Vypíše false
}
private function zend(): void
{
$acl = new \Zend\Permissions\Acl\Acl();
$acl->addRole('client');
$acl->addRole('admin', 'client');
$acl->addResource('Address');
$acl->allow('client', 'Address', 'add');
$acl->deny('admin');
bdump($acl->isAllowed('admin', 'Address', 'add'), 'Zend'); // Vypíše false
}
private function nette(): void
{
$acl = new Nette\Security\Permission();
$acl->addRole('client');
$acl->addRole('admin', 'client');
$acl->addResource('Address');
$acl->allow('client', 'Address', 'add');
$acl->deny('admin');
bdump($acl->isAllowed('admin', 'Address', 'add'), 'Nette'); // Vypíše true
}
Tedy Zend a následně tedy Laminas se chovají podle očekávání a následující pravidlo přepíše předchozí a vypíší správně False, ale Nette jediné to ignoruje a vypíše True.
Zend se dokonce chová tak, že pokud má potomek nastaveno
$acl->deny('admin');
Tak se při isAllowed
nad adminem vůbec do předků nekouká a
chová se jako by předky neměl.
Podobně se to chová, pokud nastavíme následující:
// Zend:
$acl->deny('admin', null, 'add');
$acl->allow('client', 'Address', 'add');
$acl->allow('client', 'Address', 'edit');
bdump($acl->isAllowed('admin', 'Address', 'add'), 'Zend'); // Vypíše false
bdump($acl->isAllowed('admin', 'Address', 'edit'), 'Zend'); // Vypíše true
// Nette
$acl->deny('admin', null, 'add');
$acl->allow('client', 'Address', 'add');
$acl->allow('client', 'Address', 'edit');
bdump($acl->isAllowed('admin', 'Address', 'add'), 'Zend'); // Vypíše true
bdump($acl->isAllowed('admin', 'Address', 'edit'), 'Zend'); // Vypíše true
Tedy pokud u Zendu obecně nastavíme potomkovi, že nesmí nad všemi
resourcemi provádět určité akce, tak při dotazu na danou akci se pravidla
předků ignorují.
U Nette ale převládají pravidla předka.
U definic v rámci jedné role pak samozřejmě záleží na pořadí:
// Zend 1
$acl->deny('admin', null, 'add');
$acl->allow('admin', 'Address', 'add');
bdump($acl->isAllowed('admin', 'Address', 'add'), 'Zend'); // Vypíše true
// Zend 2
$acl->allow('admin', 'Address', 'add');
$acl->deny('admin', null, 'add');
bdump($acl->isAllowed('admin', 'Address', 'add'), 'Zend'); // Vypíše false
// Nette 1
$acl->deny('admin', null, 'add');
$acl->allow('admin', 'Address', 'add');
bdump($acl->isAllowed('admin', 'Address', 'add'), 'Nette'); // Vypíše true
// Nette 2
$acl->allow('admin', 'Address', 'add');
$acl->deny('admin', null, 'add');
bdump($acl->isAllowed('admin', 'Address', 'add'), 'Nette'); // Vypíše true
Takže jak vidíme, tak v Zendu na pořadí záleží, zatímco Nette jej ignoruje a v Zendu existuje mnou předpokládaná hierarchie:
- Definice v potomkovi mají větší váhu, než definice rodičů.
- Pořadí uvedení rodičů určuje váhu definic. Tedy v případě
$acl->addRole('admin', ['user', 'guest'])
mají definice přímo v roliadmin
nejvyšší váhu, v roliuser
nižší a v roliguest
nejnižší. - Definice v rámci jedné role mají váhu podle pořadí přidání. Tedy
záleží na pořadí volání
allow
adeny
, přičemž pozdější volaný má větší váhu.
Editoval Bulldog (4. 11. 2022 15:02)