6

After i read this chapter in cookbook

http://symfony.com/doc/current/cookbook/security/entity_provider.html

i create a entity "User" which implements the "AdvancedUserInterface" and a entity "Roles" which implements the "RoleInterface". Also i create a role structure in my "security.yml".

The relation between user and roles is a "ManyToMany" relation.

Everything is fine.

For the logged in user i can check a grant like this:

$this->get('security.context')->isGranted("ROLE_EDITOR");

But how can i check this grant for other user in database?

There is something like?

$this->get('security.context')->isGranted("ROLE_EDITOR", $user);
smartcoderx
  • 1,021
  • 2
  • 14
  • 32
  • What about http://stackoverflow.com/questions/24078270/check-if-a-role-is-granted-for-a-specific-user-in-symfony2-acl? – putvande Jun 30 '15 at 14:25
  • Also, related solution: http://stackoverflow.com/questions/11288293/how-to-use-the-accessdecisionmanager-in-symfony2-for-authorization-of-arbitrary/22380765#22380765 – Jovan Perovic Jun 30 '15 at 14:27

4 Answers4

3

Warning: this code only checks if the specified user has a given role. It is useful for example for showing users' roles, but cannot be used to effectively grant access to another user, as voters aren't used. This is the case for all answers here.

Symfony 5 answer

namespace App\Controller;

...
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Security\Core\Role\RoleHierarchy;

class UserController extends AbstractController
{
    private $roleHierarchy;

    /**
     * @Route("/users", name="users")
     */
    public function usersIndex(RoleHierarchyInterface $roleHierarchy)
    {
        $this->roleHierarchy = $roleHierarchy;

        // your user service or your Doctrine code here
        $users = ...

        foreach ($users as $user) {
            $roles = $roleHierarchy->getReachableRoleNames($user->getRoles());
            \dump($roles);

            if ($this->hasRole($user, 'ROLE_SUPER_ADMIN')) {
                ...
            }
        }
        ...
    }

    private function hasRole(User $user, string $role): bool 
    {
        $reachableRoles = $this->roleHierarchy->getReachableRoleNames($user->getRoles());

        foreach ($reachableRoles as $reachableRole) {
            if ($reachableRole === $role) {
                return true;
            }
        }

        return false;
    }
}

Note: I put everything in the controller for the sake of simplicity here, but of course I'd recommend to move the Role Management code into a separate service like @leberknecht's answer.

Erdal G.
  • 2,694
  • 2
  • 27
  • 37
  • 2
    This is not a good solution, as Voters will not apply and this has to mirror symfony's isGranted perfectly at all times, which is pretty much impossible – Tofandel Jul 19 '22 at 14:33
  • @Tofandel totally agree. I've place a warning to know what you get + changed method name for less confusion – Erdal G. Jul 19 '22 at 15:19
  • The `foreach` can be replaced with an `in_array` for a simpler code – Spoody Oct 05 '22 at 11:14
2

Within PHP code, I just inject the the RoleHierarchyInterface and use this simple expression:

if (in_array($role, $this->roleHierarchy->getReachableRoleNames($user->getRoles()))) {
  // whatever
}

In Twig, I wrote this simple extension (works with current Versions: Symfony 5.2 and Twig 3.3):

<?php
// /src/Twig/AccessCheckExtension.php
declare(strict_types=1);
namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;

class AccessCheckExtension extends AbstractExtension
{
    public function getFilters(): array
    {
        return [
            new TwigFilter('has_role', [AccessCheckRuntime::class, 'hasRole'])
        ];
    }
    
    public function getFunctions(): array
    {
        return [
            new TwigFunction('has_role', [AccessCheckRuntime::class, 'hasRole'])
        ];
    }
}

and

<?php
// /src/Twig/AccessCheckRuntime.php
declare(strict_types=1);
namespace App\Twig;

use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Twig\Extension\RuntimeExtensionInterface;

class AccessCheckRuntime implements RuntimeExtensionInterface
{
    /**
     * this uses PHP 8.0 constructor property promotion to reduce the needed lines
     **/
    public function __construct(
        protected RoleHierarchyInterface $roleHierarchy
    ){}

    public function hasRole(UserInterface $user, string $role): bool
    {
        return in_array($role, $this->roleHierarchy->getReachableRoleNames($user->getRoles()));
    }
}

This can be used in a Twig template as following:

{% if user|has_role('ROLE_WHATEVER') %}
Hello {{ user.name }}
{% endif %}

or

{% if has_role(user, 'ROLE_WHATEVER') %}
Hello {{ user.name }}
{% endif %}

Pretty straightforward. I prefer the filter variant.

spackmat
  • 892
  • 9
  • 23
1

Dont know if there is something build-in by now, but its pretty straight-forward to build:

class RoleCheckerService
{
    private const PROVIDER_KEY = 'role-check';
    /**
     * @var RoleHierarchyInterface
     */
    private $roleHierarchy;

    public function __construct(RoleHierarchyInterface $roleHierarchy)
    {
        $this->roleHierarchy = $roleHierarchy;
    }

    final public function isGranted(User $user, string $role): bool 
    {
        $token = new PreAuthenticatedToken($user, null, self::PROVIDER_KEY, $user->getRoles());
        $reachableRoles = $this->roleHierarchy->getReachableRoles($token->getRoles());

        foreach ($reachableRoles as $reachableRole) {
            if ($reachableRole->getRole() === $role) {
                return true;
            }
        }

        return false;
    }
}
leberknecht
  • 1,526
  • 15
  • 27
  • Why `$token = new PreAuthenticatedToken($user, null, self::PROVIDER_KEY, $user->getRoles());` inatead of `$user->getRoles()` ? – Erdal G. Aug 30 '20 at 11:18
  • @ErdalG. You can setup role_hierarchy inside your security config. Example: ROLE_SUPER_ADMIN in my app gives all other roles to the user. Because of this you need to call RoleHierarchy::getReachableRoles to figure out what roles the user has. https://symfony.com/doc/current/security.html#hierarchical-roles – Skylord123 Mar 01 '21 at 15:13
  • @Skylord123 I get that, I was talking about doing: _preAuth stuff_ + `$token->getRoles()` instead of simpler `$security->getUser()->getRoles()`. But I don't know if there is a difference – Erdal G. Mar 02 '21 at 10:51
  • Actually `$token->getRoles()` isn't even a function and that code is wrong (least not in Symfony 5.X). It should be `$user->getRoles()`. There is `$token->getRoleNames()` though and basically just returns the same roles that were passed to the token. Maybe it's better to get the roles off the token just-in-case there is some logic in there that modifies them? That would be my guess. – Skylord123 Mar 04 '21 at 15:18
0

Following code works completely with Voters as well.

namespace App\Security;

use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken;

class SecurityManager
{
    public function __construct(private AccessDecisionManagerInterface $adm)
    {
    }

    public function isGranted(UserInterface $user, mixed $attribute, mixed $subject = null): bool
    {
        $tokenStorage = new TokenStorage();
        $token = new PostAuthenticationToken($user, 'main', $user->getRoles());
        $tokenStorage->setToken($token);
        $authorizationChecker = new AuthorizationChecker($tokenStorage, $this->adm);

        return $authorizationChecker->isGranted($attribute, $subject);
    }
}
use App\Security\SecurityManager;

class Foo
{
    public function __construct(private SecurityManager $sm)
    {
    }
    
    public function bar(UserInterface $user, Baz $baz): void
    {
        if ($this->sm->isGranted($user, 'EDIT', $baz)) {
            // do something
        }
    }
}
ttskch
  • 91
  • 1
  • 4