27 votes

Comment faire une utilisation d'ACL pour filtrer une liste de domaine-les objets en fonction d'un certain autorisations de l'utilisateur (par exemple, MODIFIER)?

Lors de l'utilisation de l'ACL mise en œuvre dans Symfony2 dans une application web, nous avons rencontré un cas d'utilisation où la manière suggérée de l'utilisation de l'Acl (vérification des autorisations des utilisateurs sur un seul domaine d'objet) devient impossible. Ainsi, nous nous demandons s'il existe une partie de l'ACL de l'API que nous pouvons utiliser pour résoudre notre problème.

Le cas d'utilisation est un contrôleur qui prépare une liste d'objets de domaine pour être présenté dans un modèle, de sorte que l'utilisateur peut choisir lequel de ses objets qu'elle veut modifier. L'utilisateur n'est pas autorisé à modifier tous les objets dans la base de données, de sorte que la liste doit être filtré en conséquence.

Cela pourrait (parmi d'autres solutions) se fait selon deux stratégies:

1) Un filtre de requête qui ajoute une requête donnée avec le id d'objet de la présente liste de contrôle d'accès de l'objet(ou les objets). I. e:

WHERE <other conditions> AND u.id IN(<list of legal object ids here>)

2) Un post-filtre de requête qui supprime les objets que l'utilisateur n'a pas les autorisations correctes pour après la liste complète a été récupéré à partir de la base de données. I. e:

$objs   = <query for objects>
$objIds = <getting all the permitted obj ids from the ACL>
for ($obj in $objs) {
    if (in_array($obj.id, $objIds) { $result[] = $obj; } 
}
return $result;

La première stratégie est préférable que la base de données est en train de faire tout le travail de filtrage, et les deux ont besoin de deux requêtes de base de données. Un pour les Acl et un pour la requête réelle, mais qui est probablement inévitable.

Est-il de la mise en œuvre de l'une de ces stratégies (ou quelque chose de plus sur l'obtention de résultats) dans Symfony2?

16voto

Problematic Points 10229

En supposant que vous avez une collection d'objets de domaine que vous voulez vérifier, vous pouvez utiliser l' security.acl.provider service findAcls() méthode de lot de la charge à l'avance de l' isGranted() des appels.

Conditions:

Base de données a été rempli avec test entités, avec les autorisations de l'objet de l' MaskBuilder::MASK_OWNER pour un utilisateur aléatoire à partir de ma base de données et catégorie autorisations d' MASK_VIEW pour le rôle IS_AUTHENTICATED_ANONYMOUSLY; MASK_CREATE pour ROLE_USER; et MASK_EDIT et MASK_DELETE pour ROLE_ADMIN.

Le Code De Test:

$repo = $this->getDoctrine()->getRepository('Foo\Bundle\Entity\Bar');
$securityContext = $this->get('security.context');
$aclProvider = $this->get('security.acl.provider');

$barCollection = $repo->findAll();

$oids = array();
foreach ($bars as $bar) {
    $oid = ObjectIdentity::fromDomainObject($bar);
    $oids[] = $oid;
}

$aclProvider->findAcls($oids); // preload Acls from database

foreach ($bars as $bar) {
    if ($securityContext->isGranted('EDIT', $bar)) {
        // permitted
    } else {
        // denied
    }
}

RÉSULTATS:

