Symfony2 Security: Creating dynamic roles (using RoleInterface)
Matthias Noback
The Symfony Security Component provides a two-layer security system: first it authenticates a user, then is authorizes him for the current request. Authentication means “identify yourself”. Authorization means: “let’s see if you have the right to be here”.
The deciding authority in this case will be the AccessDecisionManager. It has a number of voters (which you may create yourself too). Each voter will be asked if the authenticated user has the right “roles” for the current URL.
Configuring (static) roles
In security.yml
you can add “access control” rules, to define the required roles:
security:
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
In the same file, you can add a “role hierarchy”, which defines a hierarchy of roles. The configuration below means: “a user who has the role “ROLE_ADMIN” may also see any page which requires “ROLE_USER”:
security:
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
Each role appears to be just a string (not even a constant!). This gives you immense flexibility. “Defining a new role” is nothing more than dreaming up a new string, therefore, there is no cookbook article about it…
Yet, roles which are just strings, are also very inflexible, as in “very static”. Let’s say we want to have roles which are in some way dependent on a property of the authenticated user. We can accomplish this by creating role objects, which implement RoleInterface. In fact, every role defined as a string is converted “under the hood” to a role object.
Creating (dynamic) roles
The AccessDecisionManager
collects the current set of roles by calling the getRoles()
method on the authenticated user. This method is defined in UserInterface, which each user object should implement. So, take your custom user class and modify the getRoles()
:
namespace Matthias;
use Symfony\Component\Security\Core\User\UserInterface;
use Matthias\UserDependentRole;
class User implements UserInterface
{
public function getRoles()
{
return array(
'ROLE_USER', // normal "string" role
// ...,
new UserDependentRole($this), // dynamic role
);
}
}
The UserDependentRole
is a class we should still define. It should implement RoleInterface. You can make it do anything, for example:
namespace Matthias;
use Symfony\Component\Security\Core\Role\RoleInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class UserDependentRole implements RoleInterface
{
private $user;
public function __construct(UserInterface $user)
{
$this->user = $user;
}
public function getRole()
{
return 'ROLE_' . strtoupper($this->user->getUsername());
}
}
RoleInterface
just requires you to implement the method getRole()
, which should return a string that uniquely identifies the current role.
Now, in case the user “admin” is authenticated, he now also has a role called “ROLE_ADMIN”. Also, a user “matthias” has a role “ROLE_MATTHIAS”.
Using this UserDependentRole
makes it possible to allow only users with a specific username to enter certain parts of your site. Though this is generally to be considered “bad design”, this post gives you an idea as to what the possibilities are of using a custom role.
If you have any good use cases for this, I am happy to hear about them!