Passed
Push — master ( 0c1875...09be9f )
by Rutger
15:24 queued 43s
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.072

Importance

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

734
            $privateKey = 'file://' . /** @scrutinizer ignore-type */ Yii::getAlias($privateKey);
Loading history...
735
        }
736 22
        return Yii::createObject(CryptKey::class, [$privateKey, $this->privateKeyPassphrase]);
737
    }
738
739
    /**
740
     * @return CryptKey The public key of the server.
741
     * @throws InvalidConfigException
742
     * @since 1.0.0
743
     */
744 9
    public function getPublicKey()
745
    {
746 9
        $publicKey = $this->publicKey;
747 9
        if (StringHelper::startsWith($publicKey, '@')) {
748 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

748
            $publicKey = 'file://' . /** @scrutinizer ignore-type */ Yii::getAlias($publicKey);
Loading history...
749
        }
750 9
        return Yii::createObject(CryptKey::class, [$publicKey]);
751
    }
752
753
    /**
754
     * @return Oauth2AuthorizationServerInterface The authorization server.
755
     * @throws InvalidConfigException
756
     * @since 1.0.0
757
     */
758 27
    public function getAuthorizationServer()
759
    {
760 27
        if (!($this->serverRole & static::SERVER_ROLE_AUTHORIZATION_SERVER)) {
761 1
            throw new InvalidCallException('Oauth2 server role does not include authorization server.');
762
        }
763
764 26
        if (!$this->_authorizationServer) {
765 26
            $this->ensureProperties(static::REQUIRED_SETTINGS_AUTHORIZATION_SERVER);
766
767 21
            if (!$this->getCryptographer()->hasKey($this->defaultStorageEncryptionKey)) {
768 1
                throw new InvalidConfigException(
769 1
                    'Key "' . $this->defaultStorageEncryptionKey . '" is not set in $storageEncryptionKeys'
770 1
                );
771
            }
772
773
            /** @var Oauth2EncryptionKeyFactoryInterface $keyFactory */
774 19
            $keyFactory = Yii::createObject(Oauth2EncryptionKeyFactoryInterface::class);
775
            try {
776 19
                $codesEncryptionKey = $keyFactory->createFromAsciiSafeString($this->codesEncryptionKey);
777 1
            } catch (BadFormatException $e) {
778 1
                throw new InvalidConfigException(
779 1
                    '$codesEncryptionKey is malformed: ' . $e->getMessage(),
780 1
                    0,
781 1
                    $e
782 1
                );
783
            } catch (EnvironmentIsBrokenException $e) {
784
                throw new InvalidConfigException(
785
                    'Could not instantiate $codesEncryptionKey: ' . $e->getMessage(),
786
                    0,
787
                    $e
788
                );
789
            }
790
791 18
            $responseType = null;
792 18
            if ($this->enableOpenIdConnect) {
793 18
                $responseType = Yii::createObject(Oauth2OidcBearerTokenResponseInterface::class, [
794 18
                    $this,
795 18
                ]);
796
            }
797
798 18
            $this->_authorizationServer = Yii::createObject(Oauth2AuthorizationServerInterface::class, [
799 18
                $this->getClientRepository(),
800 18
                $this->getAccessTokenRepository(),
801 18
                $this->getScopeRepository(),
802 18
                $this->getPrivateKey(),
803 18
                $codesEncryptionKey,
804 18
                $responseType
805 18
            ]);
806
807 18
            if (!empty($this->grantTypes)) {
808 18
                $grantTypes = $this->grantTypes;
809
810 18
                if (is_callable($grantTypes)) {
811 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

811
                    call_user_func(/** @scrutinizer ignore-type */ $grantTypes, $this->_authorizationServer, $this);
Loading history...
812
                } else {
813 17
                    if (!is_array($grantTypes)) {
814 2
                        $grantTypes = [$grantTypes];
815
                    }
816
817 17
                    foreach ($grantTypes as $grantTypeDefinition) {
818 17
                        if ($grantTypeDefinition instanceof GrantTypeInterface) {
819 1
                            $accessTokenTTL = $this->getDefaultAccessTokenTTL();
820 1
                            $this->_authorizationServer->enableGrantType($grantTypeDefinition, $accessTokenTTL);
821
                        } elseif (
822
                            (
823 16
                                is_numeric($grantTypeDefinition)
824 16
                                && array_key_exists($grantTypeDefinition, static::DEFAULT_GRANT_TYPE_FACTORIES)
825
                            )
826 16
                            || is_a($grantTypeDefinition, Oauth2GrantTypeFactoryInterface::class, true)
827
                        ) {
828
                            if (
829 15
                                is_numeric($grantTypeDefinition)
830 15
                                && array_key_exists($grantTypeDefinition, static::DEFAULT_GRANT_TYPE_FACTORIES)
831
                            ) {
832 15
                                $grantTypeDefinition = static::DEFAULT_GRANT_TYPE_FACTORIES[$grantTypeDefinition];
833
                            }
834
835
                            /** @var Oauth2GrantTypeFactoryInterface $factory */
836 15
                            $factory = Yii::createObject([
837 15
                                'class' => $grantTypeDefinition,
838 15
                                'module' => $this,
839 15
                            ]);
840 15
                            $accessTokenTTL = $factory->getDefaultAccessTokenTTL()
841 15
                                ?? $this->getDefaultAccessTokenTTL();
842 15
                            $this->_authorizationServer->enableGrantType($factory->getGrantType(), $accessTokenTTL);
843
                        } else {
844 1
                            throw new InvalidConfigException(
845 1
                                'Unknown grantType '
846 1
                                . (
847 1
                                    is_scalar($grantTypeDefinition)
848 1
                                        ? '"' . $grantTypeDefinition . '".'
849 1
                                        : 'with data type ' . gettype($grantTypeDefinition)
850 1
                                )
851 1
                            );
852
                        }
853
                    }
854
                }
855
            }
856
        }
857
858 17
        return $this->_authorizationServer;
859
    }
