Passed
Pull Request — master (#1298)
by
unknown
33:38
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
24
class OAuthServerException extends Exception
25
{
26
    /**
27
     * @var array<string, string>
28
     */
29
    private array $payload;
30
31
    private ServerRequestInterface $serverRequest;
32
33
    /**
34
     * Throw a new exception.
35
     */
36 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 = '?')
37
    {
38 91
        parent::__construct($message, $code, $previous);
39 91
        $this->payload = [
40 91
            'error'             => $errorType,
41 91
            'error_description' => $message,
42 91
        ];
43
44 91
        if ($hint !== null) {
45 58
            $this->payload['hint'] = $hint;
46
        }
47
    }
48
49
    /**
50
     * Returns the current payload.
51
     *
52
     * @return array<string, string>
53
     */
54 10
    public function getPayload(): array
55
    {
56 10
        return $this->payload;
57
    }
58
59
    /**
60
     * Updates the current payload.
61
     *
62
     * @param array<string, string> $payload
63
     */
64
    public function setPayload(array $payload): void
65
    {
66
        $this->payload = $payload;
67
    }
68
69
    /**
70
     * Set the server request that is responsible for generating the exception
71
     */
72 19
    public function setServerRequest(ServerRequestInterface $serverRequest): void
73
    {
74 19
        $this->serverRequest = $serverRequest;
75
    }
76
77
    /**
78
     * Unsupported grant type error.
79
     */
80 2
    public static function unsupportedGrantType(): static
81
    {
82 2
        $errorMessage = 'The authorization grant type is not supported by the authorization server.';
83 2
        $hint = 'Check that all required parameters have been provided';
84
85 2
        return new static($errorMessage, 2, 'unsupported_grant_type', 400, $hint);
86
    }
87
88
    /**
89
     * Invalid request error.
90
     */
91 25
    public static function invalidRequest(string $parameter, ?string $hint = null, Throwable $previous = null): static
92
    {
93 25
        $errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' .
94 25
            'includes a parameter more than once, or is otherwise malformed.';
95 25
        $hint = ($hint === null) ? sprintf('Check the `%s` parameter', $parameter) : $hint;
96
97 25
        return new static($errorMessage, 3, 'invalid_request', 400, $hint, null, $previous);
98
    }
99
100
    /**
101
     * Invalid client error.
102
     */
103 19
    public static function invalidClient(ServerRequestInterface $serverRequest): static
104
    {
105 19
        $exception = new static('Client authentication failed', 4, 'invalid_client', 401);
106
107 19
        $exception->setServerRequest($serverRequest);
108
109 19
        return $exception;
110
    }
111
112
    /**
113
     * Invalid scope error
114
     */
115 8
    public static function invalidScope(string $scope, string|null $redirectUri = null, string $queryDelimiter = '?'): static
116
    {
117 8
        $errorMessage = 'The requested scope is invalid, unknown, or malformed';
118
119 8
        if ($scope === '') {
120
            $hint = 'Specify a scope in the request or set a default scope';
121
        } else {
122 8
            $hint = sprintf(
123 8
                'Check the `%s` scope',
124 8
                htmlspecialchars($scope, ENT_QUOTES, 'UTF-8', false)
125 8
            );
126
        }
127
128 8
        return new static($errorMessage, 5, 'invalid_scope', 400, $hint, $redirectUri, null, $queryDelimiter);
129
    }
130
131
    /**
132
     * Invalid credentials error.
133
     */
134 2
    public static function invalidCredentials(): static
135
    {
136 2
        return new static('The user credentials were incorrect.', 6, 'invalid_grant', 400);
137
    }
138
139
    /**
140
     * Server error.
141
     *
142
     * @codeCoverageIgnore
143
     */
144
    public static function serverError(string $hint, Throwable $previous = null): static
145
    {
146
        return new static(
147
            'The authorization server encountered an unexpected condition which prevented it from fulfilling'
148
            . ' the request: ' . $hint,
149
            7,
150
            'server_error',
151
            500,
152
            null,
153
            null,
154
            $previous
155
        );
156
    }
157
158
    /**
159
     * Invalid refresh token.
160
     */
161 4
    public static function invalidRefreshToken(?string $hint = null, Throwable $previous = null): static
162
    {
163 4
        return new static('The refresh token is invalid.', 8, 'invalid_grant', 400, $hint, null, $previous);
164
    }
165
166
    /**
167
     * Access denied.
168
     */
169 17
    public static function accessDenied(?string $hint = null, ?string $redirectUri = null, Throwable $previous = null, string $queryDelimiter = '?'): static
170
    {
171 17
        return new static(
172 17
            'The resource owner or authorization server denied the request.',
173 17
            9,
174 17
            'access_denied',
175 17
            401,
176 17
            $hint,
177 17
            $redirectUri,
178 17
            $previous,
179 17
            $queryDelimiter
180
        );
181
    }
182
183
    /**
184
     * Invalid grant.
185 2
     */
186
    public static function invalidGrant(string $hint = ''): static
187 2
    {
188 2
        return new static(
189 2
            'The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token '
190 2
                . 'is invalid, expired, revoked, does not match the redirection URI used in the authorization request, '
191 2
                . 'or was issued to another client.',
192 2
            10,
193 2
            'invalid_grant',
194 2
            400,
195 2
            $hint
196
        );
197
    }
198 3
199
    public function getErrorType(): string
200 3
    {
201
        return $this->errorType;
202
    }
203
204
    /**
205
     * Expired token error.
206
     *
207
     * @param Throwable $previous Previous exception
208
     *
209
     * @return static
210 1
     */
211
    public static function expiredToken(?string $hint = null, Throwable $previous = null): static
212 1
    {
213 1
        $errorMessage = 'The `device_code` has expired and the device ' .
214
                        'authorization session has concluded.';
215 1
216
        return new static($errorMessage, 11, 'expired_token', 400, $hint, null, $previous);
217
    }
218 1
219
    public static function authorizationPending(string $hint = '', Throwable $previous = null): static
220 1
    {
221 1
        return new static(
222 1
            'The authorization request is still pending as the end user ' .
223 1
            'hasn\'t yet completed the user interaction steps. The client ' .
224 1
            'SHOULD repeat the Access Token Request to the token endpoint',
225 1
            12,
226 1
            'authorization_pending',
227 1
            400,
228 1
            $hint,
229 1
            null,
230 1
            $previous
231
        );
232
    }
233
234
    /**
235
     * Slow down error used with the Device Authorization Grant.
236
     *
237
     *
238
     * @return static
239 1
     */
240
    public static function slowDown(string $hint = '', Throwable $previous = null): static
241 1
    {
242 1
        return new static(
243 1
            'The authorization request is still pending and polling should ' .
244 1
                'continue, but the interval MUST be increased ' .
245 1
                'by 5 seconds for this and all subsequent requests.',
246 1
            13,
247 1
            'slow_down',
248 1
            400,
249 1
            $hint,
250 1
            null,
251 1
            $previous
252
        );
253
    }
254
255
    /**
256
    {
257
        return $this->errorType;
258
    }
259
260
    /**
261
     * Generate a HTTP response.
262 10
     */
263
    public function generateHttpResponse(ResponseInterface $response, bool $useFragment = false, int $jsonOptions = 0): ResponseInterface
264 10
    {
265
        $headers = $this->getHttpHeaders();
266 10
267
        $payload = $this->getPayload();
268 10
269 3
        if ($this->redirectUri !== null) {
270 1
            $queryDelimiter = $useFragment === true ? '#' : $this->queryDelimiter;
271
            $this->redirectUri .= (str_contains($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 str_contains() 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

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

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