Completed
Push — master ( 739d01...5ed129 )
by Neomerx
02:14
created

BasePassportServer::logDebug()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 2
crap 2
1
<?php namespace Limoncello\Passport;
2
3
/**
4
 * Copyright 2015-2017 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use Limoncello\OAuthServer\BaseAuthorizationServer;
20
use Limoncello\OAuthServer\Contracts\AuthorizationCodeInterface;
21
use Limoncello\OAuthServer\Contracts\ClientInterface;
22
use Limoncello\OAuthServer\Contracts\GrantTypes;
23
use Limoncello\OAuthServer\Contracts\ResponseTypes;
24
use Limoncello\OAuthServer\Exceptions\OAuthCodeRedirectException;
25
use Limoncello\OAuthServer\Exceptions\OAuthRedirectException;
26
use Limoncello\OAuthServer\Exceptions\OAuthTokenBodyException;
27
use Limoncello\Passport\Contracts\Entities\TokenInterface;
28
use Limoncello\Passport\Contracts\PassportServerIntegrationInterface;
29
use Limoncello\Passport\Contracts\PassportServerInterface;
30
use Limoncello\Passport\Entities\Token;
31
use Psr\Http\Message\ResponseInterface;
32
use Psr\Http\Message\ServerRequestInterface;
33
use Psr\Log\LoggerAwareInterface as LAI;
34
use Psr\Log\LoggerAwareTrait;
35
use Zend\Diactoros\Response\JsonResponse;
36
use Zend\Diactoros\Response\RedirectResponse;
37
use Zend\Diactoros\Uri;
38
39
/**
40
 * @package Limoncello\Passport
41
 *
42
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
43
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
44
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
45
 */
