Rôle ≠ Permission
chalas_rchalasr
\\ bakslashHQ@chalas_rPourquoi ce talk ?
@chalas_rclass User implements UserInterface
{
public function getRoles(): array
{
$roles = $this->roles;
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
}@chalas_rclass User implements UserInterface
{
// ...
public function hasRole(string $role): bool
{
return in_array($role, $this->roles);
}
}@chalas_rD'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_rWikipedia
@chalas_rclass 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_rLe 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_rclass 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_rProblème
La logique de gestion d'accès prend trop de place.
@chalas_rS'il n'y a pas + de complexité métier, c'est largement suffisant.
@chalas_rrole_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_rLa possibilité pour quelqu'un de faire une action particulière ;
agrément ou autorisation.
@chalas_r@chalas_rUn rôle n'est pas une permission.
Un rôle peut être associé à
des permissions.
@chalas_rQuand 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_rVoteurs Built-In
@chalas_r👋 Custom Voters
@chalas_rCustom Voters
class BlogPostVoter extends Voter
{
protected function supports($attribute, $subject)
{
return 'POST_EDIT' === $attribute
&& $subject instanceof Post;
}
// ...
}@chalas_rclass 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_rAller 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_rRéférences
Merci !
@chalas_r