Grégoire Pineau
Contributed by Grégoire Pineau in #16601

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.

Published in #Living on the edge