46
abstract class BasePassportServer extends BaseAuthorizationServer implements PassportServerInterface, LAI
47
{
48
    use LoggerAwareTrait;
49
50
    /**
51
     * @param PassportServerIntegrationInterface $integration
52
     * @param ServerRequestInterface             $request
53
     * @param array                              $parameters
54
     * @param string                             $realm
55
     *
56
     * @return ClientInterface|null
57
     *
58
     * @SuppressWarnings(PHPMD.NPathComplexity)
59
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
60
     */
61
    abstract protected function determineClient(
62
        PassportServerIntegrationInterface $integration,
63
        ServerRequestInterface $request,
64
        array $parameters,
65
        $realm = 'OAuth'
66
    ): ?ClientInterface;
67
68
    /**
69
     * @var PassportServerIntegrationInterface
70
     */
71
    private $integration;
72
73
    /**
74
     * @param PassportServerIntegrationInterface $integration
75
     */
76 17
    public function __construct(PassportServerIntegrationInterface $integration)
77
    {
78 17
        parent::__construct();
79
80 17
        $this->setIntegration($integration);
81
    }
82
83
    /**
84
     * @inheritdoc
85
     *
86
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
87
     */
88 9
    protected function createAuthorization(array $parameters): ResponseInterface
89
    {
90
        try {
91 9
            list($client, $redirectUri) = $this->getValidClientAndRedirectUri(
92 9
                $this->codeGetClientId($parameters),
93 9
                $this->codeGetRedirectUri($parameters)
94
            );
95 9
            if ($client === null || $redirectUri === null) {
96 3
                return $this->getIntegration()->createInvalidClientAndRedirectUriErrorResponse();
97
            }
98
99 6
            $maxStateLength = $this->getMaxStateLength();
100 6
            switch ($responseType = $this->getResponseType($parameters)) {
101 6
                case ResponseTypes::AUTHORIZATION_CODE:
102 3
                    $this->logDebug('Handling code authorization.');
103
                    $response = $this
104 3
                        ->codeAskResourceOwnerForApproval($parameters, $client, $redirectUri, $maxStateLength);
105 3
                    break;
106 3
                case ResponseTypes::IMPLICIT:
107 2
                    $this->logDebug('Handling implicit authorization.');
108
                    $response = $this
109 2
                        ->implicitAskResourceOwnerForApproval($parameters, $client, $redirectUri, $maxStateLength);
110 2
                    break;
111
                default:
112
                    // @link https://tools.ietf.org/html/rfc6749#section-3.1.1 ->
113
                    // @link https://tools.ietf.org/html/rfc6749#section-4.1.2.1
114 1
                    $this->logInfo('Unsupported response type in request.', ['response_type' => $responseType]);
115 1
                    $errorCode = OAuthCodeRedirectException::ERROR_UNSUPPORTED_RESPONSE_TYPE;
116 6
                    throw new OAuthCodeRedirectException($errorCode, $redirectUri);
117
            }
118 1
        } catch (OAuthRedirectException $exception) {
119 1
            $response = $this->createRedirectErrorResponse($exception);
120
        }
121
122 6
        return $response;
123
    }
124
125
    /**
126
     * @inheritdoc
127
     */
128 8
    public function postCreateToken(ServerRequestInterface $request): ResponseInterface
129
    {
130
        try {
131 8
            $parameters       = $request->getParsedBody();
132 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 131 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...
133
134 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...
135 8
                case GrantTypes::AUTHORIZATION_CODE:
136 1
                    $this->logDebug('Handling code grant.');
137 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...
138 1
                    break;
139 7
                case GrantTypes::RESOURCE_OWNER_PASSWORD_CREDENTIALS:
140 4
                    $this->logDebug('Handling resource owner password grant.');
141 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...
142 3
                    break;
143 5
                case GrantTypes::CLIENT_CREDENTIALS:
144 2
                    $this->logDebug('Handling client credentials grant.');
145 2
                    if ($determinedClient === null) {
146 1
                        $this->logInfo('Client identification failed.');
147 1
                        throw new OAuthTokenBodyException(OAuthTokenBodyException::ERROR_INVALID_CLIENT);
148
                    }
149 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...
150 1
                    break;
151 3
                case GrantTypes::REFRESH_TOKEN:
152 2
                    $this->logDebug('Handling refresh token grant.');
153 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...
154 2
                    break;
155
                default:
156 1
                    $this->logInfo('Unknown grant type.', ['grant_type' => $grantType]);
157 6
                    throw new OAuthTokenBodyException(OAuthTokenBodyException::ERROR_UNSUPPORTED_GRANT_TYPE);
158
            }
159 4
        } catch (OAuthTokenBodyException $exception) {
160 4
            $response = $this->createBodyErrorResponse($exception);
161
        }
162
163 8
        return $response;
164
    }
165
166
    /**
167
     * @inheritdoc
168
     */
169 2
    public function createCodeResponse(TokenInterface $code, string $state = null): ResponseInterface
170
    {
171 2
        $client = $this->readClientByIdentifier($code->getClientIdentifier());
172 2
        if ($code->getRedirectUriString() === null ||
173 2
            in_array($code->getRedirectUriString(), $client->getRedirectUriStrings()) === false
174
        ) {
175 1
            $this->logInfo(
176 1
                'Code has invalid redirect URI which do not match any redirect URI for its client.',
177 1
                ['id' => $code->getIdentifier()]
178
            );
179 1
            return $this->getIntegration()->createInvalidClientAndRedirectUriErrorResponse();
180
        }
181
182 1
        $code->setCode($this->getIntegration()->generateCodeValue($code));
183
184 1
        $tokenRepo   = $this->getIntegration()->getTokenRepository();
185 1
        $createdCode = $tokenRepo->createCode($code);
186
187 1
        $response = $this->createRedirectCodeResponse($createdCode, $state);
188
189 1
        return $response;
190
    }
191
192
    /**
193
     * @inheritdoc
194
     */
195 2
    public function createTokenResponse(TokenInterface $token, string $state = null): ResponseInterface