860
861
    /**
862
     * @inheritDoc
863
     * @throws InvalidConfigException
864
     */
865 6
    public function getOidcScopeCollection()
866
    {
867 6
        if ($this->_oidcScopeCollection === null) {
868 6
            $openIdConnectScopes = $this->getOpenIdConnectScopes();
869 6
            if ($openIdConnectScopes instanceof Oauth2OidcScopeCollectionInterface) {
870 1
                $this->_oidcScopeCollection = $openIdConnectScopes;
871 5
            } elseif (is_callable($openIdConnectScopes)) {
872 1
                $this->_oidcScopeCollection = call_user_func($openIdConnectScopes, $this);
873 1
                if (!($this->_oidcScopeCollection instanceof Oauth2OidcScopeCollectionInterface)) {
874 1
                    throw new InvalidConfigException(
875 1
                        '$openIdConnectScopes must return an instance of '
876 1
                            . Oauth2OidcScopeCollectionInterface::class
877 1
                    );
878
                }
879 4
            } elseif (is_array($openIdConnectScopes) || is_string($openIdConnectScopes)) {
880 3
                $this->_oidcScopeCollection = Yii::createObject([
881 3
                    'class' => Oauth2OidcScopeCollectionInterface::class,
882 3
                    'oidcScopes' => (array)$openIdConnectScopes,
883 3
                ]);
884
            } else {
885 1
                throw new InvalidConfigException(
886 1
                    '$openIdConnectScopes must be a callable, array, string or '
887 1
                        . Oauth2OidcScopeCollectionInterface::class
888 1
                );
889
            }
890
        }
891
892 5
        return $this->_oidcScopeCollection;
893
    }
