Completed
Push — develop ( ca252b...3db5f7 )
by Neomerx
04:41
created

BasePassportServer::passReadDefaultClient()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 1
nop 0
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 18
    public function __construct(PassportServerIntegrationInterface $integration)
77
    {
78 18
        parent::__construct();
79
80 18
        $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 9
    public function postCreateToken(ServerRequestInterface $request): ResponseInterface
129
    {
130
        try {
131 9
            $parameters       = $request->getParsedBody();
132 9
            $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 9
            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 9
                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 8
                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 6
                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 4
                case GrantTypes::REFRESH_TOKEN:
152 3
                    $this->logDebug('Handling refresh token grant.');
153 3
                    $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...
Bug introduced by
It seems like $determinedClient defined by $this->determineClient($... $request, $parameters) on line 132 can be null; however, Limoncello\OAuthServer\G...it::refreshIssueToken() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
154 1
                    break;
155 1
                default:
156
                    $this->logInfo('Unknown grant type.', ['grant_type' => $grantType]);
157 2
                    throw new OAuthTokenBodyException(OAuthTokenBodyException::ERROR_UNSUPPORTED_GRANT_TYPE);
158 2
            }
159
        } catch (OAuthTokenBodyException $exception) {
160 1
            $response = $this->createBodyErrorResponse($exception);
161 6
        }
162
163 5
        return $response;
164 5
    }
165
166
    /**
167 9
     * @inheritdoc
168
     */
169
    public function createCodeResponse(TokenInterface $code, string $state = null): ResponseInterface
170
    {
171
        $client = $this->readClientByIdentifier($code->getClientIdentifier());
172
        if ($code->getRedirectUriString() === null ||
173 2
            in_array($code->getRedirectUriString(), $client->getRedirectUriStrings()) === false
174
        ) {
175 2
            $this->logInfo(
176 2
                'Code has invalid redirect URI which do not match any redirect URI for its client.',
177 2
                ['id' => $code->getIdentifier()]
178
            );
179 1
            return $this->getIntegration()->createInvalidClientAndRedirectUriErrorResponse();
180 1
        }
181 1
182
        $code->setCode($this->getIntegration()->generateCodeValue($code));
183 1
184
        $tokenRepo   = $this->getIntegration()->getTokenRepository();
185
        $createdCode = $tokenRepo->createCode($code);
186 1
187
        $response = $this->createRedirectCodeResponse($createdCode, $state);
188 1
189 1
        return $response;
190
    }
191 1
192
    /**
193 1
     * @inheritdoc
194
     */
195
    public function createTokenResponse(TokenInterface $token, string $state = null): ResponseInterface
196
    {
197
        $client = $this->readClientByIdentifier($token->getClientIdentifier());
198
        if ($token->getRedirectUriString() === null ||
199 2
            in_array($token->getRedirectUriString(), $client->getRedirectUriStrings()) === false
200
        ) {
201 2
            $this->logInfo(
202 2
                'Token has invalid redirect URI which do not match any redirect URI for its client.',
203 2
                ['id' => $token->getIdentifier()]
204
            );
205 1
            return $this->getIntegration()->createInvalidClientAndRedirectUriErrorResponse();
206 1
        }
207 1
208
        list($tokenValue, $tokenType, $tokenExpiresIn) = $this->getIntegration()->generateTokenValues($token);
209 1
210
        // refresh value must be null by the spec
211
        $refreshValue = null;
212 1
        $token->setValue($tokenValue)->setType($tokenType)->setRefreshValue($refreshValue);
213
        $savedToken = $this->getIntegration()->getTokenRepository()->createToken($token);
214
215 1
        $response = $this->createRedirectTokenResponse($savedToken, $tokenExpiresIn, $state);
216 1
217 1
        return $response;
218
    }
219 1
220
    /** @noinspection PhpTooManyParametersInspection
221 1
     * @inheritdoc
222
     *
223
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
224
     */
225
    public function codeCreateAskResourceOwnerForApprovalResponse(
226
        ClientInterface $client,
227
        string $redirectUri = null,
228
        bool $isScopeModified = false,
229 3
        array $scopeList = null,
230
        string $state = null,
231
        array $extraParameters = []
232
    ): ResponseInterface {
233
        $this->logDebug('Asking resource owner for scope approval (code grant).');
234
        return $this->getIntegration()->createAskResourceOwnerForApprovalResponse(
235
            ResponseTypes::AUTHORIZATION_CODE,
236
            $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 3
        );
243 3
    }
