Zend FR

Consultez la FAQ sur le ZF avant de poster une question

Vous n'êtes pas identifié.

#1 29-08-2010 23:27:34

Lukas P.
Membre
Date d'inscription: 28-12-2008
Messages: 11

Efficacité des assertions pour une vérification "appartient à"

Bonsoir,

Tout le monde connait la situation ou l'utilisateur connecté peut seulement modifier une ressource qui lui appartient. Le moyen classique de pouvoir effectuer ce contrôle est d'utiliser une assertion dans laquelle on récupérer l'id de l'utilisateur via Zend_Auth et l'id de la ressource dans la requête.

Je suis en train de refaire mon système d'acl et deux défauts me gènent dans cette utilisation :
- Avec ce système on récupère seulement l'id de la ressource, ce qui nous oblige à faire une requête pour récupérer la ressource dans la base de donnée, requête qui sera ensuite répétée dans notre controller si l'utilisateur est autorisé, bref niveau perf c'est pas top. Il faudrait donc ajouter derrière un système de cache des requêtes sql.
- On ne peut pas vraiment faire d'assertion générique "ressource X appartient à l'utilisateur connecté" sans bidouiller l'url (cad qui s'adapte à différentes ressources).

Tout cela me laisse penser que les assertions ne sont pas vraiment prévues pour ce type de cas mais plus pour des cas spécifiques. Peut être une raison pour laquelle dans la doc ZF l'exemple des assert soit le contrôle d'ip et non ce cas d'école ?

La solution vers laquelle je me penche serait d'ajouter ces fonctionnalités dans ma class Acl étandant de Zend_Acl et de faire le contrôle dans ma classe model, après récupération de la ressource.
Plus concrètement :

Je génère mon acl de manière centralisée

Code:

Class d'init ACL appelée dans le bootstrap:

// ajouter les rôles qui correspondent aux profils utilisateur
$this->addRole(new Zend_Acl_Role('guest'))
     ->addRole(new Zend_Acl_Role('client'), 'guest');

// ajouter les ressources qui correspondent aux controller
$this->addResource(new Zend_Acl_Resource('index'))
     ->addResource(new Zend_Acl_Resource('client'));

// deny all
$this->deny();

// on autorise l'accès à l'action "client/detail-commande" pour le rôle client
$this->allow('client', 'client', array('detail-commande'));

// et on ajoute un restrictione de type "ressource seulement accessible au propriétaire"
$this->restrictAccess(Application_Model_DbTable_Order, self::OWNER_RESTRICTION);

La méthode restrictAccess provient de ma class Acl, dans laquelle j'ai également créé la méthode checkOwnerRestriction qui vérifie si la ressource (table row) a pour propriétaire l'utilisateur connecté.

Code:

class EPZend_Acl extends Zend_Acl implements EPZend_Acl_Interface {
    
        const OWNER_RESTRICTION = 'owner_restriction';
    
    protected $_restrictions = array();

    /**
     * Ajoute une restriction à une ressource (ici une ressource est un table row)
     * 
     * @param $resource
     * @param $restriction
     */
    public function restrictAccess($resource, $restriction) {
        $this->_restrictions[] = array($resource, $restriction);
    }

    /**
     * Vérifie si l'utilisateur est propriétaire d'une ressource
     * 
     * @param $resource
     * @return boolean propriétaire de la ressource
     */
    public function checkOwnerRestriction($resource) {

        // ressource restreinte au propriétaire
        if(in_array(array($resource->getTableClass(), self::OWNER_RESTRICTION), $this->_restrictions)) {
            
            // utilisateur connecté n'est pas le propriétaire
            if($resource->user_id != Zend_Auth::getInstance()->getIdentity()->id) {
                return false;
            }
        }
        
        return true;
    }
}

Et enfin dans mon model j'ai une méthode générique qui permet de récupérer n'importe quelle ressource de n'importe quelle table par son id :

Code:

    public function getById($id, $table = null) {
        
        $table = ($table == null) ? $this->getName() : $table;
        
        $resource = $this->getDbTable($table)->getById((int) $id);
        
        // récupérer les acl
        $acl = Zend_Registry::get('acl');
        
        // ressource restreinte au propriétaire qui n'est pas l'utilisateur connecté
        // ne pas renvoyer la ressource dans ce cas
        if(!$acl->checkOwnerRestriction($resource)) {
            return null;
        }
        
        // sinon retourner la ressource
        return $resource;
    }

