Zend_Acl et les assertions

Zend_Acl est le composant du Zend Framework qui permet de gérer les droits d'accès des utilisateurs.

Son concept repose sur trois types d'objets :

  • Les rôles qui sont les entités dont on veut contrôler les droits. Le plus souvent il s'agira d'utilisateurs, mais ça pourrait être un programme externe qui interagit avec notre application par le biais d'une API.
  • Les ressources qui sont les entités dont on veut réglementer l'accès (éléments d'interface, données...)
  • Les permissions qui permettent de préciser ce que les rôles peuvent ou ne peuvent pas faire. En général, ce sera des actions (voir, modifier, supprimer...)

Si on veut par exemple indiquer dans un CMS que les auteurs peuvent créer des articles, on procédera de la façon suivante :

<?php
// On crée l'objet ACL et on le stocke dans le registre
// pour pouvoir y accéder depuis n'importe quel endroit de notre application
$acl = new Zend_Acl();
Zend_Registry::set('acl'$acl);

// Crée le rôle "auteur"
$acl->addRole(new Zend_Acl_Role('auteur'));

// Crée la ressource "articles"
$acl->add(new Zend_Acl_Resource('articles'));

// Autorise les auteurs à créer des articles
$acl->allow('auteur''articles''creer');
?>

Le rôle de chaque utilisateur sera stocké dans la session par le biais de Zend_Auth. On pourra alors interroger l'objet $acl pour déterminer par exemple si on doit afficher ou pas le bouton qui permet la création d'un nouveau article :

<?php
// On récupère l'objet ACL qu'on a stocké précédemment dans le registre
$acl Zend_Registry::get('acl');

// On récupère l'identité de l'utilisateur actuellement connecté
$viewer Zend_Auth::getInstance()->getIdentity();

if (
$acl->isAllowed($viewer->role'articles''creer')) {
  
// Affiche le bouton permettant la création d'un article
}
?>

Pour la modification de l'article, c'est un peu plus compliqué. Le plus souvent les auteurs ne pourront modifier que les articles qu'ils ont créés. Pour créer cette règle plus complexe, on a besoin d'utiliser une assertion :

<?php
// Un auteur peut modifier un article que si il en est le propriétaire
$acl->allow('auteur''articles''modifier', new Wiip_Acl_Assert_Owner);
?>

Une assertion, c'est en fait une instance d'une classe dérivée de Zend_Acl_Assert_Interface. Cette classe dispose d'une méthode nommée assert qui doit renvoyer true pour que l'utilisateur soit autorisé à accèder à la ressource.

Dans notre cas, notre classe pourrait ressembler à ceci :

<?php
class Wiip_Acl_Assert_Owner implements Zend_Acl_Assert_Interface
{
    public function 
assert(Zend_Acl $aclZend_Acl_Role_Interface $role null
        
Zend_Acl_Resource_Interface $resource null$privilege null)
    {       
        
$auth Zend_Auth::getInstance();

        
// L'utilisateur doit être identifié
        
if (!$auth->hasIdentity()) return false;

        
// Si l'ID de l'utilisateur actuellement identifié correspond au champ
        // auteur_id de l'article, on autorise l'action        
        
return $auth->getIdentity()->id == $resource->auteur_id;
    }
}
?>

On peut ensuite interroger l'ACL de cette façon :

<?php
// On récupère les informations sur l'utilisateur
$viewer Zend_Auth::getInstance()->getIdentity();

if (
$acl->isAllowed($viewer->role$article'modifier')) {
    
// L'utilisateur peut modifier l'article
    
...
}
?>

Pour que l'assertion soit en mesure de vérifier que l'utilisateur actuellement connecté et bien l'auteur de l'article, on lui passe l'objet $article, qui correspond à l'enregistrement de la base de donnée. C'est donc un objet dérivé de Zend_Db_Table_Row_Abstract. Il doit implémenter l'interface Zend_Acl_Resource_Interface pour que la méthode isAllowed l'accepte.

<?php
class Wiip_Model_Articles_Row extends Zend_Db_Table_Row_Abstract 
  
implements Zend_Acl_Resource_Interface 
{
  public function 
getResourceId()
  {
    
// Retourne le nom de la table (ici articles) en guise de nom de ressource
    
return $this->_table->info('name');
  }
}
?>

Note : le problème est apparemment résolu dans la version 1.9.1.

Le problème, c'est que ce n'est pas l'objet qui est transmis à la méthode assert, mais l'identifiant de la ressource. La classe d'assertion ci-dessus ne fonctionne donc pas.

La solution consiste à surcharger la méthode isAllowed de Zend_Acl pour que la ressource passée en argument soit stockée dans une propriété de l'objet ACL. On peut ensuite y accéder dans l'assertion à l'aide de la méthode getAssertResource

<?php
class Wiip_Acl extends Zend_Acl
{
    protected 
$_assertResource;