894
895
    /**
896
     * @return Oauth2ResourceServerInterface The resource server.
897
     * @throws InvalidConfigException
898
     * @since 1.0.0
899
     */
900 7
    public function getResourceServer()
901
    {
902 7
        if (!($this->serverRole & static::SERVER_ROLE_RESOURCE_SERVER)) {
903 1
            throw new InvalidCallException('Oauth2 server role does not include resource server.');
904
        }
905
906 6
        if (!$this->_resourceServer) {
907 6
            $this->ensureProperties(static::REQUIRED_SETTINGS_RESOURCE_SERVER);
908
909 5
            $accessTokenRepository = $this->getAccessTokenRepository()
910 5
                ->setRevocationValidation($this->resourceServerAccessTokenRevocationValidation);
911
912 5
            $this->_resourceServer = Yii::createObject(Oauth2ResourceServerInterface::class, [
913 5
                $accessTokenRepository,
914 5
                $this->getPublicKey(),
915 5
            ]);
916
        }
917
918 5
        return $this->_resourceServer;
919
    }
920
921
    /**
922
     * @return Oauth2CryptographerInterface The data cryptographer for the module.
923
     * @throws InvalidConfigException
924
     * @since 1.0.0
925
     */
926 27
    public function getCryptographer()
927
    {
928 27
        if (!$this->_cryptographer) {
929 27
            $this->_cryptographer = Yii::createObject([
930 27
                'class' => Oauth2CryptographerInterface::class,
931 27
                'keys' => $this->storageEncryptionKeys,
932 27
                'defaultKeyName' => $this->defaultStorageEncryptionKey,
933 27
            ]);
934
        }
935
936 26
        return $this->_cryptographer;
937
    }
938
939
    /**
940
     * @param string|null $newKeyName
941
     * @return array
942
     * @throws InvalidConfigException
943
     */
944 1
    public function rotateStorageEncryptionKeys($newKeyName = null)
945
    {
946 1
        $cryptographer = $this->getCryptographer();
947
948 1
        $result = [];
949 1
        foreach (static::ENCRYPTED_MODELS as $modelInterface) {
950 1
            $modelClass = DiHelper::getValidatedClassName($modelInterface);
951 1
            if (!is_a($modelClass, Oauth2EncryptedStorageInterface::class, true)) {
952
                throw new InvalidConfigException($modelInterface . ' must implement '
953
                    . Oauth2EncryptedStorageInterface::class);
954
            }
955 1
            $result[$modelClass] = $modelClass::rotateStorageEncryptionKeys($cryptographer, $newKeyName);
956
        }
957
958 1
        return $result;
959
    }
960
961
    /**
962
     * Checks if the connection is using TLS or if the remote IP address is allowed to connect without TLS.
963
     * @return bool
964
     */
965 12
    public function validateTlsConnection()
966
    {
967 12
        if (Yii::$app->request->getIsSecureConnection()) {
968 1
            return true;
969
        }
970
971
        if (
972 11
            !empty($this->nonTlsAllowedRanges)
973 11
            && (new IpValidator(['ranges' => $this->nonTlsAllowedRanges]))->validate(Yii::$app->request->getRemoteIP())
974
        ) {
975 7
            return true;
976
        }
977
978 4
        return false;
979
    }
980
981
    /**
982
     * @return array
983
     * @throws InvalidConfigException
984
     */
985
    public function getStorageEncryptionKeyUsage()
986
    {
987
        $cryptographer = $this->getCryptographer();
988
989
        $result = [];
990
        foreach (static::ENCRYPTED_MODELS as $modelInterface) {
991
            $modelClass = DiHelper::getValidatedClassName($modelInterface);
992
            if (!is_a($modelClass, Oauth2EncryptedStorageInterface::class, true)) {
993
                throw new InvalidConfigException($modelInterface . ' must implement '
994
                    . Oauth2EncryptedStorageInterface::class);
995
            }
996
997
            $result[$modelClass] = $modelClass::getUsedStorageEncryptionKeys($cryptographer);
998
        }
999
1000
        return $result;
1001
    }
