KeycloakAuthenticator.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. <?php
  2. // src/Security/KeycloakAuthenticator.php
  3. namespace App\Security;
  4. use App\Entity\User;
  5. use App\Entity\Gamemaster;
  6. use Doctrine\ORM\EntityManagerInterface;
  7. use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
  8. use League\OAuth2\Client\Provider\ResourceOwnerInterface;
  9. use Symfony\Component\HttpFoundation\Request;
  10. use Symfony\Component\HttpFoundation\Response;
  11. use Symfony\Component\HttpFoundation\RedirectResponse;
  12. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  13. use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
  14. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
  15. use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
  16. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  17. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  18. use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  19. use Symfony\Component\String\Slugger\AsciiSlugger;
  20. class KeycloakAuthenticator extends AbstractAuthenticator
  21. {
  22. public function __construct(
  23. private ClientRegistry $clientRegistry,
  24. private EntityManagerInterface $em,
  25. private UserPasswordHasherInterface $hasher,
  26. private UrlGeneratorInterface $urlGenerator
  27. ) {}
  28. public function supports(Request $request): ?bool
  29. {
  30. return $request->attributes->get('_route') === 'app_login_keycloak_connect_check';
  31. }
  32. public function authenticate(Request $request): SelfValidatingPassport
  33. {
  34. $client = $this->clientRegistry->getClient('keycloak');
  35. $accessToken = $client->getAccessToken();
  36. $keycloakUser = $client->fetchUserFromToken($accessToken);
  37. $data = $keycloakUser->toArray();
  38. if ($_ENV['KEYCLOAK_DEBUG'] == 1) {
  39. dump($data);
  40. }
  41. $email = $data[$_ENV['KEYCLOAK_USER_ATTRIBUTE_EMAIL']] ?? null;
  42. return new SelfValidatingPassport(
  43. new UserBadge($email, function($email) use ($data) {
  44. $user = $this->em->getRepository(User::class)->findOneBy(['email' => $email]);
  45. if (!$user) {
  46. $user = new User();
  47. $user->setEmail($email);
  48. $user->setFirstName($data[$_ENV['KEYCLOAK_USER_ATTRIBUTE_FIRSTNAME']] ?? '');
  49. $user->setLastName($data[$_ENV['KEYCLOAK_USER_ATTRIBUTE_LASTNAME']] ?? '');
  50. $user->setPhone($data[$_ENV['KEYCLOAK_USER_ATTRIBUTE_PHONE']] ?? '');
  51. $user->setRoles(['ROLE_USER']);
  52. $user->setIsVerified(true);
  53. $user->setPassword(
  54. $this->hasher->hashPassword($user, bin2hex(random_bytes(10)))
  55. );
  56. $this->em->persist($user);
  57. } else {
  58. // Il vient de KC, donc, l'email est fiabilisé
  59. $user->setIsVerified(true);
  60. // Mise à jour du numéro de téléphone s'il est renseigné dans KC
  61. $user->setPhone($data[$_ENV['KEYCLOAK_USER_ATTRIBUTE_PHONE']] ?? $user->getPhone());
  62. $user->setFirstName($data[$_ENV['KEYCLOAK_USER_ATTRIBUTE_FIRSTNAME']] ?? $user->getFirstName());
  63. $user->setLastName($data[$_ENV['KEYCLOAK_USER_ATTRIBUTE_LASTNAME']] ?? $user->getLastName());
  64. }
  65. // Mise à jour des rôle et du profil GM
  66. if (in_array($_ENV['KEYCLOAK_ADMIN_GROUP'], $data['groups'])) {
  67. $user->setRoles(['ROLE_ADMIN']);
  68. } elseif (in_array($_ENV['KEYCLOAK_MANAGER_GROUP'], $data['groups'])) {
  69. $user->setRoles(['ROLE_MANAGER']);
  70. } elseif (in_array($_ENV['KEYCLOAK_STAFF_GROUP'], $data['groups'])) {
  71. $user->setRoles(['ROLE_STAFF']);
  72. }
  73. if (in_array($_ENV['KEYCLOAK_GAMEMASTER_GROUP'], $data['groups'])) {
  74. if (!$user->getLinkToGamemaster()) {
  75. // Créer un profil MJ
  76. $gamemaster = new Gamemaster();
  77. $gamemaster->setFirstName($user->getFirstName());
  78. $gamemaster->setLastName($user->getLastName());
  79. $gamemaster->setPreferedName($data[$_ENV['KEYCLOAK_USER_ATTRIBUTE_NICKNAME']]);
  80. $slugger = new AsciiSlugger('fr_FR');
  81. $slug = $slugger->slug(strtolower($data[$_ENV['KEYCLOAK_USER_ATTRIBUTE_NICKNAME']]));
  82. $gamemaster->setSlug($slug);
  83. $gamemaster->setEmail($user->getEmail());
  84. $phone = $user->getPhone();
  85. if ($phone) { $gamemaster->setPhone($phone); }
  86. $gamemaster->setLinkToUser($user);
  87. $this->em->persist($gamemaster);
  88. } else {
  89. // Mettre à jour le profil MJ
  90. $gamemaster = $user->getLinkToGamemaster();
  91. $gamemaster->setFirstName($user->getFirstName());
  92. $gamemaster->setLastName($user->getLastName());
  93. $gamemaster->setPreferedName($data[$_ENV['KEYCLOAK_USER_ATTRIBUTE_NICKNAME']]);
  94. $slugger = new AsciiSlugger('fr_FR');
  95. $slug = $slugger->slug(strtolower($data[$_ENV['KEYCLOAK_USER_ATTRIBUTE_NICKNAME']]));
  96. $gamemaster->setSlug($slug);
  97. $gamemaster->setEmail($user->getEmail());
  98. $phone = $user->getPhone();
  99. if ($phone) { $gamemaster->setPhone($phone); }
  100. }
  101. }
  102. $this->em->flush();
  103. return $user;
  104. })
  105. );
  106. }
  107. public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
  108. {
  109. return new RedirectResponse($this->urlGenerator->generate('app_main'));
  110. }
  111. public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
  112. {
  113. return new RedirectResponse($this->urlGenerator->generate('app_login'));
  114. }
  115. }