244 3
245 3
    /**
246
     * @inheritdoc
247
     */
248
    public function codeReadAuthenticationCode(string $code): ?AuthorizationCodeInterface
249
    {
250
        return $this->getIntegration()->getTokenRepository()
251
            ->readByCode($code, $this->getIntegration()->getCodeExpirationPeriod());
252 1
    }
253
254 1
    /**
255 1
     * @inheritdoc
256
     */
257
    public function codeCreateAccessTokenResponse(
258
        AuthorizationCodeInterface $code,
259
        array $extraParameters = []
260
    ): ResponseInterface {
261 1
        /** @var Token $code */
262
        assert($code instanceof Token);
263
        $updatedToken = clone $code;
264
265
        $tokenExpiresIn = $this->setUpTokenValues($updatedToken);
266 1
        $this->getIntegration()->getTokenRepository()->assignValuesToCode($updatedToken, $tokenExpiresIn);
267 1
268
        $response = $this->createBodyTokenResponse($updatedToken, $tokenExpiresIn);
269 1
270 1
        $this->logInfo('Authorization code successfully exchanged for a token (code grant).');
271
272 1
        return $response;
273
    }
274 1
275
    /**
276
     * @inheritdoc
277
     */
278
    public function codeRevokeTokens(AuthorizationCodeInterface $code): void
279
    {
280 1
        assert($code instanceof TokenInterface);
281
282 1
        /** @var TokenInterface $code */
283
284
        $identifier = $code->getIdentifier();
285
        $this->logInfo('Revoking token.', ['token_id' => $identifier]);
286 1
        $this->getIntegration()->getTokenRepository()->disable($identifier);
287 1
    }
288 1
289
    /** @noinspection PhpTooManyParametersInspection
290
     * @inheritdoc
291
     *
292
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
293
     */
294
    public function implicitCreateAskResourceOwnerForApprovalResponse(
295
        ClientInterface $client,
296 2
        string $redirectUri = null,
297
        bool $isScopeModified = false,
298
        array $scopeList = null,
299
        string $state = null,
300
        array $extraParameters = []
301
    ): ResponseInterface {
302
        $response = $this->getIntegration()->createAskResourceOwnerForApprovalResponse(
303
            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 2
        );
311 2
312 2
        $this->logInfo('Created response asking resource owner for scope approval (implicit grant).');
313
314
        return $response;
315
    }
316
317
    /** @noinspection PhpTooManyParametersInspection
318
     * @inheritdoc
319
     *
320
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
321 4
     */
322
    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 4
    ): ResponseInterface {
330
        assert($client !== null);
331 4
332 1
        if (($userIdentifier = $this->getIntegration()->validateUserId($userName, $password)) === null) {
333 1
            $this->logDebug('User not found with provided username and password.', ['username' => $userName]);
334
            throw new OAuthTokenBodyException(OAuthTokenBodyException::ERROR_INVALID_GRANT);
335 3
        }
336 3
        assert(is_int($userIdentifier) === true);
337
        $this->logInfo('User authenticated with provided username and password.', ['username' => $userName]);
338 3
339 3
        $changedScopeOrNull = $this->getIntegration()->verifyAllowedUserScope($userIdentifier, $scope);
340 3
        if ($changedScopeOrNull !== null) {
341 3
            assert(is_array($changedScopeOrNull));
342 3
            $isScopeModified = true;
343
            $scope           = $changedScopeOrNull;
344
        }
345 3
346
        $unsavedToken = $this->getIntegration()->createTokenInstance();
347 3
        $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
        $isScopeModified === true ? $unsavedToken->setScopeModified() : $unsavedToken->setScopeUnmodified();
352
353 3
354 3
        $tokenExpiresIn = $this->setUpTokenValues($unsavedToken);
355
        $savedToken     = $this->getIntegration()->getTokenRepository()->createToken($unsavedToken);
356 3
357
        $response = $this->createBodyTokenResponse($savedToken, $tokenExpiresIn);
358 3
359
        return $response;
360
    }