196
    {
197 2
        $client = $this->readClientByIdentifier($token->getClientIdentifier());
198 2
        if ($token->getRedirectUriString() === null ||
199 2
            in_array($token->getRedirectUriString(), $client->getRedirectUriStrings()) === false
200
        ) {
201 1
            $this->logInfo(
202 1
                'Token has invalid redirect URI which do not match any redirect URI for its client.',
203 1
                ['id' => $token->getIdentifier()]
204
            );
205 1
            return $this->getIntegration()->createInvalidClientAndRedirectUriErrorResponse();
206
        }
207
208 1
        list($tokenValue, $tokenType, $tokenExpiresIn) = $this->getIntegration()->generateTokenValues($token);
209
210
        // refresh value must be null by the spec
211 1
        $refreshValue = null;
212 1
        $token->setValue($tokenValue)->setType($tokenType)->setRefreshValue($refreshValue);
213 1
        $savedToken = $this->getIntegration()->getTokenRepository()->createToken($token);
214
215 1
        $response = $this->createRedirectTokenResponse($savedToken, $tokenExpiresIn, $state);
216
217 1
        return $response;
218
    }
219
220
    /** @noinspection PhpTooManyParametersInspection
221
     * @inheritdoc
222
     *
223
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
224
     */
225 3
    public function codeCreateAskResourceOwnerForApprovalResponse(
226
        ClientInterface $client,
227
        string $redirectUri = null,
228
        bool $isScopeModified = false,
229
        array $scopeList = null,
230
        string $state = null,
231
        array $extraParameters = []
232
    ): ResponseInterface {
233 3
        $this->logDebug('Asking resource owner for scope approval (code grant).');
234 3
        return $this->getIntegration()->createAskResourceOwnerForApprovalResponse(
235 3
            ResponseTypes::AUTHORIZATION_CODE,
236 3
            $client,
237 3
            $redirectUri,
238 3
            $isScopeModified,
239 3
            $scopeList,
0 ignored issues
show
Bug introduced by
It seems like $scopeList defined by parameter $scopeList on line 229 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...
240 3
            $state,
241 3
            $extraParameters
242
        );
243
    }
244
245
    /**
246
     * @inheritdoc
247
     */
248 1
    public function codeReadAuthenticationCode(string $code): ?AuthorizationCodeInterface
249
    {
250 1
        return $this->getIntegration()->getTokenRepository()
251 1
            ->readByCode($code, $this->getIntegration()->getCodeExpirationPeriod());
252
    }
253
254
    /**
255
     * @inheritdoc
256
     */
257 1
    public function codeCreateAccessTokenResponse(
258
        AuthorizationCodeInterface $code,
259
        array $extraParameters = []
260
    ): ResponseInterface {
261
        /** @var Token $code */
262 1
        assert($code instanceof Token);
263 1
        $updatedToken = clone $code;
264
265 1
        $tokenExpiresIn = $this->setUpTokenValues($updatedToken);
266 1
        $this->getIntegration()->getTokenRepository()->assignValuesToCode($updatedToken, $tokenExpiresIn);
267
268 1
        $response = $this->createBodyTokenResponse($updatedToken, $tokenExpiresIn);
269
270 1
        $this->logInfo('Authorization code successfully exchanged for a token (code grant).');
271
272 1
        return $response;
273
    }
274
275
    /**
276
     * @inheritdoc
277
     */
278 1
    public function codeRevokeTokens(AuthorizationCodeInterface $code): void
279
    {
280 1
        assert($code instanceof TokenInterface);
281
282
        /** @var TokenInterface $code */
283
284 1
        $identifier = $code->getIdentifier();
285 1
        $this->logInfo('Revoking token.', ['token_id' => $identifier]);
286 1
        $this->getIntegration()->getTokenRepository()->disable($identifier);
287
    }
288
289
    /** @noinspection PhpTooManyParametersInspection
290
     * @inheritdoc
291
     *
292
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
293
     */
