0 follower

Autentificare si autorizare

Autentificarea si autorizarea sunt necesare daca o pagina Web trebuie sa fie accesata doar de anumiti utilizatori. Autentificarea in sine asigura ca cineva este cine pretinde ca este. De obicei, este nevoie de un nume de utilizator si o parola, dar poate include si alte metode pentru demonstrarea identitatii, precum smart card, amprente, etc. Autorizarea este calea prin care o persoana, o data ce a fost identificata (autentificata), are permisiunea sa acceseze/modifice anumite resurse. Acest lucru se face de obicei verificand daca respectiva persoana are un anumit rol care are permisiunea de a accesa resursele in cauza.

Yii incorporeaza un sistem de autentificare/autorizare care este usor de folosit si de modificat pentru orice nevoi.

Centrul sistemului de autentificare al Yii este componenta de aplicatie user, un obiect care implementeaza interfata IWebUser. Componenta user reprezinta informatiile persistente despre identitatea utilizatorului curent. Putem accesa aceasta componenta de oriunde din cod prin Yii::app()->user.

Folosind componenta user, putem verifica daca un utilizator este logat sau nu prin CWebUser::isGuest; putem sa logam un user prin metoda login si sa il delogam prin metoda logout; putem sa verificam daca utilizatorul are dreptul sa execute anumite operatii prin apelul metodei CWebUser::checkAccess; de asemenea, putem sa obtinem identificatorul unic si alte informatii persistente despre identitatea utilizatorului.

1. Definirea clasei de identitate

Pentru a autentifica un utilizator, definim o clasa de identitate care contine codul efectiv de autentificare. Clasa de identitate ar trebui sa implementeze interfata IUserIdentity/ Mai multe clase pot implementa abordari diferite de autentificare (ex. OpenID, LDAP). Un start bun ar fi sa derivam clasa CUserIdentity care este clasa de baza pentru autentificarea pe baza unui nume si a unei parole.

Cel mai important lucru in definirea unei clase de identitate este implementarea metodei IUserIdentity::authenticate. O clasa de identitate poate sa declare de asemenea informatii aditionale despre identitate care trebuie sa fie persistente in timpul sesiunii utilizatorului.

In urmatorul exemplu, validam numele si parola folosind tabela user dintr-o baza de date prin intermediul Active Record. De asemenea, suprascriem metoda getId pentru a returna variabila _id care este setata in timpul autentificarii (valoarea returnata implicit pentru ID este numele utilizatorului username). In timpul autentificarii, memoram informatia title prin apelarea metodei CBaseUserIdentity::setState.

class UserIdentity extends CUserIdentity
{
    private $_id;
    public function authenticate()
    {
        $record=User::model()->findByAttributes(array('username'=>$this->username));
        if($record===null)
            $this->errorCode=self::ERROR_USERNAME_INVALID;
        else if($record->password!==md5($this->password))
            $this->errorCode=self::ERROR_PASSWORD_INVALID;
        else
        {
            $this->_id=$record->id;
            $this->setState('title', $record->title);
            $this->errorCode=self::ERROR_NONE;
        }
        return !$this->errorCode;
    }
 
    public function getId()
    {
        return $this->_id;
    }
}

Informatiile memorate (prin apelul metodei CBaseUserIdentity::setState) vor fi trimise catre CWebUser care le memoreaza mai departe intr-un mediu de stocare persistent, ca de exemplu sesiunea utilizatorului. Aceste informatii pot fi accesate ca proprietati ale clasei CWebUser. De exemplu, putem obtine informatia title a utilizatorului curent prin Yii::app()->user->title (Acest lucru este disponibil incepand cu versiunea 1.0.3. In versiunile anterioare, trebuie sa folosim Yii::app()->user->getState('title')).

Info: Implicit, CWebUser foloseste sesiunea utilizatorului ca mediu de stocare persistent pentru informatiile referitoare la identitatea utilizatorului. Daca logarea bazata pe cookie-uri este activata (prin setarea CWebUser::allowAutoLogin cu valoarea true), informatiile despre identitatea utilizatorului pot fi salvate si in cookie-uri. Totusi trebuie sa ne asiguram ca nu memoram in cookie-uri informatii care ar trebui sa fie ascunse (ex. parola).

2. Login si Logout