361
362
    /**
363
     * @inheritdoc
364 4
     */
365
    public function passReadDefaultClient(): ClientInterface
366 4
    {
367
        $defaultClientId = $this->getIntegration()->getDefaultClientIdentifier();
368 4
369
        assert(is_string($defaultClientId) === true && empty($defaultClientId) === false);
370 4
371
        $defaultClient   = $this->readClientByIdentifier($defaultClientId);
372 4
373
        assert($defaultClient !== null);
374 4
375
        return $defaultClient;
376
    }
377
378
    /**
379
     * @inheritdoc
380 1
     */
381
    public function clientCreateAccessTokenResponse(
382
        ClientInterface $client,
383
        bool $isScopeModified,
384
        array $scope = null,
385
        array $extraParameters = []
386 1
    ): ResponseInterface {
387 1
        $this->logDebug('Prepare token for client.');
388
        assert($client !== null);
389 1
390
        $unsavedToken = $this->getIntegration()->createTokenInstance();
391 1
        $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
        $isScopeModified === true ? $unsavedToken->setScopeModified() : $unsavedToken->setScopeUnmodified();
395
396 1
397 1
        $tokenExpiresIn = $this->setUpTokenValue($unsavedToken);
398
        $savedToken     = $this->getIntegration()->getTokenRepository()->createToken($unsavedToken);
399 1
400
        $response = $this->createBodyTokenResponse($savedToken, $tokenExpiresIn);
401 1
402
        return $response;
403
    }
404
405
    /**
406
     * @inheritdoc
407
     *
408
     * @return TokenInterface|null
409 2
     */
410
    public function readTokenByRefreshValue(string $refreshValue): ?\Limoncello\OAuthServer\Contracts\TokenInterface
411 2
    {
412 2
        return $this->getIntegration()->getTokenRepository()->readByRefresh(
413 2
            $refreshValue,
414
            $this->getIntegration()->getTokenExpirationPeriod()
415
        );
416
    }
417
418
    /**
419
     * @inheritdoc
420
     *
421
     * @SuppressWarnings(PHPMD.ElseExpression)
422 2
     */
423
    public function refreshCreateAccessTokenResponse(
424
        ClientInterface $client,
425
        \Limoncello\OAuthServer\Contracts\TokenInterface $token,
426
        bool $isScopeModified,
427
        array $scope = null,
428
        array $extraParameters = []
429 2
    ): ResponseInterface {
430
        $this->logDebug('Prepare refresh token.');
431
432 2
        /** @var TokenInterface $token */
433
        assert($token instanceof TokenInterface);
434 2
435 2
        $updatedToken   = clone $token;
436 2
        $tokenExpiresIn = $this->getIntegration()->isRenewRefreshValue() === false ?
437
            $this->setUpTokenValue($updatedToken) : $this->setUpTokenValues($updatedToken);
438 2
439 2
        $tokenRepo = $this->getIntegration()->getTokenRepository();
440 1
        if ($isScopeModified === false) {
441
            $tokenRepo->updateValues($updatedToken);
442 1
        } 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
            $updatedToken->setScopeModified()->setScopeIdentifiers($scope);
450 2
        }
451
        $response = $this->createBodyTokenResponse($updatedToken, $tokenExpiresIn);
452 2
453
        return $response;
454
    }
455
456
    /**
457
     * @inheritdoc
458
     */
459
    public function readClientByIdentifier(string $clientIdentifier): ?ClientInterface
460
    {
461
        return $this->getIntegration()->getClientRepository()->read($clientIdentifier);
462
    }
463 9
464
    /**
465 9
     * @param string|null $clientId
466 9
     * @param string|null $redirectFromQuery
467
     *
468 9
     * @return array [client|null, uri|null]
469 9
     *
470
     * @SuppressWarnings(PHPMD.ElseExpression)
471 7
     */
472 7
    protected function getValidClientAndRedirectUri(string $clientId = null, string $redirectFromQuery = null): array
