vendor/hslavich/oneloginsaml-bundle/Security/Http/Authenticator/SamlAuthenticator.php line 35

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Hslavich\OneloginSamlBundle\Security\Http\Authenticator;
  4. use Hslavich\OneloginSamlBundle\Event\UserCreatedEvent;
  5. use Hslavich\OneloginSamlBundle\Event\UserModifiedEvent;
  6. use Hslavich\OneloginSamlBundle\Security\Http\Authenticator\Passport\Badge\SamlAttributesBadge;
  7. use Hslavich\OneloginSamlBundle\Security\Http\Authenticator\Token\SamlToken;
  8. use Hslavich\OneloginSamlBundle\Security\User\SamlUserFactoryInterface;
  9. use Hslavich\OneloginSamlBundle\Security\User\SamlUserInterface;
  10. use Psr\Log\LoggerInterface;
  11. use Symfony\Component\HttpFoundation\RedirectResponse;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  15. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  16. use Symfony\Component\Security\Core\Exception\LogicException;
  17. use Symfony\Component\Security\Core\Exception\SessionUnavailableException;
  18. use Symfony\Component\Security\Core\Exception\UserNotFoundException;
  19. use Symfony\Component\Security\Core\User\UserInterface;
  20. use Symfony\Component\Security\Core\User\UserProviderInterface;
  21. use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
  22. use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
  23. use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
  24. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
  25. use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
  26. use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
  27. use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
  28. use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
  29. use Symfony\Component\Security\Http\HttpUtils;
  30. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  31. class SamlAuthenticator implements InteractiveAuthenticatorInterfaceAuthenticationEntryPointInterface
  32. {
  33.     private $httpUtils;
  34.     private $userProvider;
  35.     private $oneLoginAuth;
  36.     private $successHandler;
  37.     private $failureHandler;
  38.     private $options;
  39.     private $userFactory;
  40.     private $eventDispatcher;
  41.     private $logger;
  42.     public function __construct(
  43.         HttpUtils $httpUtils,
  44.         UserProviderInterface $userProvider,
  45.         \OneLogin\Saml2\Auth $oneLoginAuth,
  46.         AuthenticationSuccessHandlerInterface $successHandler,
  47.         AuthenticationFailureHandlerInterface $failureHandler,
  48.         array $options,
  49.         ?SamlUserFactoryInterface $userFactory,
  50.         ?EventDispatcherInterface $eventDispatcher,
  51.         ?LoggerInterface $logger
  52.     ) {
  53.         $this->httpUtils $httpUtils;
  54.         $this->userProvider $userProvider;
  55.         $this->oneLoginAuth $oneLoginAuth;
  56.         $this->successHandler $successHandler;
  57.         $this->failureHandler $failureHandler;
  58.         $this->options $options;
  59.         $this->userFactory $userFactory;
  60.         $this->eventDispatcher $eventDispatcher;
  61.         $this->logger $logger;
  62.     }
  63.     public function supports(Request $request): ?bool
  64.     {
  65.         return $request->isMethod('POST')
  66.             && $this->httpUtils->checkRequestPath($request$this->options['check_path']);
  67.     }
  68.     public function start(Request $request, ?AuthenticationException $authException null): Response
  69.     {
  70.         return new RedirectResponse($this->httpUtils->generateUri($request$this->options['login_path']));
  71.     }
  72.     public function authenticate(Request $request): Passport
  73.     {
  74.         if (!$request->hasSession()) {
  75.             throw new SessionUnavailableException('This authentication method requires a session.');
  76.         }
  77.         if ($this->options['require_previous_session'] && !$request->hasPreviousSession()) {
  78.             throw new SessionUnavailableException('Your session has timed out, or you have disabled cookies.');
  79.         }
  80.         $this->oneLoginAuth->processResponse();
  81.         if ($this->oneLoginAuth->getErrors()) {
  82.             $errorReason $this->oneLoginAuth->getLastErrorReason();
  83.             if (null !== $this->logger) {
  84.                 $this->logger->error($errorReason);
  85.             }
  86.             throw new AuthenticationException($errorReason);
  87.         }
  88.         return $this->createPassport();
  89.     }
  90.     public function createAuthenticatedToken(PassportInterface $passportstring $firewallName): TokenInterface
  91.     {
  92.         if (!$passport instanceof Passport) {
  93.             throw new LogicException(sprintf('Passport should be an instance of "%s".'Passport::class));
  94.         }
  95.         return $this->createToken($passport$firewallName);
  96.     }
  97.     public function createToken(Passport $passportstring $firewallName): TokenInterface
  98.     {
  99.         if (!$passport->hasBadge(SamlAttributesBadge::class)) {
  100.             throw new LogicException(sprintf('Passport should contains a "%s" badge.'SamlAttributesBadge::class));
  101.         }
  102.         /** @var SamlAttributesBadge $badge */
  103.         $badge $passport->getBadge(SamlAttributesBadge::class);
  104.         return new SamlToken($passport->getUser(), $firewallName$passport->getUser()->getRoles(), $badge->getAttributes());
  105.     }
  106.     public function onAuthenticationSuccess(Request $requestTokenInterface $tokenstring $firewallName): ?Response
  107.     {
  108.         return $this->successHandler->onAuthenticationSuccess($request$token);
  109.     }
  110.     public function onAuthenticationFailure(Request $requestAuthenticationException $exception): ?Response
  111.     {
  112.         return $this->failureHandler->onAuthenticationFailure($request$exception);
  113.     }
  114.     public function isInteractive(): bool
  115.     {
  116.         return true;
  117.     }
  118.     protected function createPassport(): Passport
  119.     {
  120.         $attributes $this->extractAttributes();
  121.         $username $this->extractUsername($attributes);
  122.         $userBadge = new UserBadge(
  123.             $username,
  124.             function ($identifier) use ($attributes) {
  125.                 try {
  126.                     $user $this->userProvider->loadUserByIdentifier($identifier);
  127.                 } catch (UserNotFoundException $exception) {
  128.                     if (!$this->userFactory instanceof SamlUserFactoryInterface) {
  129.                         throw $exception;
  130.                     }
  131.                     $user $this->generateUser($identifier$attributes);
  132.                 } catch (\Throwable $exception) {
  133.                     throw new AuthenticationException('The authentication failed.'0$exception);
  134.                 }
  135.                 if ($user instanceof SamlUserInterface) {
  136.                     $user->setSamlAttributes($attributes);
  137.                     if ($this->eventDispatcher) {
  138.                         $this->eventDispatcher->dispatch(new UserModifiedEvent($user));
  139.                     }
  140.                 }
  141.                 return $user;
  142.             }
  143.         );
  144.         return new SelfValidatingPassport($userBadge, [new SamlAttributesBadge($attributes)]);
  145.     }
  146.     protected function extractAttributes(): array
  147.     {
  148.         if (isset($this->options['use_attribute_friendly_name']) && $this->options['use_attribute_friendly_name']) {
  149.             $attributes $this->oneLoginAuth->getAttributesWithFriendlyName();
  150.         } else {
  151.             $attributes $this->oneLoginAuth->getAttributes();
  152.         }
  153.         $attributes['sessionIndex'] = $this->oneLoginAuth->getSessionIndex();
  154.         return $attributes;
  155.     }
  156.     protected function extractUsername(array $attributes): string
  157.     {
  158.         if (isset($this->options['username_attribute'])) {
  159.             if (!\array_key_exists($this->options['username_attribute'], $attributes)) {
  160.                 if (null !== $this->logger) {
  161.                     $this->logger->error('Found attributes: '.print_r($attributestrue));
  162.                 }
  163.                 throw new \RuntimeException('Attribute "'.$this->options['username_attribute'].'" not found in SAML data');
  164.             }
  165.             return $attributes[$this->options['username_attribute']][0];
  166.         }
  167.         return $this->oneLoginAuth->getNameId();
  168.     }
  169.     protected function generateUser(string $username, array $attributes): UserInterface
  170.     {
  171.         $user $this->userFactory->createUser($username$attributes);
  172.         if ($this->eventDispatcher) {
  173.             $this->eventDispatcher->dispatch(new UserCreatedEvent($user));
  174.         }
  175.         return $user;
  176.     }
  177. }