Passed
Push — master ( 414e0a...d4ffb9 )
by Rutger
03:50
created

Oauth2Module::rotateStorageEncryptionKeys()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3.0123

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 15
ccs 8
cts 9
cp 0.8889
rs 9.9666
cc 3
nc 3
nop 1
crap 3.0123
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\Oauth2ClientInterface;
39
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2ClientScopeInterface;
40
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2OidcUserInterface;
41
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2UserInterface;
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 Oauth2UserInterface|string|null The Identity Class of your application,
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 bool 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
0 ignored issues
show
Bug introduced by
Are you sure $this->identityClass of type rhertogh\Yii2Oauth2Serve...th2UserInterface|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

471
                /** @scrutinizer ignore-type */ $this->identityClass . ' must implement ' . Oauth2UserInterface::class
Loading history...
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|string[]|null $scopes
568
     * @return Oauth2ClientInterface
569
     * @throws InvalidConfigException
570
     * @throws \yii\db\Exception
571
     */
572 4
    public function createClient($identifier, $name, $grantTypes, $redirectURIs, $type, $secret = null, $scopes = null)
573
    {
574 4
        if (!($this->serverRole & static::SERVER_ROLE_AUTHORIZATION_SERVER)) {
575 1
            throw new InvalidCallException('Oauth2 server role does not include authorization server.');
576
        }
577
578
        /** @var Oauth2ClientInterface $client */
579 3
        $client = Yii::createObject([
580
            'class' => Oauth2ClientInterface::class,
581
            'identifier' => $identifier,
582
            'type' => $type,
583
            'name' => $name,
584
            'redirectUri' => $redirectURIs,
585
            'grantTypes' => $grantTypes,
586
        ]);
587
588 3
        $transaction = $client::getDb()->beginTransaction();
589
590
        try {
591 3
            if ($type == Oauth2ClientInterface::TYPE_CONFIDENTIAL) {
592 3
                $client->setSecret($secret, $this->getEncryptor());
593
            }
594
595 2
            $client->persist();
596
597 2
            if (!empty($scopes)) {
598 2
                if (is_string($scopes)) {
599 2
                    $scopeIdentifiers = explode(' ', $scopes);
600
                } else {
601
                    $scopeIdentifiers = $scopes;
602
                }
603
604 2
                foreach ($scopeIdentifiers as $scopeIdentifier) {
605
606 2
                    $scope = $this->getScopeRepository()->findModelByIdentifier($scopeIdentifier);
607 2
                    if (empty($scope)) {
608 1
                        throw new InvalidArgumentException('No scope with identifier "'
609
                            . $scopeIdentifier . '" found.');
610
                    }
611
612
                    /** @var Oauth2ClientScopeInterface $clientScope */
613 1
                    $clientScope = Yii::createObject([
614
                        'class' => Oauth2ClientScopeInterface::class,
615 1
                        'client_id' => $client->getPrimaryKey(),
616 1
                        'scope_id' => $scope->getPrimaryKey(),
617
                    ]);
618 1
                    $clientScope->persist();
619
                }
620
            }
621
622 1
            $transaction->commit();
623
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
624 2
        } catch (\Exception $e) {
625 2
            $transaction->rollBack();
626 2
            throw $e;
627
        }
628
629 1
        return $client;
630
    }
631
632
    /**
633
     * @return CryptKey The private key of the server.
634
     * @throws InvalidConfigException
635
     * @since 1.0.0
636
     */
637 17
    public function getPrivateKey()
638
    {
639 17
        $privateKey = $this->privateKey;
640 17
        if (StringHelper::startsWith($privateKey, '@')) {
641 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

641
            $privateKey = 'file://' . /** @scrutinizer ignore-type */ Yii::getAlias($privateKey);
Loading history...
642
        }
643 17
        return Yii::createObject(CryptKey::class, [$privateKey, $this->privateKeyPassphrase]);
644
    }
