2011-10-24 36 views
26

Me pregunto si alguien sabe de una manera elegante de lograr esto usando el sistema Symfony2 ACL.Symfony2 ACL combinado con otro criterio

Tengo un Comment entidad (mi objeto de dominio), que tiene que ser editable por ROLE_USER pero esto sólo está permitido dentro de los 5 minutos del comentario al que se ha escrito - de lo contrario el comentario sólo puede ser editado por ROLE_ADMIN.

Haciéndolo para que solo pueda ser editado por ROLE_USER y ROLE_ADMIN es simple, solo haga un RoleSecurityIdentity para cada uno.

Ahora mi problema ocurre cuando quiero incorporar el factor de tiempo para ROLE_USER. Mi primer problema es que necesita información del objeto de dominio, no solo de la tabla de ACL, pero creo que esto se puede resolver haciendo una clase personalizada ObjectIdentity que también puede contener el tiempo en que se publicó Comment.

Ahora viene la parte difícil

Creo que necesito para crear una costumbre PermissionGrantingStrategy que sabe mirar también a la hora de creación. Esto debe cargarse cuando se comprueba un tipo Comment, pero no sé cómo cargarlo. ¿Alguien sabe si hay algún tipo de fábrica a través de la cual se pueda configurar este tipo de cosas? ¿De modo que si una entidad tiene asociado un PermissionGrantingStrategy específico, entonces se usa o, de lo contrario, se utiliza el valor predeterminado?

Sé que este es un poco largo, muchas gracias si alguien sabe cómo lograr esto ya que la documentación de ACL parece un poco escasa en este momento. Mi solución alternativa es simplemente hacer algún tipo de servicio para verificar si un Comentario puede ser editado y no molestar con ACL en absoluto.

Respuesta

23

¿Ha considerado usar un votante? Hay un cookbook recipe para implementar un votante de lista negra de IP, pero podría modificarse fácilmente para controlar la edición de objetos de comentario.

Puede ver el AclVoter predeterminado en Symfony\Component\Security\Acl\Voter\AclVoter (en línea here), aunque el suyo obviamente puede aumentar en lugar de reemplazarlo y ser mucho más simple.

Como prueba rápida del concepto:

class CommentTimestampVoter implements VoterInterface 
{ 
    public function supportsAttribute($attribute) 
    { 
     return 'edit' === $attribute; 
    } 

    public function vote(TokenInterface $token, $object, array $attributes) 
    { 
     // 1. check if $token->getUser() has ROLE_ADMIN and return VoterInterface::ACCESS_GRANTED if so 
     // 2. check if $token->getUser() equals $object->getAuthor() and return VoterInterface::ACCESS_DENIED if not 
     // 3. check that $object->getCreatedAt() is within the window allowed for editing and return VoterInterface::ACCESS_GRANTED if so 
     // 4. return VoterInterface::ACCESS_DENIED 
    } 

    public function supportsClass($class) 
    { 
     return 'Acme\CommentBundle\Entity\Comment' === $class; 
    } 
} 
+0

Esto suena como lo que necesito, se conecta muy bien y también se puede acceder a través del contexto de seguridad isGranted (que también se puede usar en mi interfaz para mostrar controles condicionalmente para editar el comentario). Le daré una oportunidad esta noche y marcaré la respuesta si funciona, pero parece que estás en un ganador :) ¡Gracias! – Kasheen

+0

Ok, después de implementar esta solución diría que es el camino a seguir, pero publicaré mi código a continuación en caso de que ayude a cualquiera ya que encontré muchas trampas al crear un votante, así que espero que mi código sirva como documentación (aunque No puedo garantizar la corrección) a otros. – Kasheen

36

Quiero poner esta solución para que otros puedan ver mi código final, pero aquí están los escollos que he encontrado en la aplicación de un votante como se sugiere problemático.

supportsAttribute: Parece que cuando se llama al método isGranted en el SecurityContext que no comprobar realmente este método antes de delegar una llamada a un voteVoterInterface tan dentro de su método vote realidad se tiene que comprobar los atributos ti mismo .

