Passed
Push — master ( 622eaa...020faf )
by Andrew
29:16 queued 27:53
created

requestHasAuthorizationHeader()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 18
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

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