Jogosultságok ellenőrzése (Autorizáció)
Az autorizáció azt vizsgálja, hogy a felhasználónak elegendő jogosultsága van-e például egy adott erőforráshoz való hozzáféréshez vagy egy adott művelet végrehajtásához. Az autorizáció feltételezi az előző sikeres authentikációt, azaz hogy a felhasználó be van jelentkezve.
A példákban a Nette\Security\User osztály
objektumát fogjuk használni, amely az aktuális felhasználót képviseli, és amelyhez úgy juthat hozzá, hogy dependency injection segítségével kéri át.
A presenterekben elegendő csak a $user = $this->getUser()
hívása.
Nagyon egyszerű, adminisztrációval rendelkező weboldalaknál, ahol nem különböztetik meg a felhasználói
jogosultságokat, autorizációs kritériumként használható a már ismert isLoggedIn()
metódus. Más szóval:
amint a felhasználó be van jelentkezve, minden jogosultsággal rendelkezik, és fordítva.
if ($user->isLoggedIn()) { // be van jelentkezve a felhasználó?
deleteItem(); // akkor van jogosultsága a művelethez
}
Szerepkörök
A szerepkörök célja, hogy pontosabb jogosultságkezelést kínáljanak, és függetlenek maradjanak a
felhasználónévtől. Minden felhasználónak a bejelentkezéskor egy vagy több szerepkört rendelünk, amelyekben fellép.
A szerepkörök lehetnek egyszerű stringek, például admin
, member
, guest
, stb. A
SimpleIdentity
konstruktorának második paramétereként adjuk meg őket, vagy stringként, vagy stringek
tömbjeként – szerepkörökként.
Autorizációs kritériumként most az isInRole()
metódust használjuk, amely megmondja, hogy a felhasználó az
adott szerepkörben lép-e fel:
if ($user->isInRole('admin')) { // a felhasználó admin szerepkörben van?
deleteItem(); // akkor van jogosultsága a művelethez
}
Ahogy már tudja, a felhasználó kijelentkezése után nem feltétlenül törlődik az identitása. Tehát a
getIdentity()
metódus továbbra is visszaadja a SimpleIdentity
objektumot, beleértve az összes
megadott szerepkört. A Nette Framework a „kevesebb kód, több biztonság” elvét vallja, ahol a kevesebb írás
biztonságosabb kódhoz vezet, ezért a szerepkörök ellenőrzésekor nem kell még azt is ellenőriznie, hogy a felhasználó be
van-e jelentkezve. Az isInRole()
metódus az effektív szerepkörökkel dolgozik, azaz ha a felhasználó be
van jelentkezve, az identitásban megadott szerepkörökből indul ki, ha nincs bejelentkezve, automatikusan a speciális
guest
szerepkört kapja.
Autorizátor
A szerepkörökön kívül bevezetjük még az erőforrás és a művelet fogalmát:
- szerepkör a felhasználó tulajdonsága – pl. moderátor, szerkesztő, látogató, regisztrált felhasználó, adminisztrátor…
- erőforrás (resource) a weboldal valamilyen logikai eleme – cikk, oldal, felhasználó, menüpont, szavazás, presenter, …
- művelet (operation) valamilyen konkrét tevékenység, amelyet a felhasználó megtehet vagy nem tehet meg az erőforrással – például törlés, szerkesztés, létrehozás, szavazás, …
Az autorizátor egy objektum, amely eldönti, hogy az adott szerepkörnek van-e engedélye egy bizonyos művelet
végrehajtására egy bizonyos erőforrással. Ez egy objektum, amely implementálja a Nette\Security\Authorizator interfészt egyetlen
isAllowed()
metódussal:
class MyAuthorizator implements Nette\Security\Authorizator
{
public function isAllowed($role, $resource, $operation): bool
{
if ($role === 'admin') {
return true;
}
if ($role === 'user' && $resource === 'article') {
return true;
}
// ...
return false;
}
}
Az autorizátort hozzáadjuk a DI konténer konfigurációjához szolgáltatásként:
services:
- MyAuthorizator
És következik a használat példája. Figyelem, ezúttal a Nette\Security\User::isAllowed()
metódust hívjuk,
nem az autorizátort, tehát ott nincs az első $role
paraméter. Ez a metódus a
MyAuthorizator::isAllowed()
-t hívja meg sorban a felhasználó összes szerepkörére, és true-t ad vissza, ha
legalább egyiküknek van engedélye.
if ($user->isAllowed('file')) { // a felhasználó bármit megtehet a 'file' erőforrással?
useFile();
}
if ($user->isAllowed('file', 'delete')) { // végrehajthatja a 'delete' műveletet a 'file' erőforráson?
deleteFile();
}
Mindkét paraméter opcionális, az alapértelmezett null
érték jelentése bármi.
Permission ACL
A Nette egy beépített autorizátor implementációval érkezik, ez a Nette\Security\Permission osztály, amely a programozónak egy könnyű és rugalmas ACL (Access Control List) réteget biztosít a jogosultságok és hozzáférések kezelésére. A vele való munka a szerepkörök, erőforrások és egyes jogosultságok definiálásából áll. A szerepkörök és erőforrások lehetővé teszik hierarchiák létrehozását. Magyarázatként megmutatunk egy példát egy webalkalmazásra:
guest
: nem bejelentkezett látogató, aki olvashatja és böngészheti a weboldal nyilvános részét, azaz olvashat cikkeket, kommenteket és szavazhat a szavazásokonregistered
: bejelentkezett regisztrált felhasználó, aki ráadásul kommentelhet isadmin
: kezelheti a cikkeket, kommenteket és szavazásokat
Tehát definiáltunk bizonyos szerepköröket (guest
, registered
és admin
), és
említettünk erőforrásokat (article
, comment
, poll
), amelyekhez a felhasználók
valamilyen szerepkörrel hozzáférhetnek vagy bizonyos műveleteket végezhetnek (view
, vote
,
add
, edit
).
Létrehozzuk a Permission osztály példányát, és definiáljuk a szerepköröket. Használhatjuk az ún. szerepkör
öröklődést, amely biztosítja, hogy pl. egy adminisztrátor szerepkörrel (admin
) rendelkező felhasználó
megteheti azt is, amit egy átlagos weboldal látogató (és természetesen többet is).
$acl = new Nette\Security\Permission;
$acl->addRole('guest');
$acl->addRole('registered', 'guest'); // a 'registered' örököl a 'guest'-től
$acl->addRole('admin', 'registered'); // és tőle örököl az 'admin'
Most definiáljuk az erőforrások listáját is, amelyekhez a felhasználók hozzáférhetnek.
$acl->addResource('article');
$acl->addResource('comment');
$acl->addResource('poll');
Az erőforrások is használhatnak öröklődést, lehetne például megadni
$acl->addResource('perex', 'article')
.
És most a legfontosabb. Definiálunk közöttük szabályokat, amelyek meghatározzák, ki mit tehet mivel:
// először senki sem tehet semmit
// a guest nézhesse a cikkeket, kommenteket és szavazásokat
$acl->allow('guest', ['article', 'comment', 'poll'], 'view');
// és a szavazásokon ráadásul szavazhasson is
$acl->allow('guest', 'poll', 'vote');
// a regisztrált örökli a guest jogait, adjunk neki ráadásul kommentelési jogot
$acl->allow('registered', 'comment', 'add');
// az adminisztrátor bármit megtekinthet és szerkeszthet
$acl->allow('admin', $acl::All, ['view', 'edit', 'add']);
Mi van, ha valakinek meg akarjuk tiltani a hozzáférést egy bizonyos erőforráshoz?
// az adminisztrátor nem szerkesztheti a szavazásokat, az nem lenne demokratikus
$acl->deny('admin', 'poll', 'edit');
Most, hogy létrehoztuk a szabályok listáját, egyszerűen feltehetünk autorizációs kérdéseket:
// a guest megtekintheti a cikkeket?
$acl->isAllowed('guest', 'article', 'view'); // true
// a guest szerkesztheti a cikkeket?
$acl->isAllowed('guest', 'article', 'edit'); // false
// a guest szavazhat a szavazásokon?
$acl->isAllowed('guest', 'poll', 'vote'); // true
// a guest kommentelhet?
$acl->isAllowed('guest', 'comment', 'add'); // false
Ugyanez érvényes a regisztrált felhasználóra is, ő azonban kommentelhet is:
$acl->isAllowed('registered', 'article', 'view'); // true
$acl->isAllowed('registered', 'comment', 'add'); // true
$acl->isAllowed('registered', 'comment', 'edit'); // false
Az adminisztrátor mindent szerkeszthet, kivéve a szavazásokat:
$acl->isAllowed('admin', 'poll', 'vote'); // true
$acl->isAllowed('admin', 'poll', 'edit'); // false
$acl->isAllowed('admin', 'comment', 'edit'); // true
A jogosultságok dinamikusan is kiértékelhetők, és a döntést saját callbackre bízhatjuk, amelynek átadjuk az összes paramétert:
$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool {
return /* ... */;
};
$acl->allow('registered', 'comment', null, $assertion);
De hogyan kezeljük például azt a helyzetet, amikor nem elegendőek csak a szerepkörök és erőforrások nevei, hanem
definiálni szeretnénk, hogy például a registered
szerepkör csak akkor szerkesztheti az article
erőforrást, ha ő a szerzője? Stringek helyett objektumokat használunk, a szerepkör egy Nette\Security\Role objektum lesz, az erőforrás pedig
egy Nette\Security\Resource objektum. A
getRoleId()
ill. getResourceId()
metódusaik visszaadják az eredeti stringeket:
class Registered implements Nette\Security\Role
{
public $id;
public function getRoleId(): string
{
return 'registered';
}
}
class Article implements Nette\Security\Resource
{
public $authorId;
public function getResourceId(): string
{
return 'article';
}
}
És most létrehozunk egy szabályt:
$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool {
$role = $acl->getQueriedRole(); // Registered objektum
$resource = $acl->getQueriedResource(); // Article objektum
return $role->id === $resource->authorId;
};
$acl->allow('registered', 'article', 'edit', $assertion);
És az ACL lekérdezése objektumok átadásával történik:
$user = new Registered(/* ... */);
$article = new Article(/* ... */);
$acl->isAllowed($user, $article, 'edit');
Egy szerepkör örökölhet egy másik szerepkörtől vagy több szerepkörtől. De mi történik, ha az egyik ősnek tiltva van egy művelet, a másiknak pedig engedélyezve? Milyenek lesznek az utód jogai? Ezt a szerepkör súlya határozza meg – az ősök listájában utoljára említett szerepkörnek van a legnagyobb súlya, az elsőként említett szerepkörnek a legkisebb. Ez a példából jobban látszik:
$acl = new Nette\Security\Permission;
$acl->addRole('admin');
$acl->addRole('guest');
$acl->addResource('backend');
$acl->allow('admin', 'backend');
$acl->deny('guest', 'backend');
// A eset: az admin szerepkörnek kisebb a súlya, mint a guest szerepkörnek
$acl->addRole('john', ['admin', 'guest']);
$acl->isAllowed('john', 'backend'); // false
// B eset: az admin szerepkörnek nagyobb a súlya, mint a guestnek
$acl->addRole('mary', ['guest', 'admin']);
$acl->isAllowed('mary', 'backend'); // true
A szerepköröket és erőforrásokat el is lehet távolítani (removeRole()
, removeResource()
), a
szabályokat is vissza lehet vonni (removeAllow()
, removeDeny()
). Az összes közvetlen szülő
szerepkör tömbjét a getRoleParents()
adja vissza, azt, hogy két entitás örököl-e egymástól, a
roleInheritsFrom()
és a resourceInheritsFrom()
adja vissza.
Hozzáadás szolgáltatásként
Az általunk létrehozott ACL-t át kell adnunk a konfigurációnak szolgáltatásként, hogy a $user
objektum
használni kezdje, tehát hogy a kódban használható legyen pl. $user->isAllowed('article', 'view')
. Ehhez
írunk rá egy factory-t:
namespace App\Model;
class AuthorizatorFactory
{
public static function create(): Nette\Security\Permission
{
$acl = new Nette\Security\Permission;
$acl->addRole(/* ... */);
$acl->addResource(/* ... */);
$acl->allow(/* ... */);
return $acl;
}
}
És hozzáadjuk a konfigurációhoz:
services:
- App\Model\AuthorizatorFactory::create
A presenterekben ezután ellenőrizheti a jogosultságokat például a startup()
metódusban:
protected function startup()
{
parent::startup();
if (!$this->getUser()->isAllowed('backend')) {
$this->error('Forbidden', 403);
}
}