Passed
Pull Request — master (#1298)
by
unknown
35:09
created

OAuthServerException::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 8
dl 0
loc 10
ccs 8
cts 8
cp 1
crap 2
rs 10
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * @author      Alex Bilbie <[email protected]>
5
 * @copyright   Copyright (c) Alex Bilbie
6
 * @license     http://mit-license.org/
7
 *
8
 * @link        https://github.com/thephpleague/oauth2-server
9
 */
10
11
declare(strict_types=1);
12
13
namespace League\OAuth2\Server\Exception;
14
15
use Exception;
16
use Psr\Http\Message\ResponseInterface;
17
use Psr\Http\Message\ServerRequestInterface;
18
use Throwable;
19
20
use function htmlspecialchars;
21
use function http_build_query;
22
use function sprintf;
23
use function strpos;
24
use function strstr;
25
26
class OAuthServerException extends Exception
27
{
28
    /**
29
     * @var array<string, string>
30
     */
31
    private array $payload;
32
33
    private ServerRequestInterface $serverRequest;
34
35
    /**
36
     * Throw a new exception.
37
     */
38 91
    final public function __construct(string $message, int $code, private string $errorType, private int $httpStatusCode = 400, private ?string $hint = null, private ?string $redirectUri = null, Throwable $previous = null, private ?string $queryDelimiter)
39
    {
40 91
        parent::__construct($message, $code, $previous);
41 91
        $this->payload = [
42 91
            'error'             => $errorType,
43 91
            'error_description' => $message,
44 91
        ];
45
46 91
        if ($hint !== null) {
47 58
            $this->payload['hint'] = $hint;
48
        }
49
    }
50
51
    /**
52
     * Returns the current payload.
53
     *
54
     * @return array<string, string>
55
     */
56 10
    public function getPayload(): array
57
    {
58 10
        return $this->payload;
59
    }
60
61
    /**
62
     * Updates the current payload.
63
     *
64
     * @param array<string, string> $payload
65
     */
66
    public function setPayload(array $payload): void
67
    {
68
        $this->payload = $payload;
69
    }
70
71
    /**
72
     * Set the server request that is responsible for generating the exception
73
     */
74 19
    public function setServerRequest(ServerRequestInterface $serverRequest): void
75
    {
76 19
        $this->serverRequest = $serverRequest;
77
    }
78
79
    /**
80
     * Unsupported grant type error.
81
     */
82 2
    public static function unsupportedGrantType(): static
83
    {
84 2
        $errorMessage = 'The authorization grant type is not supported by the authorization server.';
85 2
        $hint = 'Check that all required parameters have been provided';
86
87 2
        return new static($errorMessage, 2, 'unsupported_grant_type', 400, $hint);
0 ignored issues
show
Bug introduced by
The call to League\OAuth2\Server\Exc...xception::__construct() has too few arguments starting with queryDelimiter. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

87
        return /** @scrutinizer ignore-call */ new static($errorMessage, 2, 'unsupported_grant_type', 400, $hint);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
88
    }
89
90
    /**
91
     * Invalid request error.
92
     */
93 25
    public static function invalidRequest(string $parameter, ?string $hint = null, Throwable $previous = null): static
94
    {
95 25
        $errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' .
96 25
            'includes a parameter more than once, or is otherwise malformed.';
97 25
        $hint = ($hint === null) ? sprintf('Check the `%s` parameter', $parameter) : $hint;
98
99 25
        return new static($errorMessage, 3, 'invalid_request', 400, $hint, null, $previous);
0 ignored issues
show
Bug introduced by
The call to League\OAuth2\Server\Exc...xception::__construct() has too few arguments starting with queryDelimiter. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

99
        return /** @scrutinizer ignore-call */ new static($errorMessage, 3, 'invalid_request', 400, $hint, null, $previous);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
100
    }
101
102
    /**
103
     * Invalid client error.
104
     */
105 19
    public static function invalidClient(ServerRequestInterface $serverRequest): static
106
    {
107 19
        $exception = new static('Client authentication failed', 4, 'invalid_client', 401);
0 ignored issues
show
Bug introduced by
The call to League\OAuth2\Server\Exc...xception::__construct() has too few arguments starting with queryDelimiter. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

107
        $exception = /** @scrutinizer ignore-call */ new static('Client authentication failed', 4, 'invalid_client', 401);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
108
109 19
        $exception->setServerRequest($serverRequest);
110
111 19
        return $exception;
112
    }
113
114
    /**
115
     * Invalid scope error
116
     */
117 8
    public static function invalidScope(string $scope, string|null $redirectUri = null, string $queryDelimiter = '?'): static
118
    {
119 8
        $errorMessage = 'The requested scope is invalid, unknown, or malformed';
120
121 8
        if ($scope === '') {
122
            $hint = 'Specify a scope in the request or set a default scope';
123
        } else {
124 8
            $hint = sprintf(
125 8
                'Check the `%s` scope',
126 8
                htmlspecialchars($scope, ENT_QUOTES, 'UTF-8', false)
127 8
            );
128
        }
129
130 8
        return new static($errorMessage, 5, 'invalid_scope', 400, $hint, $redirectUri, null, $queryDelimiter);
131
    }
132
133
    /**
134
     * Invalid credentials error.
135
     */
136 2
    public static function invalidCredentials(): static
137
    {
138 2
        return new static('The user credentials were incorrect.', 6, 'invalid_grant', 400);
0 ignored issues
show
Bug introduced by
The call to League\OAuth2\Server\Exc...xception::__construct() has too few arguments starting with queryDelimiter. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

138
        return /** @scrutinizer ignore-call */ new static('The user credentials were incorrect.', 6, 'invalid_grant', 400);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
139
    }
140
141
    /**
142
     * Server error.
143
     *
144
     * @codeCoverageIgnore
145
     */
146
    public static function serverError(string $hint, Throwable $previous = null): static
147
    {
148
        return new static(
0 ignored issues
show
Bug introduced by
The call to League\OAuth2\Server\Exc...xception::__construct() has too few arguments starting with queryDelimiter. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

148
        return /** @scrutinizer ignore-call */ new static(

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
149
            'The authorization server encountered an unexpected condition which prevented it from fulfilling'
150
            . ' the request: ' . $hint,
151
            7,
152
            'server_error',
153
            500,
154
            null,
155
            null,
156
            $previous
157
        );
158
    }
159
160
    /**
161
     * Invalid refresh token.
162
     */
163 4
    public static function invalidRefreshToken(?string $hint = null, Throwable $previous = null): static
164
    {
165 4
        return new static('The refresh token is invalid.', 8, 'invalid_grant', 400, $hint, null, $previous);
0 ignored issues
show
Bug introduced by
The call to League\OAuth2\Server\Exc...xception::__construct() has too few arguments starting with queryDelimiter. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

165
        return /** @scrutinizer ignore-call */ new static('The refresh token is invalid.', 8, 'invalid_grant', 400, $hint, null, $previous);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
166
    }
167
168
    /**
169
     * Access denied.
170
     */
171 17
    public static function accessDenied(
172
        ?string $hint = null,
173 17
        ?string $redirectUri = null,
174 17
        Throwable $previous = null,
175 17
        string $queryDelimiter = '?'
176 17
    ): static {
177 17
        return new static(
178 17
            'The resource owner or authorization server denied the request.',
179 17
            9,
180 17
            'access_denied',
181 17
            401,
182
            $hint,
183
            $redirectUri,
184
            $previous,
185
            $queryDelimiter
186
        );
187 2
    }
188
189 2
    /**
190 2
     * Invalid grant.
191 2
     */
192 2
    public static function invalidGrant(string $hint = ''): static
193 2
    {
194 2
        return new static(
0 ignored issues
show
Bug introduced by
The call to League\OAuth2\Server\Exc...xception::__construct() has too few arguments starting with queryDelimiter. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

194
        return /** @scrutinizer ignore-call */ new static(

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
195 2
            'The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token '
196 2
                . 'is invalid, expired, revoked, does not match the redirection URI used in the authorization request, '
197 2
                . 'or was issued to another client.',
198
            10,
199
            'invalid_grant',
200 3
            400,
201
            $hint
202 3
        );
203
    }
204
205
    public function getErrorType(): string
206
    {
207
        return $this->errorType;
208
    }
209
210
    /**
211
     * Expired token error.
212 1
     *
213
     * @param Throwable $previous Previous exception
214 1
     *
215 1
     * @return static
216
     */
217 1
    public static function expiredToken(?string $hint = null, Throwable $previous = null): static
218
    {
219
        $errorMessage = 'The `device_code` has expired and the device ' .
220 1
                        'authorization session has concluded.';
221
222 1
        return new static($errorMessage, 11, 'expired_token', 400, $hint, null, $previous);
0 ignored issues
show
Bug introduced by
The call to League\OAuth2\Server\Exc...xception::__construct() has too few arguments starting with queryDelimiter. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

222
        return /** @scrutinizer ignore-call */ new static($errorMessage, 11, 'expired_token', 400, $hint, null, $previous);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
223 1
    }
224 1
225 1
    public static function authorizationPending(string $hint = '', Throwable $previous = null): static
226 1
    {
227 1
        return new static(
0 ignored issues
show
Bug introduced by
The call to League\OAuth2\Server\Exc...xception::__construct() has too few arguments starting with queryDelimiter. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

227
        return /** @scrutinizer ignore-call */ new static(

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
228 1
            'The authorization request is still pending as the end user ' .
229 1
            'hasn\'t yet completed the user interaction steps. The client ' .
230 1
            'SHOULD repeat the Access Token Request to the token endpoint',
231 1
            12,
232 1
            'authorization_pending',
233
            400,
234
            $hint,
235
            null,
236
            $previous
237
        );
238
    }
239
240
    /**
241 1
     * Slow down error used with the Device Authorization Grant.
242
     *
243 1
     *
244 1
     * @return static
245 1
     */
246 1
    public static function slowDown(string $hint = '', Throwable $previous = null): static
247 1
    {
248 1
        return new static(
0 ignored issues
show
Bug introduced by
The call to League\OAuth2\Server\Exc...xception::__construct() has too few arguments starting with queryDelimiter. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

248
        return /** @scrutinizer ignore-call */ new static(

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
249 1
            'The authorization request is still pending and polling should ' .
250 1
                'continue, but the interval MUST be increased ' .
251 1
                'by 5 seconds for this and all subsequent requests.',
252 1
            13,
253 1
            'slow_down',
254
            400,
255
            $hint,
256
            null,
257
            $previous
258
        );
259
    }
260
261
    /**
262
    {
263
        return $this->errorType;
264 10
    }
265
266 10
    /**
267
     * Generate a HTTP response.
268 10
     */
269
    public function generateHttpResponse(ResponseInterface $response, bool $useFragment = false, int $jsonOptions = 0): ResponseInterface
270 10
    {
271 3
        $headers = $this->getHttpHeaders();
272 1
273
        $payload = $this->getPayload();
274 2
275
        if ($this->redirectUri !== null) {
276
            $queryDelimiter = $useFragment === true ? '#' : $this->queryDelimiter;
277 3
            $this->redirectUri .= (strstr($this->redirectUri, $queryDelimiter) === false) ? $queryDelimiter : '&';
0 ignored issues
show
Bug introduced by
It seems like $queryDelimiter can also be of type null; however, parameter $needle of strstr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

277
            $this->redirectUri .= (strstr($this->redirectUri, /** @scrutinizer ignore-type */ $queryDelimiter) === false) ? $queryDelimiter : '&';
Loading history...
278
279
            return $response->withStatus(302)->withHeader('Location', $this->redirectUri . http_build_query($payload));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response->withSt..._build_query($payload)) returns the type Psr\Http\Message\MessageInterface which includes types incompatible with the type-hinted return Psr\Http\Message\ResponseInterface.
Loading history...
280 7
        }
281 7
282
        foreach ($headers as $header => $content) {
283
            $response = $response->withHeader($header, $content);
284 7
        }
285
286 7
        $jsonEncodedPayload = json_encode($payload, $jsonOptions);
287
288 7
        $responseBody = $jsonEncodedPayload === false ? 'JSON encoding of payload failed' : $jsonEncodedPayload;
289
290 7
        $response->getBody()->write($responseBody);
291
292
        return $response->withStatus($this->getHttpStatusCode());
0 ignored issues
show
Bug introduced by
The method withStatus() does not exist on Psr\Http\Message\MessageInterface. It seems like you code against a sub-type of Psr\Http\Message\MessageInterface such as Psr\Http\Message\ResponseInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

292
        return $response->/** @scrutinizer ignore-call */ withStatus($this->getHttpStatusCode());
Loading history...
293
    }
294
295
    /**
296
     * Get all headers that have to be send with the error response.
297
     *
298 10
     * @return array<string, string> Array with header values
299
     */
300 10
    public function getHttpHeaders(): array
301 10
    {
302 10
        $headers = [
303
            'Content-type' => 'application/json',
304
        ];
305
306
        // Add "WWW-Authenticate" header
307
        //
308
        // RFC 6749, section 5.2.:
309
        // "If the client attempted to authenticate via the 'Authorization'
310
        // request header field, the authorization server MUST
311
        // respond with an HTTP 401 (Unauthorized) status code and
312 10
        // include the "WWW-Authenticate" response header field
313 2
        // matching the authentication scheme used by the client.
314
        if ($this->errorType === 'invalid_client' && $this->requestHasAuthorizationHeader()) {
315 2
            $authScheme = strpos($this->serverRequest->getHeader('Authorization')[0], 'Bearer') === 0 ? 'Bearer' : 'Basic';
316
317
            $headers['WWW-Authenticate'] = $authScheme . ' realm="OAuth"';
318 10
        }
319
320
        return $headers;
321
    }
322
323
    /**
324
     * Check if the exception has an associated redirect URI.
325
     *
326
     * Returns whether the exception includes a redirect, since
327
     * getHttpStatusCode() doesn't return a 302 when there's a
328
     * redirect enabled. This helps when you want to override local
329 2
     * error pages but want to let redirects through.
330
     */
331 2
    public function hasRedirect(): bool
332
    {
333
        return $this->redirectUri !== null;
334
    }
335
336
    /**
337 1
     * Returns the Redirect URI used for redirecting.
338
     */
339 1
    public function getRedirectUri(): ?string
340
    {
341
        return $this->redirectUri;
342
    }
343
344
    /**
345 8
     * Returns the HTTP status code to send when the exceptions is output.
346
     */
347 8
    public function getHttpStatusCode(): int
348
    {
349
        return $this->httpStatusCode;
350 15
    }
351
352 15
    public function getHint(): ?string
353
    {
354
        return $this->hint;
355
    }
356
357
    /**
358
     * Check if the request has a non-empty 'Authorization' header value.
359
     *
360
     * Returns true if the header is present and not an empty string, false
361 5
     * otherwise.
362
     */
363 5
    private function requestHasAuthorizationHeader(): bool
364 2
    {
365
        if (!$this->serverRequest->hasHeader('Authorization')) {
366
            return false;
367 3
        }
368
369
        $authorizationHeader = $this->serverRequest->getHeader('Authorization');
370
371
        // Common .htaccess configurations yield an empty string for the
372
        // 'Authorization' header when one is not provided by the client.
373
        // For practical purposes that case should be treated as though the
374 3
        // header isn't present.
375 1
        // See https://github.com/thephpleague/oauth2-server/issues/1162
376
        if ($authorizationHeader === [] || $authorizationHeader[0] === '') {
377
            return false;
378 2
        }
379
380
        return true;
381
    }
382
}
383