В Symfony права доступа к данным можно проверять используя ACL модуль, но чаще всего его использование является излишним,загромаждающим приложение. Куда удобнее использовать свои созданные правила, больше походящие на простые условия.
Эти правила могут применяться в различных случаях, например, ограничение доступа к приложению для целого ряда IP адресов: Как создать правила для блокировки IP адресов.
Каким образом Symfony использует Voter
Для того, чтобы использовать классы избирателей (Voters), сначала стоит понять как Symfony с ними взаимодействует. Все они просматриваются каждый раз при вызове метода isGranted()
службы security.context
. Каждое правило либо разрешает, либо запрещает доступ к определенному ресурсу.
В целом, Symfony использует три базовых подхода в качестве реакции на ответ: подтверждение, согласование и единогласие.
Более подробную информацию вы найдете в разделе о процессе принятия решение об отказе или разрешении доступа.
Интерфейс избирателя
Созданный вами класс избирателя должнен соответствовать интерфейсу VoterInterface
:
<?php
interface VoterInterface
{
public function supportsAttribute($attribute);
public function supportsClass($class);
public function vote(TokenInterface $token, $object, array $attributes);
}
Метод supportsAttribute()
показывает поддерживает ли правило данный атрибут пользователя (роли, например, ROLE_USER
или ACL EDIT
и т.д.).
Метод supportsClass()
показывает поддерживает ли правило класс объекта, к которому обращается пользователь.
Метод vote()
отвечает за логику проверки прав доступа. Он должен вернуть одно из следующих значений:
VoterInterface::ACCESS_GRANTED
: доступ разрешенVoterInterface::ACCESS_ABSTAIN
: правило не применимо и не может вернуть однозначный ответVoterInterface::ACCESS_DENIED
: в доступе отказано
В следующем примере будут проверенны права доступа, основываясь на правилах, установленных разработчиком (пользователь должен быть владельцем объекта). Если условие не будет выполнено, то возвращаем VoterInterface::ACCESS_DENIED
, в противном случае возвращаем VoterInterface::ACCESS_GRANTED
. Если же решение о разрешении доступа не может быть принято этим правилом, то возвращаем VoterInterface::ACCESS_ABSTAIN
.
Создание класса избирателя со своими правилами
Цель примера – создание правила, которое будет проверять имеет ли пользователь права на просмотр и редактирование определенного объекта. Пример выполнения:
<?php
// src/Acme/DemoBundle/Security/Authorization/Voter/PostVoter.php
namespace Acme\DemoBundle\Security\Authorization\Voter;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class PostVoter implements VoterInterface
{
const VIEW = 'view';
const EDIT = 'edit';
public function supportsAttribute($attribute)
{
return in_array($attribute, array(
self::VIEW,
self::EDIT,
));
}
public function supportsClass($class)
{
$supportedClass = 'Acme\DemoBundle\Entity\Post';
return $supportedClass === $class || is_subclass_of($class, $supportedClass);
}
/**
* @var \Acme\DemoBundle\Entity\Post $post
*/
public function vote(TokenInterface $token, $post, array $attributes)
{
// проверяет подерживается ли класс текущего объекта избирателем
if (!$this->supportsClass(get_class($post))) {
return VoterInterface::ACCESS_ABSTAIN;
}
// проверка на корректность использования, позволен только один атрибут
// но это необязательно, просто для наглядности приведна
// проверка одного атрибута
if (1 !== count($attributes)) {
throw new \InvalidArgumentException(
'Only one attribute is allowed for VIEW or EDIT'
);
}
// set the attribute to check against
$attribute = $attributes[0];
// проверить, если данный атрибут охватывается этим избирателем
if (!$this->supportsAttribute($attribute)) {
return VoterInterface::ACCESS_ABSTAIN;
}
// получить текущего залогиненного пользователя
$user = $token->getUser();
// убедитесь, что объект яляется пользователем (т.е., что пользователь вошел в систему)
if (!$user instanceof UserInterface) {
return VoterInterface::ACCESS_DENIED;
}
switch($attribute) {
case self::VIEW:
// Объект данных может иметь, например, метод isPrivate()
// который проверяет логический атрибут $private
if (!$post->isPrivate()) {
return VoterInterface::ACCESS_GRANTED;
}
break;
case self::EDIT:
// мы предполагаем, что наша объект данных имеет метод getOwner() to
// для получения текущего владельца (пользователя) для этого объекта данных
if ($user->getId() === $post->getOwner()->getId()) {
return VoterInterface::ACCESS_GRANTED;
}
break;
}
return VoterInterface::ACCESS_DENIED;
}
}
Вот и все! Правило создано. Осталось внедрить его в систему безопасности приложения.
Объявление класса в качестве службы
Чтобы внедрить ваш класс избирателя в систему безопасности приложения, его надо объявить в качестве службы и поставить тег security.voter
.
# src/Acme/DemoBundle/Resources/config/services.yml
services:
security.access.post_voter:
class: Acme\DemoBundle\Security\Authorization\Voter\PostVoter
public: false
tags:
- { name: security.voter }
Проверка прав в контроллере
Созданное вами правила будут всегда проверяться при вызове методе isGranted()
из службы security.context
.
<?php
// src/Acme/DemoBundle/Controller/PostController.php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class PostController extends Controller
{
public function showAction($id)
{
// получаем инстанс Post
$post = ...;
// имейте в виду это будет вызывать все зарегистрированные избиратели
if (false === $this->get('security.context')->isGranted('view', $post)) {
throw new AccessDeniedException('Unauthorised access!');
}
return new Response('<h1>'.$post->getName().'</h1>');
}
}
Как видите, ничего сложного!