Assertion svojstvo Zend\Permissions\Acl komponente
Autor: Nikola Poša |
Datum:
  • # Frameworks

Upravljanje pravima pristupa i privilegijama korisnika, jednom rečju autorizacija, je dosta česta odlika neke web aplikacije koju treba sprovesti u delo. U cilju rešavanja jedne takve funkcionalnosti, Zend Framework 2 nudi veoma fleksibilnu i proširivu Zend\Permissions\Acl  komponentu za definisanje i manipulaciju pravima pristupa.

Podrazumeva se da čitalac ovog teksta poseduje barem bazično poznavanje  ZF2 ACL komponente, s obzirom da je ovaj članak usredsređen na problematiku postavljanja custom, dinamičkih dozvola, koristeći ACL Assertion svojstvo te komponente.

 

Terminologija

 

U nastavku će intenzivno biti korišćeni određeni termini specifični za samu ACL komponentu:

  • resurs – objekat u aplikaciji za kojeg se definišu pravila pristupa, npr. Article (članak), Comment, BlogPost i slično
  • rola – objekat koji pristupa određenom resursu. Konkretnu implementaciju role najčešće predstavlja korisnik aplikacije, User objekat
  • privilegija – konkretna dozvola dodeljena roli za određeni resurs. Primeri: view, edit, delete
  • ACL – lista pravila koja definiše koja rola ima pristup kojem resursu i opciono sa kojim privilegijama (instanca Zend\Permissions\Acl\Acl  klase)

 

Entiteti i ACL

Web aplikacije često manipulišu realnim objektima, tj. entitetima, a Zend\Permissions\Acl komponenta omogućava elegantan i efikasan način za njihovo uključivanje u ACL rutine.

Uzmimo za primer ovakav zahtev: imamo web sajt, na kojem autori mogu da pišu članke, pri čemu permisije treba da podesimo tako da autor može da unosi nove, ali i da menja svoje članke. Da bi fokus bio na samoj ACL implementaciji, izvorni kôd koji sledi ću maksimalno uprostiti, te će u njemu izostati konvencije i odlike kvalitetnog objektno-orijentisanog pristupa. U našem primeru postojaće dva entiteta – Article (članak) i User (korisnik/autor).

namespace Application\Entity;

interface AuthorityContentInterface
{
    /**
     * @return User
     */
    public function getAuthor();
}
namespace Application\Entity;

use Zend\Permissions\Acl\Resource\ResourceInterface;

class Article implements AuthorityContentInterface, ResourceInterface
{
    public $id;

    public $title;

    public $content;

    /**
     * @var User
     */
    public $author;

    public function getAuthor()
    {
        return $this->author;
    }

    public function getResourceId()
    {
        return 'Article';
    }
}
namespace Application\Entity;

use Zend\Permissions\Acl\Role\RoleInterface;

class User implements RoleInterface
{
    const ROLE_GUEST = 'guest';
    const ROLE_MEMBER = 'member';
    const ROLE_AUTHOR = 'author';
    const ROLE_ADMINISTRATOR = 'admin';

    public $id;

    public $name;

    public $role;

    public function getRoleId()
    {
        return $this->role;
    }
}

Kao što se može videti, pored Article  i User  entiteta, definisao sam i AuthorityContentInterface  čija je namena “markiranje” sadržaja koji ima autora, tj sadržaja koji je u vlasništvu nekog korisnika. U našem primeru, Article entitet je konkretna implementacija tog interfejsa.

Oba entiteta implementiraju i određene interfejse iz Zend\Permissions\Acl  namespace-a, sve u cilju njihove kompatibilnosti i usklađenosti sa ACL komponentom. Article je u našem slučaju resurs aplikacije, te implementira ResourceInterface , dok User predstavlja korisnika sistema sa određenom rolom, pa je samim tim ovaj entitet RoleInterface  implementacija. Što se tiče samih implementacija ovih interfejsa, stvari su krajnje pojednostavljene, pa tako Article entitet koristi string 'Article'  kao identifikator resursa, dok User za identifikator role koristi trenutno postavljenu rolu, što je zapravo neka od ROLE_*  konstanti definisanih u samoj klasi entiteta.

 

Postavljanje ACL pravila

 

