Rôle  Permission

 

Robin Chalas

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

Role-Based Access Control

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
        # ...

Rôle

Fonction de quelqu'un dans la société ;
attribution ou mission.

@chalas_r

Permission

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

Ne pas confondre Role et Permission - Symfony Live 2024

By Robin Chalas

Ne pas confondre Role et Permission - Symfony Live 2024

Il n'est pas rare de tomber sur des hiérarchies de rôles complexes et longues comme le bras. Le problème avec ces listes interminables, c'est qu'elles ont tendance à fortement complexifier le code de nos projets. En effet, les rôles Symfony étant directement liés à la notion de permissions, on les retrouve un peu partout et à tous les niveaux de notre code. De fait, plus la liste est longue, plus le code devient compliqué à appréhender et à faire évoluer. Et si je vous disais qu'une grande partie des rôles de votre application ne sont en fait pas vraiment des rôles ? Et qu'il existe de bien meilleures façons de gérer les permissions de vos utilisateurs ? Dans ce talk, nous verrons comment remettre de l'ordre dans tout ça en apprenant à différencier Rôle et Permission et à utiliser correctement ces concepts distincts dans le cadre de vos projets Symfony.

  • 561