vendor/knpuniversity/oauth2-client-bundle/src/Client/OAuth2Client.php line 70

Open in your IDE?
  1. <?php
  2. /*
  3. * OAuth2 Client Bundle
  4. * Copyright (c) KnpUniversity <http://knpuniversity.com/>
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. namespace KnpU\OAuth2ClientBundle\Client;
  10. use KnpU\OAuth2ClientBundle\Exception\InvalidStateException;
  11. use KnpU\OAuth2ClientBundle\Exception\MissingAuthorizationCodeException;
  12. use League\OAuth2\Client\Provider\AbstractProvider;
  13. use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
  14. use League\OAuth2\Client\Token\AccessToken;
  15. use Symfony\Component\HttpFoundation\RedirectResponse;
  16. use Symfony\Component\HttpFoundation\RequestStack;
  17. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  18. class OAuth2Client implements OAuth2ClientInterface
  19. {
  20. public const OAUTH2_SESSION_STATE_KEY = 'knpu.oauth2_client_state';
  21. private AbstractProvider $provider;
  22. private RequestStack $requestStack;
  23. private bool $isStateless = false;
  24. /**
  25. * OAuth2Client constructor.
  26. */
  27. public function __construct(AbstractProvider $provider, RequestStack $requestStack)
  28. {
  29. $this->provider = $provider;
  30. $this->requestStack = $requestStack;
  31. }
  32. /**
  33. * Call this to avoid using and checking "state".
  34. */
  35. public function setAsStateless()
  36. {
  37. $this->isStateless = true;
  38. }
  39. /**
  40. * Creates a RedirectResponse that will send the user to the
  41. * OAuth2 server (e.g. send them to Facebook).
  42. *
  43. * @param array $scopes The scopes you want (leave empty to use default)
  44. * @param array $options Extra options to pass to the Provider's getAuthorizationUrl()
  45. * method. For example, <code>scope</code> is a common option.
  46. * Generally, these become query parameters when redirecting.
  47. *
  48. * @return RedirectResponse
  49. */
  50. public function redirect(array $scopes = [], array $options = [])
  51. {
  52. if (!empty($scopes)) {
  53. $options['scope'] = $scopes;
  54. }
  55. $url = $this->provider->getAuthorizationUrl($options);
  56. // set the state (unless we're stateless)
  57. if (!$this->isStateless()) {
  58. $this->getSession()->set(
  59. self::OAUTH2_SESSION_STATE_KEY,
  60. $this->provider->getState()
  61. );
  62. }
  63. return new RedirectResponse($url);
  64. }
  65. /**
  66. * Call this after the user is redirected back to get the access token.
  67. *
  68. * @param array $options Additional options that should be passed to the getAccessToken() of the underlying provider
  69. *
  70. * @return AccessToken|\League\OAuth2\Client\Token\AccessTokenInterface
  71. *
  72. * @throws InvalidStateException
  73. * @throws MissingAuthorizationCodeException
  74. * @throws IdentityProviderException If token cannot be fetched
  75. */
  76. public function getAccessToken(array $options = [])
  77. {
  78. if (!$this->isStateless()) {
  79. $expectedState = $this->getSession()->get(self::OAUTH2_SESSION_STATE_KEY);
  80. $actualState = $this->getCurrentRequest()->get('state');
  81. if (!$actualState || ($actualState !== $expectedState)) {
  82. throw new InvalidStateException('Invalid state');
  83. }
  84. }
  85. $code = $this->getCurrentRequest()->get('code');
  86. if (!$code) {
  87. throw new MissingAuthorizationCodeException('No "code" parameter was found (usually this is a query parameter)!');
  88. }
  89. return $this->provider->getAccessToken(
  90. 'authorization_code',
  91. array_merge(['code' => $code], $options)
  92. );
  93. }
  94. /**
  95. * Get a new AccessToken from a refresh token.
  96. *
  97. * @param array $options Additional options that should be passed to the getAccessToken() of the underlying provider
  98. *
  99. * @return AccessToken|\League\OAuth2\Client\Token\AccessTokenInterface
  100. *
  101. * @throws IdentityProviderException If token cannot be fetched
  102. */
  103. public function refreshAccessToken(string $refreshToken, array $options = [])
  104. {
  105. return $this->provider->getAccessToken(
  106. 'refresh_token',
  107. array_merge(['refresh_token' => $refreshToken], $options)
  108. );
  109. }
  110. /**
  111. * Returns the "User" information (called a resource owner).
  112. *
  113. * @return \League\OAuth2\Client\Provider\ResourceOwnerInterface
  114. */
  115. public function fetchUserFromToken(AccessToken $accessToken)
  116. {
  117. return $this->provider->getResourceOwner($accessToken);
  118. }
  119. /**
  120. * Shortcut to fetch the access token and user all at once.
  121. *
  122. * Only use this if you don't need the access token, but only
  123. * need the user.
  124. *
  125. * @return \League\OAuth2\Client\Provider\ResourceOwnerInterface
  126. */
  127. public function fetchUser()
  128. {
  129. /** @var AccessToken $token */
  130. $token = $this->getAccessToken();
  131. return $this->fetchUserFromToken($token);
  132. }
  133. /**
  134. * Returns the underlying OAuth2 provider.
  135. *
  136. * @return AbstractProvider
  137. */
  138. public function getOAuth2Provider()
  139. {
  140. return $this->provider;
  141. }
  142. protected function isStateless(): bool
  143. {
  144. return $this->isStateless;
  145. }
  146. /**
  147. * @return \Symfony\Component\HttpFoundation\Request
  148. */
  149. private function getCurrentRequest()
  150. {
  151. $request = $this->requestStack->getCurrentRequest();
  152. if (!$request) {
  153. throw new \LogicException('There is no "current request", and it is needed to perform this action');
  154. }
  155. return $request;
  156. }
  157. /**
  158. * @return SessionInterface
  159. */
  160. private function getSession()
  161. {
  162. if (!$this->getCurrentRequest()->hasSession()) {
  163. throw new \LogicException('In order to use "state", you must have a session. Set the OAuth2Client to stateless to avoid state');
  164. }
  165. return $this->getCurrentRequest()->getSession();
  166. }
  167. }