vendor/league/oauth2-server/src/Exception/OAuthServerException.php line 241

Open in your IDE?
  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\Exception;
  10. use Exception;
  11. use Psr\Http\Message\ResponseInterface;
  12. use Psr\Http\Message\ServerRequestInterface;
  13. use Throwable;
  14. class OAuthServerException extends Exception
  15. {
  16. /**
  17. * @var int
  18. */
  19. private $httpStatusCode;
  20. /**
  21. * @var string
  22. */
  23. private $errorType;
  24. /**
  25. * @var null|string
  26. */
  27. private $hint;
  28. /**
  29. * @var null|string
  30. */
  31. private $redirectUri;
  32. /**
  33. * @var array
  34. */
  35. private $payload;
  36. /**
  37. * @var ServerRequestInterface
  38. */
  39. private $serverRequest;
  40. /**
  41. * Throw a new exception.
  42. *
  43. * @param string $message Error message
  44. * @param int $code Error code
  45. * @param string $errorType Error type
  46. * @param int $httpStatusCode HTTP status code to send (default = 400)
  47. * @param null|string $hint A helper hint
  48. * @param null|string $redirectUri A HTTP URI to redirect the user back to
  49. * @param Throwable $previous Previous exception
  50. */
  51. public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, Throwable $previous = null)
  52. {
  53. parent::__construct($message, $code, $previous);
  54. $this->httpStatusCode = $httpStatusCode;
  55. $this->errorType = $errorType;
  56. $this->hint = $hint;
  57. $this->redirectUri = $redirectUri;
  58. $this->payload = [
  59. 'error' => $errorType,
  60. 'error_description' => $message,
  61. ];
  62. if ($hint !== null) {
  63. $this->payload['hint'] = $hint;
  64. }
  65. }
  66. /**
  67. * Returns the current payload.
  68. *
  69. * @return array
  70. */
  71. public function getPayload()
  72. {
  73. $payload = $this->payload;
  74. // The "message" property is deprecated and replaced by "error_description"
  75. // TODO: remove "message" property
  76. if (isset($payload['error_description']) && !isset($payload['message'])) {
  77. $payload['message'] = $payload['error_description'];
  78. }
  79. return $payload;
  80. }
  81. /**
  82. * Updates the current payload.
  83. *
  84. * @param array $payload
  85. */
  86. public function setPayload(array $payload)
  87. {
  88. $this->payload = $payload;
  89. }
  90. /**
  91. * Set the server request that is responsible for generating the exception
  92. *
  93. * @param ServerRequestInterface $serverRequest
  94. */
  95. public function setServerRequest(ServerRequestInterface $serverRequest)
  96. {
  97. $this->serverRequest = $serverRequest;
  98. }
  99. /**
  100. * Unsupported grant type error.
  101. *
  102. * @return static
  103. */
  104. public static function unsupportedGrantType()
  105. {
  106. $errorMessage = 'The authorization grant type is not supported by the authorization server.';
  107. $hint = 'Check that all required parameters have been provided';
  108. return new static($errorMessage, 2, 'unsupported_grant_type', 400, $hint);
  109. }
  110. /**
  111. * Invalid request error.
  112. *
  113. * @param string $parameter The invalid parameter
  114. * @param null|string $hint
  115. * @param Throwable $previous Previous exception
  116. *
  117. * @return static
  118. */
  119. public static function invalidRequest($parameter, $hint = null, Throwable $previous = null)
  120. {
  121. $errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' .
  122. 'includes a parameter more than once, or is otherwise malformed.';
  123. $hint = ($hint === null) ? \sprintf('Check the `%s` parameter', $parameter) : $hint;
  124. return new static($errorMessage, 3, 'invalid_request', 400, $hint, null, $previous);
  125. }
  126. /**
  127. * Invalid client error.
  128. *
  129. * @param ServerRequestInterface $serverRequest
  130. *
  131. * @return static
  132. */
  133. public static function invalidClient(ServerRequestInterface $serverRequest)
  134. {
  135. $exception = new static('Client authentication failed', 4, 'invalid_client', 401);
  136. $exception->setServerRequest($serverRequest);
  137. return $exception;
  138. }
  139. /**
  140. * Invalid scope error.
  141. *
  142. * @param string $scope The bad scope
  143. * @param null|string $redirectUri A HTTP URI to redirect the user back to
  144. *
  145. * @return static
  146. */
  147. public static function invalidScope($scope, $redirectUri = null)
  148. {
  149. $errorMessage = 'The requested scope is invalid, unknown, or malformed';
  150. if (empty($scope)) {
  151. $hint = 'Specify a scope in the request or set a default scope';
  152. } else {
  153. $hint = \sprintf(
  154. 'Check the `%s` scope',
  155. \htmlspecialchars($scope, ENT_QUOTES, 'UTF-8', false)
  156. );
  157. }
  158. return new static($errorMessage, 5, 'invalid_scope', 400, $hint, $redirectUri);
  159. }
  160. /**
  161. * Invalid credentials error.
  162. *
  163. * @return static
  164. */
  165. public static function invalidCredentials()
  166. {
  167. return new static('The user credentials were incorrect.', 6, 'invalid_grant', 400);
  168. }
  169. /**
  170. * Server error.
  171. *
  172. * @param string $hint
  173. * @param Throwable $previous
  174. *
  175. * @return static
  176. *
  177. * @codeCoverageIgnore
  178. */
  179. public static function serverError($hint, Throwable $previous = null)
  180. {
  181. return new static(
  182. 'The authorization server encountered an unexpected condition which prevented it from fulfilling'
  183. . ' the request: ' . $hint,
  184. 7,
  185. 'server_error',
  186. 500,
  187. null,
  188. null,
  189. $previous
  190. );
  191. }
  192. /**
  193. * Invalid refresh token.
  194. *
  195. * @param null|string $hint
  196. * @param Throwable $previous
  197. *
  198. * @return static
  199. */
  200. public static function invalidRefreshToken($hint = null, Throwable $previous = null)
  201. {
  202. return new static('The refresh token is invalid.', 8, 'invalid_request', 401, $hint, null, $previous);
  203. }
  204. /**
  205. * Access denied.
  206. *
  207. * @param null|string $hint
  208. * @param null|string $redirectUri
  209. * @param Throwable $previous
  210. *
  211. * @return static
  212. */
  213. public static function accessDenied($hint = null, $redirectUri = null, Throwable $previous = null)
  214. {
  215. return new static(
  216. 'The resource owner or authorization server denied the request.',
  217. 9,
  218. 'access_denied',
  219. 401,
  220. $hint,
  221. $redirectUri,
  222. $previous
  223. );
  224. }
  225. /**
  226. * Invalid grant.
  227. *
  228. * @param string $hint
  229. *
  230. * @return static
  231. */
  232. public static function invalidGrant($hint = '')
  233. {
  234. return new static(
  235. 'The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token '
  236. . 'is invalid, expired, revoked, does not match the redirection URI used in the authorization request, '
  237. . 'or was issued to another client.',
  238. 10,
  239. 'invalid_grant',
  240. 400,
  241. $hint
  242. );
  243. }
  244. /**
  245. * @return string
  246. */
  247. public function getErrorType()
  248. {
  249. return $this->errorType;
  250. }
  251. /**
  252. * Generate a HTTP response.
  253. *
  254. * @param ResponseInterface $response
  255. * @param bool $useFragment True if errors should be in the URI fragment instead of query string
  256. * @param int $jsonOptions options passed to json_encode
  257. *
  258. * @return ResponseInterface
  259. */
  260. public function generateHttpResponse(ResponseInterface $response, $useFragment = false, $jsonOptions = 0)
  261. {
  262. $headers = $this->getHttpHeaders();
  263. $payload = $this->getPayload();
  264. if ($this->redirectUri !== null) {
  265. if ($useFragment === true) {
  266. $this->redirectUri .= (\strstr($this->redirectUri, '#') === false) ? '#' : '&';
  267. } else {
  268. $this->redirectUri .= (\strstr($this->redirectUri, '?') === false) ? '?' : '&';
  269. }
  270. return $response->withStatus(302)->withHeader('Location', $this->redirectUri . \http_build_query($payload));
  271. }
  272. foreach ($headers as $header => $content) {
  273. $response = $response->withHeader($header, $content);
  274. }
  275. $responseBody = \json_encode($payload, $jsonOptions) ?: 'JSON encoding of payload failed';
  276. $response->getBody()->write($responseBody);
  277. return $response->withStatus($this->getHttpStatusCode());
  278. }
  279. /**
  280. * Get all headers that have to be send with the error response.
  281. *
  282. * @return array Array with header values
  283. */
  284. public function getHttpHeaders()
  285. {
  286. $headers = [
  287. 'Content-type' => 'application/json',
  288. ];
  289. // Add "WWW-Authenticate" header
  290. //
  291. // RFC 6749, section 5.2.:
  292. // "If the client attempted to authenticate via the 'Authorization'
  293. // request header field, the authorization server MUST
  294. // respond with an HTTP 401 (Unauthorized) status code and
  295. // include the "WWW-Authenticate" response header field
  296. // matching the authentication scheme used by the client.
  297. if ($this->errorType === 'invalid_client' && $this->requestHasAuthorizationHeader()) {
  298. $authScheme = \strpos($this->serverRequest->getHeader('Authorization')[0], 'Bearer') === 0 ? 'Bearer' : 'Basic';
  299. $headers['WWW-Authenticate'] = $authScheme . ' realm="OAuth"';
  300. }
  301. return $headers;
  302. }
  303. /**
  304. * Check if the exception has an associated redirect URI.
  305. *
  306. * Returns whether the exception includes a redirect, since
  307. * getHttpStatusCode() doesn't return a 302 when there's a
  308. * redirect enabled. This helps when you want to override local
  309. * error pages but want to let redirects through.
  310. *
  311. * @return bool
  312. */
  313. public function hasRedirect()
  314. {
  315. return $this->redirectUri !== null;
  316. }
  317. /**
  318. * Returns the Redirect URI used for redirecting.
  319. *
  320. * @return string|null
  321. */
  322. public function getRedirectUri()
  323. {
  324. return $this->redirectUri;
  325. }
  326. /**
  327. * Returns the HTTP status code to send when the exceptions is output.
  328. *
  329. * @return int
  330. */
  331. public function getHttpStatusCode()
  332. {
  333. return $this->httpStatusCode;
  334. }
  335. /**
  336. * @return null|string
  337. */
  338. public function getHint()
  339. {
  340. return $this->hint;
  341. }
  342. /**
  343. * Check if the request has a non-empty 'Authorization' header value.
  344. *
  345. * Returns true if the header is present and not an empty string, false
  346. * otherwise.
  347. *
  348. * @return bool
  349. */
  350. private function requestHasAuthorizationHeader()
  351. {
  352. if (!$this->serverRequest->hasHeader('Authorization')) {
  353. return false;
  354. }
  355. $authorizationHeader = $this->serverRequest->getHeader('Authorization');
  356. // Common .htaccess configurations yield an empty string for the
  357. // 'Authorization' header when one is not provided by the client.
  358. // For practical purposes that case should be treated as though the
  359. // header isn't present.
  360. // See https://github.com/thephpleague/oauth2-server/issues/1162
  361. if (empty($authorizationHeader) || empty($authorizationHeader[0])) {
  362. return false;
  363. }
  364. return true;
  365. }
  366. }