<?php
namespace App\Controller;
use App\Services\AuthService;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use JMS\Serializer\SerializerInterface;
use Psr\Log\LoggerInterface;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Symfony\Component\HttpFoundation\Response;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\UnencryptedToken;
use Symfony\Component\Uid\Uuid;
class EntraController extends AbstractController
{
use TargetPathTrait;
private $entra_url;
private $jwtConfig;
private $discoveryKeysUrl;
private $httpClient;
private $logger;
private $authService;
public function __construct(ParameterBagInterface $parameterBag, LoggerInterface $logger, HttpClientInterface $httpClient, AuthService $authService)
{
$this->entra_url = $parameterBag->get('entra_parameters')['entra_url'];
$this->applicationObjectId = $parameterBag->get('entra_parameters')['entra_object_id'];
$this->discoveryKeysUrl = $this->entra_url . '/discovery/keys';
$this->logger = $logger;
$this->httpClient = $httpClient;
$this->authService = $authService;
$this->jwtConfig = Configuration::forSymmetricSigner(
new \Lcobucci\JWT\Signer\Hmac\Sha256(),
InMemory::plainText('temporary-secret-key')
);
}
/**
* Start OAuth process with Entra Server
*
* @Route("/connect/entra", name="connect_entra_start")
*/
public function connectAction(Request $request, ClientRegistry $clientRegistry, SessionInterface $session)
{
// Generate a unique ID for this login attempt
$authId = Uuid::v4()->toRfc4122();
//Generate the code verifier
$codeVerifier = bin2hex(random_bytes(64));
$codeChallenge = strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
// Store the code verifier with the unique authId in session
$session->set('oauth2_code_verifier_' . $authId, $codeVerifier);
//store the target path with the unique authId in session
if ($request->get('retryRedirectUrl') == null) {
$targetPath = $session->get('_security.main.target_path');
if ($targetPath != null) {
$session->set('login_redirect_url_' . $authId, $targetPath);
}
} else {
$session->set('login_redirect_url_' . $authId, $request->get('retryRedirectUrl'));
}
return $clientRegistry
->getClient('entra_id_external') // key used in config/packages/knpu_oauth2_client.yaml
->redirect(["email", "openid", "profile", "offline_access"], [
'code_challenge' => $codeChallenge,
'code_challenge_method' => 'S256',
'state' => $authId, // Pass the unique ID as the state parameter
'route' => $request->headers->get('request_uri')
]);
}
/**
* Callback after Entra connexion
*
* @Route("/connect/entra/check", name="connect_entra_check")
*/
public function connectCheckAction(Request $request, ClientRegistry $clientRegistry)
{
//blank because we are using a Symfony authenticator
}
/**
* @Route("/logout", name="app_logout")
*/
public function logout(Request $request, SessionInterface $session, TokenStorageInterface $tokenStorage): RedirectResponse
{
$session->invalidate();
$tokenStorage->setToken(null);
$azureLogoutUrl = $this->entra_url . '/oauth2/v2.0/logout?post_logout_redirect_uri=' . urlencode($this->generateUrl('homepage', [], UrlGeneratorInterface::ABSOLUTE_URL));
return $this->redirect($azureLogoutUrl);
// Symfony gère automatiquement le processus de déconnexion
// Si vous avez besoin d'une logique supplémentaire, vous pouvez l'ajouter ici
}
/**
* @Route("/soft-logout", name="app_soft_logout")
*/
public function softLogout(Request $request, SessionInterface $session, TokenStorageInterface $tokenStorage): Response
{
$session->invalidate();
$tokenStorage->setToken(null);
return new Response('Logout successful', Response::HTTP_OK);
}
/**
* @Route("/slo", name="app_slo", methods={"GET"}) #TODO TEST
*/
public function slo(Request $request): Response
{
$this->logger->info('Slo endpoint called');
$sid = $request->get('sid');
$this->logger->info('sid: ' . $sid);
$sessionExists = $this->authService->getSessionBySid($sid);
if ($sessionExists) {
$this->container->get('security.token_storage')->setToken(null);
$request->getSession()->invalidate();
return new Response('Logout successful', Response::HTTP_OK);
}
return new Response('Invalid logout request', Response::HTTP_BAD_REQUEST);
}
}