1002
1003
    /**
1004
     * @param Oauth2ClientInterface $client
1005
     * @param string[] $requestedScopeIdentifiers
1006
     * @throws Oauth2ServerException
1007
     */
1008 6
    public function validateAuthRequestScopes($client, $requestedScopeIdentifiers, $redirectUri = null)
1009
    {
1010 6
        if (!$client->validateAuthRequestScopes($requestedScopeIdentifiers, $unknownScopes, $unauthorizedScopes)) {
1011
            Yii::info('Invalid scope for client "' . $client->getIdentifier() . '": '
1012
                . VarDumper::export(['unauthorizedScopes' => $unauthorizedScopes, 'unknownScopes' => $unknownScopes]));
1013
1014
            if (
1015
                $client->getExceptionOnInvalidScope() === true
1016
                || (
1017
                    $client->getExceptionOnInvalidScope() === null
1018
                    && $this->exceptionOnInvalidScope === true
1019
                )
1020
            ) {
1021
                if ($unknownScopes) {
1022
                    throw Oauth2ServerException::unknownScope(array_shift($unknownScopes), $redirectUri);
1023
                }
1024
                throw Oauth2ServerException::scopeNotAllowedForClient(array_shift($unauthorizedScopes), $redirectUri);
1025
            }
1026
        }
1027
    }
1028
1029
    /**
1030
     * Generates a redirect Response to the client authorization page where the user is prompted to authorize the
1031
     * client and requested scope.
1032
     * @param Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest
1033
     * @return Response
1034
     * @since 1.0.0
1035
     */
1036 5
    public function generateClientAuthReqRedirectResponse($clientAuthorizationRequest)
1037
    {
1038 5
        $this->setClientAuthReqSession($clientAuthorizationRequest);
1039 5
        if (!empty($this->clientAuthorizationUrl)) {
1040 1
            $url = $this->clientAuthorizationUrl;
1041
        } else {
1042 4
            $url = $this->uniqueId
1043 4
                . '/' . Oauth2ConsentControllerInterface::CONTROLLER_NAME
1044 4
                . '/' . Oauth2ConsentControllerInterface::ACTION_NAME_AUTHORIZE_CLIENT;
1045
        }
1046 5
        return Yii::$app->response->redirect([
1047 5
            $url,
1048 5
            'clientAuthorizationRequestId' => $clientAuthorizationRequest->getRequestId(),
1049 5
        ]);
1050
    }
1051
1052
    /**
1053
     * Get a previously stored Client Authorization Request from the session.
1054
     * @param string $requestId
1055
     * @return Oauth2ClientAuthorizationRequestInterface|null
1056
     * @since 1.0.0
1057
     */
1058 5
    public function getClientAuthReqSession($requestId)
1059
    {
1060 5
        if (empty($requestId)) {
1061
            return null;
1062
        }
1063 5
        $key = static::CLIENT_AUTHORIZATION_REQUEST_SESSION_PREFIX . $requestId;
1064 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

1064
        /** @scrutinizer ignore-call */ 
1065
        $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...
1065 5
        if (!($clientAuthorizationRequest instanceof Oauth2ClientAuthorizationRequestInterface)) {
1066 2
            if (!empty($clientAuthorizationRequest)) {
1067 1
                Yii::warning(
1068 1
                    'Found a ClientAuthorizationRequestSession with key "' . $key
1069 1
                        . '", but it\'s not a ' . Oauth2ClientAuthorizationRequestInterface::class
1070 1
                );
1071
            }
1072 2
            return null;
1073
        }
1074 5
        if ($clientAuthorizationRequest->getRequestId() !== $requestId) {
1075 1
            Yii::warning(
1076 1
                'Found a ClientAuthorizationRequestSession with key "' . $key
1077 1
                    . '", but its request id does not match "' . $requestId . '".'
1078 1
            );
1079 1
            return null;
1080
        }
1081 5
        $clientAuthorizationRequest->setModule($this);
1082
1083 5
        return $clientAuthorizationRequest;
1084
    }
