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

#symfony #acl

#symfony #acl

Вопрос:

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

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

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

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

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

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

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

Ответ №1:

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

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

supportsClass: в ответе problemable выше казалось, что этот метод может быть ключом к выбору на основе Factory, за который VoterInterface s может голосовать, но на самом деле документация symfony2 гласит:

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

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

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

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

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

Мой код:

services.yml

 parameters:
    comment_voter.class: AcmeBundleCommentBundleSecurityAuthorizationVoterCommentVoter

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

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

 <?php

namespace AcmeBundleCommentBundleSecurityAuthorizationVoter;

use SymfonyComponentSecurityCoreAuthorizationVoterVoterInterface;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;

use AcmeBundleCommentBundleEntityComment;
use SymfonyComponentSecurityCoreUserUserInterface;

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

}
 

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

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

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

Комментарии:

1. Спасибо @Problematic и kasheen, это было бы отличным дополнением к кулинарной книге Symfony github.com/symfony/symfony-docs 😉 Вы должны подать заявку на PR.

Ответ №2:

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

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

В качестве быстрого подтверждения концепции:

 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 'AcmeCommentBundleEntityComment' === $class;
    }
}
 

Комментарии:

1. Это похоже на то, что мне нужно, хорошо подключается, а также доступно через isGranted контекста безопасности (который также можно использовать в моем интерфейсе для условного отображения элементов управления для редактирования комментария). Я попробую сделать это сегодня вечером и отмечу галочкой ответ, если это сработает, но, похоже, вы вышли на победителя 🙂 Спасибо!

2. Хорошо, после внедрения этого решения я бы сказал, что это правильный путь, но я опубликую свой код ниже, если он кому-нибудь поможет, поскольку я обнаружил много подводных камней при создании избирателя, так что, надеюсь, мой код может послужить документацией (хотя я не могу гарантировать правильность) для других.