clientRegistry = $this->createStub(ClientRegistry::class); $this->em = $this->createStub(EntityManagerInterface::class); $this->userRepository = $this->createStub(UserRepository::class); $this->router = $this->createStub(RouterInterface::class); $this->authenticator = new KeycloakAuthenticator( $this->clientRegistry, $this->em, $this->userRepository, $this->router, ); } public function testSupports(): void { $request = new Request(); $request->attributes->set('_route', 'connect_keycloak_check'); $this->assertTrue($this->authenticator->supports($request)); $request->attributes->set('_route', 'other_route'); $this->assertFalse($this->authenticator->supports($request)); } public function testAuthenticate(): void { $request = new Request(); $client = $this->createStub(OAuth2ClientInterface::class); $accessToken = new AccessToken(['access_token' => 'fake-token']); $this->clientRegistry->method('getClient')->willReturn($client); $client->method('getAccessToken')->willReturn($accessToken); $passport = $this->authenticator->authenticate($request); $this->assertInstanceOf(SelfValidatingPassport::class, $passport); $userBadge = $passport->getBadge(UserBadge::class); $this->assertNotNull($userBadge); $keycloakUser = $this->createStub(ResourceOwnerInterface::class); $keycloakUser->method('toArray')->willReturn([ 'sub' => '123', 'email' => 'test@e-cosplay.fr', 'given_name' => 'John', 'family_name' => 'Doe', 'groups' => ['superadmin'], ]); $client->method('fetchUserFromToken')->willReturn($keycloakUser); $this->userRepository->method('findOneBy')->willReturn(null); $em = $this->createMock(EntityManagerInterface::class); $em->expects($this->once())->method('persist'); $em->expects($this->once())->method('flush'); // Recreate authenticator with mock em for expectations $authenticator = new KeycloakAuthenticator( $this->clientRegistry, $em, $this->userRepository, $this->router, ); $passport = $authenticator->authenticate($request); $userBadge = $passport->getBadge(UserBadge::class); $user = $userBadge->getUser(); $this->assertInstanceOf(User::class, $user); $this->assertEquals('123', $user->getKeycloakId()); $this->assertEquals('test@e-cosplay.fr', $user->getEmail()); $this->assertContains('ROLE_ROOT', $user->getRoles()); } public function testAuthenticateExistingUserByEmail(): void { $request = new Request(); $client = $this->createStub(OAuth2ClientInterface::class); $accessToken = new AccessToken(['access_token' => 'fake-token']); $this->clientRegistry->method('getClient')->willReturn($client); $client->method('getAccessToken')->willReturn($accessToken); $passport = $this->authenticator->authenticate($request); $userBadge = $passport->getBadge(UserBadge::class); $keycloakUser = $this->createStub(ResourceOwnerInterface::class); $keycloakUser->method('toArray')->willReturn([ 'sub' => '123', 'email' => 'existing@e-cosplay.fr', 'groups' => ['gp_member'], ]); $client->method('fetchUserFromToken')->willReturn($keycloakUser); $existingUser = new User(); $this->userRepository->method('findOneBy')->willReturnCallback(function ($criteria) use ($existingUser) { if (isset($criteria['email']) && 'existing@e-cosplay.fr' === $criteria['email']) { return $existingUser; } return null; }); $user = $userBadge->getUser(); $this->assertSame($existingUser, $user); $this->assertEquals('123', $user->getKeycloakId()); $this->assertContains('ROLE_EMPLOYE', $user->getRoles()); } public function testOnAuthenticationSuccess(): void { $request = new Request(); $token = $this->createStub(TokenInterface::class); $this->router->method('generate')->willReturn('/home'); $response = $this->authenticator->onAuthenticationSuccess($request, $token, 'main'); $this->assertInstanceOf(RedirectResponse::class, $response); $this->assertEquals('/home', $response->getTargetUrl()); } public function testOnAuthenticationFailure(): void { $request = $this->createStub(Request::class); $flashBag = $this->createMock(FlashBagInterface::class); $flashBag->expects($this->once())->method('add')->with('error', $this->anything()); $session = $this->createStub(FlashBagAwareSessionInterface::class); $session->method('getFlashBag')->willReturn($flashBag); $request->method('getSession')->willReturn($session); $this->router->method('generate')->willReturn('/home'); $response = $this->authenticator->onAuthenticationFailure($request, new AuthenticationException()); $this->assertInstanceOf(RedirectResponse::class, $response); } public function testStart(): void { $request = new Request(); $this->router->method('generate')->willReturn('/home'); $response = $this->authenticator->start($request); $this->assertInstanceOf(RedirectResponse::class, $response); $this->assertEquals('/home', $response->getTargetUrl()); } public function testAuthenticateSuperAdminAssoGroup(): void { $request = new Request(); $client = $this->createStub(OAuth2ClientInterface::class); $accessToken = new AccessToken(['access_token' => 'fake-token']); $this->clientRegistry->method('getClient')->willReturn($client); $client->method('getAccessToken')->willReturn($accessToken); $keycloakUser = $this->createStub(ResourceOwnerInterface::class); $keycloakUser->method('toArray')->willReturn([ 'sub' => '456', 'email' => 'asso@e-cosplay.fr', 'given_name' => 'Asso', 'family_name' => 'Admin', 'groups' => ['super_admin_asso'], ]); $client->method('fetchUserFromToken')->willReturn($keycloakUser); $this->userRepository->method('findOneBy')->willReturn(null); $em = $this->createStub(EntityManagerInterface::class); $authenticator = new KeycloakAuthenticator( $this->clientRegistry, $em, $this->userRepository, $this->router, ); $passport = $authenticator->authenticate($request); $userBadge = $passport->getBadge(UserBadge::class); $user = $userBadge->getUser(); $this->assertContains('ROLE_ROOT', $user->getRoles()); } public function testAuthenticateUnknownGroupGetsRoleUser(): void { $request = new Request(); $client = $this->createStub(OAuth2ClientInterface::class); $accessToken = new AccessToken(['access_token' => 'fake-token']); $this->clientRegistry->method('getClient')->willReturn($client); $client->method('getAccessToken')->willReturn($accessToken); $keycloakUser = $this->createStub(ResourceOwnerInterface::class); $keycloakUser->method('toArray')->willReturn([ 'sub' => '789', 'email' => 'user@e-cosplay.fr', 'given_name' => 'Regular', 'family_name' => 'User', 'groups' => ['some_other_group'], ]); $client->method('fetchUserFromToken')->willReturn($keycloakUser); $this->userRepository->method('findOneBy')->willReturn(null); $passport = $this->authenticator->authenticate($request); $userBadge = $passport->getBadge(UserBadge::class); $user = $userBadge->getUser(); $this->assertContains('ROLE_USER', $user->getRoles()); } public function testAuthenticateNonEcosplayEmailThrows(): void { $request = new Request(); $client = $this->createStub(OAuth2ClientInterface::class); $accessToken = new AccessToken(['access_token' => 'fake-token']); $this->clientRegistry->method('getClient')->willReturn($client); $client->method('getAccessToken')->willReturn($accessToken); $keycloakUser = $this->createStub(ResourceOwnerInterface::class); $keycloakUser->method('toArray')->willReturn([ 'sub' => '000', 'email' => 'hacker@example.com', 'groups' => [], ]); $client->method('fetchUserFromToken')->willReturn($keycloakUser); $passport = $this->authenticator->authenticate($request); $userBadge = $passport->getBadge(UserBadge::class); $this->expectException(AuthenticationException::class); $userBadge->getUser(); } public function testOnAuthenticationFailureWithoutFlashBagSession(): void { $request = new Request(); // Session that does NOT implement FlashBagAwareSessionInterface $session = $this->createStub(\Symfony\Component\HttpFoundation\Session\SessionInterface::class); $request->setSession($session); $this->router->method('generate')->willReturn('/home'); $response = $this->authenticator->onAuthenticationFailure($request, new AuthenticationException('test')); $this->assertInstanceOf(RedirectResponse::class, $response); } }