645
646
    /**
647
     * @return CryptKey The public key of the server.
648
     * @throws InvalidConfigException
649
     * @since 1.0.0
650
     */
651 9
    public function getPublicKey()
652
    {
653 9
        $publicKey = $this->publicKey;
654 9
        if (StringHelper::startsWith($publicKey, '@')) {
655 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

655
            $publicKey = 'file://' . /** @scrutinizer ignore-type */ Yii::getAlias($publicKey);
Loading history...
656
        }
657 9
        return Yii::createObject(CryptKey::class, [$publicKey]);
658
    }
659
660
    /**
661
     * @return Oauth2AuthorizationServerInterface The authorization server.
662
     * @throws InvalidConfigException
663
     * @since 1.0.0
664
     */
665 22
    public function getAuthorizationServer()
666
    {
667 22
        if (!($this->serverRole & static::SERVER_ROLE_AUTHORIZATION_SERVER)) {
668 1
            throw new InvalidCallException('Oauth2 server role does not include authorization server.');
669
        }
670
671 21
        if (!$this->_authorizationServer) {
672 21
            $this->ensureProperties(static::REQUIRED_SETTINGS_AUTHORIZATION_SERVER);
673
674 16
            if (!$this->getEncryptor()->hasKey($this->defaultStorageEncryptionKey)) {
675 1
                throw new InvalidConfigException(
676 1
                    'Key "' . $this->defaultStorageEncryptionKey . '" is not set in $storageEncryptionKeys'
677
                );
678
            }
679
680
            /** @var Oauth2EncryptionKeyFactoryInterface $keyFactory */
681 14
            $keyFactory = Yii::createObject(Oauth2EncryptionKeyFactoryInterface::class);
682
            try {
683 14
                $codesEncryptionKey = $keyFactory->createFromAsciiSafeString($this->codesEncryptionKey);
684 1
            } catch (BadFormatException $e) {
685 1
                throw new InvalidConfigException(
686 1
                    '$codesEncryptionKey is malformed: ' . $e->getMessage(),
687
                    0,
688
                    $e
689
                );
690
            } catch (EnvironmentIsBrokenException $e) {
691
                throw new InvalidConfigException(
692
                    'Could not instantiate $codesEncryptionKey: ' . $e->getMessage(),
693
                    0,
694
                    $e
695
                );
696
            }
697
698 13
            $responseType = null;
699 13
            if ($this->enableOpenIdConnect) {
700 13
                $responseType = Yii::createObject(Oauth2OidcBearerTokenResponseInterface::class, [
701
                    $this,
702
                ]);
703
            }
704
705 13
            $this->_authorizationServer = Yii::createObject(Oauth2AuthorizationServerInterface::class, [
706 13
                $this->getClientRepository(),
707 13
                $this->getAccessTokenRepository(),
708 13
                $this->getScopeRepository(),
709 13
                $this->getPrivateKey(),
710
                $codesEncryptionKey,
711
                $responseType
712
            ]);
713
714 13
            if (!empty($this->grantTypes)) {
715 13
                $grantTypes = $this->grantTypes;
716
717 13
                if (is_callable($grantTypes)) {
718 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

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

928
        /** @scrutinizer ignore-call */ 
929
        $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...
929 5
        if (!($clientAuthorizationRequest instanceof Oauth2ClientAuthorizationRequestInterface)) {
930 2
            if (!empty($clientAuthorizationRequest)) {
931 1
                Yii::warning(
932 1
                    'Found a ClientAuthorizationRequestSession with key "' . $key
933
                        . '", but it\'s not a ' . Oauth2ClientAuthorizationRequestInterface::class
934
                );
935
            }
936 2
            return null;
937
        }
938 5
        if ($clientAuthorizationRequest->getRequestId() !== $requestId) {
939 1
            Yii::warning(
940 1
                'Found a ClientAuthorizationRequestSession with key "' . $key
941
                    . '", but it\'s request id does not match "' . $requestId . '".'
942
            );
943 1
            return null;
944
        }
945 5
        $clientAuthorizationRequest->setModule($this);
946
947 5
        return $clientAuthorizationRequest;
948
    }
949
950
    /**
951
     * Stores the Client Authorization Request in the session.
952
     * @param Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest
953
     * @since 1.0.0
954
     */
955 8
    public function setClientAuthReqSession($clientAuthorizationRequest)
956
    {
957 8
        $requestId = $clientAuthorizationRequest->getRequestId();
958 8
        if (empty($requestId)) {
959 1
            throw new InvalidArgumentException('$scopeAuthorization must return a request id.');
960
        }
961 7
        $key = static::CLIENT_AUTHORIZATION_REQUEST_SESSION_PREFIX . $requestId;
962 7
        Yii::$app->session->set($key, $clientAuthorizationRequest);
963
    }
964
965
    /**
966
     * Stores whether the user was authenticated during the completion of the Client Authorization Request.
967
     * @param Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest
968
     * @since 1.0.0
969
     */
970
    public function setUserAuthenticatedDuringClientAuthRequest(
971
        $clientAuthorizationRequestId,
972
        $authenticatedDuringRequest
973
    ) {
974
        $clientAuthorizationRequest = $this->getClientAuthReqSession($clientAuthorizationRequestId);
975
        if ($clientAuthorizationRequest) {
976
            $clientAuthorizationRequest->setUserAuthenticatedDuringRequest($authenticatedDuringRequest);
977
            $this->setClientAuthReqSession($clientAuthorizationRequest);
978
        }
979
    }
980
981
    /**
982
     * Stores the user identity selected during the completion of the Client Authorization Request.
983
     * @param string $clientAuthorizationRequestId
984
     * @param Oauth2UserInterface $userIdentity
985
     * @since 1.0.0
986
     */
987
    public function setClientAuthRequestUserIdentity($clientAuthorizationRequestId, $userIdentity)
988
    {
989
        $clientAuthorizationRequest = $this->getClientAuthReqSession($clientAuthorizationRequestId);
990
        if ($clientAuthorizationRequest) {
991
            $clientAuthorizationRequest->setUserIdentity($userIdentity);
992
            $this->setClientAuthReqSession($clientAuthorizationRequest);
993
        }
994
    }
995
996
    /**
997
     * Clears a Client Authorization Request from the session storage.
998
     * @param string $requestId
999
     * @since 1.0.0
1000
     */
1001 2
    public function removeClientAuthReqSession($requestId)
1002
    {
1003 2
        if (empty($requestId)) {
1004 1
            throw new InvalidArgumentException('$requestId can not be empty.');
1005
        }
1006 1
        $key = static::CLIENT_AUTHORIZATION_REQUEST_SESSION_PREFIX . $requestId;
1007 1
        Yii::$app->session->remove($key);
1008
    }
1009
1010
    /**
1011
     * Generates a redirect Response when the Client Authorization Request is completed.
1012
     * @param Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest
1013
     * @return Response
1014
     * @throws InvalidConfigException
1015
     * @since 1.0.0
1016
     */
1017 1
    public function generateClientAuthReqCompledRedirectResponse($clientAuthorizationRequest)
1018
    {
1019 1
        $clientAuthorizationRequest->processAuthorization();
1020 1
        $this->setClientAuthReqSession($clientAuthorizationRequest);
1021 1
        return Yii::$app->response->redirect($clientAuthorizationRequest->getAuthorizationRequestUrl());
1022
    }
1023
1024
    /**
1025
     * @return IdentityInterface|Oauth2UserInterface|Oauth2OidcUserInterface|null
1026
     * @throws InvalidConfigException
1027
     * @since 1.0.0
1028
     */
1029 5
    public function getUserIdentity()
1030
    {
1031 5
        $user = Yii::$app->user->identity;
1032 5
        if (!empty($user) && !($user instanceof Oauth2UserInterface)) {
1033 1
            throw new InvalidConfigException(
1034 1
                'Yii::$app->user->identity (currently ' . get_class($user)
1035
                    . ') must implement ' . Oauth2UserInterface::class
1036
            );
1037
        }
1038 4
        return $user;
1039
    }
1040
1041
    /**
1042
     * Validates a bearer token authenticated request. Note: this method does not return a result but will throw
1043
     * an exception when the authentication fails.
1044
     * @throws InvalidConfigException
1045
     * @throws OAuthServerException
1046
     * @since 1.0.0
1047
     */
1048 3
    public function validateAuthenticatedRequest()
1049
    {
1050 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

1050
        $psr7Request = Psr7Helper::yiiToPsr7Request(/** @scrutinizer ignore-type */ Yii::$app->request);
Loading history...
1051
1052 3
        $psr7Request = $this->getResourceServer()->validateAuthenticatedRequest($psr7Request);
1053
1054 3
        $this->_oauthClaims = $psr7Request->getAttributes();
1055 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...
1056
    }
1057
1058
    /**
1059
     * Find a user identity bases on an access token.
1060
     * Note: validateAuthenticatedRequest() must be called before this method is called.
1061
     * @param string $token
1062
     * @param string $type
1063
     * @return Oauth2UserInterface|null
1064
     * @throws InvalidConfigException
1065
     * @throws OAuthServerException
1066
     * @see validateAuthenticatedRequest()
1067
     * @since 1.0.0
1068
     */
1069 4
    public function findIdentityByAccessToken($token, $type)
1070
    {
1071 4
        if (!is_a($type, Oauth2HttpBearerAuthInterface::class, true)) {
1072 1
            throw new InvalidCallException($type . ' must implement ' . Oauth2HttpBearerAuthInterface::class);
1073
        }
1074
1075
        if (
1076 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

1076
            !preg_match('/^Bearer\s+(.*?)$/', /** @scrutinizer ignore-type */ $this->_oauthClaimsAuthorizationHeader, $matches)
Loading history...
1077 3
            || !Yii::$app->security->compareString($matches[1], $token)
1078
        ) {
1079 1
            throw new InvalidCallException(
1080
                'validateAuthenticatedRequest() must be called before findIdentityByAccessToken().'
1081
            );
1082
        }
1083
1084 2
        $userId = $this->getRequestOauthUserId();
1085 2
        if (empty($userId)) {
1086 1
            return null;
1087
        }
1088
1089 1
        return $this->identityClass::findIdentity($userId);
0 ignored issues
show
Bug introduced by
The method findIdentity() 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

1089
        return $this->identityClass::/** @scrutinizer ignore-call */ findIdentity($userId);

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...
1090
    }
1091
1092
    /**
1093
     * @inheritDoc
1094
     */
1095 5
    protected function getRequestOauthClaim($attribute, $default = null)
1096
    {
1097 5
        if (empty($this->_oauthClaimsAuthorizationHeader)) {
1098
            // User authorization was not processed by Oauth2Module.
1099 1
            return $default;
1100
        }
1101 4
        if (Yii::$app->request->getHeaders()->get('Authorization') !== $this->_oauthClaimsAuthorizationHeader) {
1102 1
            throw new InvalidCallException(
1103
                'App Request Authorization header does not match the processed Oauth header.'
1104
            );
1105
        }
1106 3
        return $this->_oauthClaims[$attribute] ?? $default;
1107
    }
1108
1109
    /**
1110
     * Helper function to ensure the required properties are configured for the module.
1111
     * @param $properties
1112
     * @throws InvalidConfigException
1113
     * @since 1.0.0
1114
     */
1115 27
    protected function ensureProperties($properties)
1116
    {
1117 27
        foreach ($properties as $property) {
1118 27
            if (empty($this->$property)) {
1119 6
                throw new InvalidConfigException('$' . $property . ' must be set.');
1120
            }
1121
        }
1122
    }
1123
}
1124