BasePassportServer   F
last analyzed

Complexity

Total Complexity 61

Size/Duplication

Total Lines 682
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 61
lcom 1
cbo 15
dl 0
loc 682
ccs 270
cts 270
cp 1
rs 3.438
c 0
b 0
f 0

31 Methods

Rating   Name   Duplication   Size   Complexity  
determineClient() 0 6 ?
A __construct() 0 6 1
B createAuthorization() 0 36 6
B postCreateToken() 0 37 7
A createCodeResponse() 0 22 3
A createTokenResponse() 0 24 3
A codeCreateAskResourceOwnerForApprovalResponse() 0 19 1
A codeReadAuthenticationCode() 0 5 1
A codeCreateAccessTokenResponse() 0 17 1
A codeRevokeTokens() 0 10 1
A implicitCreateAskResourceOwnerForApprovalResponse() 0 22 1
A passValidateCredentialsAndCreateAccessTokenResponse() 0 38 4
A passReadDefaultClient() 0 12 2
A clientCreateAccessTokenResponse() 0 23 2
A readTokenByRefreshValue() 0 7 1
A refreshCreateAccessTokenResponse() 0 32 3
A readClientByIdentifier() 0 4 1
A getValidClientAndRedirectUri() 0 21 4
A createBodyTokenResponse() 0 29 3
A createRedirectCodeResponse() 0 17 1
A createRedirectTokenResponse() 0 25 3
A createBodyErrorResponse() 0 14 1
A createRedirectErrorResponse() 0 18 1
A setUpTokenValue() 0 8 1
A setUpTokenValues() 0 8 1
A getIntegration() 0 4 1
A setIntegration() 0 6 1
A getBodyErrorUri() 0 6 1
A logDebug() 0 6 2
A logInfo() 0 6 2
A filterNulls() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like BasePassportServer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BasePassportServer, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
3
namespace Limoncello\Passport;
4
5
/**
6
 * Copyright 2015-2019 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Limoncello\OAuthServer\BaseAuthorizationServer;
22
use Limoncello\OAuthServer\Contracts\AuthorizationCodeInterface;
23
use Limoncello\OAuthServer\Contracts\ClientInterface;
24
use Limoncello\OAuthServer\Contracts\GrantTypes;
25
use Limoncello\OAuthServer\Contracts\ResponseTypes;
26
use Limoncello\OAuthServer\Exceptions\OAuthCodeRedirectException;
27
use Limoncello\OAuthServer\Exceptions\OAuthRedirectException;
28
use Limoncello\OAuthServer\Exceptions\OAuthTokenBodyException;
29
use Limoncello\Passport\Contracts\Entities\TokenInterface;
30
use Limoncello\Passport\Contracts\PassportServerIntegrationInterface;
31
use Limoncello\Passport\Contracts\PassportServerInterface;
32
use Limoncello\Passport\Entities\Token;
33
use Psr\Http\Message\ResponseInterface;
34
use Psr\Http\Message\ServerRequestInterface;
35
use Psr\Log\LoggerAwareInterface as LAI;
36
use Psr\Log\LoggerAwareTrait;
37
use Zend\Diactoros\Response\JsonResponse;
38
use Zend\Diactoros\Response\RedirectResponse;
39
use Zend\Diactoros\Uri;
40
use function array_filter;
41
use function assert;
42
use function in_array;
43
use function is_int;
44
use function is_string;
45
46
/**
47
 * @package Limoncello\Passport
48
 *
49
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
50
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
51
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
52
 */
