Passed
Push — master ( 745cb0...e178e9 )
by Rutger
03:00
created

Oauth2Module::createClient()   B

Complexity

Conditions 9
Paths 72

Size

Total Lines 72
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 9.0033

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 39
c 3
b 0
f 0
dl 0
loc 72
ccs 28
cts 29
cp 0.9655
rs 7.7404
cc 9
nc 72
nop 8
crap 9.0033

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * @link http://www.yiiframework.com/
5
 * @copyright Copyright (c) 2008 Yii Software LLC
6
 * @license http://www.yiiframework.com/license/
7
 */
8
9
namespace rhertogh\Yii2Oauth2Server;
10
11
use Defuse\Crypto\Exception\BadFormatException;
12
use Defuse\Crypto\Exception\EnvironmentIsBrokenException;
13
use League\OAuth2\Server\CryptKey;
14
use League\OAuth2\Server\Exception\OAuthServerException;
15
use League\OAuth2\Server\Grant\GrantTypeInterface;
16
use rhertogh\Yii2Oauth2Server\base\Oauth2BaseModule;
17
use rhertogh\Yii2Oauth2Server\controllers\console\Oauth2ClientController;
18
use rhertogh\Yii2Oauth2Server\controllers\console\Oauth2DebugController;
19
use rhertogh\Yii2Oauth2Server\controllers\console\Oauth2EncryptionController;
20
use rhertogh\Yii2Oauth2Server\controllers\console\Oauth2MigrationsController;
21
use rhertogh\Yii2Oauth2Server\helpers\DiHelper;
22
use rhertogh\Yii2Oauth2Server\helpers\Psr7Helper;
23
use rhertogh\Yii2Oauth2Server\interfaces\components\authorization\Oauth2ClientAuthorizationRequestInterface;
24
use rhertogh\Yii2Oauth2Server\interfaces\components\encryption\Oauth2EncryptorInterface;
25
use rhertogh\Yii2Oauth2Server\interfaces\components\factories\encryption\Oauth2EncryptionKeyFactoryInterface;
26
use rhertogh\Yii2Oauth2Server\interfaces\components\factories\grants\base\Oauth2GrantTypeFactoryInterface;
27
use rhertogh\Yii2Oauth2Server\interfaces\components\openidconnect\scope\Oauth2OidcScopeCollectionInterface;
28
use rhertogh\Yii2Oauth2Server\interfaces\components\openidconnect\server\Oauth2OidcBearerTokenResponseInterface;
29
use rhertogh\Yii2Oauth2Server\interfaces\components\server\Oauth2AuthorizationServerInterface;
30
use rhertogh\Yii2Oauth2Server\interfaces\components\server\Oauth2ResourceServerInterface;
31
use rhertogh\Yii2Oauth2Server\interfaces\controllers\web\Oauth2CertificatesControllerInterface;
32
use rhertogh\Yii2Oauth2Server\interfaces\controllers\web\Oauth2ConsentControllerInterface;
33
use rhertogh\Yii2Oauth2Server\interfaces\controllers\web\Oauth2OidcControllerInterface;
34
use rhertogh\Yii2Oauth2Server\interfaces\controllers\web\Oauth2ServerControllerInterface;
35
use rhertogh\Yii2Oauth2Server\interfaces\controllers\web\Oauth2WellKnownControllerInterface;
36
use rhertogh\Yii2Oauth2Server\interfaces\filters\auth\Oauth2HttpBearerAuthInterface;
37
use rhertogh\Yii2Oauth2Server\interfaces\models\base\Oauth2EncryptedStorageInterface;
38
use rhertogh\Yii2Oauth2Server\interfaces\models\external\user\Oauth2OidcUserInterface;
39
use rhertogh\Yii2Oauth2Server\interfaces\models\external\user\Oauth2UserInterface;
40
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2ClientInterface;
41
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2ClientScopeInterface;
42
use Yii;
43
use yii\base\BootstrapInterface;
44
use yii\base\InvalidArgumentException;
45
use yii\base\InvalidCallException;
46
use yii\base\InvalidConfigException;
47
use yii\console\Application as ConsoleApplication;
48
use yii\helpers\ArrayHelper;
49
use yii\helpers\StringHelper;
50
use yii\i18n\PhpMessageSource;
51
use yii\web\Application as WebApplication;
52
use yii\web\GroupUrlRule;
53
use yii\web\IdentityInterface;
54
use yii\web\Response;
55
use yii\web\UrlRule;
56
57
/**
58
 * This is the main module class for the Yii2 Oauth2 Server module.
59
 * To use it, include it as a module in the application configuration like the following:
60
 *
61
 * ~~~
62
 * return [
63
 *     'bootstrap' => ['oauth2'],
64
 *     'modules' => [
65
 *         'oauth2' => [
66
 *             'class' => 'rhertogh\Yii2Oauth2Server\Oauth2Module',
67
 *             // ... Please check docs/guide/start-installation.md further details
68
 *          ],
69
 *     ],
70
 * ]
71
 * ~~~
72
 *
73
 * @since 1.0.0
74
 */