1085
1086
    /**
1087
     * Stores the Client Authorization Request in the session.
1088
     * @param Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest
1089
     * @since 1.0.0
1090
     */
1091 8
    public function setClientAuthReqSession($clientAuthorizationRequest)
1092
    {
1093 8
        $requestId = $clientAuthorizationRequest->getRequestId();
1094 8
        if (empty($requestId)) {
1095 1
            throw new InvalidArgumentException('$scopeAuthorization must return a request id.');
1096
        }
1097 7
        $key = static::CLIENT_AUTHORIZATION_REQUEST_SESSION_PREFIX . $requestId;
1098 7
        Yii::$app->session->set($key, $clientAuthorizationRequest);
1099
    }
1100
1101
    /**
1102
     * Stores whether the user was authenticated during the completion of the Client Authorization Request.
1103
     * @param string $clientAuthorizationRequestId
1104
     * @param bool $authenticatedDuringRequest
1105
     * @since 1.0.0
1106
     */
1107
    public function setUserAuthenticatedDuringClientAuthRequest(
1108
        $clientAuthorizationRequestId,
1109
        $authenticatedDuringRequest
1110
    ) {
1111
        $clientAuthorizationRequest = $this->getClientAuthReqSession($clientAuthorizationRequestId);
1112
        if ($clientAuthorizationRequest) {
1113
            $clientAuthorizationRequest->setUserAuthenticatedDuringRequest($authenticatedDuringRequest);
1114
            $this->setClientAuthReqSession($clientAuthorizationRequest);
1115
        }
1116
    }
1117
1118
    /**
1119
     * Stores the user identity selected during the completion of the Client Authorization Request.
1120
     * @param string $clientAuthorizationRequestId
1121
     * @param Oauth2UserInterface $userIdentity
1122
     * @since 1.0.0
1123
     */
1124
    public function setClientAuthRequestUserIdentity($clientAuthorizationRequestId, $userIdentity)
1125
    {
1126
        $clientAuthorizationRequest = $this->getClientAuthReqSession($clientAuthorizationRequestId);
1127
        if ($clientAuthorizationRequest) {
1128
            $clientAuthorizationRequest->setUserIdentity($userIdentity);
1129
            $this->setClientAuthReqSession($clientAuthorizationRequest);
1130
        }
1131
    }
1132
1133
    /**
1134
     * Clears a Client Authorization Request from the session storage.
1135
     * @param string $requestId
1136
     * @since 1.0.0
1137
     */
1138 2
    public function removeClientAuthReqSession($requestId)
1139
    {
1140 2
        if (empty($requestId)) {
1141 1
            throw new InvalidArgumentException('$requestId can not be empty.');
1142
        }
1143 1
        $key = static::CLIENT_AUTHORIZATION_REQUEST_SESSION_PREFIX . $requestId;
1144 1
        Yii::$app->session->remove($key);
1145
    }
1146
1147
    /**
1148
     * Generates a redirect Response when the Client Authorization Request is completed.
1149
     * @param Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest
1150
     * @return Response
1151
     * @since 1.0.0
1152
     */
1153 1
    public function generateClientAuthReqCompledRedirectResponse($clientAuthorizationRequest)
1154
    {
1155 1
        $clientAuthorizationRequest->processAuthorization();
1156 1
        $this->setClientAuthReqSession($clientAuthorizationRequest);
1157 1
        return Yii::$app->response->redirect($clientAuthorizationRequest->getAuthorizationRequestUrl());
1158
    }
1159
1160
    /**
1161
     * @return IdentityInterface|Oauth2UserInterface|Oauth2OidcUserInterface|null
1162
     * @throws InvalidConfigException
1163
     * @since 1.0.0
1164
     */
