Passed
Push — master ( 02bea5...62dfbf )
by Rutger
03:04
created

Oauth2Module   F

Complexity

Total Complexity 105

Size/Duplication

Total Lines 1126
Duplicated Lines 0 %

Test Coverage

Coverage 91.26%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
wmc 105
eloc 410
c 7
b 0
f 0
dl 0
loc 1126
ccs 334
cts 366
cp 0.9126
rs 2

25 Methods

Rating   Name   Duplication   Size   Complexity  
A getCryptographer() 0 11 2
A rotateStorageEncryptionKeys() 0 15 3
A removeClientAuthReqSession() 0 7 2
A ensureProperties() 0 5 3
B bootstrap() 0 48 9
A getRequestOauthClaim() 0 12 3
D getAuthorizationServer() 0 101 18
A validateAuthenticatedRequest() 0 8 1
A generateClientAuthReqRedirectResponse() 0 13 2
A getPrivateKey() 0 7 2
A getUserIdentity() 0 10 3
B generatePersonalAccessToken() 0 43 8
A registerTranslations() 0 9 3
A getResourceServer() 0 19 3
A createClient() 0 47 4
A getClientAuthReqSession() 0 26 5
B init() 0 40 10
A getPublicKey() 0 7 2
A setUserAuthenticatedDuringClientAuthRequest() 0 8 2
A setClientAuthRequestUserIdentity() 0 6 2
B getOidcScopeCollection() 0 28 7
A generateClientAuthReqCompledRedirectResponse() 0 5 1
A getStorageEncryptionKeyUsage() 0 16 3
A findIdentityByAccessToken() 0 21 5
A setClientAuthReqSession() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like Oauth2Module often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Oauth2Module, and based on these observations, apply Extract Interface, too.

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

671
            $privateKey = 'file://' . /** @scrutinizer ignore-type */ Yii::getAlias($privateKey);
Loading history...
672
        }
673 22
        return Yii::createObject(CryptKey::class, [$privateKey, $this->privateKeyPassphrase]);
674
    }
675
676
    /**
677
     * @return CryptKey The public key of the server.
678
     * @throws InvalidConfigException
679
     * @since 1.0.0
680
     */
681 9
    public function getPublicKey()
682
    {
683 9
        $publicKey = $this->publicKey;
684 9
        if (StringHelper::startsWith($publicKey, '@')) {
685 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

685
            $publicKey = 'file://' . /** @scrutinizer ignore-type */ Yii::getAlias($publicKey);
Loading history...
686
        }
687 9
        return Yii::createObject(CryptKey::class, [$publicKey]);
688
    }
689
690
    /**
691
     * @return Oauth2AuthorizationServerInterface The authorization server.
692
     * @throws InvalidConfigException
693
     * @since 1.0.0
694
     */
695 27
    public function getAuthorizationServer()