294 2
    public function implicitCreateAskResourceOwnerForApprovalResponse(
295
        ClientInterface $client,
296
        string $redirectUri = null,
297
        bool $isScopeModified = false,
298
        array $scopeList = null,
299
        string $state = null,
300
        array $extraParameters = []
301
    ): ResponseInterface {
302 2
        $response = $this->getIntegration()->createAskResourceOwnerForApprovalResponse(
303 2
            ResponseTypes::IMPLICIT,
304 2
            $client,
305 2
            $redirectUri,
306 2
            $isScopeModified,
307 2
            $scopeList,
0 ignored issues
show
Bug introduced by
It seems like $scopeList defined by parameter $scopeList on line 298 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...
308 2
            $state,
309 2
            $extraParameters
310
        );
311
312 2
        $this->logInfo('Created response asking resource owner for scope approval (implicit grant).');
313
314 2
        return $response;
315
    }
316
317
    /** @noinspection PhpTooManyParametersInspection
318
     * @inheritdoc
319
     *
320
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
321
     */
322 4
    public function passValidateCredentialsAndCreateAccessTokenResponse(
323
        string $userName,
324
        string $password,
325
        ClientInterface $client = null,
326
        bool $isScopeModified = false,
327
        array $scope = null,
328
        array $extraParameters = []
329
    ): ResponseInterface {
330 4
        assert($client !== null);
331
332 4
        if (($userIdentifier = $this->getIntegration()->validateUserId($userName, $password)) === null) {
333 1
            $this->logDebug('User not found with provided username and password.', ['username' => $userName]);
334 1
            throw new OAuthTokenBodyException(OAuthTokenBodyException::ERROR_INVALID_GRANT);
335
        }
336 3
        assert(is_int($userIdentifier) === true);
337 3
        $this->logInfo('User authenticated with provided username and password.', ['username' => $userName]);
338
339 3
        $changedScopeOrNull = $this->getIntegration()->verifyAllowedUserScope($userIdentifier, $scope);
340 3
        if ($changedScopeOrNull !== null) {
341 3
            assert(is_array($changedScopeOrNull));
342 3
            $isScopeModified = true;
343 3
            $scope           = $changedScopeOrNull;
344
        }
345
346 3
        $unsavedToken = $this->getIntegration()->createTokenInstance();
347
        $unsavedToken
348 3
            ->setClientIdentifier($client->getIdentifier())
349 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...
350 3
            ->setUserIdentifier($userIdentifier);
351 3
        $isScopeModified === true ? $unsavedToken->setScopeModified() : $unsavedToken->setScopeUnmodified();
352
353
354 3
        $tokenExpiresIn = $this->setUpTokenValues($unsavedToken);
355 3
        $savedToken     = $this->getIntegration()->getTokenRepository()->createToken($unsavedToken);
356
357 3
        $response = $this->createBodyTokenResponse($savedToken, $tokenExpiresIn);
358
359 3
        return $response;
360
    }
361
362
    /**
363
     * @inheritdoc
364
     */
365 4
    public function passReadDefaultClient(): ClientInterface
366
    {
367 4
        $defaultClientId = $this->getIntegration()->getDefaultClientIdentifier();
368
369 4
        assert(is_string($defaultClientId) === true && empty($defaultClientId) === false);
370
371 4
        $defaultClient   = $this->readClientByIdentifier($defaultClientId);
372
373 4
        assert($defaultClient !== null);
374
375 4
        return $defaultClient;
376
    }
377
378
    /**
379
     * @inheritdoc
380
     */
381 1
    public function clientCreateAccessTokenResponse(
382
        ClientInterface $client,
383
        bool $isScopeModified,
384
        array $scope = null,
385
        array $extraParameters = []
386
    ): ResponseInterface {
387 1
        $this->logDebug('Prepare token for client.');
388 1
        assert($client !== null);
389
390 1
        $unsavedToken = $this->getIntegration()->createTokenInstance();
391
        $unsavedToken
392 1
            ->setClientIdentifier($client->getIdentifier())
393 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...
394 1
        $isScopeModified === true ? $unsavedToken->setScopeModified() : $unsavedToken->setScopeUnmodified();
395
396
397 1
        $tokenExpiresIn = $this->setUpTokenValue($unsavedToken);
398 1
        $savedToken     = $this->getIntegration()->getTokenRepository()->createToken($unsavedToken);
399
400 1
        $response = $this->createBodyTokenResponse($savedToken, $tokenExpiresIn);
401
402 1
        return $response;
403
    }