75
class Oauth2Module extends Oauth2BaseModule implements BootstrapInterface
76
{
77
    /**
78
     * Application type "web": http response.
79
     * @since 1.0.0
80
     */
81
    public const APPLICATION_TYPE_WEB = 'web';
82
    /**
83
     * Application type "console": cli response.
84
     * @since 1.0.0
85
     */
86
    public const APPLICATION_TYPE_CONSOLE = 'console';
87
    /**
88
     * Supported Application types.
89
     * @since 1.0.0
90
     */
91
    public const APPLICATION_TYPES = [
92
        self::APPLICATION_TYPE_WEB,
93
        self::APPLICATION_TYPE_CONSOLE,
94
    ];
95
96
    /**
97
     * "Authorization Server" Role, please see guide for details.
98
     * @since 1.0.0
99
     */
100
    public const SERVER_ROLE_AUTHORIZATION_SERVER = 1;
101
    /**
102
     * "Resource Server" Role, please see guide for details.
103
     * @since 1.0.0
104
     */
105
    public const SERVER_ROLE_RESOURCE_SERVER = 2;
106
107
    /**
108
     * Required settings when the server role includes Authorization Server
109
     * @since 1.0.0
110
     */
111
    protected const REQUIRED_SETTINGS_AUTHORIZATION_SERVER = [
112
        'codesEncryptionKey',
113
        'storageEncryptionKeys',
114
        'defaultStorageEncryptionKey',
115
        'privateKey',
116
        'publicKey',
117
    ];
118
119
    /**
120
     * Encrypted Models
121
     *
122
     * @since 1.0.0
123
     */
124
    protected const ENCRYPTED_MODELS = [
125
        Oauth2ClientInterface::class,
126
    ];
127
128
    /**
129
     * Required settings when the server role includes Resource Server
130
     * @since 1.0.0
131
     */
132
    protected const REQUIRED_SETTINGS_RESOURCE_SERVER = [
133
        'publicKey',
134
    ];
135
136
    /**
137
     * Prefix used in session storage of Client Authorization Requests
138
     * @since 1.0.0
139
     */
140
    protected const CLIENT_AUTHORIZATION_REQUEST_SESSION_PREFIX = 'OATH2_CLIENT_AUTHORIZATION_REQUEST_';
141
142
    /**
143
     * Controller mapping for the module. Will be parsed on `init()`.
144
     * @since 1.0.0
145
     */
146
    protected const CONTROLLER_MAP = [
147
        self::APPLICATION_TYPE_WEB => [
148
            Oauth2ServerControllerInterface::CONTROLLER_NAME => [
149
                'controller' => Oauth2ServerControllerInterface::class,
150
                'serverRole' => self::SERVER_ROLE_AUTHORIZATION_SERVER,
151
            ],
152
            Oauth2ConsentControllerInterface::CONTROLLER_NAME => [
153
                'controller' => Oauth2ConsentControllerInterface::class,
154
                'serverRole' => self::SERVER_ROLE_AUTHORIZATION_SERVER,
155
            ],
156
            Oauth2WellKnownControllerInterface::CONTROLLER_NAME => [
157
                'controller' => Oauth2WellKnownControllerInterface::class,
158
                'serverRole' => self::SERVER_ROLE_AUTHORIZATION_SERVER,
159
            ],
160
            Oauth2CertificatesControllerInterface::CONTROLLER_NAME => [
161
                'controller' => Oauth2CertificatesControllerInterface::class,
162
                'serverRole' => self::SERVER_ROLE_AUTHORIZATION_SERVER,
163
            ],
164
            Oauth2OidcControllerInterface::CONTROLLER_NAME => [
165
                'controller' => Oauth2OidcControllerInterface::class,
166
                'serverRole' => self::SERVER_ROLE_AUTHORIZATION_SERVER,
167
            ],
168
        ],
169
        self::APPLICATION_TYPE_CONSOLE => [
170
            'migrations' => [
171
                'controller' => Oauth2MigrationsController::class,
172
                'serverRole' => self::SERVER_ROLE_AUTHORIZATION_SERVER | self::SERVER_ROLE_RESOURCE_SERVER,
173
            ],
174
            'client' => [
175
                'controller' => Oauth2ClientController::class,
176
                'serverRole' => self::SERVER_ROLE_AUTHORIZATION_SERVER,
177
            ],
178
            'encryption' => [
179
                'controller' => Oauth2EncryptionController::class,
180
                'serverRole' => self::SERVER_ROLE_AUTHORIZATION_SERVER,
181
            ],
182
            'debug' => [
183
                'controller' => Oauth2DebugController::class,
184
                'serverRole' => self::SERVER_ROLE_AUTHORIZATION_SERVER | self::SERVER_ROLE_RESOURCE_SERVER,
185
            ],
186
        ]
187
    ];
188
189
    /**
190
     * @inheritdoc
191
     */
192
    public $controllerNamespace = __NAMESPACE__ . '\-'; // Set explicitly via $controllerMap in `init()`.
193
194
    /**
195
     * @var string|null The application type. If `null` the type will be automatically detected.
196
     * @see APPLICATION_TYPES
197
     */
198
    public $appType = null;
199
200
    /**
201
     * @var int The Oauth 2.0 Server Roles the module will perform.
202
     * @since 1.0.0
203
     */
204
    public $serverRole = self::SERVER_ROLE_AUTHORIZATION_SERVER | self::SERVER_ROLE_RESOURCE_SERVER;
205
206
    /**
207
     * @var string|null The private key for the server. Can be a string containing the key itself or point to a file.
208
     * When pointing to a file it's recommended to use an absolute path prefixed with 'file://' or start with
209
     * '@' to use a Yii path alias.
210
     * @see $privateKeyPassphrase For setting a passphrase for the private key.
211
     * @since 1.0.0
212
     */
213
    public $privateKey = null;
214
215
    /**
216
     * @var string|null The passphrase for the private key.
217
     * @since 1.0.0
218
     */
219
    public $privateKeyPassphrase = null;
220
    /**
221
     * @var string|null The public key for the server. Can be a string containing the key itself or point to a file.
222
     * When pointing to a file it's recommended to use an absolute path prefixed with 'file://' or start with
223
     * '@' to use a Yii path alias.
224
     * @since 1.0.0
225
     */
226
    public $publicKey = null;
227
228
    /**
229
     * @var string|null The encryption key for authorization and refresh codes.
230
     * @since 1.0.0
231
     */
232
    public $codesEncryptionKey = null;
233
234
    /**
235
     * @var string[]|string|null The encryption keys for storage like client secrets.
236
     * Where the array key is the name of the key, and the value the key itself. E.g.
237
     * `['2022-01-01' => 'def00000cb36fd6ed6641e0ad70805b28d....']`
238
     * If a string (instead of an array of strings) is specified it will be JSON decoded
239
     * it should contain an object where each property name is the name of the key, its value the key itself. E.g.
240
     * `{"2022-01-01": "def00000cb36fd6ed6641e0ad70805b28d...."}`
241
     *
242
     * @since 1.0.0
243
     */
244
    public $storageEncryptionKeys = null;
245
246
    /**
247
     * @var string|null The index of the default key in storageEncryptionKeys. E.g. 'myKey'.
248
     * @since 1.0.0
249
     */
250
    public $defaultStorageEncryptionKey = null;
251
252
    /**
253
     * @var class-string<Oauth2UserInterface>|null The Identity Class of your application,
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Oauth2UserInterface>|null at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Oauth2UserInterface>|null.
Loading history...
254
     * most likely the same as the 'identityClass' of your application's User Component.
255
     * @since 1.0.0
256
     */
257
    public $identityClass = null;
258
259
    /**
260
     * @var null|string Prefix used for url rules. When `null` the module's uniqueId will be used.
261
     * @since 1.0.0
262
     */
263
    public $urlRulesPrefix = null;
264
265
    /**
266
     * @var string URL path for the access token endpoint (will be prefixed with $urlRulesPrefix).
267
     * @since 1.0.0
268
     */
269
    public $authorizePath = 'authorize';
270
271
    /**
272
     * @var string URL path for the access token endpoint (will be prefixed with $urlRulesPrefix).
273
     * @since 1.0.0
274
     */
275
    public $accessTokenPath = 'access-token';
276
277
    /**
278
     * @var string URL path for the certificates jwks endpoint (will be prefixed with $urlRulesPrefix).
279
     * @since 1.0.0
280
     */
281
    public $jwksPath = 'certs';
282
283
    /**
284
     * The URL to the page where the user can perform the client/scope authorization
285
     * (if `null` the build in page will be used).
286
     * @return string
287
     * @since 1.0.0
288
     */
289
    public $clientAuthorizationUrl = null;
290
291
    /**
292
     * @var string The URL path to the build in page where the user can authorize the client for the requested scopes
293
     * (will be prefixed with $urlRulesPrefix).
294
     * Note: This setting will only be used if $clientAuthorizationUrl is `null`.
295
     * @since 1.0.0
296
     */
297
    public $clientAuthorizationPath = 'authorize-client';
298
299
    /**
300
     * @var string The view to use in the "client authorization action" for the page where the user can
301
     * authorize the client for the requested scopes.
302
     * Note: This setting will only be used if $clientAuthorizationUrl is `null`.
303
     * @since 1.0.0
304
     */
305
    public $clientAuthorizationView = 'authorize-client';
306
307
    /**
308
     * @var string The URL path to the OpenID Connect Userinfo Action (will be prefixed with $urlRulesPrefix).
309
     * Note: This setting will only be used if $enableOpenIdConnect and $openIdConnectUserinfoEndpoint are `true`.
310
     * @since 1.0.0
311
     */
312
    public $openIdConnectUserinfoPath = 'oidc/userinfo';
313
314
    /**
315
     * @var Oauth2GrantTypeFactoryInterface[]|GrantTypeInterface[]|string[]|Oauth2GrantTypeFactoryInterface|GrantTypeInterface|string|callable
316
     * The Oauth 2.0 Grant Types that the module will serve.
317
     * @since 1.0.0
318
     */
319
    public $grantTypes = [];
320
321
    /**
322
     * @var string|null Default Time To Live for the access token, used when the Grant Type does not specify it.
323
     * When `null` default value of 1 hour is used.
324
     * The format should be a DateInterval duration (https://www.php.net/manual/en/dateinterval.construct.php).
325
     * @since 1.0.0
326
     */
327
    public $defaultAccessTokenTTL = null;
328
329
    /**
330
     * @var bool Should the resource server check for revocation of the access token.
331
     * @since 1.0.0
332
     */
333
    public $resourceServerAccessTokenRevocationValidation = true;
334
335
    /**
336
     * @var bool Enable support for OpenIdvConnect.
337
     * @since 1.0.0
338
     */
339
    public $enableOpenIdConnect = false;
340
341
    /**
342
     * @var bool Enable the .well-known/openid-configuration discovery endpoint.
343
     * @since 1.0.0
344
     */
345
    public $enableOpenIdConnectDiscovery = true;
346
347
    /**
348
     * @var bool include `grant_types_supported` in the OpenIdConnect Discovery.
349
     * Note: Since grant types can be specified per client not all clients might support all enabled grant types.
350
     * @since 1.0.0
351
     */
352
    public $openIdConnectDiscoveryIncludeSupportedGrantTypes = true;
353
354
    /**
355
     * @var string URL to include in the OpenID Connect Discovery Service of a page containing
356
     * human-readable information that developers might want or need to know when using the OpenID Provider.
357
     * @see 'service_documentation' in https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3
358
     * @since 1.0.0
359
     */
360
    public $openIdConnectDiscoveryServiceDocumentationUrl = null;
361
362
    /**
363
     * @var string|bool A string to a custom userinfo endpoint or `true` to enable the build in endpoint.
364
     * @since 1.0.0
365
     */
366
    public $openIdConnectUserinfoEndpoint = true;
367
368
    /**
369
     * Warning! Enabling this setting might introduce privacy concerns since the client could poll for the
370
     * online status of a user.
371
     *
372
     * @var bool If this setting is disabled in case of OpenID Connect Context the Access Token won't include a
373
     * Refresh Token when the 'offline_access' scope is not included in the authorization request.
374
     * In some cases it might be needed to always include a Refresh Token, in that case enable this setting and
375
     * implement the `Oauth2OidcUserSessionStatusInterface` on the User Identity model.
376
     * @since 1.0.0
377
     */
378
    public $openIdConnectIssueRefreshTokenWithoutOfflineAccessScope = false;
379
380
    /**
381
     * @var int The default option for "User Account Selection' when not specified for a client.
382
     * @since 1.0.0
383
     */
384
    public $defaultUserAccountSelection = self::USER_ACCOUNT_SELECTION_DISABLED;
385
386
    /**
387
     * @var bool|null Display exception messages that might leak server details. This could be useful for debugging.
388
     * In case of `null` (default) the YII_DEBUG constant will be used.
389
     * Warning: Should NOT be enabled in production!
390
     * @since 1.0.0
391
     */
392
    public $displayConfidentialExceptionMessages = null;
393
394
    /**
395
     * @var string|null The namespace with which migrations will be created (and by which they will be located).
396
     * Note: The specified namespace must be defined as a Yii alias (e.g. '@app').
397
     * @since 1.0.0
398
     */
399
    public $migrationsNamespace = null;
400
    /**
401
     * @var string|null Optional prefix used in the name of generated migrations
402
     * @since 1.0.0
403
     */
404
    public $migrationsPrefix = null;
405
    /**
406
     * @var string|array|int|null Sets the file ownership of generated migrations
407
     * @see \yii\helpers\BaseFileHelper::changeOwnership()
408
     * @since 1.0.0
409
     */
410
    public $migrationsFileOwnership = null;
411
    /**
412
     * @var int|null Sets the file mode of generated migrations
413
     * @see \yii\helpers\BaseFileHelper::changeOwnership()
414
     * @since 1.0.0
415
     */
416
    public $migrationsFileMode = null;
417
418
    /**
419
     * @var Oauth2AuthorizationServerInterface|null Cache for the authorization server
420
     * @since 1.0.0
421
     */
422
    protected $_authorizationServer = null;
423
424
    /**
425
     * @var Oauth2ResourceServerInterface|null Cache for the resource server
426
     * @since 1.0.0
427
     */
428
    protected $_resourceServer = null;
429
430
    /**
431
     * @var Oauth2EncryptorInterface|null Cache for the Oauth2Encryptor
432
     * @since 1.0.0
433
     */
434
    protected $_encryptor = null;
435
436
    /**
437
     * @var string|null The authorization header used when the authorization request was validated.
438
     * @since 1.0.0
439
     */
440
    protected $_oauthClaimsAuthorizationHeader = null;
441
442
    /**
443
     * @inheritDoc
444
     * @throws InvalidConfigException
445
     */
446 114
    public function init()
447
    {
448 114
        parent::init();
449
450 114
        $app = Yii::$app;
451
452 114
        if ($app instanceof WebApplication || $this->appType == static::APPLICATION_TYPE_WEB) {
453 18
            $controllerMap = static::CONTROLLER_MAP[static::APPLICATION_TYPE_WEB];
454 114
        } elseif ($app instanceof ConsoleApplication || $this->appType == static::APPLICATION_TYPE_CONSOLE) {
455 114
            $controllerMap = static::CONTROLLER_MAP[static::APPLICATION_TYPE_CONSOLE];
456
        } else {
457
            throw new InvalidConfigException(
458
                'Unable to detect application type, configure it manually by setting `$appType`.'
459
            );
460
        }
461 114
        $controllerMap = array_filter(
462
            $controllerMap,
463 114
            fn($controllerSettings) => $controllerSettings['serverRole'] & $this->serverRole
464
        );
465 114
        $this->controllerMap = ArrayHelper::getColumn($controllerMap, 'controller');
466
467 114
        if (empty($this->identityClass)) {
468 1
            throw new InvalidConfigException('$identityClass must be set.');
469 114
        } elseif (!is_a($this->identityClass, Oauth2UserInterface::class, true)) {
470 1
            throw new InvalidConfigException(
471 1
                $this->identityClass . ' must implement ' . Oauth2UserInterface::class
472
            );
473
        }
474
475 114
        foreach (static::DEFAULT_INTERFACE_IMPLEMENTATIONS as $interface => $implementation) {
476 114
            if (!Yii::$container->has($interface)) {
477 114
                Yii::$container->set($interface, $implementation);
478
            }
479
        }
480
481 114
        if (empty($this->urlRulesPrefix)) {
482 114
            $this->urlRulesPrefix = $this->uniqueId;
483
        }
484
485 114
        $this->registerTranslations();
486
    }
487
488
    /**
489
     * @inheritdoc
490
     * @throws InvalidConfigException
491
     */
492 114
    public function bootstrap($app)
493
    {
494
        if (
495 114
            $app instanceof WebApplication
496 114
            && $this->serverRole & static::SERVER_ROLE_AUTHORIZATION_SERVER
497
        ) {
498
            $rules = [
499 18
                $this->accessTokenPath => Oauth2ServerControllerInterface::CONTROLLER_NAME
500
                    . '/' . Oauth2ServerControllerInterface::ACTION_NAME_ACCESS_TOKEN,
501 18
                $this->authorizePath => Oauth2ServerControllerInterface::CONTROLLER_NAME
502
                    . '/' . Oauth2ServerControllerInterface::ACTION_NAME_AUTHORIZE,
503 18
                $this->jwksPath => Oauth2CertificatesControllerInterface::CONTROLLER_NAME
504
                    . '/' . Oauth2CertificatesControllerInterface::ACTION_NAME_JWKS,
505
            ];
506
507 18
            if (empty($this->clientAuthorizationUrl)) {
508 17
                $rules[$this->clientAuthorizationPath] = Oauth2ConsentControllerInterface::CONTROLLER_NAME
509
                    . '/' . Oauth2ConsentControllerInterface::ACTION_NAME_AUTHORIZE_CLIENT;
510
            }
511
512 18
            if ($this->enableOpenIdConnect && $this->openIdConnectUserinfoEndpoint === true) {
513 18
                $rules[$this->openIdConnectUserinfoPath] =
514 18
                    Oauth2OidcControllerInterface::CONTROLLER_NAME
515
                    . '/' . Oauth2OidcControllerInterface::ACTION_NAME_USERINFO;
516
            }
517
518 18
            $urlManager = $app->getUrlManager();
519 18
            $urlManager->addRules([
520 18
                Yii::createObject([
521
                    'class' => GroupUrlRule::class,
522 18
                    'prefix' => $this->urlRulesPrefix,
523 18
                    'routePrefix' => $this->id,
524
                    'rules' => $rules,
525
                ]),
526
            ]);
527
528 18
            if ($this->enableOpenIdConnect && $this->enableOpenIdConnectDiscovery) {
529 18
                $urlManager->addRules([
530 18
                    Yii::createObject([
531
                        'class' => UrlRule::class,
532
                        'pattern' => '.well-known/openid-configuration',
533 18
                        'route' => $this->id
534
                            . '/' . Oauth2WellKnownControllerInterface::CONTROLLER_NAME
535
                            . '/' . Oauth2WellKnownControllerInterface::ACTION_NAME_OPENID_CONFIGURATION,
536
                    ]),
537
                ]);
538
            }
539
        }
540
    }
541
542
    /**
543
     * Registers the translations for the module
544
     * @param bool $force Force the setting of the translations (even if they are already defined).
545
     * @since 1.0.0
546
     */
547 114
    public function registerTranslations($force = false)
548
    {
549 114
        if ($force || !array_key_exists('oauth2', Yii::$app->i18n->translations)) {
550 114
            Yii::$app->i18n->translations['oauth2'] = [
551
                'class' => PhpMessageSource::class,
552
                'sourceLanguage' => 'en-US',
553 114
                'basePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages',
554
                'fileMap' => [
555
                    'oauth2' => 'oauth2.php',
556
                ],
557
            ];
558
        }
559
    }
560
561
    /**
562
     * @param string $identifier
563
     * @param string $name
564
     * @param int $grantTypes
565
     * @param string|string[] $redirectURIs
566
     * @param string $type
567
     * @param string|null $secret
568
     * @param string|string[]|array[]|null $scopes
569
     * @param int|null $userId
570
     * @return Oauth2ClientInterface
571
     * @throws InvalidConfigException
572
     * @throws \yii\db\Exception
573
     */
574 4
    public function createClient(
575
        $identifier,
576
        $name,
577
        $grantTypes,
578
        $redirectURIs,
579
        $type,
580
        $secret = null,
581
        $scopes = null,
582
        $userId = null
583
    ) {
584 4
        if (!($this->serverRole & static::SERVER_ROLE_AUTHORIZATION_SERVER)) {
585 1
            throw new InvalidCallException('Oauth2 server role does not include authorization server.');
586
        }
587
588
        /** @var Oauth2ClientInterface $client */
589 3
        $client = Yii::createObject([
590
            'class' => Oauth2ClientInterface::class,
591
            'identifier' => $identifier,
592
            'type' => $type,
593
            'name' => $name,
594
            'redirectUri' => $redirectURIs,
595
            'grantTypes' => $grantTypes,
596
            'client_credentials_grant_user_id' => $userId
597
        ]);
598
599 3
        $transaction = $client::getDb()->beginTransaction();
600
601
        try {
602 3
            if ($type == Oauth2ClientInterface::TYPE_CONFIDENTIAL) {
603 3
                $client->setSecret($secret, $this->getEncryptor());
604
            }
605
606 2
            $client->persist();
607
608 2
            if (!empty($scopes)) {
609 2
                if (is_string($scopes)) {
610 2
                    $scopeIdentifiers = explode(' ', $scopes);
611
                } else {
612
                    $scopeIdentifiers = $scopes;
613
                }
614
615 2
                foreach ($scopeIdentifiers as $scopeIdentifier => $scopeConfig) {
616 2
                    if (is_string($scopeConfig)) {
617 2
                        $scopeIdentifier = $scopeConfig;
618 2
                        $scopeConfig = [];
619
                    }
620 2
                    $scope = $this->getScopeRepository()->findModelByIdentifier($scopeIdentifier);
621 2
                    if (empty($scope)) {
622 1
                        throw new InvalidArgumentException('No scope with identifier "'
623
                            . $scopeIdentifier . '" found.');
624
                    }
625
626
                    /** @var Oauth2ClientScopeInterface $clientScope */
627 1
                    $clientScope = Yii::createObject(ArrayHelper::merge(
628
                        $scopeConfig,
629
                        [
630 1
                            'class' => Oauth2ClientScopeInterface::class,
631 1
                            'client_id' => $client->getPrimaryKey(),
632 1
                            'scope_id' => $scope->getPrimaryKey(),
633
                        ]
634
                    ));
635 1
                    $clientScope->persist();
636
                }
637
            }
638
639 1
            $transaction->commit();
640 2
        } catch (\Exception $e) {
641 2
            $transaction->rollBack();
642 2
            throw $e;
643
        }
644
645 1
        return $client;
646
    }
647
648
    /**
649
     * @return CryptKey The private key of the server.
650
     * @throws InvalidConfigException
651
     * @since 1.0.0
652
     */
653 17
    public function getPrivateKey()
654
    {
655 17
        $privateKey = $this->privateKey;
656 17
        if (StringHelper::startsWith($privateKey, '@')) {
657 14
            $privateKey = 'file://' . Yii::getAlias($privateKey);
0 ignored issues
show
Bug introduced by
Are you sure Yii::getAlias($privateKey) of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

657
            $privateKey = 'file://' . /** @scrutinizer ignore-type */ Yii::getAlias($privateKey);
Loading history...
658
        }
659 17
        return Yii::createObject(CryptKey::class, [$privateKey, $this->privateKeyPassphrase]);
660
    }
661
662
    /**
663
     * @return CryptKey The public key of the server.
664
     * @throws InvalidConfigException
665
     * @since 1.0.0
666
     */
667 9
    public function getPublicKey()
668
    {
669 9
        $publicKey = $this->publicKey;
670 9
        if (StringHelper::startsWith($publicKey, '@')) {
671 6
            $publicKey = 'file://' . Yii::getAlias($publicKey);
0 ignored issues
show
Bug introduced by
Are you sure Yii::getAlias($publicKey) of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

671
            $publicKey = 'file://' . /** @scrutinizer ignore-type */ Yii::getAlias($publicKey);
Loading history...
672
        }
673 9
        return Yii::createObject(CryptKey::class, [$publicKey]);
674
    }
675
676
    /**
677
     * @return Oauth2AuthorizationServerInterface The authorization server.
678
     * @throws InvalidConfigException
679
     * @since 1.0.0
680
     */
681 22
    public function getAuthorizationServer()
682
    {
683 22
        if (!($this->serverRole & static::SERVER_ROLE_AUTHORIZATION_SERVER)) {
684 1
            throw new InvalidCallException('Oauth2 server role does not include authorization server.');
685
        }
686
687 21
        if (!$this->_authorizationServer) {
688 21
            $this->ensureProperties(static::REQUIRED_SETTINGS_AUTHORIZATION_SERVER);
689
690 16
            if (!$this->getEncryptor()->hasKey($this->defaultStorageEncryptionKey)) {
691 1
                throw new InvalidConfigException(
692 1
                    'Key "' . $this->defaultStorageEncryptionKey . '" is not set in $storageEncryptionKeys'
693
                );
694
            }
695
696
            /** @var Oauth2EncryptionKeyFactoryInterface $keyFactory */
697 14
            $keyFactory = Yii::createObject(Oauth2EncryptionKeyFactoryInterface::class);
698
            try {
699 14
                $codesEncryptionKey = $keyFactory->createFromAsciiSafeString($this->codesEncryptionKey);
700 1
            } catch (BadFormatException $e) {
701 1
                throw new InvalidConfigException(
702 1
                    '$codesEncryptionKey is malformed: ' . $e->getMessage(),
703
                    0,
704
                    $e
705
                );
706
            } catch (EnvironmentIsBrokenException $e) {
707
                throw new InvalidConfigException(
708
                    'Could not instantiate $codesEncryptionKey: ' . $e->getMessage(),
709
                    0,
710
                    $e
711
                );
712
            }
713
714 13
            $responseType = null;
715 13
            if ($this->enableOpenIdConnect) {
716 13
                $responseType = Yii::createObject(Oauth2OidcBearerTokenResponseInterface::class, [
717
                    $this,
718
                ]);
719
            }
720
721 13
            $this->_authorizationServer = Yii::createObject(Oauth2AuthorizationServerInterface::class, [
722 13
                $this->getClientRepository(),
723 13
                $this->getAccessTokenRepository(),
724 13
                $this->getScopeRepository(),
725 13
                $this->getPrivateKey(),
726
                $codesEncryptionKey,
727
                $responseType
728
            ]);
729
730 13
            if (!empty($this->grantTypes)) {
731 13
                $grantTypes = $this->grantTypes;
732
733 13
                if (is_callable($grantTypes)) {
734 1
                    call_user_func($grantTypes, $this->_authorizationServer, $this);
0 ignored issues
show
Bug introduced by
It seems like $grantTypes can also be of type League\OAuth2\Server\Grant\GrantTypeInterface and rhertogh\Yii2Oauth2Serve...antTypeFactoryInterface; however, parameter $callback of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

734
                    call_user_func(/** @scrutinizer ignore-type */ $grantTypes, $this->_authorizationServer, $this);
Loading history...
735
                } else {
736 12
                    if (!is_array($grantTypes)) {
737 2
                        $grantTypes = [$grantTypes];
738
                    }
739
740 12
                    foreach ($grantTypes as $grantTypeDefinition) {
741 12
                        if ($grantTypeDefinition instanceof GrantTypeInterface) {
742 1
                            $accessTokenTTL = $this->defaultAccessTokenTTL
743
                                ? new \DateInterval($this->defaultAccessTokenTTL)
744 1
                                : null;
745 1
                            $this->_authorizationServer->enableGrantType($grantTypeDefinition, $accessTokenTTL);
746
                        } elseif (
747
                            (
748 11
                                is_numeric($grantTypeDefinition)
749 11
                                && array_key_exists($grantTypeDefinition, static::DEFAULT_GRANT_TYPE_FACTORIES)
750
                            )
751 11
                            || is_a($grantTypeDefinition, Oauth2GrantTypeFactoryInterface::class, true)
752
                        ) {
753
                            if (
754 10
                                is_numeric($grantTypeDefinition)
755 10
                                && array_key_exists($grantTypeDefinition, static::DEFAULT_GRANT_TYPE_FACTORIES)
756
                            ) {
757 10
                                $grantTypeDefinition = static::DEFAULT_GRANT_TYPE_FACTORIES[$grantTypeDefinition];
758
                            }
759
760
                            /** @var Oauth2GrantTypeFactoryInterface $factory */
761 10
                            $factory = Yii::createObject([
762
                                'class' => $grantTypeDefinition,
763
                                'module' => $this,
764
                            ]);
765 10
                            $accessTokenTTL = $factory->accessTokenTTL ?? $this->defaultAccessTokenTTL ?? null;
0 ignored issues
show
Bug introduced by
Accessing accessTokenTTL on the interface rhertogh\Yii2Oauth2Serve...antTypeFactoryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
766 10
                            $this->_authorizationServer->enableGrantType(
767 10
                                $factory->getGrantType(),
768 10
                                $accessTokenTTL ? new \DateInterval($accessTokenTTL) : null
769
                            );
770
                        } else {
771 1
                            throw new InvalidConfigException(
772
                                'Unknown grantType '
773
                                . (
774 1
                                    is_scalar($grantTypeDefinition)
775 1
                                        ? '"' . $grantTypeDefinition . '".'
776 1
                                        : 'with data type ' . gettype($grantTypeDefinition)
777
                                )
778
                            );
779
                        }
780
                    }
781
                }
782
            }
783
        }
784
785 12
        return $this->_authorizationServer;
786
    }
787
788
    /**
789
     * @inheritDoc
790
     * @throws InvalidConfigException
791
     */
792 5
    public function getOidcScopeCollection()
793
    {
794 5
        if ($this->_oidcScopeCollection === null) {
795 5
            $openIdConnectScopes = $this->getOpenIdConnectScopes();
796 5
            if ($openIdConnectScopes instanceof Oauth2OidcScopeCollectionInterface) {
797 1
                $this->_oidcScopeCollection = $openIdConnectScopes;
798 4
            } elseif (is_callable($openIdConnectScopes)) {
799 1
                $this->_oidcScopeCollection = call_user_func($openIdConnectScopes, $this);
800 1
                if (!($this->_oidcScopeCollection instanceof Oauth2OidcScopeCollectionInterface)) {
801
                    throw new InvalidConfigException(
802
                        '$openIdConnectScopes must return an instance of '
803
                            . Oauth2OidcScopeCollectionInterface::class
804
                    );
805
                }
806 3
            } elseif (is_array($openIdConnectScopes) || is_string($openIdConnectScopes)) {
807 2
                $this->_oidcScopeCollection = Yii::createObject([
808
                    'class' => Oauth2OidcScopeCollectionInterface::class,
809 2
                    'oidcScopes' => (array)$openIdConnectScopes,
810
                ]);
811
            } else {
812 1
                throw new InvalidConfigException(
813
                    '$openIdConnectScopes must be a callable, array, string or '
814
                        . Oauth2OidcScopeCollectionInterface::class
815
                );
816
            }
817
        }
818
819 4
        return $this->_oidcScopeCollection;
820
    }
821
822
    /**
823
     * @return Oauth2ResourceServerInterface The resource server.
824
     * @throws InvalidConfigException
825
     * @since 1.0.0
826
     */
827 7
    public function getResourceServer()
828
    {
829 7
        if (!($this->serverRole & static::SERVER_ROLE_RESOURCE_SERVER)) {
830 1
            throw new InvalidCallException('Oauth2 server role does not include resource server.');
831
        }
832
833 6
        if (!$this->_resourceServer) {
834 6
            $this->ensureProperties(static::REQUIRED_SETTINGS_RESOURCE_SERVER);
835
836 5
            $accessTokenRepository = $this->getAccessTokenRepository()
837 5
                ->setRevocationValidation($this->resourceServerAccessTokenRevocationValidation);
838
839 5
            $this->_resourceServer = Yii::createObject(Oauth2ResourceServerInterface::class, [
840
                $accessTokenRepository,
841 5
                $this->getPublicKey(),
842
            ]);
843
        }
844
845 5
        return $this->_resourceServer;
846
    }
847
848
    /**
849
     * @return Oauth2EncryptorInterface The data encryptor for the module.
850
     * @throws InvalidConfigException
851
     * @since 1.0.0
852
     */
853 21
    public function getEncryptor()
854
    {
855 21
        if (!$this->_encryptor) {
856 21
            $this->_encryptor = Yii::createObject([
857
                'class' => Oauth2EncryptorInterface::class,
858 21
                'keys' => $this->storageEncryptionKeys,
859 21
                'defaultKeyName' => $this->defaultStorageEncryptionKey,
860
            ]);
861
        }
862
863 20
        return $this->_encryptor;
864
    }
865
866
    /**
867
     * @param string|null $newKeyName
868
     * @return array
869
     * @throws InvalidConfigException
870
     */
871 1
    public function rotateStorageEncryptionKeys($newKeyName = null)
872
    {
873 1
        $encryptor = $this->getEncryptor();
874
875 1
        $result = [];
876 1
        foreach (static::ENCRYPTED_MODELS as $modelInterface) {
877 1
            $modelClass = DiHelper::getValidatedClassName($modelInterface);
878 1
            if (!is_a($modelClass, Oauth2EncryptedStorageInterface::class, true)) {
879
                throw new InvalidConfigException($modelInterface . ' must implement '
880
                    . Oauth2EncryptedStorageInterface::class);
881
            }
882 1
            $result[$modelClass] = $modelClass::rotateStorageEncryptionKeys($encryptor, $newKeyName);
883
        }
884
885 1
        return $result;
886
    }
887
888
    /**
889
     * @return array
890
     * @throws InvalidConfigException
891
     */
892
    public function getStorageEncryptionKeyUsage()
893
    {
894
        $encryptor = $this->getEncryptor();
895
896
        $result = [];
897
        foreach (static::ENCRYPTED_MODELS as $modelInterface) {
898
            $modelClass = DiHelper::getValidatedClassName($modelInterface);
899
            if (!is_a($modelClass, Oauth2EncryptedStorageInterface::class, true)) {
900
                throw new InvalidConfigException($modelInterface . ' must implement '
901
                    . Oauth2EncryptedStorageInterface::class);
902
            }
903
904
            $result[$modelClass] = $modelClass::getUsedStorageEncryptionKeys($encryptor);
905
        }
906
907
        return $result;
908
    }
909
910
    /**
911
     * Generates a redirect Response to the client authorization page where the user is prompted to authorize the
912
     * client and requested scope.
913
     * @param Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest
914
     * @return Response
915
     * @since 1.0.0
916
     */
917 5
    public function generateClientAuthReqRedirectResponse($clientAuthorizationRequest)
918
    {
919 5
        $this->setClientAuthReqSession($clientAuthorizationRequest);
920 5
        if (!empty($this->clientAuthorizationUrl)) {
921 1
            $url = $this->clientAuthorizationUrl;
922
        } else {
923 4
            $url = $this->uniqueId
924
                . '/' . Oauth2ConsentControllerInterface::CONTROLLER_NAME
925
                . '/' . Oauth2ConsentControllerInterface::ACTION_NAME_AUTHORIZE_CLIENT;
926
        }
927 5
        return Yii::$app->response->redirect([
928
            $url,
929 5
            'clientAuthorizationRequestId' => $clientAuthorizationRequest->getRequestId(),
930
        ]);
931
    }
932
933
    /**
934
     * Get a previously stored Client Authorization Request from the session.
935
     * @param string $requestId
936
     * @return Oauth2ClientAuthorizationRequestInterface|null
937
     * @since 1.0.0
938
     */
939 5
    public function getClientAuthReqSession($requestId)
940
    {
941 5
        if (empty($requestId)) {
942
            return null;
943
        }
944 5
        $key = static::CLIENT_AUTHORIZATION_REQUEST_SESSION_PREFIX . $requestId;
945 5
        $clientAuthorizationRequest = Yii::$app->session->get($key);
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

945
        /** @scrutinizer ignore-call */ 
946
        $clientAuthorizationRequest = Yii::$app->session->get($key);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
946 5
        if (!($clientAuthorizationRequest instanceof Oauth2ClientAuthorizationRequestInterface)) {
947 2
            if (!empty($clientAuthorizationRequest)) {
948 1
                Yii::warning(
949 1
                    'Found a ClientAuthorizationRequestSession with key "' . $key
950
                        . '", but it\'s not a ' . Oauth2ClientAuthorizationRequestInterface::class
951
                );
952
            }
953 2
            return null;
954
        }
955 5
        if ($clientAuthorizationRequest->getRequestId() !== $requestId) {
956 1
            Yii::warning(
957 1
                'Found a ClientAuthorizationRequestSession with key "' . $key
958
                    . '", but it\'s request id does not match "' . $requestId . '".'
959
            );
960 1
            return null;
961
        }
962 5
        $clientAuthorizationRequest->setModule($this);
963
964 5
        return $clientAuthorizationRequest;
965
    }
966
967
    /**
968
     * Stores the Client Authorization Request in the session.
969
     * @param Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest
970
     * @since 1.0.0
971
     */
972 8
    public function setClientAuthReqSession($clientAuthorizationRequest)
973
    {
974 8
        $requestId = $clientAuthorizationRequest->getRequestId();
975 8
        if (empty($requestId)) {
976 1
            throw new InvalidArgumentException('$scopeAuthorization must return a request id.');
977
        }
978 7
        $key = static::CLIENT_AUTHORIZATION_REQUEST_SESSION_PREFIX . $requestId;
979 7
        Yii::$app->session->set($key, $clientAuthorizationRequest);
980
    }
981
982
    /**
983
     * Stores whether the user was authenticated during the completion of the Client Authorization Request.
984
     * @param string $clientAuthorizationRequestId
985
     * @param bool $authenticatedDuringRequest
986
     * @since 1.0.0
987
     */
988
    public function setUserAuthenticatedDuringClientAuthRequest(
989
        $clientAuthorizationRequestId,
990
        $authenticatedDuringRequest
991
    ) {
992
        $clientAuthorizationRequest = $this->getClientAuthReqSession($clientAuthorizationRequestId);
993
        if ($clientAuthorizationRequest) {
994
            $clientAuthorizationRequest->setUserAuthenticatedDuringRequest($authenticatedDuringRequest);
995
            $this->setClientAuthReqSession($clientAuthorizationRequest);
996
        }
997
    }
998
999
    /**
1000
     * Stores the user identity selected during the completion of the Client Authorization Request.
1001
     * @param string $clientAuthorizationRequestId
1002
     * @param Oauth2UserInterface $userIdentity
1003
     * @since 1.0.0
1004
     */
1005
    public function setClientAuthRequestUserIdentity($clientAuthorizationRequestId, $userIdentity)
1006
    {
1007
        $clientAuthorizationRequest = $this->getClientAuthReqSession($clientAuthorizationRequestId);
1008
        if ($clientAuthorizationRequest) {
1009
            $clientAuthorizationRequest->setUserIdentity($userIdentity);
1010
            $this->setClientAuthReqSession($clientAuthorizationRequest);
1011
        }
1012
    }
1013
1014
    /**
1015
     * Clears a Client Authorization Request from the session storage.
1016
     * @param string $requestId
1017
     * @since 1.0.0
1018
     */
1019 2
    public function removeClientAuthReqSession($requestId)
1020
    {
1021 2
        if (empty($requestId)) {
1022 1
            throw new InvalidArgumentException('$requestId can not be empty.');
1023
        }
1024 1
        $key = static::CLIENT_AUTHORIZATION_REQUEST_SESSION_PREFIX . $requestId;
1025 1
        Yii::$app->session->remove($key);
1026
    }
1027
1028
    /**
1029
     * Generates a redirect Response when the Client Authorization Request is completed.
1030
     * @param Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest
1031
     * @return Response
1032
     * @since 1.0.0
1033
     */
1034 1
    public function generateClientAuthReqCompledRedirectResponse($clientAuthorizationRequest)
1035
    {
1036 1
        $clientAuthorizationRequest->processAuthorization();
1037 1
        $this->setClientAuthReqSession($clientAuthorizationRequest);
1038 1
        return Yii::$app->response->redirect($clientAuthorizationRequest->getAuthorizationRequestUrl());
1039
    }
1040
1041
    /**
1042
     * @return IdentityInterface|Oauth2UserInterface|Oauth2OidcUserInterface|null
1043
     * @throws InvalidConfigException
1044
     * @since 1.0.0
1045
     */
1046 5
    public function getUserIdentity()
1047
    {
1048 5
        $user = Yii::$app->user->identity;
1049 5
        if (!empty($user) && !($user instanceof Oauth2UserInterface)) {
1050 1
            throw new InvalidConfigException(
1051 1
                'Yii::$app->user->identity (currently ' . get_class($user)
1052
                    . ') must implement ' . Oauth2UserInterface::class
1053
            );
1054
        }
1055 4
        return $user;
1056
    }
1057
1058
    /**
1059
     * Validates a bearer token authenticated request. Note: this method does not return a result but will throw
1060
     * an exception when the authentication fails.
1061
     * @throws InvalidConfigException
1062
     * @throws OAuthServerException
1063
     * @since 1.0.0
1064
     */
1065 3
    public function validateAuthenticatedRequest()
1066
    {
1067 3
        $psr7Request = Psr7Helper::yiiToPsr7Request(Yii::$app->request);
0 ignored issues
show
Bug introduced by
It seems like Yii::app->request can also be of type yii\console\Request; however, parameter $request of rhertogh\Yii2Oauth2Serve...per::yiiToPsr7Request() does only seem to accept yii\web\Request, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1067
        $psr7Request = Psr7Helper::yiiToPsr7Request(/** @scrutinizer ignore-type */ Yii::$app->request);
Loading history...
1068
1069 3
        $psr7Request = $this->getResourceServer()->validateAuthenticatedRequest($psr7Request);
1070
1071 3
        $this->_oauthClaims = $psr7Request->getAttributes();
1072 3
        $this->_oauthClaimsAuthorizationHeader = Yii::$app->request->getHeaders()->get('Authorization');
0 ignored issues
show
Documentation Bug introduced by
It seems like Yii::app->request->getHe...)->get('Authorization') can also be of type array. However, the property $_oauthClaimsAuthorizationHeader is declared as type null|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1073
    }
