This post aims to be a kind of second part of this post so you may have to read it to understand this question. Having that info and the answer from previous post and also having found this useful UserBundle which help me as an example to illustrate possible relationship as Many To Many
between roles
and users
I may ask:
- I have dynamic roles and now how I use those new
ROLES
?
What I mean, for example I want to limit a existent function to role ROLE_NEWROLE
which has been created dynamically and therefore doesn't exists on the base code (original sources) then how do I restrict a existent function to that new role? Take the docs here as an example:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
class PostController extends Controller
{
/**
* @Security("has_role('ROLE_ADMIN')")
*/
public function indexAction()
{
// ...
}
}
The code above presume that ROLE_ADMIN
is already declared somewhere and somehow but what if I want to add the new ROLE_NEWROLE
to that function through security component? Do I need to touch my code all the time? That isn't funny at all so I want to know your opinions about this topic.
As we talked about this before, you need to implement EventListener
which will listen to your onKernelRequest
.
In plain English, this means that all of your Controller Actions will execute onKernelRequest
first before giving access to the original controller. So this way you won't have to write
/**
* @Security("has_role('ROLE_ADMIN')")
*/
in every controller action.
Now, its upto you what you want to do in this method. My approach was to make a table which associates a ROLE
with a ROUTE
. This table will be comparatively big because you have to include all ROLES
you want to give access to all ROUTES
.
The table structure can be something like this:
ACCESSID ROLENAME ROUTENAME
1 ROLE_NEWUSER contacts_lookup_homepage
2 ROLE_SUPER_USER contacts_lookup_homepage
According to this table only ROLE_NEWUSER
and ROLE_SUPER_USER
are eligible to access the route contacts_lookup_homepage
This way now only those roles are allowed to access contacts_lookup_homepage
route. Now on the onKernelRequest
all you will do is query this table and check if there is a match with that role with that route. You have access to both in this method. These routes are the same as the one you define in your routing.yml
file of every route. If you're not sure, it looks something like this:
contacts_lookup_homepage:
path: /Contacts/Lookup
defaults: { _controller: ContactsLookupBundle:Default:index }
Now finally in your onKernelRequest
you can do something like this:
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$route = $request->attributes->get('_route');
$routeArr = array('fos_js_routing_js', 'fos_user_security_login', '_wdt'); //These are excluded routes. These are always allowed. Required for login page
$roleArr = $this->token_storage->getToken()->getUser()->getRoles();
if(!is_int(array_search($route, $routeArr))) //This is for excluding routes that you don't want to check for.
{
//Check for a matching role and route
$qb = $this->em->getRepository('AppBundle:UserAccess')->createQueryBuilder('o');
$qb
->select('o')
->where('o.ROLENAME IN (:roleArr)')
->setParameter('roleArr', $roleArr)
->andWhere('o.ROUTENAME = :route')
->setParameter('route', $route)
;
$result = $qb->getQuery()->getArrayResult();
if(empty($result))
{
//A matching role and route was not found so we do not give access to the user here and redirect to another page.
$event->setResponse(new RedirectResponse($this->router->generate('user_management_unauthorized_user', array())));
}
}
}
The services.yml
can be like this:
services:
app.tokens.action_listener:
class: EventListenerBundle\EventListener\TokenListener
arguments:
entityManager: "@doctrine.orm.entity_manager"
token_storage: "@security.token_storage"
templating: "@templating"
router: "@router"
resolver: "@controller_resolver"
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
This will guarantee that no unauthorized user accesses the controller action which is not authorized. I hope that gives you an idea about the implementation.