Symfony 2 ships with a powerful Security Component that can be used to manage user authorization and authentication, handling all the common facilities from the user login to the access control. The access control part is the one we will focus on in this article. In particular, we will see how to simplify per-route access control through a proper route custom tagging and the creation of a dedicated Voter, giving the application administrator an easy way to give access to classes of resources without knowing anything about roles. 

You should be familiar with the Symfony Security and Routing components before reading further.

We are going to take some simple steps:

add a new role that activates our custom Voter

tag our routes with proper keywords specified as route options

create a Voter class that gets the current route and checks if the user has the proper roles for accessing the route tags 

We will take for granted that you already configured authentication with a custom user provider or through FOS User Bundle.

Let's add a new SECTION_CHECK role constraint on the ^/dashboard path that will activate our custom Voter:

# app/config/security.yml
security:
 
# [...]
 
    access_control:
        - { path: ^/dashboard, role: [SECTION_CHECK] }

Let's specify a section tag inside each of our dashboard routes, using the route options:

# src/Spaghetti/DashboardBundle/Resources/config/routing.yml
 
spaghetti_dashboard_article_list:
    pattern:  /articles
    defaults: { _controller: "SpaghettiDashboardBundle:Article:list" }
    options:
        section: magazine
 
# [...]
 
spaghetti_dashboard_blog_post_list:
    pattern:  /blog-posts
    defaults: { _controller: "SpaghettiDashboardBundle:BlogPost:list" }
    options:
        section: blog

Finally, let's create and register a new Voter that will do the magic:

<?php
 
namespace SpaghettiUserBundleSecurityAuthorizationVoter;
 
use SymfonyComponentDependencyInjectionContainerInterface;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;
use SymfonyComponentSecurityCoreAuthorizationVoterVoterInterface;
use SymfonyComponentSecurityCoreUserUserInterface;
 
class SectionVoter implements VoterInterface
{
    protected $container;
 
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }
 
    public function supportsAttribute($attribute)
    {
        return 'SECTION_CHECK' === $attribute;
    }
 
    public function supportsClass($class)
    {
        return true;
    }
 
    public function vote(TokenInterface $token, $object, array $attributes)
    {
        $support = false;
 
        foreach ($attributes as $attribute) {
            if ($this->supportsAttribute($attribute)) {
                $support = true;
            }
        }
 
        if (!$support) {
            return VoterInterface::ACCESS_ABSTAIN;
        }
 
        $user = $token->getUser();
 
        if (!$user instanceof UserInterface) {
            return VoterInterface::ACCESS_DENIED;
        }
 
        if ($user->hasRole('ROLE_ADMIN')) {
            return VoterInterface::ACCESS_GRANTED;
        }
        $roles = $user->getRoles();
 
        $router = $this->container->get('router');
        $routeCollection = $router->getRouteCollection();
        $request = $this->container->get('request_stack')->getCurrentRequest();
        $route = $routeCollection->get($request->get('_route'));
 
        $section = $route->getOption('section');
 
        foreach ($roles as $role) {
            if ($role == 'ROLE_' . strtoupper($section)) {
                return VoterInterface::ACCESS_GRANTED;
            }
        }
 
        return VoterInterface::ACCESS_DENIED;
    }
}

As you can see, inside the voter we grant access if the user is admin or has a role resembling the tag associated to the current route (e.g. ROLE_MAGAZINE for the spaghetti_dashboard_article_list route). Here the rule is simple and based on a naming convention ($role == 'ROLE_' . strtoupper($section)) but you can make it as complex as you like.

Giorgio Cefaro

Freelance Software Engineer , Website , Git home page , @giorrrgio , Linkedin profile
Software Engineer, PHP specialist, coach, public speaker, open source developer and enthusiast, works in the IT field since 1999. TDD addicted, DDD worshipper

All articles by Giorgio Cefaro

Comments

comments powered by Disqus

cloudparty

Follow Us