supportsClass: La respuesta de De problemática anteriormente parecía que este método podría ser una clave para una selección basada en fábrica de los cuales VoterInterface s pueden votar, pero en realidad la documentación de Symfony2 lee:

El supportsClass() método se usa para verificar si el votante admite la clase de token de usuario actual.

Por lo tanto, parece ser que el Voter es compatible con el tipo de token. Para empeorar las cosas, el PHP Doc parece vago:

Comprueba si el votante admite la clase dada.

En todo caso el problema principal es que este método no está marcada por el SecurityContext antes de delegar la llamada al método vote de cualquier votante - incluso si este método es que codifica a return falsevote todavía se llamará!

Así que, básicamente, la moraleja de la historia parecía ser: comprobar el $attributes y $object enviado desde la vote método manual.

Mi Código:

services.yml

parameters: 
    comment_voter.class: Acme\Bundle\CommentBundle\Security\Authorization\Voter\CommentVoter 

services: 
    comment_voter: 
     class: %comment_voter.class% 
     arguments: [@service_container] 
     public: false 
     tags: 
      - { name: security.voter } 

y la clase de votantes:

<?php 

namespace Acme\Bundle\CommentBundle\Security\Authorization\Voter; 

use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; 
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 

use Acme\Bundle\CommentBundle\Entity\Comment; 
use Symfony\Component\Security\Core\User\UserInterface; 

/** 
* A class to check editing privileges for Comments. 
*/ 
class CommentVoter implements VoterInterface { 

    const AUTHOR_EDIT_TIME_LIMIT = 300; 

    private $container; 

    public function __construct($container) { 
     $this->container = $container; 
    } 

    public function supportsAttribute($attribute) { 
     return $attribute === 'EDIT'; 
    } 

    public function supportsClass($class) { 
     return true; 
    } 

    /** 
    * Checks whether or not the current user can edit a comment. 
    * 
    * Users with the role ROLE_COMMENT_MODERATOR may always edit. 
    * A comment's author can only edit within 5 minutes of it being posted. 
    * 
    * {@inheritdoc} 
    */ 
    public function vote(TokenInterface $token, $object, array $attributes) { 
     if (!($object instanceof Comment)) { 
      return VoterInterface::ACCESS_ABSTAIN; 
     } 

     // Only supports 'EDIT' for now. 
     if (!$this->supportsAttribute($attributes[0])) { 
      return VoterInterface::ACCESS_ABSTAIN; 
     } 

     $user = $token->getUser(); 
     if (!($user instanceof UserInterface)) { 
      return VoterInterface::ACCESS_DENIED; 
     } 

     // Is the token a comment moderator? 
     if ($this->container->get('security.context')->isGranted('ROLE_COMMENT_MODERATOR')) { 
      return VoterInterface::ACCESS_GRANTED; 
     } 

     // Is the token the author of the post and within the edit window. 
     $originalRevision = $object->getOriginalRevision(); 
     if ($originalRevision->getAuthor()->equals($user)) { 
      if ( 
       (time() - $originalRevision->getCreationDate()->getTimestamp()) 
       <= self::AUTHOR_EDIT_TIME_LIMIT 
      ) { 
       return VoterInterface::ACCESS_GRANTED; 
      } 
     } 

     return VoterInterface::ACCESS_DENIED; 
    } 

} 

y finalmente plantilla:

{% if is_granted('EDIT', comment) %}<a href="#">Edit</a>{% endif %} 

Espero que esto ayude a alguien más en el futuro y un gran agradecimiento a Problematic por apuntarme en la dirección de los votantes.

+6

_Great_ addition aquí. Gracias por su trabajo de campo! – Problematic

+6

Gracias @Problematic y kasheen, Esta sería una gran adición al libro de cocina de Symfony https://github.com/symfony/symfony-docs;) Debería enviar un PR. – maxwell2022