Les utilisateurs Classeo étaient limités à un seul rôle, alors que dans la réalité scolaire un directeur peut aussi être enseignant, ou un parent peut avoir un rôle vie scolaire. Cette limitation obligeait à créer des comptes distincts par fonction. Le modèle User supporte désormais plusieurs rôles simultanés avec basculement via le header. L'admin peut attribuer/retirer des rôles depuis l'interface de gestion, avec des garde-fous : pas d'auto- destitution, pas d'escalade de privilèges (seul SUPER_ADMIN peut attribuer SUPER_ADMIN), vérification du statut actif pour le switch de rôle, et TTL explicite sur le cache de rôle actif.
114 lines
3.0 KiB
PHP
114 lines
3.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Administration\Infrastructure\Security;
|
|
|
|
use App\Administration\Domain\Model\User\Role;
|
|
use App\Administration\Domain\Model\User\User;
|
|
use App\Administration\Infrastructure\Api\Resource\UserResource;
|
|
|
|
use function in_array;
|
|
|
|
use Override;
|
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
|
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
|
|
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
|
use Symfony\Component\Security\Core\User\UserInterface;
|
|
|
|
/**
|
|
* Voter pour les autorisations sur la gestion des utilisateurs.
|
|
*
|
|
* Seuls ADMIN et SUPER_ADMIN peuvent gérer les utilisateurs.
|
|
*
|
|
* @extends Voter<string, User|UserResource>
|
|
*/
|
|
final class UserVoter extends Voter
|
|
{
|
|
public const string VIEW = 'USER_VIEW';
|
|
public const string CREATE = 'USER_CREATE';
|
|
public const string BLOCK = 'USER_BLOCK';
|
|
public const string UNBLOCK = 'USER_UNBLOCK';
|
|
public const string RESEND_INVITATION = 'USER_RESEND_INVITATION';
|
|
public const string MANAGE_ROLES = 'USER_MANAGE_ROLES';
|
|
|
|
private const array SUPPORTED_ATTRIBUTES = [
|
|
self::VIEW,
|
|
self::CREATE,
|
|
self::BLOCK,
|
|
self::UNBLOCK,
|
|
self::RESEND_INVITATION,
|
|
self::MANAGE_ROLES,
|
|
];
|
|
|
|
#[Override]
|
|
protected function supports(string $attribute, mixed $subject): bool
|
|
{
|
|
if (!in_array($attribute, self::SUPPORTED_ATTRIBUTES, true)) {
|
|
return false;
|
|
}
|
|
|
|
if ($subject === null) {
|
|
return true;
|
|
}
|
|
|
|
return $subject instanceof User || $subject instanceof UserResource;
|
|
}
|
|
|
|
#[Override]
|
|
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
|
|
{
|
|
$user = $token->getUser();
|
|
|
|
if (!$user instanceof UserInterface) {
|
|
return false;
|
|
}
|
|
|
|
$roles = $user->getRoles();
|
|
|
|
return match ($attribute) {
|
|
self::VIEW => $this->canView($roles),
|
|
self::CREATE, self::BLOCK, self::UNBLOCK, self::RESEND_INVITATION, self::MANAGE_ROLES => $this->canManage($roles),
|
|
default => false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param string[] $roles
|
|
*/
|
|
private function canView(array $roles): bool
|
|
{
|
|
return $this->hasAnyRole($roles, [
|
|
Role::SUPER_ADMIN->value,
|
|
Role::ADMIN->value,
|
|
Role::SECRETARIAT->value,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param string[] $roles
|
|
*/
|
|
private function canManage(array $roles): bool
|
|
{
|
|
return $this->hasAnyRole($roles, [
|
|
Role::SUPER_ADMIN->value,
|
|
Role::ADMIN->value,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param string[] $userRoles
|
|
* @param string[] $allowedRoles
|
|
*/
|
|
private function hasAnyRole(array $userRoles, array $allowedRoles): bool
|
|
{
|
|
foreach ($userRoles as $role) {
|
|
if (in_array($role, $allowedRoles, true)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|