1165 5
    public function getUserIdentity()
1166
    {
1167 5
        $user = Yii::$app->user->identity;
1168 5
        if (!empty($user) && !($user instanceof Oauth2UserInterface)) {
1169 1
            throw new InvalidConfigException(
1170 1
                'Yii::$app->user->identity (currently ' . get_class($user)
1171 1
                    . ') must implement ' . Oauth2UserInterface::class
1172 1
            );
1173
        }
1174 4
        return $user;
1175
    }
1176
1177
    /**
1178
     * Validates a bearer token authenticated request. Note: this method does not return a result but will throw
1179
     * an exception when the authentication fails.
1180
     * @throws InvalidConfigException
1181
     * @throws Oauth2ServerException
1182
     * @since 1.0.0
1183
     */
1184 3
    public function validateAuthenticatedRequest()
1185
    {
1186 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

1186
        $psr7Request = Psr7Helper::yiiToPsr7Request(/** @scrutinizer ignore-type */ Yii::$app->request);
Loading history...
1187
1188 3
        $psr7Request = $this->getResourceServer()->validateAuthenticatedRequest($psr7Request);
1189
1190 3
        $this->_oauthClaims = $psr7Request->getAttributes();
1191 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...
1192
    }
1193
1194
    /**
1195
     * Find a user identity bases on an access token.
1196
     * Note: validateAuthenticatedRequest() must be called before this method is called.
1197
     * @param string $token
1198
     * @param string $type
1199
     * @return Oauth2UserInterface|null
1200
     * @throws InvalidConfigException
1201
     * @throws Oauth2ServerException
1202
     * @see validateAuthenticatedRequest()
1203
     * @since 1.0.0
1204
     */
1205 4
    public function findIdentityByAccessToken($token, $type)
1206
    {
1207 4
        if (!is_a($type, Oauth2HttpBearerAuthInterface::class, true)) {
1208 1
            throw new InvalidCallException($type . ' must implement ' . Oauth2HttpBearerAuthInterface::class);
1209
        }
1210
1211
        if (
1212 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

1212
            !preg_match('/^Bearer\s+(.*?)$/', /** @scrutinizer ignore-type */ $this->_oauthClaimsAuthorizationHeader, $matches)
Loading history...
1213 3
            || !Yii::$app->security->compareString($matches[1], $token)
1214
        ) {
1215 1
            throw new InvalidCallException(
1216 1
                'validateAuthenticatedRequest() must be called before findIdentityByAccessToken().'
1217 1
            );
1218
        }
1219
1220 2
        $userId = $this->getRequestOauthUserId();
1221 2
        if (empty($userId)) {
1222 1
            return null;
1223
        }
1224
1225 1
        return $this->identityClass::findIdentity($userId);
1226
    }
1227
1228
    /**
1229
     * Generate a "Personal Access Token" (PAT) which can be used as an alternative to using passwords
1230
     * for authentication (e.g. when using an API or command line).
1231
     *
1232
     * Note: Personal Access Tokens are intended to access resources on behalf users themselves.
1233
     *       To grant access to resources on behalf of an organization, or for long-lived integrations,
1234
     *       you most likely want to define an Oauth2 Client with the "Client Credentials" grant
1235
     *       (https://oauth.net/2/grant-types/client-credentials).
1236
     *
1237
     * @param string $clientIdentifier The Oauth2 client identifier for which the PAT should be generated.
1238
     * @param int|string $userIdentifier The identifier (primary key) of the user for which the PAT should be generated.
1239
     * @param Oauth2ScopeInterface[]|string[]|string|null $scope The Access Token scope.
1240
     * @param string|true|null $clientSecret If the client is a "confidential" client the secret is required.
1241
     *        If the boolean value `true` is passed, the client secret is automatically injected.
1242
     * @return Oauth2AccessTokenData
1243
     */