Folosind calasa de identitate si componenta user, putem implementa usor action-urile de logare si delogare.

// Logheaza un utilizator cu numele si parola date
$identity=new UserIdentity($username,$password);
if($identity->authenticate())
    Yii::app()->user->login($identity);
else
    echo $identity->errorMessage;
......
// Delogheaza utilizatorului curent
Yii::app()->user->logout();

Implicit, un utilizator va fi delogat dupa o anumita perioada de timp de inactivitate, perioada care depinde de configuratia sesiunii. Pentru a modifica acest comportament, putem seta cu true proprietatea allowAutoLogin componentei user si sa transmitem un parametru cu durata catre metoda CWebUser::login. Utilizatorul va ramane dupa aceea logat o perioada egala cu perioada specificata in acest parametru, chiar daca inchide fereastra browser-ului web. Este de notat faptul ca acest feature presupune ca browser-ul utilizatorului accepta cookie-uri.

// tinem userul logat timp de 7 zile.
// trebuie sa ne asiguram ca proprietatea allowAutoLogin este setata cu true in componenta user.
Yii::app()->user->login($identity,3600*24*7);

3. Filtrul de control al accesului

Filtrul de control al accesului este o schema preliminara de autorizare care verifica daca utilizatorul curent poate executa action-ul cerut. Autorizarea se foloseste de numele utilizatorului, IP-ul clientului si tipul cererii. Este pus la dispozitie ca un filtru cu numele "accessControl".

Sfat: Filtrul de control al accesului este suficient pentru scenariile simple. Pentru un control al accesului complex, va trebui probabil sa folositi RBAC (acces bazat pe roluri) care va fi explicat mai jos.

Pentru a controla accesul la action-urile unui controller, instalam filtrul de control al accesului prin suprascrierea CController::filters (vedeti Filter pentru mia multe detalii despre instalarea filtrelor).

class PostController extends CController
{
    ......
    public function filters()
    {
        return array(
            'accessControl',
        );
    }
}

In codul de mai sus, specificam ca filtrul access control ar trebui sa fie aplicat pentru toate action-urile controller-ului PostController. Regulile de autorizarea detaliate folosite de catre filtru sunt specificate prin suprascrierea CController::accessRules din clasa controller-ului.

class PostController extends CController
{
    ......
    public function accessRules()
    {
        return array(
            array('deny',
                'actions'=>array('create', 'edit'),
                'users'=>array('?'),
            ),
            array('allow',
                'actions'=>array('delete'),
                'roles'=>array('admin'),
            ),
            array('deny',
                'actions'=>array('delete'),
                'users'=>array('*'),
            ),
        );
    }
}

Codul de mai sus specifica trei reguli, fiecare reprezentata de un array. Primul element din array este 'allow' sau 'deny' iar restul perechilor nume-valoare specifica parametrii pattern ai regulii. Aceste reguli ne spun: action-urile create si edit nu pot fi executate de catre utilizatorii anonimi; action-ul delete poate fi executat de utilizatorii cu rolul admin; action-ul delete nu poate fi executat de nimeni.

Regulile de acces sunt evaluate una cate una in ordinea in care sunt specificate. Prima regula care se potriveste cu pattern-ul curent (ex. numele utilizatorului, rolurile, IP-ul client, adresa) determina rezultatul autorizarii. Daca aceasta regula este o regula allow, action-ul poate fi executat; daca este o regula deny, action-ul nu poate fi executat; daca nici una dintre aceste reguli nu se potriveste cu contextul, action va fi executat.

Sfat: Pentru a ne asigura ca un action nu va fi executat in anumite contexte, este bine sa specificam mereu o regula deny la sfarsitul setului de reguli care sa interzica executarea action-ului:

return array(
    // ... alte reguli...
    // urmatoarea regula interzice action-ul 'delete' in absolut orice context
    array('deny',
        'action'=>'delete',
    ),
);

Motivul pentru care adaugam aceasta regula este ca daca nici o regula nu se potriveste, action-ul va fi executat.

