<?php
namespace App\Security;
use App\Business\User;
use App\Security\User\ApiUserProvider;
use Es\FootprintBundle\Services\Auth;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
class TokenAuthenticator extends AbstractAuthenticator
{
private $es_auth_stateless;
private $apiUserProvider;
private $clientRegistry;
private $tokenStorage;
private $session;
public function __construct(Auth $auth, ClientRegistry $clientRegistry, TokenStorageInterface $tokenStorage, SessionInterface $session)
{
$this->es_auth_stateless = $auth;
$this->clientRegistry = $clientRegistry;
$this->tokenStorage = $tokenStorage;
$this->session = $session;
$this->apiUserProvider = new ApiUserProvider($this->es_auth_stateless);
}
/**
* Called on every request to decide if this authenticator should be
* used for the request. Returning `false` will cause this authenticator
* to be skipped.
*/
public function supports(Request $request): ?bool
{
return true;
}
public function authenticate(Request $request): Passport
{
$result = $this->apiUserProvider->loadUserByIdentifier($request->getUri());
if (null === $result) {
// The token header was empty, authentication fails with HTTP Status
// Code 401 "Unauthorized"
throw new CustomUserMessageAuthenticationException('No API token provided');
}
$passport = new SelfValidatingPassport(new UserBadge($request->getUri()), []);
return $passport;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$apiKey = $credentials['url'];
return $userProvider->loadUserByUsername($apiKey);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
$user = $token->getUser();
if ($user instanceof User) {
$this->checkAndRefreshToken($user);
}
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
$data = [
// you may want to customize or obfuscate the message first
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
// or to translate this message
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
/**
* Called when authentication is needed, but it's not sent
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$data = [
'message' => 'Authentication Required'
];
if (!$request->get('myWebServiceID') && !$request->headers->get('myWebServiceID')) {
$data = [' message' => 'User WebServiceID doesn\' exist!'];
}
if (!$request->get('footprint') && !$request->headers->get('footprint')) {
$data = ['message' => 'User FootPrint is missing!'];
}
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
private function getAzureClient()
{
return $this->clientRegistry
// "facebook_main" is the key used in config/packages/knpu_oauth2_client.yaml
->getClient('entra_id_external');
}
public function checkAndRefreshToken(UserInterface $user): UserInterface
{
if ($this->isTokenExpired($user)) {
return $this->refreshToken($user);
}
return $user;
}
private function isTokenExpired(UserInterface $user): bool
{
$expiresAt = $user->getExpires();
return $expiresAt <= (new \DateTime())->getTimestamp();
}
public function refreshToken(UserInterface $user): UserInterface
{
$refreshToken = $user->getRefreshToken();
// Récupérer le client OAuth2
$client = $this->getAzureClient();
try {
// Rafraîchir le token
$newAccessToken = $client->refreshAccessToken($refreshToken);
} catch (IdentityProviderException $e) {
// Déconnexion de l'utilisateur si le rafraîchissement échoue
$this->logoutUser();
throw new AuthenticationException('Failed to refresh access token and user has been logged out.', 0, $e);
}
// Mettre à jour l'utilisateur avec les nouveaux tokens
$user->setAccessToken($newAccessToken->getToken());
$user->setRefreshToken($newAccessToken->getRefreshToken() ?? $refreshToken); // Garder l'ancien si pas fourni
$user->setExpires(((new \DateTime())->setTimestamp($newAccessToken->getExpires()))->getTimestamp());
return $user;
}
private function logoutUser(): void
{
// Invalidate the session and clear the security token
$this->tokenStorage->setToken(null);
$this->session->invalidate();
}
public function supportsRememberMe()
{
return false;
}
}