404
405
    /**
406
     * @inheritdoc
407
     *
408
     * @return TokenInterface|null
409
     */
410 2
    public function readTokenByRefreshValue(string $refreshValue): ?\Limoncello\OAuthServer\Contracts\TokenInterface
411
    {
412 2
        return $this->getIntegration()->getTokenRepository()->readByRefresh(
413 2
            $refreshValue,
414 2
            $this->getIntegration()->getTokenExpirationPeriod()
415
        );
416
    }
417
418
    /**
419
     * @inheritdoc
420
     *
421
     * @SuppressWarnings(PHPMD.ElseExpression)
422
     */
423 2
    public function refreshCreateAccessTokenResponse(
424
        ClientInterface $client,
425
        \Limoncello\OAuthServer\Contracts\TokenInterface $token,
426
        bool $isScopeModified,
427
        array $scope = null,
428
        array $extraParameters = []
429
    ): ResponseInterface {
430 2
        $this->logDebug('Prepare refresh token.');
431
432
        /** @var TokenInterface $token */
433 2
        assert($token instanceof TokenInterface);
434
435 2
        $updatedToken   = clone $token;
436 2
        $tokenExpiresIn = $this->getIntegration()->isRenewRefreshValue() === false ?
437 2
            $this->setUpTokenValue($updatedToken) : $this->setUpTokenValues($updatedToken);
438
439 2
        $tokenRepo = $this->getIntegration()->getTokenRepository();
440 2
        if ($isScopeModified === false) {
441 1
            $tokenRepo->updateValues($updatedToken);
442
        } else {
443 1
            assert(is_array($scope));
444 1
            $tokenRepo->inTransaction(function () use ($tokenRepo, $updatedToken, $scope) {
445 1
                $tokenRepo->updateValues($updatedToken);
446 1
                $tokenRepo->unbindScopes($updatedToken->getIdentifier());
447 1
                $tokenRepo->bindScopeIdentifiers($updatedToken->getIdentifier(), $scope);
448 1
            });
449 1
            $updatedToken->setScopeModified()->setScopeIdentifiers($scope);
450
        }
451 2
        $response = $this->createBodyTokenResponse($updatedToken, $tokenExpiresIn);
452
453 2
        return $response;
454
    }
455
456
    /**
457
     * @inheritdoc
458
     */
459 13
    public function readClientByIdentifier(string $clientIdentifier): ?ClientInterface
460
    {
461 13
        return $this->getIntegration()->getClientRepository()->read($clientIdentifier);
462
    }
463
464
    /**
465
     * @param string|null $clientId
466
     * @param string|null $redirectFromQuery
467
     *
468
     * @return array [client|null, uri|null]
469
     *
470
     * @SuppressWarnings(PHPMD.ElseExpression)
471
     */
472 9
    protected function getValidClientAndRedirectUri(string $clientId = null, string $redirectFromQuery = null): array
473
    {
474 9
        $client           = null;
475 9
        $validRedirectUri = null;
476
477 9
        if ($clientId !== null &&
478 9
            ($client = $this->readClientByIdentifier($clientId)) !== null
479
        ) {
480 7
            $validRedirectUri = $this->selectValidRedirectUri($client, $redirectFromQuery);
481 7
            if ($validRedirectUri === null) {
482 1
                $this->logDebug(
483 1
                    'Choosing valid redirect URI for client failed.',
484 7
                    ['client_id' => $clientId, 'redirect_uri_from_query' => $redirectFromQuery]
485
                );
486
            }
487
        } else {
488 2
            $this->logDebug('Client is not found.', ['client_id' => $clientId]);
489
        }
490
491 9
        return [$client, $validRedirectUri];
492
    }
493
494
    /**
495
     * @param TokenInterface $token
496
     * @param int            $tokenExpiresIn
497
     *
498
     * @return ResponseInterface
499
     */
