EN | CS | Přihlásit | Registrovat

Nette\Security\Per­mission

Ověřuje práva a přístupy k objektům na základě rolí.

Nette\Security\Permission je objekt implementující rozhraní Nette\Security\IAuthorizator poskytující programátorovi lehkou a flexibilní ACL vrstvu pro řízení práv a přístupu.

Tato vrstva velmi úzce souvisí s objektem Nette\Web\User a jejím základem je definování pravidel (rules).

Autorizace (zjištění, zda má uživatel právo to či ono udělat) rozhoduje se na základě rolí (roles), zdrojů/objektů (resources) a práv/akcí (privileges), která v celé aplikaci určí kdo může přistupovat k chráněnému objektu a jaké akce s ním může vykonávat.


Lépe poslouží jako vysvětlení následující příklad webové aplikace:

  • Nepřihlášený (neautentizovaný) návštěvník webu (což je výchozí role Nette\Web\User) může číst a procházet veřejnou část webu, tzn. číst články, komentáře, novinky a volit v anketách.
  • Oproti tomu přihlášený uživatel, který je registrován může dostávat novinky mailem a komentovat.
  • Další uživatelé mají přístup k administrační části a mohou provádět úkony jako psát a spravovat své příspěvky a provádět různé změny v aplikaci (třeba změnit vzhed, záhlaví, zápatí) a mají samozřejmě i práva procházet veřejnou část a dostávat novinky mailem.

Nadefinovali jsme si tedy určité role (guest, registered a administrator) a objekty (news, article, poll, comments, backend), ke kterým mohou uživatelé s nějakou rolí přistupovat nebo provádět určité akce (view, vote, comment, feed, edit).

Jednoduše řečeno

  • role (role) je vlastnost uživatele, který může přistupovat k objektům/zdrojům
  • zdroj (resource) je objekt který je kontrolován
  • práva (privilege) jsou akce, které může s objektem provádět uživatel s rolí

Vzájemné vazby kdo co může s čím dělat ale určují až pravidla (rules), která jsou uchovávána právě objektem Permission. Ten toho umí samozřejmě víc, než jen uchovávat pravidla.

Zároveň je tu ještě jedna vazba, možná ne na první pohled zcela zřejmá, a to dědičnost rolí, která zajistí, že uživatel s rolí administrátor může dělat i to co obyčejný návštěvník webu.

role unikátní práva rodič
guest view, vote NULL
registered feed, comment guest
administrator edit registered

Poznámka: práva administrátora lze nadefinovat i jako ‚bez omezení‘ tzn. bez rodičů od kterých by dědil nějaká omezení (viz níže).

Začínáme

Nejprve si ukážeme, jak nadefinovat objektu Permission uživatelské role.
Ještě před tím je ale třeba vytvořit instanci třídy, se kterou budeme pracovat.

$acl = new Permission();

Role (Roles)

Jak již bylo řečeno, budeme postupně vytvářet stromovou strukturu rolí, která dědí oprávnění od svých rodičů.
Použijeme výše zmíněný příklad webové aplikace o třech rolích. Mějmě tedy role guest, registered a administrator.

$acl->addRole('guest');
$acl->addRole('registered', 'guest');
$acl->addRole('administrator', 'registered');

Docela triviální že? Tímto zajístíme, že se nám vlastnosti přenášejí z rodičovské role na potomky.
Rodiče lze zadat i jako pole s více rolemi.

Za zmínku stojí metoda getRoleParents(), která vrací pole se všemi rodičovskými rolemi a také metoda roleInheritsFrom(), která zjistí, zda-li od sebe dědí dvě role. Jejich použití:

$acl->roleInheritsFrom('administrator', 'guest'); // TRUE
$acl->getRoleParents('administrator'); // array('guest', 'registered')

Zdroje neboli objekty (Resources)

Nyní je čas nadefinovat i seznam objektů, ke kterým mohou uživatelé s rolemi přistupovat.

$acl->addResource('news');
$acl->addResource('article');
$acl->addResource('poll');
$acl->addResource('comments');
$acl->addResource('backend');

I zdroje/objekty mohou vytvářet stromovou strukturu.
Využítí? Napadá mě možná e-shop, kde by kategorie dědila produkt.
Metody pro manipulaci se zdroji jsou podobné jako s objekty, liší se jen názvy: resourceInheritsFrom(), removeResource() atd.

Práva a pravidla (Privileges & Rules)

A teď to nejdůležitější. Samotné role a objekty by nám byly k ničemu, musíme mezi nimi vytvořit ještě pomocí práv/akcí vazby (neboli pravidla).

// host může prohlížet obsah jen veřejné části, hlasovat v anketách
$acl->allow('guest', array('news', 'article', 'poll', 'comments'), 'view');

// předchozí příkaz alternativně ve dvou příkazech: (ale trochu přes koleno)
$acl->allow('guest', NULL, 'view');
$acl->deny('guest', 'backend', 'view');
$acl->allow('guest', 'poll', 'vote');

// registrovaný dědí právo view od hosta, ale má i právo komentovat a dostávat novinky mailem
$acl->allow('registered', 'comments', 'comment');
$acl->allow('registered', 'news', 'feed');

// dědí se i omezení, takže aby měl administrator přístup do administrace,
// který jsme zamezili hostovi a registrovanému, musíme mu to výslovně povolit
// a na víc ke všemu dostane práva view a edit
$acl->allow('administrator', NULL, array('view', 'edit'));
$acl->allow('administrator', 'backend', 'view');

Autorizace

