Completed
Push — master ( 7c3a00...4fd5ad )
by Neomerx
04:42
created

BasePassportServer::createBodyTokenResponse()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 15
nc 4
nop 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
    );
67
68
    /**
69
     * @var PassportServerIntegrationInterface
70
     */
71
    private $integration;
72
73
    /**
74
     * @param PassportServerIntegrationInterface $integration
75
     */
76
    public function __construct(PassportServerIntegrationInterface $integration)
77
    {
78
        parent::__construct();
79
80
        $this->setIntegration($integration);
81
    }
82
83
    /**
84
     * @inheritdoc
85
     *
86
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
87
     */
88
    protected function createAuthorization(array $parameters): ResponseInterface
89
    {
90
        try {
91
            list($client, $redirectUri) = $this->getValidClientAndRedirectUri(
92
                $this->codeGetClientId($parameters),
93
                $this->codeGetRedirectUri($parameters)
94
            );
95
            if ($client === null || $redirectUri === null) {
96
                return $this->getIntegration()->createInvalidClientAndRedirectUriErrorResponse();
97
            }
98
99
            $maxStateLength = $this->getMaxStateLength();
100
            switch ($responseType = $this->getResponseType($parameters)) {
101
                case ResponseTypes::AUTHORIZATION_CODE:
102
                    $this->logDebug('Handling code authorization.');
103
                    $response = $this
104
                        ->codeAskResourceOwnerForApproval($parameters, $client, $redirectUri, $maxStateLength);
105
                    break;
106
                case ResponseTypes::IMPLICIT:
107
                    $this->logDebug('Handling implicit authorization.');
108
                    $response = $this
109
                        ->implicitAskResourceOwnerForApproval($parameters, $client, $redirectUri, $maxStateLength);
110
                    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
                    $this->logDebug('Unsupported response type in request.', ['response_type' => $responseType]);
115
                    $errorCode = OAuthCodeRedirectException::ERROR_UNSUPPORTED_RESPONSE_TYPE;
116
                    throw new OAuthCodeRedirectException($errorCode, $redirectUri);
117
            }
118
        } catch (OAuthRedirectException $exception) {
119
            $response = $this->createRedirectErrorResponse($exception);
120
        }
121
122
        return $response;
123
    }
124
125
    /**
126
     * @inheritdoc
127
     */
128
    public function postCreateToken(ServerRequestInterface $request): ResponseInterface
129
    {
130
        try {
131
            $parameters       = $request->getParsedBody();
132
            $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
            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
                case GrantTypes::AUTHORIZATION_CODE:
136
                    $this->logDebug('Handling code grant.');
137
                    $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
                    break;
139
                case GrantTypes::RESOURCE_OWNER_PASSWORD_CREDENTIALS:
140
                    $this->logDebug('Handling resource owner password grant.');
141
                    $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
                    break;
143 View Code Duplication
                case GrantTypes::CLIENT_CREDENTIALS:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
144
                    $this->logDebug('Handling client credentials grant.');
145
                    if ($determinedClient === null) {
146
                        $this->logDebug('Client identification failed.');
147
                        throw new OAuthTokenBodyException(OAuthTokenBodyException::ERROR_INVALID_CLIENT);
148
                    }
149
                    $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
                    break;
151 View Code Duplication
                case GrantTypes::REFRESH_TOKEN:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
152
                    $this->logDebug('Handling refresh token grant.');
153
                    if ($determinedClient === null) {
154
                        $this->logDebug('Client identification failed.');
155
                        throw new OAuthTokenBodyException(OAuthTokenBodyException::ERROR_INVALID_CLIENT);
156
                    }
157
                    $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...
158
                    break;
159
                default:
160
                    $this->logDebug('Unknown grant type.', ['grant_type' => $grantType]);
161
                    throw new OAuthTokenBodyException(OAuthTokenBodyException::ERROR_UNSUPPORTED_GRANT_TYPE);
162
            }
163
        } catch (OAuthTokenBodyException $exception) {
164
            $response = $this->createBodyErrorResponse($exception);
165
        }
