Rôle ≠ Permission
chalas_r
chalasr
\\ bakslashHQ
@chalas_r
Pourquoi ce talk ?
@chalas_r
class User implements UserInterface
{
public function getRoles(): array
{
$roles = $this->roles;
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
}
@chalas_r
class User implements UserInterface
{
// ...
public function hasRole(string $role): bool
{
return in_array($role, $this->roles);
}
}
@chalas_r
D'où ça vient ?
Le RBAC est un modèle de contrôle d'accès à un système d'information dans lequel chaque décision d'accès est basée sur le rôle auquel l'utilisateur est associé.
@chalas_r
Wikipedia
@chalas_r
class ModifyPostController
{
#[Route('/posts/{id}')]
#[IsGranted('ROLE_BLOGGER')]
public function __invoke(Post $post): Response
{
if ($user !== $post->getOwner()) {
throw new AccessDeniedException('Ce blogpost ne vous appartient pas.');
}
// On peut procéder à la modification
}
}
RBAC - Le cas simple
@chalas_r
Le métier évolue
# config/packages/security.yaml
security:
role_hierarchy:
- ROLE_ADMIN: [ROLE_MODERATOR]
@chalas_r
#[IsGranted(new Expression(
'is_granted("ROLE_MODERATOR") or is_granted("ROLE_BLOGGER")'
))]
public function __invoke(
#[CurrentUser] UserInterface $user,
Post $post,
): Response {
if ($user !== $post->getOwner() && !$user->hasRole('ROLE_MODERATOR')) {
throw new AccessDeniedException('Ce post ne vous appartient pas.');
}
// ...
}
Impact
@chalas_r
class User implements UserInterface
{
// ...
public function hasRole(string $role): bool
{
return in_array($role, $this->roles);
}
}
@chalas_r
#[IsGranted(new Expression(
'is_granted("ROLE_MODERATOR")
or is_granted("ROLE_BLOGGER")'
))]
public function __invoke(
#[CurrentUser] UserInterface $user,
Post $post,
): Response {
if ($user !== $post->getOwner() && !$this->isGranted('ROLE_MODERATOR')) {
throw new AccessDeniedException('Ce poste ne vous appartient pas.');
}
// ...
}
@chalas_r
Problème
La logique de gestion d'accès prend trop de place.
@chalas_r
S'il n'y a pas + de complexité métier, c'est largement suffisant.
@chalas_r
role_hierarchy:
ROLE_REVIEWER:
- ROLE_COMMENT
- ROLE_POST_SUGGEST_EDIT
- ROLE_POST_SUGGEST_CENSURE
ROLE_MODERATOR:
- ROLE_REVIEWER
- ROLE_DELETE_COMMENT
- ROLE_ACCEPT_SUGGESTED_EDIT
- ROLE_REQUEST_EDIT
- ROLE_UNPUBLISH_POST
- ROLE_DELETE_POST
- ROLE_WARN_BLOGGER
- ROLE_BAN_BLOGGER
# ...
Fonction de quelqu'un dans la société ;
attribution ou mission.
@chalas_r
La possibilité pour quelqu'un de faire une action particulière ;
agrément ou autorisation.
@chalas_r
@chalas_r
Un rôle n'est pas une permission.
Un rôle peut être associé à
des permissions.
@chalas_r
Quand les rôles seuls
ne suffisent pas ?
@chalas_r
#[IsGranted(
attribute: new Expression('user === subject'),
subject: new Expression('args["post"].getAuthor()'),
)]
public function __invoke(Post $post): Response
{
// ...
}
@chalas_r
👋 ABAC
Attribute-Based Access Control
@chalas_r
@chalas_r
@chalas_r
Voteurs Built-In
@chalas_r
👋 Custom Voters
@chalas_r
Custom Voters
class BlogPostVoter extends Voter
{
protected function supports($attribute, $subject)
{
return 'POST_EDIT' === $attribute
&& $subject instanceof Post;
}
// ...
}
@chalas_r
class BlogPostVoter extends Voter
{
// ...
protected function voteOnAttribute($attribute, $blogPost, TokenInterface $token)
{
// ...
// Modérateur -> accès autorisé
if ($this->decisionManager->decide($token, ['ROLE_MODERATOR'])) {
return true;
}
// Auteur du post -> accès autorisé
if ($blogPost->getAuthor() === $user) {
return true;
}
// Sinon -> accès non-autorisé
return false;
}
@chalas_r
Aller plus loin
class PermissionVoter implements VoterInterface
{
public function vote(TokenInterface $token, $subject, array $attributes)
{
$permissions = $this->permissionRepository->findBy([
'attribute' => $attributes[0],
'subject' => $subject,
]);
foreach ($permissions as $permission) {
// Si l'utilisateur n'a pas de rôle qui match cette permission -> on skip
if (!$this->decisionManager->decide($token, [$permission->getRole()])) {
continue;
}
// Si permission liée à une expression -> on l'exécute, sinon -> Autorisé
if (!($expr = $permission->getExpression())
|| $this->decisionManager->decide($token, [$expr])
) {
return self::ACCESS_GRANTED;
}
return self::ACCESS_DENIED; // Sinon -> Accès non autorisé
}
}
}
@chalas_r
Références
Merci !
@chalas_r