Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
654 views
in Technique[技术] by (71.8m points)

php - How to dynamic handle roles|permissions in Symfony2: restrict functions with dynamic roles

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 SensioBundleFrameworkExtraBundleConfigurationSecurity;

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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: EventListenerBundleEventListenerTokenListener
        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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...