O regula de acces poate sa se potriveasca cu urmatorii parametri de context:

  • actions: precizeaza lista de action-uri pentru care se aplica regula.

  • users: precizeaza utilizatorii pentru care se aplica regula. Este folosit numele utilizatorului curent. Trei caractere speciale pot fi folosite aici:

    • *: orice utilizator, anonim sau autentificat.
    • ?: utilizatorii anonimi.
    • @: utilizatorii autentificati.
  • roles: precizeaza caror roluri li se aplica regula curenta. Se foloseste controlul accesului bazat pe roluri care va fi descris in sub-sectiunea urmatoare. In particular, regula este aplicata daca CWebUser::checkAccess returneaza true pentru unul dintre roluri. De notat este faptul ca ar trebui sa folosim rolurile in regulile allow pentru ca, prin definitie, un rol reprezinta o permisiune de a face ceva.

  • ips: precizeaza caror adrese IP li se va aplica regula.

  • verbs: precizeaza caror tipuri de cereri (ex. GET, POST) li se va aplica regula.

  • expression: precizeaza o expresie PHP a carei valoare indica daca aceasta regula se va aplica sau nu. In aceasta expresie putem folosi variabila $user care se refera la Yii::app()->user. Aceasta optiune este disponibila incepand cu versiunea 1.0.3.

Tratarea rezultatelor de autorizare

Cand autorizarea esueaza, adica utilizatorul nu are dreptul sa executa action-ul specificat, se poate intampla unul dintre urmatoarele doua scenarii:

  • If the user is not logged in and if the loginUrl property of the user component is configured to be the URL of the login page, the browser will be redirected to that page.

  • Otherwise an HTTP exception will be displayed with error code 401.

When configuring the loginUrl property, one can provide a relative or absolute URL. One can also provide an array which will be used to generate a URL by calling CWebApplication::createUrl. The first array element should specify the route to the login controller action, and the rest name-value pairs are GET parameters. For example,

array(
    ......
    'components'=>array(
        'user'=>array(
            // this is actually the default value
            'loginUrl'=>array('site/login'),
        ),
    ),
)

If the browser is redirected to the login page and the login is successful, we may want to redirect the browser back to the page that caused the authorization failure. How do we know the URL for that page? We can get this information from the returnUrl property of the user component. We can thus do the following to perform the redirection:

Yii::app()->request->redirect(Yii::app()->user->returnUrl);

4. Role-Based Access Control

Role-Based Access Control (RBAC) provides a simple yet powerful centralized access control. Please refer to the Wiki article for more details about comparing RBAC with other more traditional access control schemes.

Yii implements a hierarchical RBAC scheme via its authManager application component. In the following ,we first introduce the main concepts used in this scheme; we then describe how to define authorization data; at the end we show how to make use of the authorization data to perform access checking.

Overview

A fundamental concept in Yii's RBAC is authorization item. An authorization item is a permission to do something (e.g. creating new blog posts, managing users). According to its granularity and targeted audience, authorization items can be classified as operations, tasks and roles. A role consists of tasks, a task consists of operations, and an operation is a permission that is atomic. For example, we can have a system with administrator role which consists of post management task and user management task. The user management task may consist of create user, update user and delete user operations. For more flexibility, Yii also allows a role to consist of other roles or operations, a task to consist of other tasks, and an operation to consist of other operations.

An authorization item is uniquely identified by its name.

An authorization item may be associated with a business rule. A business rule is a piece of PHP code that will be executed when performing access checking with respect to the item. Only when the execution returns true, will the user be considered to have the permission represented by the item. For example, when defining an operation updatePost, we would like to add a business rule that checks if the user ID is the same as the post's author ID so that only the author himself can have the permission to update a post.

Using authorization items, we can build up an authorization hierarchy. An item A is a parent of another item B in the hierarchy if A consists of B (or say A inherits the permission(s) represented by B). An item can have multiple child items, and it can also have multipe parent items. Therefore, an authorization hierarchy is a partial-order graph rather than a tree. In this hierarchy, role items sit on top levels, operation items on bottom levels, while task items in between.

Once we have an authorization hierarchy, we can assign roles in this hierarchy to application users. A user, once assigned with a role, will have the permissions represented by the role. For example, if we assign the administrator role to a user, he will have the administrator permissions which include post management and user management (and the corresponding operations such as create user).

Now the fun part starts. In a controller action, we want to check if the current user can delete the specified post. Using the RBAC hierarchy and assignment, this can be done easily as follows:

if(Yii::app()->user->checkAccess('deletePost'))
{
    // delete the post
}

Configuring Authorization Manager

