Symfony2 ACL combinado con otros criterios


Me pregunto si alguien conoce una forma elegante de lograr esto usando el sistema Symfony2 ACL.

Tengo una entidad Comment (mi objeto de dominio) que necesita ser editable por ROLE_USER pero esto solo se permite dentro de los 5 minutos posteriores a la publicación del comentario; de lo contrario, el comentario solo puede ser editado por ROLE_ADMIN.

Hacerlo de manera 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 la factor de tiempo para ROLE_USER. Mi primer problema es que necesita información del objeto domain, no solo la tabla ACL, pero creo que esto se puede resolver haciendo una clase personalizada ObjectIdentity que también puede contener el tiempo que se publicó el Comment.

Ahora la parte difícil

Creo que necesito crear un PermissionGrantingStrategy personalizado que sepa también mirar el tiempo de creación. Esto tiene que cargarse cuando se comprueba un tipo Comment, pero no se como hacer que se cargue. ¿Alguien sabe si ¿hay algún tipo de fábrica a través de la cual se puede configurar este tipo de cosas? De modo que si una entidad tiene un PermissionGrantingStrategy específico asociado con ella, entonces se usa de lo contrario, se usa el valor predeterminado?

Sé que esto es un poco largo, muchas gracias si alguien sabe cómo lograrlo, 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 se puede editar y no molestarse con ACL en absoluto.

 27
Author: Kasheen, 2011-10-24

2 answers

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

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

Como una prueba rápida de 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;
    }
}
 24
Author: Problematic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-24 21:07:04

Estoy publicando esta solución para que otros puedan ver mi código final, pero aquí están las trampas que encontré al implementar un Votante como Sugerido Problemático.

SupportsAttribute : Parece que cuando llamas al método isGranted en el SecurityContext que en realidad no verifica este método antes de delegar una llamada vote a un VoterInterface, por lo que dentro de tu método vote realmente tienes que verificar los atributos tú mismo.

SupportsClass: En la respuesta de problematic arriba parecía que este método podría ser una clave para una selección basada en fábrica de la cual VoterInterface s puede votar, pero en realidad la documentación de symfony2 dice:

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

Por lo tanto, en realidad parece pertenecer a si el Voter soporta o no el tipo de token. Para empeorar las cosas, el documento PHP parece vago:

Comprueba si el votante apoya la clase.

En cualquier caso, el principal problema es que este método nunca es verificado por el SecurityContext antes de delegar la llamada al método vote de cualquier votante - incluso si este método está codificado para return false vote todavía se llamará!

Así que básicamente la moraleja de la historia parecía ser: comprobar el $attributes y $object entrando en el método vote manualmente.

Mi Código:

Servicios.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 el clase de votante:

<?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 señalarme en la dirección de los votantes.

 36
Author: Kasheen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-27 20:44:53