feat: Attribution de rôles multiples par utilisateur
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.
This commit is contained in:
@@ -18,6 +18,9 @@ use DateTimeImmutable;
|
||||
use function in_array;
|
||||
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use RuntimeException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Cache-based UserRepository for development and testing.
|
||||
@@ -71,7 +74,7 @@ final readonly class CacheUserRepository implements UserRepository
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var array{id: string, email: string, role: string, tenant_id: string, school_name: string, statut: string, hashed_password: string|null, date_naissance: string|null, created_at: string, activated_at: string|null, first_name?: string, last_name?: string, invited_at?: string|null, blocked_at?: string|null, blocked_reason?: string|null, consentement_parental: array{parent_id: string, eleve_id: string, date_consentement: string, ip_address: string}|null} $data */
|
||||
/** @var array{id: string, email: string, roles?: string[], role?: string, tenant_id: string, school_name: string, statut: string, hashed_password: string|null, date_naissance: string|null, created_at: string, activated_at: string|null, first_name?: string, last_name?: string, invited_at?: string|null, blocked_at?: string|null, blocked_reason?: string|null, consentement_parental: array{parent_id: string, eleve_id: string, date_consentement: string, ip_address: string}|null} $data */
|
||||
$data = $item->get();
|
||||
|
||||
return $this->deserialize($data);
|
||||
@@ -136,7 +139,7 @@ final readonly class CacheUserRepository implements UserRepository
|
||||
return [
|
||||
'id' => (string) $user->id,
|
||||
'email' => (string) $user->email,
|
||||
'role' => $user->role->value,
|
||||
'roles' => array_map(static fn (Role $r) => $r->value, $user->roles),
|
||||
'tenant_id' => (string) $user->tenantId,
|
||||
'school_name' => $user->schoolName,
|
||||
'statut' => $user->statut->value,
|
||||
@@ -162,7 +165,8 @@ final readonly class CacheUserRepository implements UserRepository
|
||||
* @param array{
|
||||
* id: string,
|
||||
* email: string,
|
||||
* role: string,
|
||||
* roles?: string[],
|
||||
* role?: string,
|
||||
* tenant_id: string,
|
||||
* school_name: string,
|
||||
* statut: string,
|
||||
@@ -194,10 +198,19 @@ final readonly class CacheUserRepository implements UserRepository
|
||||
$invitedAt = ($data['invited_at'] ?? null) !== null ? new DateTimeImmutable($data['invited_at']) : null;
|
||||
$blockedAt = ($data['blocked_at'] ?? null) !== null ? new DateTimeImmutable($data['blocked_at']) : null;
|
||||
|
||||
// Support both legacy single role and new multi-role format
|
||||
$roleStrings = $data['roles'] ?? (isset($data['role']) ? [$data['role']] : []);
|
||||
|
||||
if ($roleStrings === []) {
|
||||
throw new RuntimeException(sprintf('User %s has no roles in cache data.', $data['id']));
|
||||
}
|
||||
|
||||
$roles = array_map(static fn (string $r) => Role::from($r), $roleStrings);
|
||||
|
||||
return User::reconstitute(
|
||||
id: UserId::fromString($data['id']),
|
||||
email: new Email($data['email']),
|
||||
role: Role::from($data['role']),
|
||||
roles: $roles,
|
||||
tenantId: TenantId::fromString($data['tenant_id']),
|
||||
schoolName: $data['school_name'],
|
||||
statut: StatutCompte::from($data['statut']),
|
||||
|
||||
Reference in New Issue
Block a user