Passed
Pull Request — master (#1298)
by
unknown
62:34 queued 27:34
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);
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);
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);
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);
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(
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);
166
    }
167
168
    /**
169
     * Access denied.
170
     */
171 17
    public static function accessDenied(?string $hint = null, ?string $redirectUri = null, Throwable $previous = null, string $queryDelimiter = '?'): static
172
    {
173 17
        return new static(
174 17
            'The resource owner or authorization server denied the request.',
175 17
            9,
176 17
            'access_denied',
177 17
            401,
178 17
            $hint,
179 17
            $redirectUri,
180 17
            $previous,
181 17
            $queryDelimiter
182
        );
183
    }
184
185
    /**
186
     * Invalid grant.
187 2
     */
188
    public static function invalidGrant(string $hint = ''): static
189 2
    {
190 2
        return new static(
191 2
            'The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token '
192 2
                . 'is invalid, expired, revoked, does not match the redirection URI used in the authorization request, '
193 2
                . 'or was issued to another client.',
194 2
            10,
195 2
            'invalid_grant',
196 2
            400,
197 2
            $hint
198
        );
199
    }
200 3
201
    public function getErrorType(): string
202 3
    {
203
        return $this->errorType;
204
    }
205
206
    /**
207
     * Expired token error.
208
     *
209
     * @param Throwable $previous Previous exception
210
     *
211
     * @return static
212 1
     */
213
    public static function expiredToken(?string $hint = null, Throwable $previous = null): static
214 1
    {
215 1
        $errorMessage = 'The `device_code` has expired and the device ' .
216
                        'authorization session has concluded.';
217 1
218
        return new static($errorMessage, 11, 'expired_token', 400, $hint, null, $previous);
219
    }
220 1
221
    public static function authorizationPending(string $hint = '', Throwable $previous = null): static
222 1
    {
223 1
        return new static(
224 1
            'The authorization request is still pending as the end user ' .
225 1
            'hasn\'t yet completed the user interaction steps. The client ' .
226 1
            'SHOULD repeat the Access Token Request to the token endpoint',
227 1
            12,
228 1
            'authorization_pending',
229 1
            400,
230 1
            $hint,
231 1
            null,
232 1
            $previous
233
        );
234
    }
235
236
    /**
237
     * Slow down error used with the Device Authorization Grant.
238
     *
239
     *
240
     * @return static
241 1
     */
242
    public static function slowDown(string $hint = '', Throwable $previous = null): static
243 1
    {
244 1
        return new static(
245 1
            'The authorization request is still pending and polling should ' .
246 1
                'continue, but the interval MUST be increased ' .
247 1
                'by 5 seconds for this and all subsequent requests.',
248 1
            13,
249 1
            'slow_down',
250 1
            400,
251 1
            $hint,
252 1
            null,
253 1
            $previous
254
        );
255
    }
256
257
    /**
258
    {
259
        return $this->errorType;
260
    }
261
262
    /**
263
     * Generate a HTTP response.
264 10
     */
265
    public function generateHttpResponse(ResponseInterface $response, bool $useFragment = false, int $jsonOptions = 0): ResponseInterface
266 10
    {
267
        $headers = $this->getHttpHeaders();
268 10
269
        $payload = $this->getPayload();
270 10
271 3
        if ($this->redirectUri !== null) {
272 1
            $queryDelimiter = $useFragment === true ? '#' : $this->queryDelimiter;
273
            $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

273
            $this->redirectUri .= (strstr($this->redirectUri, /** @scrutinizer ignore-type */ $queryDelimiter) === false) ? $queryDelimiter : '&';
Loading history...
274 2
275
            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...
276
        }
277 3
278
        foreach ($headers as $header => $content) {
279
            $response = $response->withHeader($header, $content);
280 7
        }
281 7
282
        $jsonEncodedPayload = json_encode($payload, $jsonOptions);
283
284 7
        $responseBody = $jsonEncodedPayload === false ? 'JSON encoding of payload failed' : $jsonEncodedPayload;
285
286 7
        $response->getBody()->write($responseBody);
287
288 7
        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

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