473 1
    {
474 1
        $client           = null;
475 7
        $validRedirectUri = null;
476
477
        if ($clientId !== null &&
478
            ($client = $this->readClientByIdentifier($clientId)) !== null
479 2
        ) {
480
            $validRedirectUri = $this->selectValidRedirectUri($client, $redirectFromQuery);
481
            if ($validRedirectUri === null) {
482 9
                $this->logDebug(
483
                    'Choosing valid redirect URI for client failed.',
484
                    ['client_id' => $clientId, 'redirect_uri_from_query' => $redirectFromQuery]
485
                );
486
            }
487
        } else {
488
            $this->logDebug('Client is not found.', ['client_id' => $clientId]);
489
        }
490
491 5
        return [$client, $validRedirectUri];
492
    }
493 5
494
    /**
495 5
     * @param TokenInterface $token
496 5
     * @param int            $tokenExpiresIn
497
     *
498
     * @return ResponseInterface
499 5
     */
500 5
    protected function createBodyTokenResponse(TokenInterface $token, int $tokenExpiresIn): ResponseInterface
501 5
    {
502 5
        $this->logDebug('Sending token as JSON response.');
503 5
504 5
        $scopeList  = $token->isScopeModified() === false || empty($token->getScopeIdentifiers()) === true ?
505
            null : $token->getScopeList();
506
507
        // for access token format @link https://tools.ietf.org/html/rfc6749#section-5.1
508
        $parameters = $this->filterNulls([
509
            'access_token'  => $token->getValue(),
510
            'token_type'    => $token->getType(),
511 5
            'expires_in'    => $tokenExpiresIn,
512
            'refresh_token' => $token->getRefreshValue(),
513 5
            'scope'         => $scopeList,
514 5
        ]);
515
516
        // extra parameters
517
        // https://tools.ietf.org/html/rfc6749#section-4.1.4
518 5
        // https://tools.ietf.org/html/rfc6749#section-4.3.3
519
        // https://tools.ietf.org/html/rfc6749#section-4.4.3
520
        $extraParameters = $this->getIntegration()->getBodyTokenExtraParameters($token);
521
522
        $response = new JsonResponse($parameters + $extraParameters, 200, [
523
            'Cache-Control' => 'no-store',
524
            'Pragma'        => 'no-cache'
525
        ]);
526
527 1
        return $response;
528
    }
529 1
530
    /**
531
     * @param TokenInterface $code
532 1
     * @param string|null    $state
533 1
     *
534 1
     * @return ResponseInterface
535
     */
536
    protected function createRedirectCodeResponse(TokenInterface $code, string $state = null): ResponseInterface
537 1
    {
538 1
        $this->logDebug('Sending code as redirect response.');
539
540 1
        // for access token format @link https://tools.ietf.org/html/rfc6749#section-4.1.3
541
        $parameters = $this->filterNulls([
542 1
            'code'  => $code->getCode(),
543
            'state' => $state,
544
        ]);
545
546
        $redirectUri = $code->getRedirectUriString();
547
        $query       = $this->encodeAsXWwwFormUrlencoded($parameters);
548
549
        $response = new RedirectResponse((new Uri($redirectUri))->withQuery($query));
550
551
        return $response;
552 1
    }
553
554
    /**
555
     * @param TokenInterface $token
556
     * @param int            $tokenExpiresIn
557 1
     * @param string|null    $state
558
     *
559 1
     * @return ResponseInterface
560 1
     */
561
    protected function createRedirectTokenResponse(
562
        TokenInterface $token,
563 1
        int $tokenExpiresIn,
564 1
        string $state = null
565 1
    ): ResponseInterface {
566 1
        $this->logDebug('Sending token as redirect response.');
567 1
568 1
        $scopeList  = $token->isScopeModified() === false || empty($token->getScopeIdentifiers()) === true ?
569
            null : $token->getScopeList();
570
571 1
        // for access token format @link https://tools.ietf.org/html/rfc6749#section-5.1
572
        $parameters = $this->filterNulls([
573 1
            'access_token' => $token->getValue(),
574
            'token_type'   => $token->getType(),
575 1
            'expires_in'   => $tokenExpiresIn,
576
            'scope'        => $scopeList,
577
            'state'        => $state,
578
        ]);
579
580
        $fragment = $this->encodeAsXWwwFormUrlencoded($parameters);
581
582
        $response = new RedirectResponse((new Uri($token->getRedirectUriString()))->withFragment($fragment));
583 5
584
        return $response;
585 5
    }