Nyní když máme vytvořený seznam pravidel, můžeme jednoduše provádět autorizaci.

$acl->isAllowed('guest', 'article', 'view') ? "allowed" : "denied";  // allowed
$acl->isAllowed('guest', 'article', 'edit') ? "allowed" : "denied"; // denied
$acl->isAllowed('guest', 'backend', 'view') ? "allowed" : "denied"; // denied
$acl->isAllowed('guest', 'poll', 'vote') ? "allowed" : "denied"; // allowed

// registrovaný dědí od hosta jak práva tak omezení
$acl->isAllowed('registered', 'article', 'view') ? "allowed" : "denied"; // allowed
$acl->isAllowed('registered', 'comments', 'comment') ? "allowed" : "denied"; // allowed
$acl->isAllowed('registered', 'backend', 'view') ? "allowed" : "denied"; // denied

// administrátor nemá nyní žádné omezení
$acl->isAllowed('administrator', 'poll', 'vote') ? "allowed" : "denied"; // allowed
$acl->isAllowed('administrator', 'backend', 'view') ? "allowed" : "denied"; // allowed


Práva administrátora lze nadefinovat i jako ‚bez omezení‘ tzn. bez rodičů od kterých by dědil nějaká omezení. Vypadalo by to asi takto:

$acl->removeRole('administrator');  // odeberu roli z pravidel
$acl->addRole('administrator'); // vytvořím roli znova, ale bez předků
$acl->allow('administrator'); // nastavím pravidlo: všechna práva a všechny zdroje pro administrátora bez omezení

Můžete si všimnout že kdykoliv za běhu aplikace můžeme i odebrat roli. A nejenom ji, odebírat ze seznamu pravidel lze objekty: removeResource(), removeAllResources(); ale i samotná pravidla: removeAllow(), removeDeny(). Máme zde i metody na kontrolu přítomnosti nějaké role nebo objektu: needRole(), needResource(); které vyhodí výjimku InvalidStateEx­ception pokud není zjištěna jejich přítomnost.

Jak již bylo řečeno, 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ší. Více názorné je to z příkladu:

$acl = new Permission();
$acl->addResource('backend');
$acl->addRole('admin');
$acl->addRole('guest');

$acl->allow('admin', 'backend');
$acl->deny('guest', 'backend');

// případ A: role admin má menší váhu než role guest
$acl->addRole('john', array('admin', 'guest'));
$acl->isAllowed('john', 'backend'); // FALSE

// případ B: role admin má větší váhu než guest
$acl->addRole('mary', array('guest', 'admin'));
$acl->isAllowed('mary', 'backend'); // TRUE

Napojení na Nette\Web\User

Na začátku jsem zmiňoval, že celá třída Nette\Security\Permission velmi úzce navazuje na Nette\Web\User. Dá se používat zcela bez něj, ale spolu dosáhnou maximální efektivity.

Pro začátek je potřeba zaregistrovat autorizační a autentizační handler:

Environment::getServiceLocator()->addService(new MyAuthenticator, 'Nette\Security\IAuthenticator');
Environment::getServiceLocator()->addService(new Permission, 'Nette\Security\IAuthorizator');

nebo v config.ini

service.Nette-Security-IAuthenticator = MyAuthenticator
service.Nette-Security-IAuthorizator  = Permission

Autentizační handler MyAuthenticator může vypadat například takto:

class MyAuthenticator implements IAuthenticator
{
/**
* @param array
* @return IIdentity
* @throws AuthenticationException
*/

function authenticate(array $credentials)
{
// jméno, heslo i role mohou být získány třeba z databáze
$username = 'john';
$password = 'xxx';
$roles = array('admin', 'editor');

if ($credentials['username'] !== $username) {
throw new AuthenticationException('Unknown user', self::IDENTITY_NOT_FOUND);
}

if ($credentials['password'] !== $password) {
throw new AuthenticationException('Password not match', self::INVALID_CREDENTIAL);
}

return new Identity('John Doe', $roles); // zde je důležité právě předání rolí
}

}

Kdekoliv v aplikaci, nebo v nějakém vašem modelu, komponentě stačí zavolat:

// s využitím třídy Environment
$acl = Environment::getService('Nette\Security\IAuthorizator');
$user = Environment::getUser();

// bez Environment
$acl = new Permission;
$user = new User;

// následně naplňíme autorizační handler pravidly
$acl->addRole('editor');
$acl->addRole('admin');
$acl->addResource('file');
$acl->addResource('jany');
$acl->allow('admin', 'file', 'delete_file');
$acl->allow('editor', 'jany', 'say_hello');
$acl->deny('editor', 'jany', 'sleep_with_jany');

// pokud nepoužíváme služby použijeme k nastavení handlerů metody
$user->setAuthenticationHandler(new MyAuthenticator);
$user->setAuthorizationHandler($acl);

// samotná autorizace
$user->isAllowed('file', 'delete_file'); // TRUE
$user->isAllowed('jany', 'sleep_with_jany'); // FALSE
$user->isAllowed('jany', 'say_hello'); // TRUE

// obecně
$user->isAllowed($resource, $privilege);

Metoda isAllowed() má nyní jen dva parametry, role se sama doplní pomocí metody getRoles() a v cyklu se projdou všechny role a při prvním kladném vyhodnocení se kladně vyhodnotí i podmínka výše. To vše je již implementováno v Nette\Web\User.
U proměnné $user můžeme dále používat veškeré metody uvedené v Nette\Web\User.

Viz také:


Login to submit a comment