1244 3
    public function generatePersonalAccessToken($clientIdentifier, $userIdentifier, $scope = null, $clientSecret = null)
1245
    {
1246 3
        if (is_array($scope)) {
1247 2
            $scopeIdentifiers = [];
1248 2
            foreach ($scope as $scopeItem) {
1249 2
                if (is_string($scopeItem)) {
1250 1
                    $scopeIdentifiers[] = $scopeItem;
1251 1
                } elseif ($scopeItem instanceof Oauth2ScopeInterface) {
1252 1
                    $scopeIdentifiers[] = $scopeItem->getIdentifier();
1253
                } else {
1254
                    throw new InvalidArgumentException('If $scope is an array its elements must be either'
1255
                        . ' a string or an instance of ' . Oauth2ScopeInterface::class);
1256
                }
1257
            }
1258 2
            $scope = implode(' ', $scopeIdentifiers);
1259
        }
1260
1261 3
        if ($clientSecret === true) {
1262
            /** @var Oauth2ClientInterface $client */
1263 3
            $client = $this->getClientRepository()->findModelByIdentifier($clientIdentifier);
1264 3
            if ($client && $client->isConfidential()) {
1265 3
                $clientSecret = $client->getDecryptedSecret($this->getCryptographer());
1266
            } else {
1267
                $clientSecret = null;
1268
            }
1269
        }
1270
1271 3
        $request = (new Psr7ServerRequest('POST', ''))->withParsedBody([
1272 3
            'grant_type' => static::GRANT_TYPE_IDENTIFIER_PERSONAL_ACCESS_TOKEN,
1273 3
            'client_id' => $clientIdentifier,
1274 3
            'client_secret' => $clientSecret,
1275 3
            'user_id' => $userIdentifier,
1276 3
            'scope' => $scope,
1277 3
        ]);
1278
1279 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

1279
        return new Oauth2AccessTokenData(/** @scrutinizer ignore-type */ Json::decode(
Loading history...
1280 3
            $this->getAuthorizationServer()
1281 3
                ->respondToAccessTokenRequest(
1282 3
                    $request,
1283 3
                    new Psr7Response()
1284 3
                )
1285 3
                ->getBody()
1286 3
                ->__toString()
1287 3
        ));
1288
    }
1289
1290
    /**
1291
     * @inheritDoc
1292
     */
1293 5
    protected function getRequestOauthClaim($attribute, $default = null)
1294
    {
1295 5
        if (empty($this->_oauthClaimsAuthorizationHeader)) {
1296
            // User authorization was not processed by Oauth2Module.
1297 1
            return $default;
1298
        }
1299 4
        if (Yii::$app->request->getHeaders()->get('Authorization') !== $this->_oauthClaimsAuthorizationHeader) {
1300 1
            throw new InvalidCallException(
1301 1
                'App Request Authorization header does not match the processed Oauth header.'
1302 1
            );
1303
        }
1304 3
        return $this->_oauthClaims[$attribute] ?? $default;
1305
    }
1306
1307
    /**
1308
     * Helper function to ensure the required properties are configured for the module.
1309
     * @param string[] $properties
1310
     * @throws InvalidConfigException
1311
     * @since 1.0.0
1312
     */
1313 32
    protected function ensureProperties($properties)
1314
    {
1315 32
        foreach ($properties as $property) {
1316 32
            if (empty($this->$property)) {
1317 6
                throw new InvalidConfigException(__CLASS__ . '::$' . $property . ' must be set.');
1318
            }
1319
        }
1320
    }
1321
1322
    public function logoutUser()
1323
    {
1324
        Yii::$app->user->logout();
0 ignored issues
show
Bug introduced by
The method logout() 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

1324
        Yii::$app->user->/** @scrutinizer ignore-call */ 
1325
                         logout();

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...
1325
    }
1326
}
1327