#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. Хорошо, после внедрения этого решения я бы сказал, что это правильный путь, но я опубликую свой код ниже, если он кому-нибудь поможет, поскольку я обнаружил много подводных камней при создании избирателя, так что, надеюсь, мой код может послужить документацией (хотя я не могу гарантировать правильность) для других.