Before we set off to define an authorization hierarchy and perform access checking, we need to configure the authManager application component. Yii provides two types of authorization managers: CPhpAuthManager and CDbAuthManager. The former uses a PHP script file to store authorization data, while the latter stores authorization data in database. When we configure the authManager application component, we need to specify which component class to use and what are the initial property values for the component. For example,

return array(
    'components'=>array(
        'db'=>array(
            'class'=>'CDbConnection',
            'connectionString'=>'sqlite:path/to/file.db',
        ),
        'authManager'=>array(
            'class'=>'CDbAuthManager',
            'connectionID'=>'db',
        ),
    ),
);

We can then access the authManager application component using Yii::app()->authManager.

Defining Authorization Hierarchy

Defining authorization hierarchy involves three steps: defining authorization items, establishing relationships between authorization items, and assigning roles to application users. The authManager application component provides a whole set of APIs to accomplish these tasks.

To define an authorization item, call one of the following methods, depending on the type of the item:

Once we have a set of authorization items, we can call the following methods to establish relationships between authorization items:

And finally, we call the following methods to assign role items to individual users:

Below we show an example about building an authorization hierarchy with the provided APIs:

$auth=Yii::app()->authManager;
 
$auth->createOperation('createPost','create a post');
$auth->createOperation('readPost','read a post');
$auth->createOperation('updatePost','update a post');
$auth->createOperation('deletePost','delete a post');
 
$bizRule='return Yii::app()->user->id==$params["post"]->authID;';
$task=$auth->createTask('updateOwnPost','update a post by author himself',$bizRule);
$task->addChild('updatePost');
 
$role=$auth->createRole('reader');
$role->addChild('readPost');
 
$role=$auth->createRole('author');
$role->addChild('reader');
$role->addChild('createPost');
$role->addChild('updateOwnPost');
 
$role=$auth->createRole('editor');
$role->addChild('reader');
$role->addChild('updatePost');
 
$role=$auth->createRole('admin');
$role->addChild('editor');
$role->addChild('author');
$role->addChild('deletePost');
 
$auth->assign('reader','readerA');
$auth->assign('author','authorB');
$auth->assign('editor','editorC');
$auth->assign('admin','adminD');

Note that we associate a business rule with the updateOwnPost task. In the business rule we simply check if the current user ID is the same as the specified post's author ID. The post information in the $params array is supplied by developers when performing access checking.

Info: While the above example looks long and tedious, it is mainly for demonstrative purpose. Developers usually need to develop some user interfaces so that end users can use to establish an authorization hierarchy more intuitively.

Access Checking

To perform access checking, we first need to know the name of the authorization item. For example, to check if the current user can create a post, we would check if he has the permission represented by the createPost operation. We then call CWebUser::checkAccess to perform the access checking:

if(Yii::app()->user->checkAccess('createPost'))
{
    // create post
}

If the authorization rule is associated with a business rule which requires additional parameters, we can pass them as well. For example, to check if a user can update a post, we would do

$params=array('post'=>$post);
if(Yii::app()->user->checkAccess('updateOwnPost',$params))
{
    // update post
}

Using Default Roles

Note: The default role feature has been available since version 1.0.3

Many Web applications need some very special roles that would be assigned to every or most of the system users. For example, we may want to assign some privileges to all authenticated users. It poses a lot of maintenance trouble if we explicitly specify and store these role assignments. We can exploit default roles to solve this problem.

A default role is a role that is implicitly assigned to every user, including both authenticated and guest. We do not need to explicitly assign it to a user. When CWebUser::checkAccess, default roles will be checked first as if they are assigned to the user.

Default roles must be declared in the CAuthManager::defaultRoles property. For example, the following configuration declares two roles to be default roles: authenticated and guest.

return array(
    'components'=>array(
        'authManager'=>array(
            'class'=>'CDbAuthManager',
            'defaultRoles'=>array('authenticated', 'guest'),
        ),
    ),
);

Because a default role is assigned to every user, it usually needs to be associated with a business rule that determines whether the role really applies to the user. For example, the following code defines two roles, "authenticated" and "guest", which effectively apply to authenticated users and guest users, respectively.

$bizRule='return !Yii::app()->user->isGuest;';
$auth->createRole('authenticated',$bizRule);
 
$bizRule='return Yii::app()->user->isGuest;';
$auth->createRole('guest',$bizRule);