Avec l'appel à l' $aclProvider->findAcls($oids);, le testeur affiche que ma demande contenait des 3 requêtes de base de données (en tant qu'utilisateur anonyme).

Sans l'appel à findAcls(), la même demande contient 51 requêtes.

Notez que l' findAcls() méthode des charges par lots de 30 (avec 2 requêtes par lots), de sorte que votre nombre de requêtes va aller avec de grands ensembles de données. Ce test a été fait en environ 15 minutes à la fin de la journée de travail; lorsque j'ai une chance, je vais examiner et réviser les méthodes de manière plus approfondie pour voir si il y a d'autres utiles utilise le système d'ACL et un rapport ici.

8voto

Diego Points 371

Itinerating, les entités n'est pas possible si vous avez un couple de millième entités - il les garder devient plus lent et consomme plus de mémoire, vous forçant à utiliser doctrine capacités de traitement par lots, donc rendre votre code plus complexe (et innefective parce qu'après tout vous avez seulement besoin de l'id de faire une requête - pas l'ensemble de l'acl/entités en mémoire)

Ce que nous avons fait pour résoudre ce problème est de remplacer acl.prestataire de service avec notre propre et le service ajouter une méthode pour faire une requête directe à la base de données:

private function _getEntitiesIdsMatchingRoleMaskSql($className, array $roles, $requiredMask)
{
    $rolesSql = array();
    foreach($roles as $role) {
        $rolesSql[] = 's.identifier = ' . $this->connection->quote($role);
    }
    $rolesSql =  '(' . implode(' OR ', $rolesSql) . ')';

    $sql = <<<SELECTCLAUSE
        SELECT 
            oid.object_identifier
        FROM 
            {$this->options['entry_table_name']} e
        JOIN 
            {$this->options['oid_table_name']} oid ON (
            oid.class_id = e.class_id
        )
        JOIN {$this->options['sid_table_name']} s ON (
            s.id = e.security_identity_id
        )     
        JOIN {$this->options['class_table_nambe']} class ON (
            class.id = e.class_id
        )
        WHERE 
            {$this->connection->getDatabasePlatform()->getIsNotNullExpression('e.object_identity_id')} AND
            (e.mask & %d) AND
            $rolesSql AND
            class.class_type = %s
       GROUP BY
            oid.object_identifier    
SELECTCLAUSE;

    return sprintf(
        $sql,
        $requiredMask,
        $this->connection->quote($role),
        $this->connection->quote($className)
    );

} 

Puis l'appel de cette méthode de la méthode publique qui obtient les entités id:

/**
 * Get the entities Ids for the className that match the given role & mask
 * 
 * @param string $className
 * @param string $roles
 * @param integer $mask 
 * @param bool $asString - Return a comma-delimited string with the ids instead of an array
 * 
 * @return bool|array|string - True if its allowed to all entities, false if its not
 *          allowed, array or string depending on $asString parameter.
 */
public function getAllowedEntitiesIds($className, array $roles, $mask, $asString = true)
{

    // Check for class-level global permission (its a very similar query to the one
    // posted above
    // If there is a class-level grant permission, then do not query object-level
    if ($this->_maskMatchesRoleForClass($className, $roles, $requiredMask)) {
        return true;
    }         

    // Query the database for ACE's matching the mask for the given roles
    $sql = $this->_getEntitiesIdsMatchingRoleMaskSql($className, $roles, $mask);
    $ids = $this->connection->executeQuery($sql)->fetchAll(\PDO::FETCH_COLUMN);

    // No ACEs found
    if (!count($ids)) {
        return false;
    }

    if ($asString) {
        return implode(',', $ids);
    }

    return $ids;
}

De cette façon, nous pouvons maintenant utiliser le code pour ajouter des filtres de requêtes DQL:

// Some action in a controller or form handler...

// This service is our own aclProvider version with the methods mentioned above
$aclProvider = $this->get('security.acl.provider');

$ids = $aclProvider->getAllowedEntitiesIds('SomeEntityClass', array('role1'), MaskBuilder::VIEW, true);

if (is_string($ids)) {
   $queryBuilder->andWhere("entity.id IN ($ids)");
}
// No ACL found: deny all
elseif ($ids===false) {
   $queryBuilder->andWhere("entity.id = 0")
}
elseif ($ids===true) {
   // Global-class permission: allow all
}

// Run query...etc

Inconvénients: Cette méthode permet d'être amélioré afin de prendre en compte la complexité de l'héritage ACL et des stratégies, mais pour de simples cas d'utilisation, il fonctionne très bien. Aussi un cache doit être mis en place pour éviter la répétition du double de la requête (une par niveau de classe, l'autre avec objetc niveau)

-3voto

PAStheLoD Points 441

Utilisez des jointures, et dans le cas où vous êtes à l'aide de la Doctrine, il arrive à générer des jointures pour vous, car ils sont presque toujours plus rapide. Par conséquent, vous devez concevoir votre ACL schéma que faire de ces filtres rapides sont possibles.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X