Подтвердить что ты не робот

Symfony2 ACL в сочетании с другими критериями

Мне интересно, знает ли кто-нибудь об элегантном способе достижения этого, используя систему ACL Symfony2.

У меня есть объект Comment (мой объект домена), который должен быть доступен для редактирования ROLE_USER, но это разрешено только в течение 5 минут после публикации комментария, иначе комментарий может быть отредактирован только ROLE_ADMIN.

Сделать это так, чтобы его можно редактировать только с помощью ROLE_USER и ROLE_ADMIN просто, просто сделайте RoleSecurityIdentity для каждого.

Теперь моя проблема возникает, когда я хочу включить фактор времени для ROLE_USER. Моя первая проблема заключается в том, что ему нужна информация от объекта домена, а не только таблицы ACL, но я думаю, что это разрешимо, создав собственный ObjectIdentity класс, который также может содержать время, в течение которого был отправлен Comment.

Теперь для жесткой части

Мне кажется, мне нужно создать пользовательский PermissionGrantingStrategy, который знает, как посмотреть время создания. Это нужно загружать, когда проверяется тип Comment, но я не знаю, как его загрузить. Кто-нибудь знает, есть ли какой-то factory, через который можно настроить такую ​​вещь? Итак, если объект имеет конкретный PermissionGrantingStrategy, связанный с ним, тогда он используется, иначе используется значение по умолчанию?

Я знаю, что это немного длинный, большое спасибо, если кто-то знает, как этого добиться, поскольку документация ACL кажется немного редкой на данный момент. Мое резервное решение состоит в том, чтобы просто сделать какой-то сервис, чтобы проверить, можно ли редактировать комментарий и не беспокоить ACL вообще.

4b9b3361

Ответ 1

Рассматривали ли вы использование избирателя? Там рецепт кулинарной книги для реализации избирателя черного списка черного списка, но его можно легко изменить, чтобы обработать проверку прав на объекты Comment.

Вы можете посмотреть AclVoter по умолчанию на Symfony\Component\Security\Acl\Voter\AclVoter (онлайн здесь), хотя ваш, очевидно, может увеличиваться вместо замены и намного проще.

В качестве быстрого доказательства концепции:

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

Ответ 2

Я отправляю это решение, чтобы другие могли видеть мой окончательный код, но вот те ошибки, которые я обнаружил при реализации предложения Voter as Problematic.

supportAttribute. Похоже, что когда вы вызываете метод isGranted в SecurityContext, который фактически не проверяет этот метод до делегирования вызова vote на VoterInterface, поэтому внутри вашего метода vote вы должны сами проверить атрибуты.

поддерживаетClass. В проблемном ответе выше казалось, что этот метод может быть ключом для выбора Factory, из которого VoterInterface может голосовать, но на самом деле документация symfony2 гласит:

Метод supportsClass() используется для проверки того, поддерживает ли избиратель текущий класс токена пользователя.

Поэтому на самом деле это похоже на то, поддерживает ли Voter тип токена. Хуже того, PHP Doc кажется неопределенным:

Проверяет, поддерживает ли избиратель данный класс.

Во всяком случае главная проблема заключается в том, что этот метод никогда не проверяется SecurityContext перед тем, как делегировать вызов методу vote любого избирателя - , даже если этот метод жестко запрограммирован на return false vote будет по-прежнему вызываться!

Итак, в основном мораль этой истории выглядела так: проверьте $attributes и $object на метод vote вручную.

Мой код:

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 }

и класс избирателя:

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

}

и, наконец, шаблон:

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

Я надеюсь, что это поможет кому-то еще в будущем и большое спасибо Проблематике за то, что указали мне в сторону Избирателей.