696
    {
697 27
        if (!($this->serverRole & static::SERVER_ROLE_AUTHORIZATION_SERVER)) {
698 1
            throw new InvalidCallException('Oauth2 server role does not include authorization server.');
699
        }
700
701 26
        if (!$this->_authorizationServer) {
702 26
            $this->ensureProperties(static::REQUIRED_SETTINGS_AUTHORIZATION_SERVER);
703
704 21
            if (!$this->getCryptographer()->hasKey($this->defaultStorageEncryptionKey)) {
705 1
                throw new InvalidConfigException(
706 1
                    'Key "' . $this->defaultStorageEncryptionKey . '" is not set in $storageEncryptionKeys'
707 1
                );
708
            }
709
710
            /** @var Oauth2EncryptionKeyFactoryInterface $keyFactory */
711 19
            $keyFactory = Yii::createObject(Oauth2EncryptionKeyFactoryInterface::class);
712
            try {
713 19
                $codesEncryptionKey = $keyFactory->createFromAsciiSafeString($this->codesEncryptionKey);
714 1
            } catch (BadFormatException $e) {
715 1
                throw new InvalidConfigException(
716 1
                    '$codesEncryptionKey is malformed: ' . $e->getMessage(),
717 1
                    0,
718 1
                    $e
719 1
                );
720
            } catch (EnvironmentIsBrokenException $e) {
721
                throw new InvalidConfigException(
722
                    'Could not instantiate $codesEncryptionKey: ' . $e->getMessage(),
723
                    0,
724
                    $e
725
                );
726
            }
727
728 18
            $responseType = null;
729 18
            if ($this->enableOpenIdConnect) {
730 18
                $responseType = Yii::createObject(Oauth2OidcBearerTokenResponseInterface::class, [
731 18
                    $this,
732 18
                ]);
733
            }
734
735 18
            $this->_authorizationServer = Yii::createObject(Oauth2AuthorizationServerInterface::class, [
736 18
                $this->getClientRepository(),
737 18
                $this->getAccessTokenRepository(),
738 18
                $this->getScopeRepository(),
739 18
                $this->getPrivateKey(),
740 18
                $codesEncryptionKey,
741 18
                $responseType
742 18
            ]);
743
744 18
            if (!empty($this->grantTypes)) {
745 18
                $grantTypes = $this->grantTypes;
746
747 18
                if (is_callable($grantTypes)) {
748 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

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

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

1077
        $psr7Request = Psr7Helper::yiiToPsr7Request(/** @scrutinizer ignore-type */ Yii::$app->request);
Loading history...
1078
1079 3
        $psr7Request = $this->getResourceServer()->validateAuthenticatedRequest($psr7Request);
1080
1081 3
        $this->_oauthClaims = $psr7Request->getAttributes();
1082 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...
1083
    }
1084
1085
    /**
1086
     * Find a user identity bases on an access token.
1087
     * Note: validateAuthenticatedRequest() must be called before this method is called.
1088
     * @param string $token
1089
     * @param string $type
1090
     * @return Oauth2UserInterface|null
1091
     * @throws InvalidConfigException
1092
     * @throws Oauth2ServerException
1093
     * @see validateAuthenticatedRequest()
1094
     * @since 1.0.0
1095
     */
1096 4
    public function findIdentityByAccessToken($token, $type)
1097
    {
1098 4
        if (!is_a($type, Oauth2HttpBearerAuthInterface::class, true)) {
1099 1
            throw new InvalidCallException($type . ' must implement ' . Oauth2HttpBearerAuthInterface::class);
1100
        }
1101
1102
        if (
1103 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

1103
            !preg_match('/^Bearer\s+(.*?)$/', /** @scrutinizer ignore-type */ $this->_oauthClaimsAuthorizationHeader, $matches)
Loading history...
1104 3
            || !Yii::$app->security->compareString($matches[1], $token)
1105
        ) {
1106 1
            throw new InvalidCallException(
1107 1
                'validateAuthenticatedRequest() must be called before findIdentityByAccessToken().'
1108 1
            );
1109
        }
1110
1111 2
        $userId = $this->getRequestOauthUserId();
1112 2
        if (empty($userId)) {
1113 1
            return null;
1114
        }
1115
1116 1
        return $this->identityClass::findIdentity($userId);
1117
    }
1118
1119
    /**
1120
     * Generate a "Personal Access Token" (PAT) which can be used as an alternative to using passwords
1121
     * for authentication (e.g. when using an API or command line).
1122
     *
1123
     * Note: Personal Access Tokens are intended to access resources on behalf users themselves.
1124
     *       To grant access to resources on behalf of an organization, or for long-lived integrations,
1125
     *       you most likely want to define an Oauth2 Client with the "Client Credentials" grant
1126
     *       (https://oauth.net/2/grant-types/client-credentials).
1127
     *
1128
     * @param string $clientIdentifier The Oauth2 client identifier for which the PAT should be generated.
1129
     * @param int|string $userIdentifier The identifier (primary key) of the user for which the PAT should be generated.
1130
     * @param Oauth2ScopeInterface[]|string[]|string|null $scope The Access Token scope.
1131
     * @param string|true|null $clientSecret If the client is a "confidential" client the secret is required.
1132
     *        If the boolean value `true` is passed, the client secret is automatically injected.
1133
     * @return Oauth2AccessTokenData
1134
     */
1135 3
    public function generatePersonalAccessToken($clientIdentifier, $userIdentifier, $scope = null, $clientSecret = null)
1136
    {
1137 3
        if (is_array($scope)) {
1138 2
            $scopeIdentifiers = [];
1139 2
            foreach ($scope as $scopeItem) {
1140 2
                if (is_string($scopeItem)) {
1141 1
                    $scopeIdentifiers[] = $scopeItem;
1142 1
                } elseif ($scopeItem instanceof Oauth2ScopeInterface) {
1143 1
                    $scopeIdentifiers[] = $scopeItem->getIdentifier();
1144
                } else {
1145
                    throw new InvalidArgumentException('If $scope is an array its elements must be either'
1146
                        . ' a string or an instance of ' . Oauth2ScopeInterface::class);
1147
                }
1148
            }
1149 2
            $scope = implode(' ', $scopeIdentifiers);
1150
        }
1151
1152 3
        if ($clientSecret === true) {
1153
            /** @var Oauth2ClientInterface $client */
1154 3
            $client = $this->getClientRepository()->findModelByIdentifier($clientIdentifier);
1155 3
            if ($client && $client->isConfidential()) {
1156 3
                $clientSecret = $client->getDecryptedSecret($this->getCryptographer());
1157
            } else {
1158
                $clientSecret = null;
1159
            }
1160
        }
1161
1162 3
        $request = (new Psr7ServerRequest('POST', ''))->withParsedBody([
1163 3
            'grant_type' => static::GRANT_TYPE_IDENTIFIER_PERSONAL_ACCESS_TOKEN,
1164 3
            'client_id' => $clientIdentifier,
1165 3
            'client_secret' => $clientSecret,
1166 3
            'user_id' => $userIdentifier,
1167 3
            'scope' => $scope,
1168 3
        ]);
1169
1170 3
        return new Oauth2AccessTokenData(Json::decode(
0 ignored issues
show
Bug introduced by
It seems like yii\helpers\Json::decode...etBody()->__toString()) can also be of type null; however, parameter $data of rhertogh\Yii2Oauth2Serve...okenData::__construct() does only seem to accept array, 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

1170
        return new Oauth2AccessTokenData(/** @scrutinizer ignore-type */ Json::decode(
Loading history...
1171 3
            $this->getAuthorizationServer()
1172 3
                ->respondToAccessTokenRequest(
1173 3
                    $request,
1174 3
                    new Psr7Response()
1175 3
                )
1176 3
                ->getBody()
1177 3
                ->__toString()
1178 3
        ));
1179
    }
1180
1181
    /**
1182
     * @inheritDoc
1183
     */
1184 5
    protected function getRequestOauthClaim($attribute, $default = null)
1185
    {
1186 5
        if (empty($this->_oauthClaimsAuthorizationHeader)) {
1187
            // User authorization was not processed by Oauth2Module.
1188 1
            return $default;
1189
        }
1190 4
        if (Yii::$app->request->getHeaders()->get('Authorization') !== $this->_oauthClaimsAuthorizationHeader) {
1191 1
            throw new InvalidCallException(
1192 1
                'App Request Authorization header does not match the processed Oauth header.'
1193 1
            );
1194
        }
1195 3
        return $this->_oauthClaims[$attribute] ?? $default;
1196
    }
1197
1198
    /**
1199
     * Helper function to ensure the required properties are configured for the module.
1200
     * @param string[] $properties
1201
     * @throws InvalidConfigException
1202
     * @since 1.0.0
1203
     */
1204 32
    protected function ensureProperties($properties)
1205
    {
1206 32
        foreach ($properties as $property) {
1207 32
            if (empty($this->$property)) {
1208 6
                throw new InvalidConfigException(__CLASS__ . '::$' . $property . ' must be set.');
1209
            }
1210
        }
1211
    }
1212
}
1213