166
167
        return $response;
168
    }
169
170
    /**
171
     * @inheritdoc
172
     */
173
    public function createCodeResponse(TokenInterface $code, string $state = null): ResponseInterface
174
    {
175
        $client = $this->getIntegration()->getClientRepository()->read($code->getClientIdentifier());
176 View Code Duplication
        if ($code->getRedirectUriString() === null ||
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
177
            in_array($code->getRedirectUriString(), $client->getRedirectUriStrings()) === false
178
        ) {
179
            $this->logDebug(
180
                'Code has invalid redirect URI which do not match any redirect URI for its client.',
181
                ['id' => $code->getIdentifier()]
182
            );
183
            return $this->getIntegration()->createInvalidClientAndRedirectUriErrorResponse();
184
        }
185
186
        $code->setCode($this->getIntegration()->generateCodeValue($code));
187
188
        $tokenRepo   = $this->getIntegration()->getTokenRepository();
189
        $createdCode = $tokenRepo->createCode($code);
190
191
        $response = $this->createRedirectCodeResponse($createdCode, $state);
192
193
        return $response;
194
    }
195
196
    /**
197
     * @inheritdoc
198
     */
199
    public function createTokenResponse(TokenInterface $token, string $state = null): ResponseInterface
200
    {
201
        $client = $this->getIntegration()->getClientRepository()->read($token->getClientIdentifier());
202 View Code Duplication
        if ($token->getRedirectUriString() === null ||
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
            in_array($token->getRedirectUriString(), $client->getRedirectUriStrings()) === false
204
        ) {
205
            $this->logDebug(
206
                'Token has invalid redirect URI which do not match any redirect URI for its client.',
207
                ['id' => $token->getIdentifier()]
208
            );
209
            return $this->getIntegration()->createInvalidClientAndRedirectUriErrorResponse();
210
        }
211
212
        list($tokenValue, $tokenType, $tokenExpiresIn) = $this->getIntegration()->generateTokenValues($token);
213
214
        // refresh value must be null by the spec
215
        $refreshValue = null;
216
        $token->setValue($tokenValue)->setType($tokenType)->setRefreshValue($refreshValue);
217
        $savedToken = $this->getIntegration()->getTokenRepository()->createToken($token);
218
219
        $response = $this->createRedirectTokenResponse($savedToken, $tokenExpiresIn, $state);
220
221
        return $response;
222
    }
223
224
    /** @noinspection PhpTooManyParametersInspection
225
     * @inheritdoc
226
     *
227
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
228
     */
229 View Code Duplication
    public function codeCreateAskResourceOwnerForApprovalResponse(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
230
        ClientInterface $client,
231
        string $redirectUri = null,
232
        bool $isScopeModified = false,
233
        array $scopeList = null,
234
        string $state = null,
235
        array $extraParameters = []
236
    ): ResponseInterface {
237
        $this->logDebug('Asking resource owner for scope approval (code grant).');
238
        return $this->getIntegration()->createAskResourceOwnerForApprovalResponse(
239
            ResponseTypes::AUTHORIZATION_CODE,
240
            $client,
241
            $redirectUri,
242
            $isScopeModified,
243
            $scopeList,
0 ignored issues
show
Bug introduced by
It seems like $scopeList defined by parameter $scopeList on line 233 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...
244
            $state,
245
            $extraParameters
246
        );
247
    }
248
249
    /**
250
     * @inheritdoc
251
     */
252
    public function codeReadAuthenticationCode(string $code)
253
    {
254
        return $this->getIntegration()->getTokenRepository()
255
            ->readByCode($code, $this->getIntegration()->getCodeExpirationPeriod());
256
    }
257
258
    /**
259
     * @inheritdoc
260
     */
