Security Voters are the recommended way to check for user permissions in
Symfony applications. In Symfony 2.6 we introduced an AbstractVoter
class
to reduce the boilerplate code needed to define a voter. Instead of implementing
the VoterInterface
, you could just extend from AbstractVoter
.
In Symfony 2.8 we have simplified voters a bit more thanks to the new Voter class.
This article shows the main changes needed to upgrade a voter to use the new class.
The first step is to make your voters extend the new Voter
class instead of
the previous AbstractVoter
class:
1 2 3 4 5 6 7 8 9 10
namespace AppBundle\Security\Voter;
use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
// Before Symfony 2.8
class CustomVoter extends AbstractVoter { ... }
// In Symfony 2.8
class CustomVoter extends Voter { ... }
The second important change is that supportsAttribute()
and supportsClass()
from VoterInterface
are deprecated in Symfony 2.8 and they are removed in 3.0.
These methods (and the getSupportedAttributes()
and getSupportedClasses()
methods of the AbstractVoter
class) have been replaced by the new
supports($attribute, $subject)
method, which checks if the given attribute
and "subject" are supported by the voter.
The "subject" can be anything you want to secure. Although it's usually an object
(e.g. BlogPost
, Invoice
, Product
) you can also pass simple variables
like a string (e.g. the path to a resource to be downloaded).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
// Before Symfony 2.8
class CustomVoter extends AbstractVoter
{
const VIEW = 'view';
const EDIT = 'edit';
protected function getSupportedAttributes()
{
return array(self::VIEW, self::EDIT);
}
protected function getSupportedClasses()
{
return array('AppBundle\Entity\BlogPost');
}
}
// In Symfony 2.8
use AppBundle\Entity\BlogPost;
class CustomVoter extends Voter
{
const VIEW = 'view';
const EDIT = 'edit';
public function supports($attribute, $subject)
{
return $subject instanceof BlogPost && in_array($attribute, array(
self::VIEW, self::EDIT
));
}
}
The last change is the introduction of a new voteOnAttribute()
method which
performs a single access check operation on a given attribute, "subject" and the
security token (this is almost identical to AbstractVoter
class):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
// ...
use Symfony\Component\Security\Core\User\UserInterface;
use AppBundle\Entity\User;
// Before Symfony 2.8
class CustomVoter extends AbstractVoter
{
// ...
protected function isGranted($attribute, $post, $user = null)
{
if ($attribute === self::VIEW && !$post->isPrivate()) {
return true;
}
if (!$user instanceof UserInterface) {
return false;
}
if ($attribute === self::EDIT && $user->getId() === $post->getOwner()->getId()) {
return true;
}
return false;
}
}
// In Symfony 2.8
class CustomVoter extends Voter
{
// ...
protected function voteOnAttribute($attribute, $post, TokenInterface $token)
{
if ($attribute === self::VIEW && !$post->isPrivate()) {
return true;
}
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
if ($attribute === self::EDIT && $user->getId() === $post->getOwner()->getId()) {
return true;
}
return false;
}
}
In summary, the new Voter
class requires you to implement two methods:
1 2 3 4 5 6 7
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
abstract class Voter implements VoterInterface
{
abstract protected function supports($attribute, $subject);
abstract protected function voteOnAttribute($attribute, $subject, TokenInterface $token);
}
The existing AbstractVoter
class has been deprecated since version 2.8 and it's been removed in 3.0. If you plan to upgrade to Symfony 3 soon, start updating
your voters to use the Voter
class instead.
AbstractVoter will not be removed in 3.0. It has been removed in 3.0. You should update your blog post template for deprecations as 3.0 is already released
@Christophe I've fixed the error. Thanks!
supportsAttribute should be supports in the second code example.
@Wouter very nice catch! Fixed. Thanks.
"have been replaced by the new supports($attribute, $subject) method" in the article, but "public function supportsAttribute($attribute, $subject)" in the code, which one is correct?
@Alex the old method is
supportsAttribute()
and the new onesupports()
. I've updated the sample code to fix the previous error. Thanks!Is use AppBundle\Entity\User (still) necessary in this case ?
@Marc in this example is needed because we do this check:
if (!$user instanceof User) { return false; }
So if I understand this correctly in the newer example we could ommit use Symfony\Component\Security\Core\User\UserInterface; ?
@Marc yes, but I added it because it's required by the old example.
I remember the old way to do the same thing in Sf < 2.6, the new version is REALLY simpler! Thanks a lot for this improvement!