How to use the AccessDecisionManager in Symfony2 for authorization of arbitrary users?

后端 未结 7 1865
庸人自扰
庸人自扰 2020-12-14 21:14

I\'d like to be able to verify whether or not attributes (roles) are granted to any arbitrary object implementing UserInterface in Symfony2. Is this possible?

相关标签:
7条回答
  • 2020-12-14 21:47

    Create a service AccessDecisionMaker (used Shady's solution)

    <?php
    namespace Bp\CommonBundle\Service;
    
    use Symfony\Component\DependencyInjection\Container;
    use Symfony\Component\Security\Core\Role\RoleInterface;
    use Symfony\Component\Security\Core\User\UserInterface;
    use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
    use Symfony\Component\Security\Core\SecurityContext;
    
    class AccessDecisionMaker
    {
        /** @var Container */
        private $container;
    
        /** @var  SecurityContext */
        private $securityContext;
    
        function __construct($container)
        {
            $this->container = $container;
    
            if (!$this->securityContext) {
                // Ensure security context is created only once
                $this->securityContext = new SecurityContext($this->container->get(
                    'security.authentication.manager'
                ), $this->container->get('security.access.decision_manager'));
            }
        }
    
        public function isGranted($roleToCheck, UserInterface $user)
        {
            if (!is_string($roleToCheck)) {
                if (!($roleToCheck instanceof RoleInterface)) {
                    throw new \InvalidArgumentException('First argument expects a string or instance of RoleInterface');
                }
                $roleToCheck = $roleToCheck->getRole();
            }
    
            $token = new UsernamePasswordToken($user, null, $this->container->getParameter(
                'fos_user.firewall_name'
            ), $user->getRoles());
            $this->securityContext->setToken($token);
            if ($this->securityContext->isGranted($roleToCheck)) {
                return true;
            }
    
            return false;
        }
    
    }
    

    Configure this as a service

    bp.access_decision_maker:
        class: Bp\CommonBundle\Service\AccessDecisionMaker
        arguments:  [@service_container ]
    

    Use it

    $this->container->get('bp.access_decision_maker')->isGranted("ROLE_ADMIN",$user);
    
    0 讨论(0)
  • 2020-12-14 21:53

    You need only AccessDecisionManager for this, no need for security context since you don't need authentication.

    $user = new Core\Model\User();
    
    $token = new UsernamePasswordToken($user, 'none', 'none', $user->getRoles());
    $isGranted = $this->get('security.access.decision_manager')
        ->decide($token, array('ROLE_ADMIN'));
    

    This will correctly take role hierarchy into account, since RoleHierarchyVoter is registered by default

    Update

    As noted by @redalaanait, security.access.decision_manager is a private service, so accessing it directly is not a good thing to do. It's better to use service aliasing, which allows you to access private services.

    0 讨论(0)
  • 2020-12-14 22:00

    This looks like an issue with the:

    abstract class AbstractToken implements TokenInterface

    Look at the constructor. Looks like roles are created on instantiation and not queried at run time.

    public function __construct(array $roles = array())
    {
        $this->authenticated = false;
        $this->attributes = array();
    
        $this->roles = array();
        foreach ($roles as $role) {
            if (is_string($role)) {
                $role = new Role($role);
            } elseif (!$role instanceof RoleInterface) {
                throw new \InvalidArgumentException(sprintf('$roles must be an array of strings, or RoleInterface instances, but got %s.', gettype($role)));
            }
    
            $this->roles[] = $role;
        }
    }
    

    Hence, the roles cannot change after the token has been created. I think the option is to write your own voter. I'm still looking around.

    0 讨论(0)
  • 2020-12-14 22:01

    Maybe you can instantiate a new securityContext instance and use it to check if user is granted :

    $securityContext = new \Symfony\Component\Security\Core\SecurityContext($this->get('security.authentication.manager'), $this->get('security.access.decision_manager'));
    $token           = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken($user, null, $this->container->getParameter('fos_user.firewall_name'), $user->getRoles());
    $securityContext->setToken($token);
    if ($securityContext->isGranted('ROLE_ADMIN')) {
        // some stuff to do
    }
    
    0 讨论(0)
  • 2020-12-14 22:02

    RoleVoter disregards the $object passed through from SecurityContext->isGranted(). This results in the RoleHierarchyVoter extracting roles from the Token instead of a provided UserInterface $object (if exists), so I had to find a different route.

    Maybe there is a better way to go about this and if there is I'd sure like to know, but this is the solution I came up with:

    First I implemented ContainerAwareInterface in my User class so I could access the security component from within it:

    final class User implements AdvancedUserInterface, ContainerAwareInterface
    {
        // ...
    
        /**
         * @var ContainerInterface
         */
        private $container;
    
        // ...
    
        public function setContainer(ContainerInterface $container = null)
        {
            if (null === $container) {
                throw new \Exception('First argument to User->setContainer() must be an instance of ContainerInterface');
            }
    
            $this->container = $container;
        }
    
        // ...
    }
    

    Then I defined a hasRole() method:

    /**
     * @param string|\Symfony\Component\Security\Core\Role\RoleInterface $roleToCheck
     * @return bool
     * @throws \InvalidArgumentException
     */
    public function hasRole($roleToCheck)
    {
        if (!is_string($roleToCheck)) {
            if (!($roleToCheck instanceof \Symfony\Component\Security\Core\Role\RoleInterface)) {
                throw new \InvalidArgumentException('First argument expects a string or instance of RoleInterface');
            }
            $roleToCheck = $roleToCheck->getRole();
        }
    
        /**
         * @var \Symfony\Component\Security\Core\SecurityContext $thisSecurityContext
         */
        $thisSecurityContext = $this->container->get('security.context');
        $clientUser = $thisSecurityContext->getToken()->getUser();
    
        // determine if we're checking a role on the currently authenticated client user
        if ($this->equals($clientUser)) {
            // we are, so use the AccessDecisionManager and voter system instead
            return $thisSecurityContext->isGranted($roleToCheck);
        }
    
        /**
         * @var \Symfony\Component\Security\Core\Role\RoleHierarchy $thisRoleHierarchy
         */
        $thisRoleHierarchy = $this->container->get('security.role_hierarchy');
        $grantedRoles = $thisRoleHierarchy->getReachableRoles($this->getRoles());
    
        foreach ($grantedRoles as $grantedRole) {
            if ($roleToCheck === $grantedRole->getRole()) {
                return TRUE;
            }
        }
    
        return FALSE;
    }
    

    From a controller:

    $user = new User();
    $user->setContainer($this->container);
    
    var_dump($user->hasRole('ROLE_ADMIN'));
    var_dump($this->get('security.context')->isGranted('ROLE_ADMIN'));
    var_dump($this->get('security.context')->isGranted('ROLE_ADMIN', $user));
    
    $user->addUserSecurityRole('ROLE_ADMIN');
    var_dump($user->hasRole('ROLE_ADMIN'));
    

    Output:

    boolean false
    boolean true
    boolean true
    
    boolean true
    

    Although it does not involve the AccessDecisionManager or registered voters (unless the instance being tested is the currently authenticated user), it is sufficient for my needs as I just need to ascertain whether or not a given user has a particular role.

    0 讨论(0)
  • 2020-12-14 22:03

    security.context Is deprecated since 2.6.

    Use AuthorizationChecker:

    $token = new UsernamePasswordToken(
         $user,
         null,
         'secured_area',
         $user->getRoles()
    );
    $tokenStorage = $this->container->get('security.token_storage');
    $tokenStorage->setToken($token);
    $authorizationChecker = new AuthorizationChecker(
         $tokenStorage,
         $this->container->get('security.authentication.manager'),
         $this->container->get('security.access.decision_manager')
    );
    if (!$authorizationChecker->isGranted('ROLE_ADMIN')) {
        throw new AccessDeniedException();
    }
    
    0 讨论(0)
提交回复
热议问题