261
    public function codeCreateAccessTokenResponse(
262
        AuthorizationCodeInterface $code,
263
        array $extraParameters = []
264
    ): ResponseInterface {
265
        /** @var Token $code */
266
        assert($code instanceof Token);
267
        $updatedToken = clone $code;
268
269
        $tokenExpiresIn = $this->setUpTokenValues($updatedToken);
270
        $this->getIntegration()->getTokenRepository()->assignValuesToCode($updatedToken, $tokenExpiresIn);
271
272
        $response = $this->createBodyTokenResponse($updatedToken, $tokenExpiresIn);
273
274
        return $response;
275
    }
276
277
    /**
278
     * @inheritdoc
279
     */
280
    public function codeRevokeTokens(AuthorizationCodeInterface $code)
281
    {
282
        assert($code instanceof TokenInterface);
283
284
        /** @var TokenInterface $code */
285
286
        $identifier = $code->getIdentifier();
287
        $this->logDebug('Revoking token.', ['token_id' => $identifier]);
288
        $this->getIntegration()->getTokenRepository()->disable($identifier);
289
    }
290
291
    /** @noinspection PhpTooManyParametersInspection
292
     * @inheritdoc
293
     *
294
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
295
     */
296 View Code Duplication
    public function implicitCreateAskResourceOwnerForApprovalResponse(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
297
        ClientInterface $client,
298
        string $redirectUri = null,
299
        bool $isScopeModified = false,
300
        array $scopeList = null,
301
        string $state = null,
302
        array $extraParameters = []
303
    ): ResponseInterface {
304
        $this->logDebug('Asking resource owner for scope approval (implicit grant).');
305
        return $this->getIntegration()->createAskResourceOwnerForApprovalResponse(
306
            ResponseTypes::IMPLICIT,
307
            $client,
308
            $redirectUri,
309
            $isScopeModified,
310
            $scopeList,
0 ignored issues
show
Bug introduced by
It seems like $scopeList defined by parameter $scopeList on line 300 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...
311
            $state,
312
            $extraParameters
313
        );
314
    }
315
316
    /** @noinspection PhpTooManyParametersInspection
317
     * @inheritdoc
318
     *
319
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
320
     */
321
    public function passValidateCredentialsAndCreateAccessTokenResponse(
322
        $userName,
323
        $password,
324
        ClientInterface $client = null,
325
        bool $isScopeModified = false,
326
        array $scope = null,
327
        array $extraParameters = []
328
    ): ResponseInterface {
329
        assert($client !== null);
330
331
        if (($userIdentifier = $this->getIntegration()->validateUserId($userName, $password)) === null) {
332
            $this->logDebug('User not found with provided username and password.', ['username' => $userName]);
333
            throw new OAuthTokenBodyException(OAuthTokenBodyException::ERROR_INVALID_GRANT);
334
        }
335
        assert(is_int($userIdentifier) === true);
336
        $this->logDebug('User authenticated with provided username and password.', ['username' => $userName]);
337
338
        $changedScopeOrNull = $this->getIntegration()->verifyAllowedUserScope($userIdentifier, $scope);
339
        if ($changedScopeOrNull !== null) {
340
            assert(is_array($changedScopeOrNull));
341
            $isScopeModified = true;
342
            $scope           = $changedScopeOrNull;
343
        }
344
345
        $unsavedToken = $this->getIntegration()->createTokenInstance();
346
        $unsavedToken
347
            ->setClientIdentifier($client->getIdentifier())
348
            ->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...
349
            ->setUserIdentifier($userIdentifier);
350
        $isScopeModified === true ? $unsavedToken->setScopeModified() : $unsavedToken->setScopeUnmodified();
351
352
353
        $tokenExpiresIn = $this->setUpTokenValues($unsavedToken);
354
        $savedToken     = $this->getIntegration()->getTokenRepository()->createToken($unsavedToken);
355
356
        $response = $this->createBodyTokenResponse($savedToken, $tokenExpiresIn);
357
358
        return $response;
359
    }
360
361
    /**
362
     * @inheritdoc
363
     */
364
    public function passReadDefaultClient(): ClientInterface