Ce n'est qu'un premier jet issu de mes tests de cette fin de soirée, il y'a encore beaucoup de chose à améliorer / régler mais l'idée est la :
Je définie mes ACL proprement et je précise les ressources nécessitant ce genre de restriction (ce qui revient au final au même qu'ajouter un objet Zend_Acl_Assert dans la méthode allow()).
Ensuite lorsque dans mon controller je souhaite récupérer la ressource via un id récupéré dans l'url la vérification est faite automatiquement.

Qu'en pensez vous ? La démarche est la bonne ? Ou alors j'ai loupé quelque chose dans les assertions ?

Excusez mes explications peut être un peu confuse mais c'est un post fait à chaud et je suis fatigué ^^
Je suis preneur de tout les avis / critiques de ceux qui auront le courage de lire ceci.

Merci, bonne soirée.

Hors ligne

 

#2 30-08-2010 09:40:30

Delprog
Administrateur
Date d'inscription: 29-09-2008
Messages: 670

Re: Efficacité des assertions pour une vérification "appartient à"

Salut,

Le cache est à gérer en amont du contrôle des ACLs. Dans ta solution tu devras de toute façon peupler ton objet et donc effectuer une requête dans la persistance, ça revient au même.

Je pense que Zend_Acl est suffisant, puisque tu peux soit passer un nom de ressource ACL (String), soit un Objet qui étend Zend_Acl_Resource_Interface.


Un exemple :

Code:

$this->deny();
$this->allow('member', 'article', 'create');
$this->allow('member', 'article', 'edit', new App_Acl_AssertOwner);
$this->allow('member', 'article', 'delete', new App_Acl_AssertOwner);

Code:

class App_Acl_AssertOwner implements Zend_Acl_Assert_Interface
{
    public function assert(Zend_Acl $acl, Zend_Acl_Role_Interface $user = null, 
        Zend_Acl_Resource_Interface $resource = null, $privilege = null)
    {           
        return $user->getId() == $resource->getUserId();
    }
}

(service)

Code:

public function createArticle(Model_Article $article)
{               
    if ( ! $this->getAuth()->isAllowed( $this->getAuth()->getCurrentUser(), 'article', 'create') ) {
        throw new Service_Exception_Unauthorized('Unauthorized', 401);
    }

    // suite du code [...]
}


public function updateArticle(Model_Article $article)
{               
    // cache à gérer ici si besoin
    $article = $this->_articleRepository->find( $article->getId() );
    if (null === $article ) {
        throw new Service_Exception_InvalidArgument('Article not found', 404);            
    }        

    // utilise l'assertion et cette fois ci je passe mon objet article
    // qui implémente Zend_Acl_Resource_Interface
    if ( ! $this->getAuth()->isAllowed($this->getAuth()->getCurrentUser(), $article, 'edit') ) {
        throw new Service_Exception_Unauthorized('Unauthorized', 401);
    }

    // suite du code [...]
}

Code:

class Model_Article implements Zend_Acl_Resource_Interface
{
    /**
     * @var int
     */
    protected $userId;

    public function setUserId($userId)
    {
        $this->userId = (int)$userId;
        return $this;
    }

    public function getUserId()
    {
        return $this->userId;
    }

    public function getResourceId()
    {
        return 'article';
    }

    // [...]
}

getAuth() est un objet perso qui rassemble les méthodes qu'on peut invoquer sur Zend_Acl et Zend_Auth, ça n'a pas d'importance ici dans l'exemple.

Normalement, à moins que le métier intègre des notions de sécurité, l'objet métier n'est pas censé gérer les autorisations.


A+ benjamin.

Dernière modification par Delprog (30-08-2010 09:44:52)


http://www.anonymation.com/ - anonymation - Studio de création.
http://code.anonymation.com/ - anonymation - blog - développement et architecture web

Hors ligne

 

#3 31-08-2010 15:33:07

Lukas P.
Membre
Date d'inscription: 28-12-2008
Messages: 11

Re: Efficacité des assertions pour une vérification "appartient à"

Merci pour ton retour, en effet j'étais tellement dans les contrôles ACL sur les controlleurs / action que j'ai pas pensé à les implémenter sur les ressources issues de la base.

Mon objet User qui étand de Zend_Db_Table_Row_Abstract implémente maintenant les interfaces Zend_Acl_Role_Interface et Zend_Acl_Resource_Interface (il est le rôle dans les acl mais peut aussi être une ressource, les autres objets n'implémentant que l'interface resource). Le reste correspond dans l'idée au exemples que tu as fournis, tout fonctionne bien et en effet c'est plus propre que de mélanger la couche métier et les ACLs.

La seule contrainte est que du coup je définis les autorisations sur des ressources de type "controller" et des ressources de type "objet" mais au moins on peut être sur que tout est couvert par les ACLs.

Merci pour ton aide.

Hors ligne

 

Pied de page des forums

Propulsé par PunBB
© Copyright 2002–2005 Rickard Andersson
Traduction par punbb.fr

Graphisme réalisé par l'agence Rodolphe Eveilleau
Développement par Kitpages