Passed
Pull Request — master (#1298)
by
unknown
31:36
created

OAuthServerException::generateHttpResponse()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 6

Importance

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