365
    {
366
        $defaultClientId = $this->getIntegration()->getDefaultClientIdentifier();
367
368
        assert(is_string($defaultClientId) === true && empty($defaultClientId) === false);
369
370
        $defaultClient   = $this->getIntegration()->getClientRepository()->read($defaultClientId);
371
372
        assert($defaultClient !== null);
373
374
        return $defaultClient;
375
    }
376
377
    /**
378
     * @inheritdoc
379
     */
380
    public function clientCreateAccessTokenResponse(
381
        ClientInterface $client,
382
        bool $isScopeModified,
383
        array $scope = null,
384
        array $extraParameters = []
385
    ): ResponseInterface {
386
        $this->logDebug('Prepare token for client.');
387
        assert($client !== null);
388
389
        $unsavedToken = $this->getIntegration()->createTokenInstance();
390
        $unsavedToken
391
            ->setClientIdentifier($client->getIdentifier())
392
            ->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...
393
        $isScopeModified === true ? $unsavedToken->setScopeModified() : $unsavedToken->setScopeUnmodified();
394
395
396
        $tokenExpiresIn = $this->setUpTokenValue($unsavedToken);
397
        $savedToken     = $this->getIntegration()->getTokenRepository()->createToken($unsavedToken);
398
399
        $response = $this->createBodyTokenResponse($savedToken, $tokenExpiresIn);
400
401
        return $response;
402
    }
403
404
    /**
405
     * @inheritdoc
406
     *
407
     * @return TokenInterface|null
408
     */
409
    public function readTokenByRefreshValue(string $refreshValue)
410
    {
411
        return $this->getIntegration()->getTokenRepository()->readByRefresh(
412
            $refreshValue,
413
            $this->getIntegration()->getTokenExpirationPeriod()
414
        );
415
    }
416
417
    /**
418
     * @inheritdoc
419
     *
420
     * @SuppressWarnings(PHPMD.ElseExpression)
421
     */
422
    public function refreshCreateAccessTokenResponse(
423
        ClientInterface $client,
424
        \Limoncello\OAuthServer\Contracts\TokenInterface $token,
425
        bool $isScopeModified,
426
        array $scope = null,
427
        array $extraParameters = []
428
    ): ResponseInterface {
429
        $this->logDebug('Prepare refresh token.');
430
431
        /** @var TokenInterface $token */
432
        assert($token instanceof TokenInterface);
433
434
        $updatedToken   = clone $token;
435
        $tokenExpiresIn = $this->getIntegration()->isRenewRefreshValue() === false ?
436
            $this->setUpTokenValue($updatedToken) : $this->setUpTokenValues($updatedToken);
437
438
        $tokenRepo = $this->getIntegration()->getTokenRepository();
439
        if ($isScopeModified === false) {
440
            $tokenRepo->updateValues($updatedToken);
441
        } else {
442
            assert(is_array($scope));
443
            $tokenRepo->inTransaction(function () use ($tokenRepo, $updatedToken, $scope) {
444
                $tokenRepo->updateValues($updatedToken);
445
                $tokenRepo->unbindScopes($updatedToken->getIdentifier());
446
                $tokenRepo->bindScopeIdentifiers($updatedToken->getIdentifier(), $scope);
447
            });
448
            $updatedToken->setScopeModified()->setScopeIdentifiers($scope);
449
        }
450
        $response = $this->createBodyTokenResponse($updatedToken, $tokenExpiresIn);
451
452
        return $response;
453
    }
454
455
    /**
456
     * @param string|null $clientId
457
     * @param string|null $redirectFromQuery
458
     *
459
     * @return array [client|null, uri|null]
460
     *
461
     * @SuppressWarnings(PHPMD.ElseExpression)
462
     */
463
    protected function getValidClientAndRedirectUri(string $clientId = null, string $redirectFromQuery = null)