586 5
587 5
    /**
588 5
     * @param OAuthTokenBodyException $exception
589
     *
590
     * @return ResponseInterface
591 5
     */
592
    protected function createBodyErrorResponse(OAuthTokenBodyException $exception): ResponseInterface
593 5
    {
594
        $data = $this->filterNulls([
595 5
            'error'             => $exception->getErrorCode(),
596
            'error_description' => $exception->getErrorDescription(),
597
            'error_uri'         => $this->getBodyErrorUri($exception),
598
        ]);
599
600
        $this->logDebug('Sending OAuth error as JSON response.', $data);
601
602
        $response = new JsonResponse($data, $exception->getHttpCode(), $exception->getHttpHeaders());
603 1
604
        return $response;
605 1
    }
606 1
607 1
    /**
608 1
     * @param OAuthRedirectException $exception
609 1
     *
610
     * @return ResponseInterface
611
     */
612 1
    protected function createRedirectErrorResponse(OAuthRedirectException $exception): ResponseInterface
613
    {
614 1
        $parameters = $this->filterNulls([
615 1
            'error'             => $exception->getErrorCode(),
616
            'error_description' => $exception->getErrorDescription(),
617 1
            'error_uri'         => $exception->getErrorUri(),
618
            'state'             => $exception->getState(),
619 1
        ]);
620
621
        $this->logDebug('Sending OAuth error via redirect.', $parameters);
622
623
        $fragment = $this->encodeAsXWwwFormUrlencoded($parameters);
624
        $uri      = (new Uri($exception->getRedirectUri()))->withFragment($fragment);
625
626
        $response = new RedirectResponse($uri, 302, $exception->getHttpHeaders());
627 1
628
        return $response;
629
    }
630 1
631 1
    /**
632
     * @param TokenInterface $token
633 1
     *
634
     * @return int
635
     */
636
    protected function setUpTokenValue(TokenInterface $token): int
637
    {
638
        list($tokenValue, $tokenType, $tokenExpiresIn) =
639
            $this->getIntegration()->generateTokenValues($token);
640
        $token->setValue($tokenValue)->setType($tokenType);
641 4
642
        return $tokenExpiresIn;
643
    }
644 4
645 4
    /**
646
     * @param TokenInterface $token
647 4
     *
648
     * @return int
649
     */
650
    protected function setUpTokenValues(TokenInterface $token): int
651
    {
652
        list($tokenValue, $tokenType, $tokenExpiresIn, $refreshValue) =
653 17
            $this->getIntegration()->generateTokenValues($token);
654
        $token->setValue($tokenValue)->setType($tokenType)->setRefreshValue($refreshValue);
655 17
656
        return $tokenExpiresIn;
657
    }
658
659
    /**
660
     * @return PassportServerIntegrationInterface
661
     */
662
    protected function getIntegration(): PassportServerIntegrationInterface
663 18
    {
664
        return $this->integration;
665 18
    }
666
667 18
    /**
668
     * @param PassportServerIntegrationInterface $integration
669
     *
670
     * @return self
671
     */
672
    protected function setIntegration(PassportServerIntegrationInterface $integration): self
673
    {
674
        $this->integration = $integration;
675 5
676
        return $this;
677 5
    }
678
679 5
    /**
680
     * @param OAuthTokenBodyException $exception
681
     *
682
     * @return null|string
683
     */
684
    protected function getBodyErrorUri(OAuthTokenBodyException $exception)
685
    {
686
        assert($exception !== null);
687
688 17
        return null;
689
    }
690 17
691 17
    /**
692
     * @param string $message
693
     * @param array  $context
694
     *
695
     * @return void
696
     */
697
    protected function logDebug(string $message, array $context = []): void
698
    {
699
        if ($this->logger !== null) {
700
            $this->logger->debug($message, $context);
701
        }
702 11
    }
703 11
704 11
    /**
705
     * @param string $message
706
     * @param array  $context
707
     *
708
     * @return void
709
     */
710
    protected function logInfo(string $message, array $context = []): void
711
    {
712
        if ($this->logger !== null) {
713
            $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
        return array_filter($array, function ($value) {
725
            return $value !== null;
726
        });
727
    }
728
}
729