vendor/biceps/oauth/Security/Authenticator.php line 81

Open in your IDE?
  1. <?php
  2. /**
  3.  * @copyright Copyright (c) 2020 Biceps
  4.  */
  5. namespace Biceps\OAuth\Security;
  6. use Biceps\OAuth\Client\Provider;
  7. use Biceps\OAuth\Grant\ResetToken;
  8. use Exception;
  9. use Biceps\OAuth\Client\OAuthClient;
  10. use Biceps\OAuth\Client\RemoteUser;
  11. use Biceps\OAuth\Entity\User;
  12. use Biceps\OAuth\Security\Exception\NoTokenFoundException;
  13. use Doctrine\ORM\EntityManagerInterface;
  14. use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
  15. use KnpU\OAuth2ClientBundle\Client\OAuth2ClientInterface;
  16. use KnpU\OAuth2ClientBundle\Exception\InvalidStateException;
  17. use KnpU\OAuth2ClientBundle\Exception\MissingAuthorizationCodeException;
  18. use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
  19. use KnpU\OAuth2ClientBundle\Security\Exception\IdentityProviderAuthenticationException;
  20. use KnpU\OAuth2ClientBundle\Security\Exception\InvalidStateAuthenticationException;
  21. use KnpU\OAuth2ClientBundle\Security\Exception\NoAuthCodeAuthenticationException;
  22. use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
  23. use League\OAuth2\Client\Token\AccessToken;
  24. use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
  25. use Symfony\Component\HttpFoundation\RedirectResponse;
  26. use Symfony\Component\HttpFoundation\Request;
  27. use Symfony\Component\HttpFoundation\Response;
  28. use Symfony\Component\HttpFoundation\Session\Session;
  29. use Symfony\Component\Routing\RouterInterface;
  30. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  31. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  32. use Symfony\Component\Security\Core\Exception\LazyResponseException;
  33. use Symfony\Component\Security\Core\User\UserInterface;
  34. use Symfony\Component\Security\Core\User\UserProviderInterface;
  35. class Authenticator extends SocialAuthenticator
  36. {
  37.     const ROLE_OWNER 'ROLE_BICEPS_OWNER';
  38.     const ROLE_PROFILE 'ROLE_BICEPS_PROFILE';
  39.     const TOKEN_SESSION_KEY 'BicepsOAuthSecurityAccessToken';
  40.     const VALIDITY_SESSION_KEY 'BicepsOAuthSecurityValidity';
  41.     const URI_SESSION_KEY 'BicepsOAuthSecurityURI';
  42.     const ACTION_SESSION_KEY 'BicepsOAuthSecurityAction';
  43.     const ERROR_SESSION_KEY 'BicepsOAuthSecurityError';
  44.     const TOKEN_VALIDITY 5;
  45.     const ACTION_RESET 'reset';
  46.     /** @var ClientRegistry */
  47.     protected $clientRegistry;
  48.     /** @var EntityManagerInterface */
  49.     protected $em;
  50.     /** @var RouterInterface */
  51.     protected $router;
  52.     /**
  53.      * Authenticator constructor.
  54.      *
  55.      * @param ClientRegistry $clientRegistry
  56.      * @param EntityManagerInterface $em
  57.      * @param RouterInterface $router
  58.      */
  59.     public function __construct(ClientRegistry $clientRegistryEntityManagerInterface $emRouterInterface $router)
  60.     {
  61.         $this->clientRegistry $clientRegistry;
  62.         $this->em $em;
  63.         $this->router $router;
  64.     }
  65.     /**
  66.      * @inheritDoc
  67.      */
  68.     public function start(Request $requestAuthenticationException $authException null)
  69.     {
  70.         if (!$request->getSession()->get(self::URI_SESSION_KEY)) {
  71.             $request->getSession()->set(self::URI_SESSION_KEY$request->getUri());
  72.         }
  73.         return new RedirectResponse($this->router->generate('oauth_biceps_connect'));
  74.     }
  75.     /**
  76.      * @inheritDoc
  77.      */
  78.     public function supports(Request $request)
  79.     {
  80.         return $request->getPathInfo() === $this->router->generate('oauth_biceps_check') && $request->isMethod('GET');
  81.     }
  82.     /**
  83.      * @param Request $request
  84.      */
  85.     public function reset(Request $request$redirectUri)
  86.     {
  87.         $session $request->getSession();
  88.         $this->clearSessionData($requesttrue);
  89.         $session->set(self::URI_SESSION_KEY$redirectUri);
  90.         $session->set(self::ACTION_SESSION_KEYself::ACTION_RESET);
  91.         return new RedirectResponse($this->router->generate('oauth_biceps_connect'));
  92.     }
  93.     /**
  94.      * @param Request $request
  95.      * @param $redirectUri
  96.      */
  97.     public function logout(Request $request)
  98.     {
  99.         $session $request->getSession();
  100.         $accessToken $this->restoreAccessTokenFromSession($session);
  101.         $this->clearSessionData($request);
  102.         if ($accessToken) {
  103.             try {
  104.                 $this->getClient()->getLogoutToken($accessToken);
  105.             } catch (Exception $e) {
  106.             }
  107.         }
  108.     }
  109.     /**
  110.      * @inheritDoc
  111.      */
  112.     public function getCredentials(Request $request)
  113.     {
  114.         try {
  115.             $session $request->getSession();
  116.             /** @var AccessToken $accessToken */
  117.             $accessToken $this->fetchAccessToken($this->getClient(), [
  118.                 'scope' => implode(' '$this->getCurrentScopes($request)),
  119.             ]);
  120.             switch ($session->get(self::ACTION_SESSION_KEY)) {
  121.                 case self::ACTION_RESET:
  122.                     $accessToken $this->getClient()->getResetToken($accessToken);
  123.                     break;
  124.             }
  125.             $session->set(self::TOKEN_SESSION_KEY$accessToken->jsonSerialize());
  126.             $session->set(self::VALIDITY_SESSION_KEYtime() + self::TOKEN_VALIDITY);
  127.             return $accessToken;
  128.         } catch (\Exception $e) {
  129.             $this->handleException($e$request);
  130.         } finally {
  131.             $request->getSession()->remove(self::ACTION_SESSION_KEY);
  132.         }
  133.         return false;
  134.     }
  135.     /**
  136.      * @param Request $request
  137.      * @return array
  138.      */
  139.     public function getCurrentScopes(Request $request)
  140.     {
  141.         $action $request->getSession()->get(self::ACTION_SESSION_KEYnull);
  142.         $additionals = [];
  143.         switch ($action) {
  144.             case self::ACTION_RESET:
  145.                 $additionals = ['online_mode'];
  146.         }
  147.         return $this->getClient()->getOAuth2Provider()->getScopes($additionals);
  148.     }
  149.     /**
  150.      * @inheritDoc
  151.      */
  152.     public function getUser($credentialsUserProviderInterface $userProvider)
  153.     {
  154.         /** @var RemoteUser $remoteUser */
  155.         $remoteUser $this->getClient()->fetchUserFromToken($credentials);
  156.         // get className from userProvider
  157.         if (method_exists($userProvider'getClass')) {
  158.             $getUserClass = function () {
  159.                 return $this->getClass();
  160.             };
  161.             $userClassName $getUserClass->call($userProvider);
  162.             if (!($userClassName === User::class || is_subclass_of($userClassNameUser::class))) {
  163.                 throw new Exception(sprintf('"%s" do not extends "%s"'$userClassNameUser::class), 1591553523);
  164.             }
  165.         } else {
  166.             throw new Exception(sprintf('"%s" must have method "getClass" to get user class name'get_class($userProvider)), 1591605229);
  167.         }
  168.         $username $remoteUser->getUsername();
  169.         $user $this->em->getRepository($userClassName)->findOneBy(['username' => $username]);
  170.         if (!$user) {
  171.             $user = new $userClassName();
  172.             $user->setUsername($username);
  173.             $roles $this->getClient()->getOAuth2Provider()->getRoles([$remoteUser->isOwner() ? self::ROLE_OWNER self::ROLE_PROFILE]);
  174.             foreach($roles as $role){
  175.                 $user->addRole($role);
  176.             }
  177.             $this->em->persist($user);
  178.             $this->em->flush();
  179.         }
  180.         return $user;
  181.     }
  182.     /**
  183.      * @inheritDoc
  184.      */
  185.     public function onAuthenticationFailure(Request $requestAuthenticationException $exception)
  186.     {
  187.         // TODO: Implement onAuthenticationFailure() method.
  188.     }
  189.     /**
  190.      * @inheritDoc
  191.      */
  192.     public function onAuthenticationSuccess(Request $requestTokenInterface $tokenstring $providerKey)
  193.     {
  194.         // TODO: Implement onAuthenticationSuccess() method.
  195.     }
  196.     public function refreshToken(Request $request)
  197.     {
  198.         $session $request->getSession();
  199.         $accessToken $this->restoreAccessTokenFromSession($session);
  200.         if ($accessToken && !$accessToken->hasExpired()) {
  201.             $validity = (int)$session->get(self::VALIDITY_SESSION_KEY0);
  202.             if (time() < $validity) {
  203.                 return true;
  204.             }
  205.             $newAccessToken $this->getClient()->getRefreshToken($accessToken);
  206.             if (!$newAccessToken->hasExpired()) {
  207.                 $session->set(self::TOKEN_SESSION_KEY$newAccessToken->jsonSerialize());
  208.                 $session->set(self::VALIDITY_SESSION_KEYtime() + self::TOKEN_VALIDITY);
  209.             }
  210.             return true;
  211.         }
  212.         return false;
  213.     }
  214.     /**
  215.      * @return OAuthClient
  216.      */
  217.     protected function getClient()
  218.     {
  219.         $client $this->clientRegistry->getClient('biceps');
  220.         if (!($client instanceof OAuthClient)) {
  221.             $exception = new InvalidConfigurationException('Invalid client class! Client must implement OAuthClientInterface.'1588239787);
  222.             $exception->setPath('knpu_oauth2_client.clients.biceps.client_class');
  223.             throw $exception;
  224.         }
  225.         return $client;
  226.     }
  227.     /**
  228.      * @param Exception $exception
  229.      */
  230.     protected function handleException(Exception $exceptionRequest $request)
  231.     {
  232.         $session $request->getSession();
  233.         if ($exception instanceof IdentityProviderAuthenticationException) {
  234.             /** @var IdentityProviderException $previous */
  235.             $exception $exception->getPrevious();
  236.         }
  237.         if ($exception instanceof IdentityProviderException) {
  238.             $body $exception->getResponseBody();
  239.             if (!is_array($body) || !isset($body['error'])) {
  240.                 // If body do not contain an error code, user should contact an administrator
  241.                 throw new IdentityProviderAuthenticationException($exception);
  242.             }
  243.             $session->set(self::ERROR_SESSION_KEY, [
  244.                 'code' => $body['error'],
  245.                 'description' => $body['error_description'] ?? '',
  246.                 'licence_id' => $body['licence_id'] ?? '',
  247.                 'redirectUri' => $session->get(self::URI_SESSION_KEY),
  248.             ]);;
  249.             // Clear session data
  250.             $this->clearSessionData($requestfalse);
  251.             throw new LazyResponseException(new RedirectResponse($this->router->generate('oauth_biceps_exception')));
  252.         }
  253.         $this->clearSessionData($requesttrue);
  254.         throw $exception;
  255.     }
  256.     /**
  257.      * @param Session $session
  258.      *
  259.      * @return AccessToken|null
  260.      */
  261.     protected function restoreAccessTokenFromSession(Session $session): ?AccessToken
  262.     {
  263.         if (!$session->has(self::TOKEN_SESSION_KEY)) {
  264.             return null;
  265.         }
  266.         $accessTokenData $session->get(self::TOKEN_SESSION_KEY);
  267.         if (!is_array($accessTokenData)) {
  268.             $session->remove(self::TOKEN_SESSION_KEY);
  269.             return null;
  270.         }
  271.         $accessToken = new AccessToken($accessTokenData);
  272.         return !$accessToken->hasExpired() ? $accessToken null;
  273.     }
  274.     /**
  275.      * @param Request $request
  276.      * @param bool $includeError
  277.      */
  278.     protected function clearSessionData(Request $request$includeError true)
  279.     {
  280.         $session $request->getSession();
  281.         $session->remove(self::VALIDITY_SESSION_KEY);
  282.         $session->remove(self::URI_SESSION_KEY);
  283.         $session->remove(self::ACTION_SESSION_KEY);
  284.         if ($includeError) {
  285.             $session->remove(self::ERROR_SESSION_KEY);
  286.         }
  287.     }
  288. }