feat: Permettre au super admin de se connecter et accéder à son dashboard

Le super admin (table super_admins, master DB) ne pouvait pas se connecter
via /api/login car ce firewall n'utilisait que le provider tenant. De même,
le JWT n'était pas enrichi pour les super admins, l'endpoint /api/me/roles
les rejetait, et le frontend redirigeait systématiquement vers /dashboard.

Un chain provider (super_admin + tenant) résout l'authentification,
le JwtPayloadEnricher et MyRolesProvider gèrent désormais les deux types
d'utilisateurs, et le frontend redirige selon le rôle après login.
This commit is contained in:
2026-02-17 10:07:10 +01:00
parent c856dfdcda
commit 0951322d71
68 changed files with 4049 additions and 8 deletions

View File

@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace App\Tests\Unit\Administration\Infrastructure\Api\Provider;
use ApiPlatform\Metadata\Get;
use App\Administration\Application\Port\ActiveRoleStore;
use App\Administration\Application\Service\RoleContext;
use App\Administration\Domain\Model\User\Email;
use App\Administration\Domain\Model\User\Role;
use App\Administration\Domain\Model\User\StatutCompte;
use App\Administration\Domain\Model\User\User;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Domain\Repository\UserRepository;
use App\Administration\Infrastructure\Api\Provider\MyRolesProvider;
use App\Administration\Infrastructure\Security\SecurityUser;
use App\Shared\Domain\Tenant\TenantId;
use App\SuperAdmin\Domain\Model\SuperAdmin\SuperAdminId;
use App\SuperAdmin\Infrastructure\Security\SecuritySuperAdmin;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
final class MyRolesProviderTest extends TestCase
{
#[Test]
public function provideReturnsSuperAdminRoleForSecuritySuperAdmin(): void
{
$security = $this->createMock(Security::class);
$security->method('getUser')->willReturn(
new SecuritySuperAdmin(
superAdminId: SuperAdminId::generate(),
email: 'sadmin@test.com',
hashedPassword: 'hashed',
)
);
$userRepository = $this->createMock(UserRepository::class);
$roleContext = new RoleContext(new NullActiveRoleStore());
$provider = new MyRolesProvider($security, $userRepository, $roleContext);
$output = $provider->provide(new Get());
self::assertSame('ROLE_SUPER_ADMIN', $output->activeRole);
self::assertSame('Super Admin', $output->activeRoleLabel);
self::assertCount(1, $output->roles);
self::assertSame('ROLE_SUPER_ADMIN', $output->roles[0]['value']);
self::assertSame('Super Admin', $output->roles[0]['label']);
}
#[Test]
public function provideReturnsUserRolesForSecurityUser(): void
{
$userId = UserId::generate();
$tenantId = TenantId::fromString('550e8400-e29b-41d4-a716-446655440002');
$securityUser = new SecurityUser(
userId: $userId,
email: 'user@example.com',
hashedPassword: 'hashed',
tenantId: $tenantId,
roles: ['ROLE_PARENT'],
);
$security = $this->createMock(Security::class);
$security->method('getUser')->willReturn($securityUser);
$user = User::reconstitute(
id: $userId,
email: new Email('user@example.com'),
roles: [Role::PARENT],
tenantId: $tenantId,
schoolName: 'Test',
statut: StatutCompte::ACTIF,
dateNaissance: null,
createdAt: new DateTimeImmutable(),
hashedPassword: 'hashed',
activatedAt: new DateTimeImmutable(),
consentementParental: null,
);
$userRepository = $this->createMock(UserRepository::class);
$userRepository->method('get')->willReturn($user);
$roleContext = new RoleContext(new NullActiveRoleStore());
$provider = new MyRolesProvider($security, $userRepository, $roleContext);
$output = $provider->provide(new Get());
self::assertSame('ROLE_PARENT', $output->activeRole);
}
#[Test]
public function provideThrowsUnauthorizedForUnknownUserType(): void
{
$security = $this->createMock(Security::class);
$security->method('getUser')->willReturn(null);
$userRepository = $this->createMock(UserRepository::class);
$roleContext = new RoleContext(new NullActiveRoleStore());
$provider = new MyRolesProvider($security, $userRepository, $roleContext);
$this->expectException(UnauthorizedHttpException::class);
$provider->provide(new Get());
}
}
/**
* @internal
*/
final class NullActiveRoleStore implements ActiveRoleStore
{
public function store(User $user, Role $role): void
{
}
public function get(User $user): ?Role
{
return null;
}
public function clear(User $user): void
{
}
}

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace App\Tests\Unit\Administration\Infrastructure\Security;
use App\Administration\Infrastructure\Security\JwtPayloadEnricher;
use App\SuperAdmin\Domain\Model\SuperAdmin\SuperAdminId;
use App\SuperAdmin\Infrastructure\Security\SecuritySuperAdmin;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
final class JwtPayloadEnricherSuperAdminTest extends TestCase
{
private JwtPayloadEnricher $enricher;
protected function setUp(): void
{
$this->enricher = new JwtPayloadEnricher();
}
#[Test]
public function onJWTCreatedAddsSuperAdminClaimsToPayload(): void
{
$superAdminId = SuperAdminId::generate();
$securitySuperAdmin = new SecuritySuperAdmin(
superAdminId: $superAdminId,
email: 'sadmin@test.com',
hashedPassword: 'hashed',
);
$initialPayload = ['username' => 'sadmin@test.com'];
$event = new JWTCreatedEvent($initialPayload, $securitySuperAdmin);
$this->enricher->onJWTCreated($event);
$payload = $event->getData();
self::assertSame((string) $superAdminId, $payload['user_id']);
self::assertSame('super_admin', $payload['user_type']);
self::assertSame(['ROLE_SUPER_ADMIN'], $payload['roles']);
self::assertArrayNotHasKey('tenant_id', $payload);
}
#[Test]
public function onJWTCreatedPreservesExistingPayloadForSuperAdmin(): void
{
$securitySuperAdmin = new SecuritySuperAdmin(
superAdminId: SuperAdminId::generate(),
email: 'sadmin@test.com',
hashedPassword: 'hashed',
);
$initialPayload = [
'username' => 'sadmin@test.com',
'iat' => 1706436600,
'exp' => 1706438400,
];
$event = new JWTCreatedEvent($initialPayload, $securitySuperAdmin);
$this->enricher->onJWTCreated($event);
$payload = $event->getData();
self::assertSame('sadmin@test.com', $payload['username']);
self::assertSame(1706436600, $payload['iat']);
self::assertSame(1706438400, $payload['exp']);
}
}