1074
1075
    /**
1076
     * Find a user identity bases on an access token.
1077
     * Note: validateAuthenticatedRequest() must be called before this method is called.
1078
     * @param string $token
1079
     * @param string $type
1080
     * @return Oauth2UserInterface|null
1081
     * @throws InvalidConfigException
1082
     * @throws OAuthServerException
1083
     * @see validateAuthenticatedRequest()
1084
     * @since 1.0.0
1085
     */
1086 4
    public function findIdentityByAccessToken($token, $type)
1087
    {
1088 4
        if (!is_a($type, Oauth2HttpBearerAuthInterface::class, true)) {
1089 1
            throw new InvalidCallException($type . ' must implement ' . Oauth2HttpBearerAuthInterface::class);
1090
        }
1091
1092
        if (
1093 3
            !preg_match('/^Bearer\s+(.*?)$/', $this->_oauthClaimsAuthorizationHeader, $matches)
0 ignored issues
show
Bug introduced by
It seems like $this->_oauthClaimsAuthorizationHeader can also be of type null; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1093
            !preg_match('/^Bearer\s+(.*?)$/', /** @scrutinizer ignore-type */ $this->_oauthClaimsAuthorizationHeader, $matches)
Loading history...
1094 3
            || !Yii::$app->security->compareString($matches[1], $token)
1095
        ) {
1096 1
            throw new InvalidCallException(
1097
                'validateAuthenticatedRequest() must be called before findIdentityByAccessToken().'
1098
            );
1099
        }
1100
1101 2
        $userId = $this->getRequestOauthUserId();
1102 2
        if (empty($userId)) {
1103 1
            return null;
1104
        }
1105
1106 1
        return $this->identityClass::findIdentity($userId);
1107
    }
1108
1109
    /**
1110
     * @inheritDoc
1111
     */
1112 5
    protected function getRequestOauthClaim($attribute, $default = null)
1113
    {
1114 5
        if (empty($this->_oauthClaimsAuthorizationHeader)) {
1115
            // User authorization was not processed by Oauth2Module.
1116 1
            return $default;
1117
        }
1118 4
        if (Yii::$app->request->getHeaders()->get('Authorization') !== $this->_oauthClaimsAuthorizationHeader) {
1119 1
            throw new InvalidCallException(
1120
                'App Request Authorization header does not match the processed Oauth header.'
1121
            );
1122
        }
1123 3
        return $this->_oauthClaims[$attribute] ?? $default;
1124
    }
1125
1126
    /**
1127
     * Helper function to ensure the required properties are configured for the module.
1128
     * @param string[] $properties
1129
     * @throws InvalidConfigException
1130
     * @since 1.0.0
1131
     */
1132 27
    protected function ensureProperties($properties)
1133
    {
1134 27
        foreach ($properties as $property) {
1135 27
            if (empty($this->$property)) {
1136 6
                throw new InvalidConfigException('$' . $property . ' must be set.');
1137
            }
1138
        }
1139
    }
1140
}
1141