    public function 
getAssertResource()
    {
        return 
$this->_assertResource;
    }

    public function 
isAllowed($role null$resource null$privilege null)
    {
        
$this->_assertResource $resource;
        return 
parent::isAllowed($role$resource$privilege);
    }
}
?>

On utilisera alors Wiip_Acl à la place de Zend_Acl et notre classe d'assertion sera réécrite de la façon suivante :

<?php
class Wiip_Acl_Assert_Owner implements Zend_Acl_Assert_Interface
{
    public function 
assert(Zend_Acl $aclZend_Acl_Role_Interface $role null
        
Zend_Acl_Resource_Interface $resource null$privilege null)
    {       
        
$auth Zend_Auth::getInstance();

        
// L'utilisateur doit être identifié
        
if (!$auth->hasIdentity()) return false;

        
// On récupère l'enregistrement
        
$article $acl->getAssertResource();

        
// Si l'ID de l'utilisateur actuellement identifié correspond au champ
        // auteur_id de l'article, on autorise l'action        
        
return $auth->getIdentity()->id == $article->auteur_id;
    }
}
?>

Voir aussi

Le problème du passage de l'objet ressource à la classe d'assertion est un problème connu, qui fait l'objet de deux rapports de bogues :

Commentaires

Bonjour et merci pour ce tuto.
J'ai un problème avec les assertions, lorsque je teste si l'utilisateur connecté est bien le propriétaire du fichier je me retrouve avec l'erreur suivante :

Catchable fatal error: Object of class Zend_Db_Table_Row could not be converted to string in C:\wamp\includes\library\Zend\Acl.php on line 292

j'ai bien implémenté la méthode getResourceId() de l'interface Zend_Acl_Resource_Interface.

Avez-vous une idée ?
Merci

Je pense que c'est parce que ton objet Zend_Db_Table_Row n'a pas de méthode getResourceId() qui permet de convertir un objet en identifiant de ressource. Les objets qui sont passés à isAllowed() doivent implémenter Zend_Acl_Resource_Interface.

Merci pour ta réponse, mais comme tu l'indiques dans ton tuto, j'ai bien créé la classe suivante dans ma librairie.

class My_Model_Articles_Row extends Zend_Db_Table_Row_Abstract
implements Zend_Acl_Resource_Interface
{

public function getResourceId()
{
// Retourne le nom de la table (ici articles) en guise de nom de ressource
return $this->_table->info('name');
}
}

mais la méthode getResourceId n'est jamais appelée.

le code où se produit l'erreur est le suivant (ligne en gras):

public function get($resource)
{
if ($resource instanceof Zend_Acl_Resource_Interface) {
$resourceId = $resource->getResourceId();
} else {
$resourceId = (string) $resource;
}

if (!$this->has($resource)) {
require_once 'Zend/Acl/Exception.php';
throw new Zend_Acl_Exception("Resource '$resourceId' not found");
}

return $this->_resources[$resourceId]['instance'];
}

j'en déduis donc que mon objet (ressource) n'est pas une instance de Zend_Acl_Resource_Interface.
Comment dire au framework que ma ressource est bien de ce type.(je suis débutant avec zend)
Merci

Ça devrait fonctionner. Tu passes bien le row et pas la table à assert ?

Voici mon code lors de l'appel à l'assertion

$prelevement = new Prelevement();
$rowPrelevement=$prelevement->fetchRow($prelevement->select()->where("idprelevement = ? ",$form->getValue('idprelevement')));

if ($acl->isAllowed($viewer->userRole,$rowPrelevement , 'editer')) {
echo 'test';
}

Tu as bien défini $_rowClass = 'My_Model_Articles_Row' dans Prelevement ?

En effet en je n'avais pas préciser $_rowClass = 'My_Model_Articles_Row' dans Prelevement.
Suite à l'ajout je n'est plus l'erreur mais mon assertion ne fonctionne pas car j'ai l'impression que l'erreur vient du type de l'objet ressource.

dans le fichier acl.php (de la librairie zend) il y a le test ci dessous :

if ($resource instanceof Zend_Acl_Resource_Interface) {
$resourceId = $resource->getResourceId();
} else {
$resourceId = (string) $resource;
}

lors de l'éxecution de mon code je ne rentre jamais dans la 1er condition pourtant comme précisé ci-dessus je passe bien un row a mon test

$prelevement = new Prelevement();
$rowPrelevement=$prelevement->fetchRow($prelevement->select()->where("idprelevement = ? ",$form->getValue('idprelevement')));

if ($acl->isAllowed($viewer->userRole,$rowPrelevement , 'editer')) {
echo 'test';
}

Merci encore pour ta rapidité de réponse

Merci pour tous tes conseils mon problème est résolu (erreur au niveau des ACL ).
Très bon blog félicitation

Ajouter un commentaire