vlan tracer resueltos pares para listas lista impares extendida ejemplos definicion control bloquear acceso php permissions acl symfony domain-object

php - tracer - lista de control de acceso pdf



¿Cómo se usa ACL para filtrar una lista de objetos de dominio de acuerdo con los permisos de un determinado usuario(por ejemplo, EDITAR)? (4)

Cuando utilizamos la implementación de ACL en Symfony2 en una aplicación web, nos hemos topado con un caso de uso en el que la forma sugerida de utilizar las ACL (verificar los permisos de un usuario sobre un solo objeto de dominio) resulta inviable. Por lo tanto, nos preguntamos si existe alguna parte de la API de ACL que podamos usar para resolver nuestro problema.

El caso de uso está en un controlador que prepara una lista de objetos de dominio para presentar en una plantilla, de modo que el usuario pueda elegir cuál de sus objetos quiere editar. El usuario no tiene permiso para editar todos los objetos en la base de datos, por lo que la lista debe filtrarse en consecuencia.

Esto podría (entre otras soluciones) hacerse de acuerdo con dos estrategias:

1) Un filtro de consulta que agrega una consulta dada con los ID de objeto válidos de la ACL del usuario actual para el objeto (u objetos). Es decir:

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

2) Un filtro posterior a la consulta que elimina los objetos para los que el usuario no tiene los permisos correctos una vez que se ha recuperado la lista completa de la base de datos. Es decir:

$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 primera estrategia es preferible ya que la base de datos está haciendo todo el trabajo de filtrado, y ambos requieren dos consultas de base de datos. Uno para las ACL y otro para la consulta real, pero probablemente sea inevitable.

¿Hay alguna implementación de una de estas estrategias (o algo que logre los resultados deseados) en Symfony2?


Itinerar sobre las entidades no es factible si tiene un par de miles de entidades: se volverá cada vez más lenta y consumirá más memoria, lo que le obligará a utilizar capacidades de procesamiento por lotes de doctrinas, haciendo su código más complejo (e ineficaz porque después de todo solo necesita el ids para hacer una consulta - no todo el acl / entidades en la memoria)

Lo que hicimos para resolver este problema es reemplazar el servicio acl.provider por el nuestro y en ese servicio agregar un método para hacer una consulta directa a la base de datos:

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) ); }

Luego, llamando a este método desde el método público real que obtiene los ID de las entidades:

/** * 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 esta forma, ahora podemos usar el código para agregar filtros a las consultas 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

Inconvenientes: se deben mejorar estos métodos para tener en cuenta las complejidades de la herencia de ACL y las estrategias, pero para casos de uso simple funciona bien. También se debe implementar un caché para evitar la doble consulta repetitiva (una con nivel de clase, otra con nivel de objeto)


Suponiendo que tiene una colección de objetos de dominio que desea verificar, puede usar el método findAcls() del servicio findAcls() para cargar por lotes antes de las llamadas isGranted() .

Condiciones:

La base de datos se MaskBuilder::MASK_OWNER con entidades de prueba, con permisos de objeto de MaskBuilder::MASK_OWNER para un usuario aleatorio de mi base de datos y permisos de clase de MASK_VIEW para la función IS_AUTHENTICATED_ANONYMOUSLY ; MASK_CREATE para ROLE_USER ; y MASK_EDIT y MASK_DELETE para ROLE_ADMIN .

Código de prueba:

$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 ($barCollection as $bar) { $oid = ObjectIdentity::fromDomainObject($bar); $oids[] = $oid; } $aclProvider->findAcls($oids); // preload Acls from database foreach ($barCollection as $bar) { if ($securityContext->isGranted(''EDIT'', $bar)) { // permitted } else { // denied } }

RESULTADOS:

Con la llamada a $aclProvider->findAcls($oids); , el generador de perfiles muestra que mi solicitud contenía 3 consultas de base de datos (como usuario anónimo).

Sin la llamada a findAcls() , la misma solicitud contenía 51 consultas.

Tenga en cuenta que el método findAcls() se carga en lotes de 30 (con 2 consultas por lote), por lo que su número de consultas aumentará con conjuntos de datos más grandes. Esta prueba se realizó en aproximadamente 15 minutos al final del día de trabajo; cuando tenga la oportunidad, revisaré los métodos relevantes más exhaustivamente para ver si hay otros usos útiles del sistema ACL e informaré aquí.


Usa uniones, y en caso de que estés usando Doctrine, haz que genere uniones para ti, ya que casi siempre son más rápidas. Por lo tanto, debe diseñar su esquema de ACL que haciendo estos filtros rápidos son factibles.


Vincular Symfony ACL a la aplicación y usarlo como clasificación no es un buen enfoque. Está mezclando y acoplando 2 o 3 capas de aplicación juntas. La funcionalidad de ACL es responder "SÍ / NO" a la pregunta "¿Se me permite hacer esto?" Si necesita algún tipo de artículos propios / editables, puede usar alguna columna como CreatedBy o group Created By para los criterios de otra tabla. Algunos grupos de usuarios o cuentas.