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?
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);
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.
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.
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
}
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.
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();
}