53
abstract class BasePassportServer extends BaseAuthorizationServer implements PassportServerInterface, LAI
54
{
55
    use LoggerAwareTrait;
56
57
    /**
58
     * @param PassportServerIntegrationInterface $integration
59
     * @param ServerRequestInterface             $request
60
     * @param array                              $parameters
61
     * @param string                             $realm
62
     *
63
     * @return ClientInterface|null
64
     *
65
     * @SuppressWarnings(PHPMD.NPathComplexity)
66
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
67
     */
68
    abstract protected function determineClient(
69
        PassportServerIntegrationInterface $integration,
70
        ServerRequestInterface $request,
71
        array $parameters,
72
        $realm = 'OAuth'
73
    ): ?ClientInterface;
74
75
    /**
76
     * @var PassportServerIntegrationInterface
77
     */
78
    private $integration;
79
80
    /**
81
     * @param PassportServerIntegrationInterface $integration
82
     */
83 17
    public function __construct(PassportServerIntegrationInterface $integration)
84
    {
85 17
        parent::__construct();
86
87 17
        $this->setIntegration($integration);
88
    }
89
90
    /**
91
     * @inheritdoc
92
     *
93
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
94
     */
95 9
    protected function createAuthorization(array $parameters): ResponseInterface
96
    {
97
        try {
98 9
            list($client, $redirectUri) = $this->getValidClientAndRedirectUri(
99 9
                $this->codeGetClientId($parameters),
100 9
                $this->codeGetRedirectUri($parameters)
101
            );
102 9
            if ($client === null || $redirectUri === null) {
103 3
                return $this->getIntegration()->createInvalidClientAndRedirectUriErrorResponse();
104
            }
105
106 6
            $maxStateLength = $this->getMaxStateLength();
107 6
            switch ($responseType = $this->getResponseType($parameters)) {
108 6
                case ResponseTypes::AUTHORIZATION_CODE:
109 3
                    $this->logDebug('Handling code authorization.');
110
                    $response = $this
111 3
                        ->codeAskResourceOwnerForApproval($parameters, $client, $redirectUri, $maxStateLength);
112 3
                    break;
113 3
                case ResponseTypes::IMPLICIT:
114 2
                    $this->logDebug('Handling implicit authorization.');
115
                    $response = $this
116 2
                        ->implicitAskResourceOwnerForApproval($parameters, $client, $redirectUri, $maxStateLength);
117 2
                    break;
118
                default:
119
                    // @link https://tools.ietf.org/html/rfc6749#section-3.1.1 ->
120
                    // @link https://tools.ietf.org/html/rfc6749#section-4.1.2.1
121 1
                    $this->logInfo('Unsupported response type in request.', ['response_type' => $responseType]);
122 1
                    $errorCode = OAuthCodeRedirectException::ERROR_UNSUPPORTED_RESPONSE_TYPE;
123 6
                    throw new OAuthCodeRedirectException($errorCode, $redirectUri);
124
            }
125 1
        } catch (OAuthRedirectException $exception) {
126 1
            $response = $this->createRedirectErrorResponse($exception);
127
        }
128
129 6
        return $response;
130
    }
131
132
    /**
133
     * @inheritdoc
134
     */
135 8
    public function postCreateToken(ServerRequestInterface $request): ResponseInterface
136
    {
137
        try {
138 8
            $parameters       = $request->getParsedBody();
139 8
            $determinedClient = $this->determineClient($this->getIntegration(), $request, $parameters);
0 ignored issues
show
Bug introduced by
It seems like $parameters defined by $request->getParsedBody() on line 138 can also be of type null or object; however, Limoncello\Passport\Base...rver::determineClient() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
140
141 8
            switch ($grantType = $this->getGrantType($parameters)) {
0 ignored issues
show
Documentation introduced by
$parameters is of type null|array|object, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
142 8
                case GrantTypes::AUTHORIZATION_CODE:
143 1
                    $this->logDebug('Handling code grant.');
144 1
                    $response = $this->codeIssueToken($parameters, $determinedClient);
0 ignored issues
show
Documentation introduced by
$parameters is of type null|array|object, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
145 1
                    break;
146 7
                case GrantTypes::RESOURCE_OWNER_PASSWORD_CREDENTIALS:
147 4
                    $this->logDebug('Handling resource owner password grant.');
148 4
                    $response = $this->passIssueToken($parameters, $determinedClient);
0 ignored issues
show
Documentation introduced by
$parameters is of type null|array|object, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
149 3
                    break;
150 5
                case GrantTypes::CLIENT_CREDENTIALS:
151 2
                    $this->logDebug('Handling client credentials grant.');
152 2
                    if ($determinedClient === null) {
153 1
                        $this->logInfo('Client identification failed.');
154 1
                        throw new OAuthTokenBodyException(OAuthTokenBodyException::ERROR_INVALID_CLIENT);
155
                    }
156 1
                    $response = $this->clientIssueToken($parameters, $determinedClient);
0 ignored issues
show
Documentation introduced by
$parameters is of type null|array|object, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
157 1
                    break;
158 3
                case GrantTypes::REFRESH_TOKEN:
159 2
                    $this->logDebug('Handling refresh token grant.');
160 2
                    $response = $this->refreshIssueToken($parameters, $determinedClient);
0 ignored issues
show
Documentation introduced by
$parameters is of type null|array|object, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
161 2
                    break;
162
                default:
163 1
                    $this->logInfo('Unknown grant type.', ['grant_type' => $grantType]);
164 6
                    throw new OAuthTokenBodyException(OAuthTokenBodyException::ERROR_UNSUPPORTED_GRANT_TYPE);
165
            }
166 4
        } catch (OAuthTokenBodyException $exception) {
167 4
            $response = $this->createBodyErrorResponse($exception);
168
        }
169
170 8
        return $response;
171
    }
172
173
    /**
174
     * @inheritdoc
175
     */
176 2
    public function createCodeResponse(TokenInterface $code, string $state = null): ResponseInterface
177
    {
178 2
        $client = $this->readClientByIdentifier($code->getClientIdentifier());
179 2
        if ($code->getRedirectUriString() === null ||
180 2
            in_array($code->getRedirectUriString(), $client->getRedirectUriStrings()) === false
181
        ) {
182 1
            $this->logInfo(
183 1
                'Code has invalid redirect URI which do not match any redirect URI for its client.',
184 1
                ['id' => $code->getIdentifier()]
185
            );
186 1
            return $this->getIntegration()->createInvalidClientAndRedirectUriErrorResponse();
187
        }
188
189 1
        $code->setCode($this->getIntegration()->generateCodeValue($code));
190
191 1
        $tokenRepo   = $this->getIntegration()->getTokenRepository();
192 1
        $createdCode = $tokenRepo->createCode($code);
193
194 1
        $response = $this->createRedirectCodeResponse($createdCode, $state);
195
196 1
        return $response;
197
    }
198
199
    /**
200
     * @inheritdoc
201
     */
202 2
    public function createTokenResponse(TokenInterface $token, string $state = null): ResponseInterface
203
    {
204 2
        $client = $this->readClientByIdentifier($token->getClientIdentifier());
205 2
        if ($token->getRedirectUriString() === null ||
206 2
            in_array($token->getRedirectUriString(), $client->getRedirectUriStrings()) === false
207
        ) {
208 1
            $this->logInfo(
209 1
                'Token has invalid redirect URI which do not match any redirect URI for its client.',
210 1
                ['id' => $token->getIdentifier()]
211
            );
212 1
            return $this->getIntegration()->createInvalidClientAndRedirectUriErrorResponse();
213
        }
214
215 1
        list($tokenValue, $tokenType, $tokenExpiresIn) = $this->getIntegration()->generateTokenValues($token);
216
217
        // refresh value must be null by the spec
218 1
        $refreshValue = null;
219 1
        $token->setValue($tokenValue)->setType($tokenType)->setRefreshValue($refreshValue);
220 1
        $savedToken = $this->getIntegration()->getTokenRepository()->createToken($token);
221
222 1
        $response = $this->createRedirectTokenResponse($savedToken, $tokenExpiresIn, $state);
223
224 1
        return $response;
225
    }
226
227
    /** @noinspection PhpTooManyParametersInspection
228
     * @inheritdoc
229
     *
230
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
231
     */
232 3
    public function codeCreateAskResourceOwnerForApprovalResponse(
233
        ClientInterface $client,
234
        string $redirectUri = null,
235
        bool $isScopeModified = false,
236
        array $scopeList = null,
237
        string $state = null,
238
        array $extraParameters = []
239
    ): ResponseInterface {
240 3
        $this->logDebug('Asking resource owner for scope approval (code grant).');
241 3
        return $this->getIntegration()->createAskResourceOwnerForApprovalResponse(
242 3
            ResponseTypes::AUTHORIZATION_CODE,
243 3
            $client,
244 3
            $redirectUri,
245 3
            $isScopeModified,
246 3
            $scopeList,
0 ignored issues
show
Bug introduced by
It seems like $scopeList defined by parameter $scopeList on line 236 can also be of type array; however, Limoncello\Passport\Cont...erForApprovalResponse() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
247 3
            $state,
248 3
            $extraParameters
249
        );
250
    }
251
252
    /**
253
     * @inheritdoc
254
     */
255 1
    public function codeReadAuthenticationCode(string $code): ?AuthorizationCodeInterface
256
    {
257 1
        return $this->getIntegration()->getTokenRepository()
258 1
            ->readByCode($code, $this->getIntegration()->getCodeExpirationPeriod());
259
    }
260
261
    /**
262
     * @inheritdoc
263
     */
264 1
    public function codeCreateAccessTokenResponse(
265
        AuthorizationCodeInterface $code,
266
        array $extraParameters = []
267
    ): ResponseInterface {
268
        /** @var Token $code */
269 1
        assert($code instanceof Token);
270 1
        $updatedToken = clone $code;
271
272 1
        $tokenExpiresIn = $this->setUpTokenValues($updatedToken);
273 1
        $this->getIntegration()->getTokenRepository()->assignValuesToCode($updatedToken, $tokenExpiresIn);
274
275 1
        $response = $this->createBodyTokenResponse($updatedToken, $tokenExpiresIn);
276
277 1
        $this->logInfo('Authorization code successfully exchanged for a token (code grant).');
278
279 1
        return $response;
280
    }
281
282
    /**
283
     * @inheritdoc
284
     */
285 1
    public function codeRevokeTokens(AuthorizationCodeInterface $code): void
286
    {
287 1
        assert($code instanceof TokenInterface);
288
289
        /** @var TokenInterface $code */
290
291 1
        $identifier = $code->getIdentifier();
292 1
        $this->logInfo('Revoking token.', ['token_id' => $identifier]);
293 1
        $this->getIntegration()->getTokenRepository()->disable($identifier);
294
    }
295
296
    /** @noinspection PhpTooManyParametersInspection
297
     * @inheritdoc
298
     *
299
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
300
     */
301 2
    public function implicitCreateAskResourceOwnerForApprovalResponse(
302
        ClientInterface $client,
303
        string $redirectUri = null,
304
        bool $isScopeModified = false,
305
        array $scopeList = null,
306
        string $state = null,
307
        array $extraParameters = []
308
    ): ResponseInterface {
309 2
        $response = $this->getIntegration()->createAskResourceOwnerForApprovalResponse(
310 2
            ResponseTypes::IMPLICIT,
311 2
            $client,
312 2
            $redirectUri,
313 2
            $isScopeModified,
314 2
            $scopeList,
0 ignored issues
show
Bug introduced by
It seems like $scopeList defined by parameter $scopeList on line 305 can also be of type array; however, Limoncello\Passport\Cont...erForApprovalResponse() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
315 2
            $state,
316 2
            $extraParameters
317
        );
318
319 2
        $this->logInfo('Created response asking resource owner for scope approval (implicit grant).');
320
321 2
        return $response;
322
    }
323
324
    /** @noinspection PhpTooManyParametersInspection
325
     * @inheritdoc
326
     *
327
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
328
     */
329 4
    public function passValidateCredentialsAndCreateAccessTokenResponse(
330
        string $userName,
331
        string $password,
332
        ClientInterface $client = null,
333
        bool $isScopeModified = false,
334
        array $scope = null,
335
        array $extraParameters = []
336
    ): ResponseInterface {
337 4
        assert($client !== null);
338
339 4
        if (($userIdentifier = $this->getIntegration()->validateUserId($userName, $password)) === null) {
340 1
            $this->logDebug('User not found with provided username and password.', ['username' => $userName]);
341 1
            throw new OAuthTokenBodyException(OAuthTokenBodyException::ERROR_INVALID_GRANT);
342
        }
343 3
        assert(is_int($userIdentifier) === true);
344 3
        $this->logInfo('User authenticated with provided username and password.', ['username' => $userName]);
345
346 3
        $changedScopeOrNull = $this->getIntegration()->verifyAllowedUserScope($userIdentifier, $scope);
347 3
        if ($changedScopeOrNull !== null) {
348 3
            assert(is_array($changedScopeOrNull));
349 3
            $isScopeModified = true;
350 3
            $scope           = $changedScopeOrNull;
351
        }
352
353 3
        $unsavedToken = $this->getIntegration()->createTokenInstance();
354
        $unsavedToken
355 3
            ->setClientIdentifier($client->getIdentifier())
356 3
            ->setScopeIdentifiers($scope)
0 ignored issues
show
Documentation introduced by
$scope is of type null|array, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
357 3
            ->setUserIdentifier($userIdentifier);
358 3
        $isScopeModified === true ? $unsavedToken->setScopeModified() : $unsavedToken->setScopeUnmodified();
359
360 3
        $tokenExpiresIn = $this->setUpTokenValues($unsavedToken);
361 3
        $savedToken     = $this->getIntegration()->getTokenRepository()->createToken($unsavedToken);
362
363 3
        $response = $this->createBodyTokenResponse($savedToken, $tokenExpiresIn);
364
365 3
        return $response;
366
    }
367
368
    /**
369
     * @inheritdoc
370
     */
371 4
    public function passReadDefaultClient(): ClientInterface
372
    {
373 4
        $defaultClientId = $this->getIntegration()->getDefaultClientIdentifier();
374
375 4
        assert(is_string($defaultClientId) === true && empty($defaultClientId) === false);
376
377 4
        $defaultClient   = $this->readClientByIdentifier($defaultClientId);
378
379 4
        assert($defaultClient !== null);
380
381 4
        return $defaultClient;
382
    }
383
384
    /**
385
     * @inheritdoc
386
     */
387 1
    public function clientCreateAccessTokenResponse(
388
        ClientInterface $client,
389
        bool $isScopeModified,
390
        array $scope = null,
391
        array $extraParameters = []
392
    ): ResponseInterface {
393 1
        $this->logDebug('Prepare token for client.');
394 1
        assert($client !== null);
395
396 1
        $unsavedToken = $this->getIntegration()->createTokenInstance();
397
        $unsavedToken
398 1
            ->setClientIdentifier($client->getIdentifier())
399 1
            ->setScopeIdentifiers($scope);
0 ignored issues
show
Documentation introduced by
$scope is of type null|array, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
400 1
        $isScopeModified === true ? $unsavedToken->setScopeModified() : $unsavedToken->setScopeUnmodified();
401
402
403 1
        $tokenExpiresIn = $this->setUpTokenValue($unsavedToken);
404 1
        $savedToken     = $this->getIntegration()->getTokenRepository()->createToken($unsavedToken);
405
406 1
        $response = $this->createBodyTokenResponse($savedToken, $tokenExpiresIn);
407
408 1
        return $response;
409
    }
410
411
    /**
412
     * @inheritdoc
413
     *
414
     * @return TokenInterface|null
415
     */
416 2
    public function readTokenByRefreshValue(string $refreshValue): ?\Limoncello\OAuthServer\Contracts\TokenInterface
417
    {
418 2
        return $this->getIntegration()->getTokenRepository()->readByRefresh(
419 2
            $refreshValue,
420 2
            $this->getIntegration()->getTokenExpirationPeriod()
421
        );
422
    }
423
424
    /**
425
     * @inheritdoc
426
     *
427
     * @SuppressWarnings(PHPMD.ElseExpression)
428
     */
429 2
    public function refreshCreateAccessTokenResponse(
430
        ClientInterface $client,
431
        \Limoncello\OAuthServer\Contracts\TokenInterface $token,
432
        bool $isScopeModified,
433
        array $scope = null,
434
        array $extraParameters = []
435
    ): ResponseInterface {
436 2
        $this->logDebug('Prepare refresh token.');
437
438
        /** @var TokenInterface $token */
439 2
        assert($token instanceof TokenInterface);
440
441 2
        $updatedToken   = clone $token;
442 2
        $tokenExpiresIn = $this->getIntegration()->isRenewRefreshValue() === false ?
443 2
            $this->setUpTokenValue($updatedToken) : $this->setUpTokenValues($updatedToken);
444
445 2
        $tokenRepo = $this->getIntegration()->getTokenRepository();
446 2
        if ($isScopeModified === false) {
447 1
            $tokenRepo->updateValues($updatedToken);
448
        } else {
449 1
            assert(is_array($scope));
450
            $tokenRepo->inTransaction(function () use ($tokenRepo, $updatedToken, $scope) {
451 1
                $tokenRepo->updateValues($updatedToken);
452 1
                $tokenRepo->unbindScopes($updatedToken->getIdentifier());
453 1
                $tokenRepo->bindScopeIdentifiers($updatedToken->getIdentifier(), $scope);
454 1
            });
455 1
            $updatedToken->setScopeModified()->setScopeIdentifiers($scope);
456
        }
457 2
        $response = $this->createBodyTokenResponse($updatedToken, $tokenExpiresIn);
458
459 2
        return $response;
460
    }
461
462
    /**
463
     * @inheritdoc
464
     */
465 13
    public function readClientByIdentifier(string $clientIdentifier): ?ClientInterface
466
    {
467 13
        return $this->getIntegration()->getClientRepository()->read($clientIdentifier);
468
    }
469
470
    /**
471
     * @param string|null $clientId
472
     * @param string|null $redirectFromQuery
473
     *
474
     * @return array [client|null, uri|null]
475
     *
476
     * @SuppressWarnings(PHPMD.ElseExpression)
477
     */
478 9
    protected function getValidClientAndRedirectUri(string $clientId = null, string $redirectFromQuery = null): array
479
    {
480 9
        $client           = null;
481 9
        $validRedirectUri = null;
482
483 9
        if ($clientId !== null &&
484 9
            ($client = $this->readClientByIdentifier($clientId)) !== null
485
        ) {
486 7
            $validRedirectUri = $this->selectValidRedirectUri($client, $redirectFromQuery);
487 7
            if ($validRedirectUri === null) {
488 1
                $this->logDebug(
489 1
                    'Choosing valid redirect URI for client failed.',
490 7
                    ['client_id' => $clientId, 'redirect_uri_from_query' => $redirectFromQuery]
491
                );
492
            }
493
        } else {
494 2
            $this->logDebug('Client is not found.', ['client_id' => $clientId]);
495
        }
496
497 9
        return [$client, $validRedirectUri];
498
    }
499
500
    /**
501
     * @param TokenInterface $token
502
     * @param int            $tokenExpiresIn
503
     *
504
     * @return ResponseInterface
505
     */
506 5
    protected function createBodyTokenResponse(TokenInterface $token, int $tokenExpiresIn): ResponseInterface
507
    {
508 5
        $this->logDebug('Sending token as JSON response.');
509
510 5
        $scopeList  = $token->isScopeModified() === false || empty($token->getScopeIdentifiers()) === true ?
511 5
            null : $token->getScopeList();
512
513
        // for access token format @link https://tools.ietf.org/html/rfc6749#section-5.1
514 5
        $parameters = $this->filterNulls([
515 5
            'access_token'  => $token->getValue(),
516 5
            'token_type'    => $token->getType(),
517 5
            'expires_in'    => $tokenExpiresIn,
518 5
            'refresh_token' => $token->getRefreshValue(),
519 5
            'scope'         => $scopeList,
520
        ]);
521
522
        // extra parameters
523
        // https://tools.ietf.org/html/rfc6749#section-4.1.4
524
        // https://tools.ietf.org/html/rfc6749#section-4.3.3
525
        // https://tools.ietf.org/html/rfc6749#section-4.4.3
526 5
        $extraParameters = $this->getIntegration()->getBodyTokenExtraParameters($token);
527
528 5
        $response = new JsonResponse($parameters + $extraParameters, 200, [
529 5
            'Cache-Control' => 'no-store',
530
            'Pragma'        => 'no-cache'
531
        ]);
532
533 5
        return $response;
534
    }
535
536
    /**
537
     * @param TokenInterface $code
538
     * @param string|null    $state
539
     *
540
     * @return ResponseInterface
541
     */
542 1
    protected function createRedirectCodeResponse(TokenInterface $code, string $state = null): ResponseInterface
543
    {
544 1
        $this->logDebug('Sending code as redirect response.');
545
546
        // for access token format @link https://tools.ietf.org/html/rfc6749#section-4.1.3
547 1
        $parameters = $this->filterNulls([
548 1
            'code'  => $code->getCode(),
549 1
            'state' => $state,
550
        ]);
551
552 1
        $redirectUri = $code->getRedirectUriString();
553 1
        $query       = $this->encodeAsXWwwFormUrlencoded($parameters);
554
555 1
        $response = new RedirectResponse((new Uri($redirectUri))->withQuery($query));
556
557 1
        return $response;
558
    }
559
560
    /**
561
     * @param TokenInterface $token
562
     * @param int            $tokenExpiresIn
563
     * @param string|null    $state
564
     *
565
     * @return ResponseInterface
566
     */
567 1
    protected function createRedirectTokenResponse(
568
        TokenInterface $token,
569
        int $tokenExpiresIn,
570
        string $state = null
571
    ): ResponseInterface {
572 1
        $this->logDebug('Sending token as redirect response.');
573
574 1
        $scopeList  = $token->isScopeModified() === false || empty($token->getScopeIdentifiers()) === true ?
575 1
            null : $token->getScopeList();
576
577
        // for access token format @link https://tools.ietf.org/html/rfc6749#section-5.1
578 1
        $parameters = $this->filterNulls([
579 1
            'access_token' => $token->getValue(),
580 1
            'token_type'   => $token->getType(),
581 1
            'expires_in'   => $tokenExpiresIn,
582 1
            'scope'        => $scopeList,
583 1
            'state'        => $state,
584
        ]);
585
586 1
        $fragment = $this->encodeAsXWwwFormUrlencoded($parameters);
587
588 1
        $response = new RedirectResponse((new Uri($token->getRedirectUriString()))->withFragment($fragment));
589
590 1
        return $response;
591
    }
592
593
    /**
594
     * @param OAuthTokenBodyException $exception
595
     *
596
     * @return ResponseInterface
597
     */
598 4
    protected function createBodyErrorResponse(OAuthTokenBodyException $exception): ResponseInterface
599
    {
600 4
        $data = $this->filterNulls([
601 4
            'error'             => $exception->getErrorCode(),
602 4
            'error_description' => $exception->getErrorDescription(),
603 4
            'error_uri'         => $this->getBodyErrorUri($exception),
604
        ]);
605
606 4
        $this->logDebug('Sending OAuth error as JSON response.', $data);
607
608 4
        $response = new JsonResponse($data, $exception->getHttpCode(), $exception->getHttpHeaders());
609
610 4
        return $response;
611
    }
612
613
    /**
614
     * @param OAuthRedirectException $exception
615
     *
616
     * @return ResponseInterface
617
     */
618 1
    protected function createRedirectErrorResponse(OAuthRedirectException $exception): ResponseInterface
619
    {
620 1
        $parameters = $this->filterNulls([
621 1
            'error'             => $exception->getErrorCode(),
622 1
            'error_description' => $exception->getErrorDescription(),
623 1
            'error_uri'         => $exception->getErrorUri(),
624 1
            'state'             => $exception->getState(),
625
        ]);
626
627 1
        $this->logDebug('Sending OAuth error via redirect.', $parameters);
628
629 1
        $fragment = $this->encodeAsXWwwFormUrlencoded($parameters);
630 1
        $uri      = (new Uri($exception->getRedirectUri()))->withFragment($fragment);
631
632 1
        $response = new RedirectResponse($uri, 302, $exception->getHttpHeaders());
633
634 1
        return $response;
635
    }
636
637
    /**
638
     * @param TokenInterface $token
639
     *
640
     * @return int
641
     */
642 1
    protected function setUpTokenValue(TokenInterface $token): int
643
    {
644
        list($tokenValue, $tokenType, $tokenExpiresIn) =
645 1
            $this->getIntegration()->generateTokenValues($token);
646 1
        $token->setValue($tokenValue)->setType($tokenType);
647
648 1
        return $tokenExpiresIn;
649
    }
650
651
    /**
652
     * @param TokenInterface $token
653
     *
654
     * @return int
655
     */
656 4
    protected function setUpTokenValues(TokenInterface $token): int
657
    {
658
        list($tokenValue, $tokenType, $tokenExpiresIn, $refreshValue) =
659 4
            $this->getIntegration()->generateTokenValues($token);
660 4
        $token->setValue($tokenValue)->setType($tokenType)->setRefreshValue($refreshValue);
661
662 4
        return $tokenExpiresIn;
663
    }
664
665
    /**
666
     * @return PassportServerIntegrationInterface
667
     */
668 16
    protected function getIntegration(): PassportServerIntegrationInterface
669
    {
670 16
        return $this->integration;
671
    }
672
673
    /**
674
     * @param PassportServerIntegrationInterface $integration
675
     *
676
     * @return self
677
     */
678 17
    protected function setIntegration(PassportServerIntegrationInterface $integration): self
679
    {
680 17
        $this->integration = $integration;
681
682 17
        return $this;
683
    }
684
685
    /**
686
     * @param OAuthTokenBodyException $exception
687
     *
688
     * @return null|string
689
     */
690 4
    protected function getBodyErrorUri(OAuthTokenBodyException $exception)
691
    {
692 4
        assert($exception !== null);
693
694 4
        return null;
695
    }
696
697
    /**
698
     * @param string $message
699
     * @param array  $context
700
     *
701
     * @return void
702
     */
703 16
    protected function logDebug(string $message, array $context = []): void
704
    {
705 16
        if ($this->logger !== null) {
706 16
            $this->logger->debug($message, $context);
707
        }
708
    }
709
710
    /**
711
     * @param string $message
712
     * @param array  $context
713
     *
714
     * @return void
715
     */
716 10
    protected function logInfo(string $message, array $context = []): void
717
    {
718 10
        if ($this->logger !== null) {
719 10
            $this->logger->info($message, $context);
720
        }
721
    }
722
723
    /**
724
     * @param array $array
725
     *
726
     * @return array
727
     */
728 10
    private function filterNulls(array $array): array
729
    {
730
        return array_filter($array, function ($value) {
731 10
            return $value !== null;
732 10
        });
733
    }
734
}
735