500 5
    protected function createBodyTokenResponse(TokenInterface $token, int $tokenExpiresIn): ResponseInterface
501
    {
502 5
        $this->logDebug('Sending token as JSON response.');
503
504 5
        $scopeList  = $token->isScopeModified() === false || empty($token->getScopeIdentifiers()) === true ?
505 5
            null : $token->getScopeList();
506
507
        // for access token format @link https://tools.ietf.org/html/rfc6749#section-5.1
508 5
        $parameters = $this->filterNulls([
509 5
            'access_token'  => $token->getValue(),
510 5
            'token_type'    => $token->getType(),
511 5
            'expires_in'    => $tokenExpiresIn,
512 5
            'refresh_token' => $token->getRefreshValue(),
513 5
            'scope'         => $scopeList,
514
        ]);
515
516
        // extra parameters
517
        // https://tools.ietf.org/html/rfc6749#section-4.1.4
518
        // https://tools.ietf.org/html/rfc6749#section-4.3.3
519
        // https://tools.ietf.org/html/rfc6749#section-4.4.3
520 5
        $extraParameters = $this->getIntegration()->getBodyTokenExtraParameters($token);
521
522 5
        $response = new JsonResponse($parameters + $extraParameters, 200, [
523 5
            'Cache-Control' => 'no-store',
524
            'Pragma'        => 'no-cache'
525
        ]);
526
527 5
        return $response;
528
    }
529
530
    /**
531
     * @param TokenInterface $code
532
     * @param string|null    $state
533
     *
534
     * @return ResponseInterface
535
     */
536 1
    protected function createRedirectCodeResponse(TokenInterface $code, string $state = null): ResponseInterface
537
    {
538 1
        $this->logDebug('Sending code as redirect response.');
539
540
        // for access token format @link https://tools.ietf.org/html/rfc6749#section-4.1.3
541 1
        $parameters = $this->filterNulls([
542 1
            'code'  => $code->getCode(),
543 1
            'state' => $state,
544
        ]);
545
546 1
        $redirectUri = $code->getRedirectUriString();
547 1
        $query       = $this->encodeAsXWwwFormUrlencoded($parameters);
548
549 1
        $response = new RedirectResponse((new Uri($redirectUri))->withQuery($query));
550
551 1
        return $response;
552
    }
553
554
    /**
555
     * @param TokenInterface $token
556
     * @param int            $tokenExpiresIn
557
     * @param string|null    $state
558
     *
559
     * @return ResponseInterface
560
     */
561 1
    protected function createRedirectTokenResponse(
562
        TokenInterface $token,
563
        int $tokenExpiresIn,
564
        string $state = null
565
    ): ResponseInterface {
566 1
        $this->logDebug('Sending token as redirect response.');
567
568 1
        $scopeList  = $token->isScopeModified() === false || empty($token->getScopeIdentifiers()) === true ?
569 1
            null : $token->getScopeList();
570
571
        // for access token format @link https://tools.ietf.org/html/rfc6749#section-5.1
572 1
        $parameters = $this->filterNulls([
573 1
            'access_token' => $token->getValue(),
574 1
            'token_type'   => $token->getType(),
575 1
            'expires_in'   => $tokenExpiresIn,
576 1
            'scope'        => $scopeList,
577 1
            'state'        => $state,
578
        ]);
579
580 1
        $fragment = $this->encodeAsXWwwFormUrlencoded($parameters);
581
582 1
        $response = new RedirectResponse((new Uri($token->getRedirectUriString()))->withFragment($fragment));
583
584 1
        return $response;
585
    }
586
587
    /**
588
     * @param OAuthTokenBodyException $exception
589
     *
590
     * @return ResponseInterface
591
     */
592 4
    protected function createBodyErrorResponse(OAuthTokenBodyException $exception): ResponseInterface
