src/Controller/SecurityController.php line 171
<?phpnamespace App\Controller;use App\DTO\AzureApplicationInterface;use App\Enum\LoginErrorEnum;use App\Event\Admin\AdminLoginFailureEvent;use App\Exception\EmailDomainNotAuthorizedForSsoAdminException;use App\Exception\UnsupportedSsoAdminEntityException;use App\Utility\SsoAdminFactory;use Composer\CaBundle\CaBundle;use GuzzleHttp\Client;use GuzzleHttp\RequestOptions;use League\OAuth2\Client\Token\AccessToken;use LogicException;use Psr\Log\LoggerInterface;use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;use Symfony\Component\Filesystem\Path;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Routing\Annotation\Route;use Symfony\Component\Routing\Generator\UrlGeneratorInterface;use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;use TheNetworg\OAuth2\Client\Provider\Azure;use TheNetworg\OAuth2\Client\Provider\AzureResourceOwner;class SecurityController extends AbstractController{public function __construct(private readonly EventDispatcherInterface $dispatcher,private readonly SsoAdminFactory $ssoAdminFactory,private readonly LoggerInterface $logger,) {}private function getProvider(string $email): Azure{$this->logger->debug('Looking for provider by email', ['email' => $email]);$foundSsoAdminApplication = $this->ssoAdminFactory->getSsoAdminApplication($email);if (!$foundSsoAdminApplication) {$this->logger->error('Could not find an SSO admin application with the supplied email address', ['email' => $email]);throw new EmailDomainNotAuthorizedForSsoAdminException();}if (!$foundSsoAdminApplication instanceof AzureApplicationInterface) {$this->logger->error('The system only supports Azure applications current', ['email' => $email, 'application' => $foundSsoAdminApplication]);throw new UnsupportedSsoAdminEntityException();}$this->logger->debug('Found a provider', ['email' => $email]);$httpClient = new Client(['headers' => ['User-Agent' => 'AANA.com Admin SSO',],RequestOptions::VERIFY => Path::canonicalize(CaBundle::getSystemCaRootBundlePath()),]);return new Azure(['clientId' => $foundSsoAdminApplication->getClientId(),'clientSecret' => $foundSsoAdminApplication->getClientSecret(),'redirectUri' => $this->generateUrl('app_login_azure_redirect', referenceType: UrlGeneratorInterface::ABSOLUTE_URL),'tenant' => $foundSsoAdminApplication->getTenantId(),'scopes' => ['openid'],'defaultEndPointVersion' => '2.0',],['httpClient' => $httpClient,]);}private function tryGetLoginPostEmail(Request $request): ?string{if (!$request->isMethod('POST')) {return null;}$email = mb_strtolower(trim($request->get('email', '')));if (!$email) {$this->addFlash('error', 'Please enter a username');return null;}$emailParts = explode('@', $email);if (2 !== count($emailParts)) {$this->addFlash('error', 'Invalid username');$this->logger->info('Invalid username', ['email' => $email]);$this->dispatcher->dispatch(new AdminLoginFailureEvent($email));return null;}return $email;}#[Route(path: '/login_azure_redirect', name: 'app_login_azure_redirect')]public function login_azure_redirect(Request $request): Response{$email = $request->getSession()->get('aana.email');$this->logger->debug('Azure SSO redirect', ['email' => $email]);try {$provider = $this->getProvider($email);} catch (EmailDomainNotAuthorizedForSsoAdminException|UnsupportedSsoAdminEntityException $e) {$this->addFlash('error', 'The supplied credentials are not valid on this site');$this->logger->warning('Supplied email does not have have an admin SSO provider', ['email' => $email]);return $this->redirectToRoute('app_login');}$request->getSession()->remove('OAuth2.state');$accessToken = $provider->getAccessToken('authorization_code', ['code' => $_GET['code'],]);$this->logger->debug('SSO token received from provider', ['token' => $accessToken]);if (!$accessToken instanceof AccessToken) {$this->logger->error('Invalid SSO token');throw new \RuntimeException('Invalid token type');}/** @var AzureResourceOwner $resourceOwner */$resourceOwner = $provider->getResourceOwner($accessToken);$this->logger->debug('Found resource owner', ['resourceOwner' => $resourceOwner->toArray()]);$request->getSession()->set('azure', $resourceOwner);// $this->dispatcher->dispatch(new AdminLoginSuccessEvent($resourceOwner->claim('email')));return $this->redirectToRoute('admin_dashboard');}#[Route(path: '/login_azure', name: 'app_login_azure')]public function login_azure(Request $request): Response{$knownEmailAddress = $this->tryGetLoginPostEmail($request);try {$provider = $this->getProvider($knownEmailAddress);} catch (EmailDomainNotAuthorizedForSsoAdminException|UnsupportedSsoAdminEntityException $e) {$this->addFlash('error', 'The supplied credentials are not valid on this site');return $this->redirectToRoute('app_login');}// Set to use v2 API, skip the line or set the value to Azure::ENDPOINT_VERSION_1_0 if willing to use v1 API$provider->defaultEndPointVersion = Azure::ENDPOINT_VERSION_2_0;$baseGraphUri = $provider->getRootMicrosoftGraphUri(null);$provider->scope = 'openid profile email offline_access '.$baseGraphUri.'/User.Read';$authorizationUrl = $provider->getAuthorizationUrl(['scope' => $provider->scope, 'login_hint' => $knownEmailAddress]);$request->getSession()->set('aana.email', $knownEmailAddress);$request->getSession()->set('OAuth2.state', $provider->getState());return $this->redirect($authorizationUrl);}#[Route(path: '/login', name: 'app_login')]public function login(): Response{return $this->render('security/login.html.twig',['formAction' => 'app_login_azure',]);}#[Route(path: '/login-error/{id}', name: 'app_login_error')]public function login_error(int $id): Response{$message = match (LoginErrorEnum::tryFrom($id)) {LoginErrorEnum::NotLocal => 'The supplied user does not have permission to access this site',default => 'An unknown error occurred while attempting to sign the user on',};$this->addFlash('error', $message);return $this->redirectToRoute('app_login');}#[Route(path: '/logout', name: 'app_logout')]public function logout(): void{throw new LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');}}