Definisanje samih ACL pravila ćemo obaviti kroz custom Zend\Permissions\Acl\Acl  instancu, unutar koje ćemo izvršiti svu neophodnu inicijalizaciju:

namespace Application\Permissions\Acl;

use Zend\Permissions\Acl\Acl as ZendAcl;
use Application\Entity\User;
use Application\Permissions\Acl\Assertion\Ownership as OwnershipAssertion;

class Acl extends ZendAcl
{
    public function __construct()
    {
        $this->addRole(User::ROLE_GUEST);
        $this->addRole(User::ROLE_MEMBER, User::ROLE_GUEST);
        $this->addRole(User::ROLE_AUTHOR, User::ROLE_MEMBER);
        $this->addRole(User::ROLE_ADMINISTRATOR);
        
        $this->addResource('Article');
        
        $this->allow(User::ROLE_GUEST, null, 'view');
        
        $this->allow(User::ROLE_AUTHOR, 'Article', 'create');
        $ownershipAssertion = new OwnershipAssertion();
        $this->allow(User::ROLE_AUTHOR, 'Article', 'edit', $ownershipAssertion);
        $this->allow(User::ROLE_AUTHOR, 'Article', 'delete', $ownershipAssertion);
        
        $this->allow(User::ROLE_ADMINISTRATOR);
    }
}

Najpre vršimo registraciju rola i resursa koje imamo u sistemu, a zatim definišemo i sama ACL pravila. Ključan deo je onaj u kojem se postavljaju permisije za autorsku rolu. Setite se primera sa početka ovog dela teksta, tačnije zahteva u kojem se traži da prava pristupa budu podešena tako da autor može da unosi nove, ali i da menja svoje članke. Realizacija dozvole koja omogućava unos članaka je jasna:

$this->allow(User::ROLE_AUTHOR, 'Article', 'create');

 … dok je situacija sa omogućavanjem moderacije sopstvenih članaka nešto složenija. Naime, samo ACL pravila za izmenu/brisanje sam dopunio odgovarajućim Assertion-om (Zend\Permissions\Acl\Assertion\AssertionInterface  implementacija), formulisanog kao OwnershipAssertion:

namespace Application\Permissions\Acl\Assertion;

use Zend\Permissions\Acl\Assertion\AssertionInterface;
use Zend\Permissions\Acl\Acl;
use Zend\Permissions\Acl\Role\RoleInterface;
use Zend\Permissions\Acl\Resource\ResourceInterface;
use Application\Entity\User;
use Application\Entity\AuthorityContentInterface;

class Ownership implements AssertionInterface
{
    public function assert(Acl $acl, RoleInterface $user = null, ResourceInterface $resource = null, $privilege = null)
    {
        if (
            $user instanceof User && $resource instanceof AuthorityContentInterface 
            && $user->id != $resource->getAuthor()->id
        ) {
            return false;
        }
        
        return true;
    }    
}

Logika samog Assertion-a je jasna. Njegova provera se svodi na utvrđivanju vlasništva nekog AuthorityContent resursa, tako što se User instanca autora resursa poredi sa User instancom za koju se radi sâm ACL upit. Takođe, ovaj generički Assertion je upotrebljiv (reusable) u svim sličnim slučajevima.

Konačno, ovako postavljenu custom ACL instancu možemo registrovati kao servis i zatim nad njom raditi proveru na adekvatnom mestu, na primer u akciji nekog kontrolera:

$article = $this->articlesService->getById(1);

$acl = $this->serviceLocator->get('AppAcl');
$user = $this->serviceLocator->get('AuthenticationService')->getIdentity(); //trenutni User iz sesije

if (!$acl->isAllowed($user, $article, 'edit')) {
    throw new \RuntimeException('Insufficient permissions');
}

 

Rezime

 

Moć ZF2 ACL Assertion svojstva se ogleda u njegovoj jednostavnosti. Programer ima potpunu slobodu što se tiče konkretne implementacije assert()  metoda Assertion interfejsa, u smislu da je u njemu moguće postaviti bilo kakav vid provere. Na taj način je pored apsolutnih, statičkih, omogućeno i definisanje dodatno uslovljenih, dinamičkih ACL pravila. Svojom koncepcijom i odlikama, ACL komponenta Zend Framework-a omogućava upravljanje celokupnom autorizacionom logikom aplikacije kroz jedan kontekst, tačnije kroz Zend\Permi