PageRenderTime 22ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/league/oauth2-server/src/Grant/AuthCodeGrant.php

https://bitbucket.org/roome2018/roome-web
PHP | 371 lines | 264 code | 46 blank | 61 comment | 31 complexity | aef547fd35375ebc73fb9f573b9800f2 MD5 | raw file
Possible License(s): MIT, LGPL-2.1, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause
  1. <?php
  2. /**
  3. * @author Alex Bilbie <hello@alexbilbie.com>
  4. * @copyright Copyright (c) Alex Bilbie
  5. * @license http://mit-license.org/
  6. *
  7. * @link https://github.com/thephpleague/oauth2-server
  8. */
  9. namespace League\OAuth2\Server\Grant;
  10. use League\OAuth2\Server\Entities\ClientEntityInterface;
  11. use League\OAuth2\Server\Entities\ScopeEntityInterface;
  12. use League\OAuth2\Server\Entities\UserEntityInterface;
  13. use League\OAuth2\Server\Exception\OAuthServerException;
  14. use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
  15. use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
  16. use League\OAuth2\Server\RequestEvent;
  17. use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
  18. use League\OAuth2\Server\ResponseTypes\RedirectResponse;
  19. use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
  20. use Psr\Http\Message\ServerRequestInterface;
  21. class AuthCodeGrant extends AbstractAuthorizeGrant
  22. {
  23. /**
  24. * @var \DateInterval
  25. */
  26. private $authCodeTTL;
  27. /**
  28. * @var bool
  29. */
  30. private $enableCodeExchangeProof = false;
  31. /**
  32. * @param AuthCodeRepositoryInterface $authCodeRepository
  33. * @param RefreshTokenRepositoryInterface $refreshTokenRepository
  34. * @param \DateInterval $authCodeTTL
  35. */
  36. public function __construct(
  37. AuthCodeRepositoryInterface $authCodeRepository,
  38. RefreshTokenRepositoryInterface $refreshTokenRepository,
  39. \DateInterval $authCodeTTL
  40. ) {
  41. $this->setAuthCodeRepository($authCodeRepository);
  42. $this->setRefreshTokenRepository($refreshTokenRepository);
  43. $this->authCodeTTL = $authCodeTTL;
  44. $this->refreshTokenTTL = new \DateInterval('P1M');
  45. }
  46. public function enableCodeExchangeProof()
  47. {
  48. $this->enableCodeExchangeProof = true;
  49. }
  50. /**
  51. * Respond to an access token request.
  52. *
  53. * @param ServerRequestInterface $request
  54. * @param ResponseTypeInterface $responseType
  55. * @param \DateInterval $accessTokenTTL
  56. *
  57. * @throws OAuthServerException
  58. *
  59. * @return ResponseTypeInterface
  60. */
  61. public function respondToAccessTokenRequest(
  62. ServerRequestInterface $request,
  63. ResponseTypeInterface $responseType,
  64. \DateInterval $accessTokenTTL
  65. ) {
  66. // Validate request
  67. $client = $this->validateClient($request);
  68. $encryptedAuthCode = $this->getRequestParameter('code', $request, null);
  69. if ($encryptedAuthCode === null) {
  70. throw OAuthServerException::invalidRequest('code');
  71. }
  72. // Validate the authorization code
  73. try {
  74. $authCodePayload = json_decode($this->decrypt($encryptedAuthCode));
  75. if (time() > $authCodePayload->expire_time) {
  76. throw OAuthServerException::invalidRequest('code', 'Authorization code has expired');
  77. }
  78. if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) {
  79. throw OAuthServerException::invalidRequest('code', 'Authorization code has been revoked');
  80. }
  81. if ($authCodePayload->client_id !== $client->getIdentifier()) {
  82. throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client');
  83. }
  84. // The redirect URI is required in this request
  85. $redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
  86. if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) {
  87. throw OAuthServerException::invalidRequest('redirect_uri');
  88. }
  89. if ($authCodePayload->redirect_uri !== $redirectUri) {
  90. throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI');
  91. }
  92. $scopes = [];
  93. foreach ($authCodePayload->scopes as $scopeId) {
  94. $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeId);
  95. if ($scope instanceof ScopeEntityInterface === false) {
  96. // @codeCoverageIgnoreStart
  97. throw OAuthServerException::invalidScope($scopeId);
  98. // @codeCoverageIgnoreEnd
  99. }
  100. $scopes[] = $scope;
  101. }
  102. // Finalize the requested scopes
  103. $scopes = $this->scopeRepository->finalizeScopes(
  104. $scopes,
  105. $this->getIdentifier(),
  106. $client,
  107. $authCodePayload->user_id
  108. );
  109. } catch (\LogicException $e) {
  110. throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code');
  111. }
  112. // Validate code challenge
  113. if ($this->enableCodeExchangeProof === true) {
  114. $codeVerifier = $this->getRequestParameter('code_verifier', $request, null);
  115. if ($codeVerifier === null) {
  116. throw OAuthServerException::invalidRequest('code_verifier');
  117. }
  118. // Validate code_verifier according to RFC-7636
  119. // @see: https://tools.ietf.org/html/rfc7636#section-4.1
  120. if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeVerifier) !== 1) {
  121. throw OAuthServerException::invalidRequest(
  122. 'code_verifier',
  123. 'Code Verifier must follow the specifications of RFC-7636.'
  124. );
  125. }
  126. switch ($authCodePayload->code_challenge_method) {
  127. case 'plain':
  128. if (hash_equals($codeVerifier, $authCodePayload->code_challenge) === false) {
  129. throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.');
  130. }
  131. break;
  132. case 'S256':
  133. if (
  134. hash_equals(
  135. strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_'),
  136. $authCodePayload->code_challenge
  137. ) === false
  138. ) {
  139. throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.');
  140. }
  141. // @codeCoverageIgnoreStart
  142. break;
  143. default:
  144. throw OAuthServerException::serverError(
  145. sprintf(
  146. 'Unsupported code challenge method `%s`',
  147. $authCodePayload->code_challenge_method
  148. )
  149. );
  150. // @codeCoverageIgnoreEnd
  151. }
  152. }
  153. // Issue and persist access + refresh tokens
  154. $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes);
  155. $refreshToken = $this->issueRefreshToken($accessToken);
  156. // Inject tokens into response type
  157. $responseType->setAccessToken($accessToken);
  158. $responseType->setRefreshToken($refreshToken);
  159. // Revoke used auth code
  160. $this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id);
  161. return $responseType;
  162. }
  163. /**
  164. * Return the grant identifier that can be used in matching up requests.
  165. *
  166. * @return string
  167. */
  168. public function getIdentifier()
  169. {
  170. return 'authorization_code';
  171. }
  172. /**
  173. * {@inheritdoc}
  174. */
  175. public function canRespondToAuthorizationRequest(ServerRequestInterface $request)
  176. {
  177. return (
  178. array_key_exists('response_type', $request->getQueryParams())
  179. && $request->getQueryParams()['response_type'] === 'code'
  180. && isset($request->getQueryParams()['client_id'])
  181. );
  182. }
  183. /**
  184. * {@inheritdoc}
  185. */
  186. public function validateAuthorizationRequest(ServerRequestInterface $request)
  187. {
  188. $clientId = $this->getQueryStringParameter(
  189. 'client_id',
  190. $request,
  191. $this->getServerParameter('PHP_AUTH_USER', $request)
  192. );
  193. if (is_null($clientId)) {
  194. throw OAuthServerException::invalidRequest('client_id');
  195. }
  196. $client = $this->clientRepository->getClientEntity(
  197. $clientId,
  198. $this->getIdentifier(),
  199. null,
  200. false
  201. );
  202. if ($client instanceof ClientEntityInterface === false) {
  203. $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
  204. throw OAuthServerException::invalidClient();
  205. }
  206. $redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
  207. if ($redirectUri !== null) {
  208. if (
  209. is_string($client->getRedirectUri())
  210. && (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
  211. ) {
  212. $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
  213. throw OAuthServerException::invalidClient();
  214. } elseif (
  215. is_array($client->getRedirectUri())
  216. && in_array($redirectUri, $client->getRedirectUri(), true) === false
  217. ) {
  218. $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
  219. throw OAuthServerException::invalidClient();
  220. }
  221. } elseif (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1
  222. || empty($client->getRedirectUri())) {
  223. $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
  224. throw OAuthServerException::invalidClient();
  225. } else {
  226. $redirectUri = is_array($client->getRedirectUri())
  227. ? $client->getRedirectUri()[0]
  228. : $client->getRedirectUri();
  229. }
  230. $scopes = $this->validateScopes(
  231. $this->getQueryStringParameter('scope', $request, $this->defaultScope),
  232. $redirectUri
  233. );
  234. $stateParameter = $this->getQueryStringParameter('state', $request);
  235. $authorizationRequest = new AuthorizationRequest();
  236. $authorizationRequest->setGrantTypeId($this->getIdentifier());
  237. $authorizationRequest->setClient($client);
  238. $authorizationRequest->setRedirectUri($redirectUri);
  239. $authorizationRequest->setState($stateParameter);
  240. $authorizationRequest->setScopes($scopes);
  241. if ($this->enableCodeExchangeProof === true) {
  242. $codeChallenge = $this->getQueryStringParameter('code_challenge', $request);
  243. if ($codeChallenge === null) {
  244. throw OAuthServerException::invalidRequest('code_challenge');
  245. }
  246. $codeChallengeMethod = $this->getQueryStringParameter('code_challenge_method', $request, 'plain');
  247. if (in_array($codeChallengeMethod, ['plain', 'S256'], true) === false) {
  248. throw OAuthServerException::invalidRequest(
  249. 'code_challenge_method',
  250. 'Code challenge method must be `plain` or `S256`'
  251. );
  252. }
  253. // Validate code_challenge according to RFC-7636
  254. // @see: https://tools.ietf.org/html/rfc7636#section-4.2
  255. if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeChallenge) !== 1) {
  256. throw OAuthServerException::invalidRequest(
  257. 'code_challenged',
  258. 'Code challenge must follow the specifications of RFC-7636.'
  259. );
  260. }
  261. $authorizationRequest->setCodeChallenge($codeChallenge);
  262. $authorizationRequest->setCodeChallengeMethod($codeChallengeMethod);
  263. }
  264. return $authorizationRequest;
  265. }
  266. /**
  267. * {@inheritdoc}
  268. */
  269. public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
  270. {
  271. if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) {
  272. throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
  273. }
  274. $finalRedirectUri = ($authorizationRequest->getRedirectUri() === null)
  275. ? is_array($authorizationRequest->getClient()->getRedirectUri())
  276. ? $authorizationRequest->getClient()->getRedirectUri()[0]
  277. : $authorizationRequest->getClient()->getRedirectUri()
  278. : $authorizationRequest->getRedirectUri();
  279. // The user approved the client, redirect them back with an auth code
  280. if ($authorizationRequest->isAuthorizationApproved() === true) {
  281. $authCode = $this->issueAuthCode(
  282. $this->authCodeTTL,
  283. $authorizationRequest->getClient(),
  284. $authorizationRequest->getUser()->getIdentifier(),
  285. $authorizationRequest->getRedirectUri(),
  286. $authorizationRequest->getScopes()
  287. );
  288. $payload = [
  289. 'client_id' => $authCode->getClient()->getIdentifier(),
  290. 'redirect_uri' => $authCode->getRedirectUri(),
  291. 'auth_code_id' => $authCode->getIdentifier(),
  292. 'scopes' => $authCode->getScopes(),
  293. 'user_id' => $authCode->getUserIdentifier(),
  294. 'expire_time' => (new \DateTime())->add($this->authCodeTTL)->format('U'),
  295. 'code_challenge' => $authorizationRequest->getCodeChallenge(),
  296. 'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(),
  297. ];
  298. $response = new RedirectResponse();
  299. $response->setRedirectUri(
  300. $this->makeRedirectUri(
  301. $finalRedirectUri,
  302. [
  303. 'code' => $this->encrypt(
  304. json_encode(
  305. $payload
  306. )
  307. ),
  308. 'state' => $authorizationRequest->getState(),
  309. ]
  310. )
  311. );
  312. return $response;
  313. }
  314. // The user denied the client, redirect them back with an error
  315. throw OAuthServerException::accessDenied(
  316. 'The user denied the request',
  317. $this->makeRedirectUri(
  318. $finalRedirectUri,
  319. [
  320. 'state' => $authorizationRequest->getState(),
  321. ]
  322. )
  323. );
  324. }
  325. }