464
    {
465
        $client           = null;
466
        $validRedirectUri = null;
467
468
        if ($clientId !== null &&
469
            ($client = $this->getIntegration()->getClientRepository()->read($clientId)) !== null
470
        ) {
471
            $validRedirectUri = $this->selectValidRedirectUri($client, $redirectFromQuery);
472
            if ($validRedirectUri === null) {
473
                $this->logDebug(
474
                    'Choosing valid redirect URI for client failed.',
475
                    ['client_id' => $clientId, 'redirect_uri_from_query' => $redirectFromQuery]
476
                );
477
            }
478
        } else {
479
            $this->logDebug('Client is not found.', ['client_id' => $clientId]);
480
        }
481
482
        return [$client, $validRedirectUri];
483
    }
484
485
    /**
486
     * @param TokenInterface $token
487
     * @param int            $tokenExpiresIn
488
     *
489
     * @return ResponseInterface
490
     */
491
    protected function createBodyTokenResponse(TokenInterface $token, int $tokenExpiresIn): ResponseInterface
492
    {
493
        $this->logDebug('Sending token as JSON response.');
494
495
        $scopeList  = $token->isScopeModified() === false || empty($token->getScopeIdentifiers()) === true ?
496
            null : $token->getScopeList();
497
498
        // for access token format @link https://tools.ietf.org/html/rfc6749#section-5.1
499
        $parameters = $this->filterNulls([
500
            'access_token'  => $token->getValue(),
501
            'token_type'    => $token->getType(),
502
            'expires_in'    => $tokenExpiresIn,
503
            'refresh_token' => $token->getRefreshValue(),
504
            'scope'         => $scopeList,
505
        ]);
506
507
        // extra parameters
508
        // https://tools.ietf.org/html/rfc6749#section-4.1.4
509
        // https://tools.ietf.org/html/rfc6749#section-4.3.3
510
        // https://tools.ietf.org/html/rfc6749#section-4.4.3
511
        $extraParameters = $this->getIntegration()->getBodyTokenExtraParameters($token);
512
513
        $response = new JsonResponse($parameters + $extraParameters, 200, [
514
            'Cache-Control' => 'no-store',
515
            'Pragma'        => 'no-cache'
516
        ]);
517
518
        return $response;
519
    }
520
521
    /**
522
     * @param TokenInterface $code
523
     * @param string|null    $state
524
     *
525
     * @return ResponseInterface
526
     */
527
    protected function createRedirectCodeResponse(TokenInterface $code, string $state = null): ResponseInterface
528
    {
529
        $this->logDebug('Sending code as redirect response.');
530
531
        // for access token format @link https://tools.ietf.org/html/rfc6749#section-4.1.3
532
        $parameters = $this->filterNulls([
533
            'code'  => $code->getCode(),
534
            'state' => $state,
535
        ]);
536
537
        $redirectUri = $code->getRedirectUriString();
538
        $query       = $this->encodeAsXWwwFormUrlencoded($parameters);
539
540
        $response = new RedirectResponse((new Uri($redirectUri))->withQuery($query));
541
542
        return $response;
543
    }
544
545
    /**
546
     * @param TokenInterface $token
547
     * @param int            $tokenExpiresIn
548
     * @param string|null    $state
549
     *
550
     * @return ResponseInterface
551
     */
552
    protected function createRedirectTokenResponse(
553
        TokenInterface $token,
554
        int $tokenExpiresIn,
555
        string $state = null
556
    ): ResponseInterface {
557
        $this->logDebug('Sending token as redirect response.');
558
559
        $scopeList  = $token->isScopeModified() === false || empty($token->getScopeIdentifiers()) === true ?
560
            null : $token->getScopeList();
561
562
        // for access token format @link https://tools.ietf.org/html/rfc6749#section-5.1
563
        $parameters = $this->filterNulls([
564
            'access_token' => $token->getValue(),
565
            'token_type'   => $token->getType(),
566
            'expires_in'   => $tokenExpiresIn,
567
            'scope'        => $scopeList,
568
            'state'        => $state,
569
        ]);
570
571
        $fragment = $this->encodeAsXWwwFormUrlencoded($parameters);
572
573
        $response = new RedirectResponse((new Uri($token->getRedirectUriString()))->withFragment($fragment));
574
575
        return $response;
576
    }