593
    {
594 4
        $data = $this->filterNulls([
595 4
            'error'             => $exception->getErrorCode(),
596 4
            'error_description' => $exception->getErrorDescription(),
597 4
            'error_uri'         => $this->getBodyErrorUri($exception),
598
        ]);
599
600 4
        $this->logDebug('Sending OAuth error as JSON response.', $data);
601
602 4
        $response = new JsonResponse($data, $exception->getHttpCode(), $exception->getHttpHeaders());
603
604 4
        return $response;
605
    }
606
607
    /**
608
     * @param OAuthRedirectException $exception
609
     *
610
     * @return ResponseInterface
611
     */
612 1
    protected function createRedirectErrorResponse(OAuthRedirectException $exception): ResponseInterface
613
    {
614 1
        $parameters = $this->filterNulls([
615 1
            'error'             => $exception->getErrorCode(),
616 1
            'error_description' => $exception->getErrorDescription(),
617 1
            'error_uri'         => $exception->getErrorUri(),
618 1
            'state'             => $exception->getState(),
619
        ]);
620
621 1
        $this->logDebug('Sending OAuth error via redirect.', $parameters);
622
623 1
        $fragment = $this->encodeAsXWwwFormUrlencoded($parameters);
624 1
        $uri      = (new Uri($exception->getRedirectUri()))->withFragment($fragment);
625
626 1
        $response = new RedirectResponse($uri, 302, $exception->getHttpHeaders());
627
628 1
        return $response;
629
    }
630
631
    /**
632
     * @param TokenInterface $token
633
     *
634
     * @return int
635
     */
636 1
    protected function setUpTokenValue(TokenInterface $token): int
637
    {
638
        list($tokenValue, $tokenType, $tokenExpiresIn) =
639 1
            $this->getIntegration()->generateTokenValues($token);
640 1
        $token->setValue($tokenValue)->setType($tokenType);
641
642 1
        return $tokenExpiresIn;
643
    }
644
645
    /**
646
     * @param TokenInterface $token
647
     *
648
     * @return int
649
     */
650 4
    protected function setUpTokenValues(TokenInterface $token): int
651
    {
652
        list($tokenValue, $tokenType, $tokenExpiresIn, $refreshValue) =
653 4
            $this->getIntegration()->generateTokenValues($token);
654 4
        $token->setValue($tokenValue)->setType($tokenType)->setRefreshValue($refreshValue);
655
656 4
        return $tokenExpiresIn;
657
    }
658
659
    /**
660
     * @return PassportServerIntegrationInterface
661
     */
662 16
    protected function getIntegration(): PassportServerIntegrationInterface
663
    {
664 16
        return $this->integration;
665
    }
666
667
    /**
668
     * @param PassportServerIntegrationInterface $integration
669
     *
670
     * @return self
671
     */
672 17
    protected function setIntegration(PassportServerIntegrationInterface $integration): self
673
    {
674 17
        $this->integration = $integration;
675
676 17
        return $this;
677
    }
678
679
    /**
680
     * @param OAuthTokenBodyException $exception
681
     *
682
     * @return null|string
683
     */
684 4
    protected function getBodyErrorUri(OAuthTokenBodyException $exception)
685
    {
686 4
        assert($exception !== null);
687
688 4
        return null;
689
    }
690
691
    /**
692
     * @param string $message
693
     * @param array  $context
694
     *
695
     * @return void
696
     */
697 16
    protected function logDebug(string $message, array $context = []): void
698
    {
699 16
        if ($this->logger !== null) {
700 16
            $this->logger->debug($message, $context);
701
        }
702
    }
703
704
    /**
705
     * @param string $message
706
     * @param array  $context
707
     *
708
     * @return void
709
     */
710 10
    protected function logInfo(string $message, array $context = []): void
711
    {
712 10
        if ($this->logger !== null) {
713 10
            $this->logger->info($message, $context);
714
        }
715
    }
716
717
    /**
718
     * @param array $array
719
     *
720
     * @return array
721
     */
722
    private function filterNulls(array $array): array
723
    {
724 10
        return array_filter($array, function ($value) {
725 10
            return $value !== null;
726 10
        });
727
    }
728
}
729