2012-07-02 13 views
8

Me gustaría poder verificar si los atributos (roles) se otorgan o no a cualquier objeto arbitrario que implemente UserInterface en Symfony2. es posible?¿Cómo se usa AccessDecisionManager en Symfony2 para la autorización de usuarios arbitrarios?

UserInterface->getRoles() no es adecuado para mis necesidades porque no tiene en cuenta la jerarquía de roles, y prefiero no reinventar la rueda en ese departamento, por lo que me gustaría usar Access Decision Manager si es posible .

Gracias.

En respuesta a la solución de Olivier continuación, aquí es mi experiencia:

Usted puede utilizar el servicio security.context con el método isGranted. Puede pasar un segundo argumento que es su objeto.

$user = new Core\Model\User(); 
var_dump($user->getRoles(), $this->get('security.context')->isGranted('ROLE_ADMIN', $user)); 

Salida:

array (size=1) 
    0 => string 'ROLE_USER' (length=9) 

boolean true 

Mi jerarquía de roles:

role_hierarchy: 
    ROLE_USER:   ~ 
    ROLE_VERIFIED_USER: [ROLE_USER] 
    ROLE_ADMIN:   [ROLE_VERIFIED_USER] 
    ROLE_SUPERADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] 
    ROLE_ALLOWED_TO_SWITCH: ~ 

Mi UserInterface->getRoles() método:

public function getRoles() 
{ 
    $roles = [$this->isVerified() ? 'ROLE_VERIFIED_USER' : 'ROLE_USER']; 

    /** 
    * @var UserSecurityRole $userSecurityRole 
    */ 
    foreach ($this->getUserSecurityRoles() as $userSecurityRole) { 
     $roles[] = $userSecurityRole->getRole(); 
    } 

    return $roles; 
} 

ROLE_ADMIN debe asignarse de forma explícita, sin embargo isGranted('ROLE_ADMIN', $user) vuelve TRUE incluso si el usuario se acaba de crear y no se ha asignado ninguna función distinta de la predeterminada ROLE_USER, siempre y cuando el usuario actualmente conectado se concede ROLE_ADMIN. Esto me lleva a creer que el segundo argumento para isGranted() se ignora y que el Token proporcionado a AccessDecisionManager->decide() por el SecurityContext se usa en su lugar.

Si esto es un error, enviaré un informe, pero ¿tal vez sigo haciendo algo mal?

Respuesta

1

RoleVoter no tiene en cuenta el objeto $ pasado a través de SecurityContext->isGranted(). Esto da como resultado RoleHierarchyVoter extrayendo roles del Token en lugar de un objeto proporcionado UserInterface $ (si existe), así que tuve que encontrar una ruta diferente.

Tal vez hay una mejor manera de ir sobre esto y si hay me gustaría asegurarse gustaría saber, pero esta es la solución que se me ocurrió:

En primer lugar he implementado ContainerAwareInterface en mi clase de usuario por lo que podría acceder al componente de seguridad desde dentro de ella:

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

    // ... 
} 

Entonces definido un método hasRole():

/** 
* @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; 
} 

desde un controlador:

$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')); 

Salida:

boolean false 
boolean true 
boolean true 

boolean true 

Aunque no se trate de los AccessDecisionManager o registrados los votantes (a menos que la instancia está probando está el usuario actualmente autenticado), es suficiente para mis necesidades como yo sólo necesito determinar si un usuario dado tiene o no un rol particular.

+0

Obteniendo el error dentro de '$ grantedRole-> getRole()' en Symfony 2.3 –

-3

Puede utilizar el servicio security.context con el método isGranted.

Puede pasar un segundo argumento, que es su objeto (consulte here).

En un controlador:

$this->get('security.context')->isGranted('ROLE_FOOBAR', $myUser) 
+0

Gracias, pero esto fue uno de los primeros métodos que intentó y no parece funcionar. Todavía decide sobre la base del token del usuario actual de lo que puedo decir. He editado mi publicación original y proporcioné el resultado de su sugerencia sobre mi configuración. –

+1

Después de rastrear la ejecución de 'SecurityContext-> isGranted()', aparece '$ object' nunca se considera en el proceso de votación. 'RoleVoter-> vote()' acepta '$ object' como argumento, pero la variable no se usa en absoluto en el cuerpo del método y los roles se extraen del argumento' $ token' (se pasa desde 'AccessDecisionManager-> decide() 'llamar originando en' isGranted() ', con el valor establecido en la propiedad' token' de 'SecurityContext'). –

+4

Esto verificará los permisos para el * usuario autenticado actualmente * y no para $ myUser. –

0

Esto se ve como un problema con el:

abstract class AbstractToken implements TokenInterface

Mira el constructor. Parece que los roles se crean en la instanciación y no se consultan en tiempo de ejecución.

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

Por lo tanto, los roles no pueden cambiar después de que se haya creado el token. Creo que la opción es escribir tu propio votante. Todavía estoy mirando alrededor.

+0

Esto todavía no explica por qué Symfony hace caso omiso de la jerarquía de roles y el segundo argumento para 'SecurityContext-> isGranted'. Parece que la implementación posiblemente esté incompleta. De cualquier manera, no creo que la solución sea registrar a otro votante. Desde entonces, he revisado un poco mi solución publicada y la he trasladado a un servicio del modelo, y ha funcionado bien. Publicaré la solución actualizada pronto. –

+0

La jerarquía de roles se tiene en cuenta por 'RoleHierarchyVoter', está integrada en el componente de seguridad de Symfony –

3

Tal vez se puede crear una instancia de una nueva instancia SecurityContext y utilizarlo para comprobar si el usuario se concede:

$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

Crear una (solución de Shady utilizado) Servicio AccessDecisionMaker

<?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 esto como un servicio

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

Úselo

$this->container->get('bp.access_decision_maker')->isGranted("ROLE_ADMIN",$user); 
13

Para esto solo necesita AccessDecisionManager, sin necesidad de contexto de seguridad ya que no necesita autenticación.

$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')); 

Esto se toman correctamente jerarquía de roles en cuenta, ya RoleHierarchyVoter se registra de manera predeterminada

actualización

Como señaló @redalaanait, security.access.decision_manager es un servicio privado , por lo que acceder a él directamente no es algo bueno que hacer. Es mejor usar service aliasing, que le permite acceder a servicios privados.

+2

Esta es la solución más elegante. Gracias –

+1

Creo que no podemos obtener 'security.access.decision_manager' directamente del contenedor porque es un servicio privado ?? –

+1

@redalaanait Tienes razón, acceder a un servicio privado no es algo bueno. Pero puede usar [aliasing de servicio] (http://symfony.com/doc/current/components/dependency_injection/advanced.html#aliasing), que le permite acceder incluso a servicios privados. –

2

security.context Está en desuso desde 2.6.

Uso 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(); 
} 
Cuestiones relacionadas