577
578
    /**
579
     * @param OAuthTokenBodyException $exception
580
     *
581
     * @return ResponseInterface
582
     */
583
    protected function createBodyErrorResponse(OAuthTokenBodyException $exception): ResponseInterface
584
    {
585
        $data = $this->filterNulls([
586
            'error'             => $exception->getErrorCode(),
587
            'error_description' => $exception->getErrorDescription(),
588
            'error_uri'         => $this->getBodyErrorUri($exception),
589
        ]);
590
591
        $this->logDebug('Sending OAuth error as JSON response.', $data);
592
593
        $response = new JsonResponse($data, $exception->getHttpCode(), $exception->getHttpHeaders());
594
595
        return $response;
596
    }
597
598
    /**
599
     * @param OAuthRedirectException $exception
600
     *
601
     * @return ResponseInterface
602
     */
603
    protected function createRedirectErrorResponse(OAuthRedirectException $exception): ResponseInterface
604
    {
605
        $parameters = $this->filterNulls([
606
            'error'             => $exception->getErrorCode(),
607
            'error_description' => $exception->getErrorDescription(),
608
            'error_uri'         => $exception->getErrorUri(),
609
            'state'             => $exception->getState(),
610
        ]);
611
612
        $this->logDebug('Sending OAuth error via redirect.', $parameters);
613
614
        $fragment = $this->encodeAsXWwwFormUrlencoded($parameters);
615
        $uri      = (new Uri($exception->getRedirectUri()))->withFragment($fragment);
616
617
        $response = new RedirectResponse($uri, 302, $exception->getHttpHeaders());
618
619
        return $response;
620
    }
621
622
    /**
623
     * @param TokenInterface $token
624
     *
625
     * @return int
626
     */
627 View Code Duplication
    protected function setUpTokenValue(TokenInterface $token): int
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
628
    {
629
        list($tokenValue, $tokenType, $tokenExpiresIn) =
630
            $this->getIntegration()->generateTokenValues($token);
631
        $token->setValue($tokenValue)->setType($tokenType);
632
633
        return $tokenExpiresIn;
634
    }
635
636
    /**
637
     * @param TokenInterface $token
638
     *
639
     * @return int
640
     */
641 View Code Duplication
    protected function setUpTokenValues(TokenInterface $token): int
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
642
    {
643
        list($tokenValue, $tokenType, $tokenExpiresIn, $refreshValue) =
644
            $this->getIntegration()->generateTokenValues($token);
645
        $token->setValue($tokenValue)->setType($tokenType)->setRefreshValue($refreshValue);
646
647
        return $tokenExpiresIn;
648
    }
649
650
    /**
651
     * @return PassportServerIntegrationInterface
652
     */
653
    protected function getIntegration(): PassportServerIntegrationInterface
654
    {
655
        return $this->integration;
656
    }
657
658
    /**
659
     * @param PassportServerIntegrationInterface $integration
660
     *
661
     * @return self
662
     */
663
    protected function setIntegration(PassportServerIntegrationInterface $integration): self
664
    {
665
        $this->integration = $integration;
666
667
        return $this;
668
    }
669
670
    /**
671
     * @param OAuthTokenBodyException $exception
672
     *
673
     * @return null|string
674
     */
675
    protected function getBodyErrorUri(OAuthTokenBodyException $exception)
676
    {
677
        assert($exception !== null);
678
679
        return null;
680
    }
681
682
    /**
683
     * @param string $message
684
     * @param array  $context
685
     *
686
     * @return void
687
     */
688
    protected function logDebug(string $message, array $context = [])
689
    {
690
        if ($this->logger !== null) {
691
            $this->logger->debug($message, $context);
692
        }
693
    }
694
695
    /**
696
     * @param array $array
697
     *
698
     * @return array
699
     */
700
    private function filterNulls(array $array): array
701
    {
702
        return array_filter($array, function ($value) {
703
            return $value !== null;
704
        });
705
    }
706
}
707