Passed
Pull Request — master (#25)
by
unknown
10:43
created

Oauth2Module::getJwtConfiguration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

823
            $privateKey = 'file://' . /** @scrutinizer ignore-type */ Yii::getAlias($privateKey);
Loading history...
824
        }
825
        return Yii::createObject(CryptKey::class, [$privateKey, $this->privateKeyPassphrase]);
826
    }
827
828 9
    /**
829
     * @return CryptKey The public key of the server.
830 9
     * @throws InvalidConfigException
831 9
     * @since 1.0.0
832 6
     */
833
    public function getPublicKey()
834 9
    {
835
        $publicKey = $this->publicKey;
836
        if (StringHelper::startsWith($publicKey, '@')) {
837
            $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

837
            $publicKey = 'file://' . /** @scrutinizer ignore-type */ Yii::getAlias($publicKey);
Loading history...
838
        }
839
        return Yii::createObject(CryptKey::class, [$publicKey]);
840
    }
841
842 27
    /**
843
     * @return Oauth2AuthorizationServerInterface The authorization server.
844 27
     * @throws InvalidConfigException
845 1
     * @since 1.0.0
846
     */
847
    public function getAuthorizationServer()
848 26
    {
849 26
        if (!($this->serverRole & static::SERVER_ROLE_AUTHORIZATION_SERVER)) {
850
            throw new InvalidCallException('Oauth2 server role does not include authorization server.');
851 21
        }
852 1
853 1
        if (!$this->_authorizationServer) {
854 1
            $this->ensureProperties(static::REQUIRED_SETTINGS_AUTHORIZATION_SERVER);
855
856
            if (!$this->getCryptographer()->hasKey($this->defaultStorageEncryptionKey)) {
857
                throw new InvalidConfigException(
858 19
                    'Key "' . $this->defaultStorageEncryptionKey . '" is not set in $storageEncryptionKeys'
859
                );
860 19
            }
861 1
862 1
            /** @var Oauth2EncryptionKeyFactoryInterface $keyFactory */
863 1
            $keyFactory = Yii::createObject(Oauth2EncryptionKeyFactoryInterface::class);
864 1
            try {
865 1
                $codesEncryptionKey = $keyFactory->createFromAsciiSafeString($this->codesEncryptionKey);
866 1
            } catch (BadFormatException $e) {
867
                throw new InvalidConfigException(
868
                    '$codesEncryptionKey is malformed: ' . $e->getMessage(),
869
                    0,
870
                    $e
871
                );
872
            } catch (EnvironmentIsBrokenException $e) {
873
                throw new InvalidConfigException(
874
                    'Could not instantiate $codesEncryptionKey: ' . $e->getMessage(),
875 18
                    0,
876 18
                    $e
877
                );
878
            }
879
880 18
            if ($this->enableOpenIdConnect) {
881 18
                $responseTypeClass = Oauth2OidcBearerTokenResponseInterface::class;
882 18
            } else {
883
                $responseTypeClass = Oauth2BearerTokenResponseInterface::class;
884 18
            }
885 18
            $responseType = Yii::createObject($responseTypeClass, [
886 18
                $this,
887 18
            ]);
888 18
889 18
            $this->_authorizationServer = Yii::createObject(Oauth2AuthorizationServerInterface::class, [
890 18
                $this->getClientRepository(),
891 18
                $this->getAccessTokenRepository(),
892
                $this->getScopeRepository(),
893 18
                $this->getPrivateKey(),
894 18
                $codesEncryptionKey,
895
                $responseType
896 18
            ]);
897 1
898
            if (!empty($this->grantTypes)) {
899 17
                $grantTypes = $this->grantTypes;
900 2
901
                if (is_callable($grantTypes)) {
902
                    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

902
                    call_user_func(/** @scrutinizer ignore-type */ $grantTypes, $this->_authorizationServer, $this);
Loading history...
903 17
                } else {
904 17
                    if (!is_array($grantTypes)) {
905 1
                        $grantTypes = [$grantTypes];
906 1
                    }
907
908
                    foreach ($grantTypes as $grantTypeDefinition) {
909 16
                        if ($grantTypeDefinition instanceof GrantTypeInterface) {
910 16
                            $accessTokenTTL = $this->getDefaultAccessTokenTTL();
911
                            $this->_authorizationServer->enableGrantType($grantTypeDefinition, $accessTokenTTL);
912 16
                        } elseif (
913
                            (
914
                                is_numeric($grantTypeDefinition)
915 15
                                && array_key_exists($grantTypeDefinition, static::DEFAULT_GRANT_TYPE_FACTORIES)
916 15
                            )
917
                            || is_a($grantTypeDefinition, Oauth2GrantTypeFactoryInterface::class, true)
918 15
                        ) {
919
                            if (
920
                                is_numeric($grantTypeDefinition)
921
                                && array_key_exists($grantTypeDefinition, static::DEFAULT_GRANT_TYPE_FACTORIES)
922 15
                            ) {
923 15
                                $grantTypeDefinition = static::DEFAULT_GRANT_TYPE_FACTORIES[$grantTypeDefinition];
924 15
                            }
925 15
926 15
                            /** @var Oauth2GrantTypeFactoryInterface $factory */
927 15
                            $factory = Yii::createObject([
928 15
                                'class' => $grantTypeDefinition,
929
                                'module' => $this,
930 1
                            ]);
931 1
                            $accessTokenTTL = $factory->getDefaultAccessTokenTTL()
932 1
                                ?? $this->getDefaultAccessTokenTTL();
933 1
                            $this->_authorizationServer->enableGrantType($factory->getGrantType(), $accessTokenTTL);
934 1
                        } else {
935 1
                            throw new InvalidConfigException(
936 1
                                'Unknown grantType '
937 1
                                . (
938
                                    is_scalar($grantTypeDefinition)
939
                                        ? '"' . $grantTypeDefinition . '".'
940
                                        : 'with data type ' . gettype($grantTypeDefinition)
941
                                )
942
                            );
943
                        }
944 17
                    }
945
                }
946
            }
947
        }
948
949
        return $this->_authorizationServer;
950
    }
951 6
952
    /**
953 6
     * @inheritDoc
954 6
     * @throws InvalidConfigException
955 6
     */
956 1
    public function getOidcScopeCollection()
957 5
    {
958 1
        if ($this->_oidcScopeCollection === null) {
959 1
            $openIdConnectScopes = $this->getOpenIdConnectScopes();
960 1
            if ($openIdConnectScopes instanceof Oauth2OidcScopeCollectionInterface) {
961 1
                $this->_oidcScopeCollection = $openIdConnectScopes;
962 1
            } elseif (is_callable($openIdConnectScopes)) {
963 1
                $this->_oidcScopeCollection = call_user_func($openIdConnectScopes, $this);
964
                if (!($this->_oidcScopeCollection instanceof Oauth2OidcScopeCollectionInterface)) {
965 4
                    throw new InvalidConfigException(
966 3
                        '$openIdConnectScopes must return an instance of '
967 3
                            . Oauth2OidcScopeCollectionInterface::class
968 3
                    );
969 3
                }
970
            } elseif (is_array($openIdConnectScopes) || is_string($openIdConnectScopes)) {
971 1
                $this->_oidcScopeCollection = Yii::createObject([
972 1
                    'class' => Oauth2OidcScopeCollectionInterface::class,
973 1
                    'oidcScopes' => (array)$openIdConnectScopes,
974 1
                ]);
975
            } else {
976
                throw new InvalidConfigException(
977
                    '$openIdConnectScopes must be a callable, array, string or '
978 5
                        . Oauth2OidcScopeCollectionInterface::class
979
                );
980
            }
981
        }
982
983
        return $this->_oidcScopeCollection;
984
    }
985
986 7
    /**
987
     * @return Oauth2ResourceServerInterface The resource server.
988 7
     * @throws InvalidConfigException
989 1
     * @since 1.0.0
990
     */
991
    public function getResourceServer()
992 6
    {
993 6
        if (!($this->serverRole & static::SERVER_ROLE_RESOURCE_SERVER)) {
994
            throw new InvalidCallException('Oauth2 server role does not include resource server.');
995 5
        }
996 5
997
        if (!$this->_resourceServer) {
998 5
            $this->ensureProperties(static::REQUIRED_SETTINGS_RESOURCE_SERVER);
999 5
1000 5
            $accessTokenRepository = $this->getAccessTokenRepository()
1001 5
                ->setRevocationValidation($this->resourceServerAccessTokenRevocationValidation);
1002
1003
            $this->_resourceServer = Yii::createObject(Oauth2ResourceServerInterface::class, [
1004 5
                $accessTokenRepository,
1005
                $this->getPublicKey(),
1006
            ]);
1007
        }
1008
1009
        return $this->_resourceServer;
1010
    }
1011
1012 27
    /**
1013
     * @return Oauth2CryptographerInterface The data cryptographer for the module.
1014 27
     * @throws InvalidConfigException
1015 27
     * @since 1.0.0
1016 27
     */
1017 27
    public function getCryptographer()
1018 27
    {
1019 27
        if (!$this->_cryptographer) {
1020
            $this->_cryptographer = Yii::createObject([
1021
                'class' => Oauth2CryptographerInterface::class,
1022 26
                'keys' => $this->storageEncryptionKeys,
1023
                'defaultKeyName' => $this->defaultStorageEncryptionKey,
1024
            ]);
1025
        }
1026
1027
        return $this->_cryptographer;
1028
    }
1029
1030 1
    /**
1031
     * @param string|null $newKeyName
1032 1
     * @return array
1033
     * @throws InvalidConfigException
1034 1
     */
1035 1
    public function rotateStorageEncryptionKeys($newKeyName = null)
1036 1
    {
1037 1
        $cryptographer = $this->getCryptographer();
1038
1039
        $result = [];
1040
        foreach (static::ENCRYPTED_MODELS as $modelInterface) {
1041 1
            $modelClass = DiHelper::getValidatedClassName($modelInterface);
1042
            if (!is_a($modelClass, Oauth2EncryptedStorageInterface::class, true)) {
1043
                throw new InvalidConfigException($modelInterface . ' must implement '
1044 1
                    . Oauth2EncryptedStorageInterface::class);
1045
            }
1046
            $result[$modelClass] = $modelClass::rotateStorageEncryptionKeys($cryptographer, $newKeyName);
1047
        }
1048
1049
        return $result;
1050
    }
1051 12
1052
    /**
1053 12
     * Checks if the connection is using TLS or if the remote IP address is allowed to connect without TLS.
1054 1
     * @return bool
1055
     */
1056
    public function validateTlsConnection()
1057
    {
1058 11
        if (Yii::$app->request->getIsSecureConnection()) {
1059 11
            return true;
1060
        }
1061 7
1062
        if (
1063
            !empty($this->nonTlsAllowedRanges)
1064 4
            && (new IpValidator(['ranges' => $this->nonTlsAllowedRanges]))->validate(Yii::$app->request->getRemoteIP())
1065
        ) {
1066
            return true;
1067
        }
1068
1069
        return false;
1070
    }
1071
1072
    /**
1073
     * @return array
1074
     * @throws InvalidConfigException
1075
     */
1076
    public function getStorageEncryptionKeyUsage()
1077
    {
1078
        $cryptographer = $this->getCryptographer();
1079
1080
        $result = [];
1081
        foreach (static::ENCRYPTED_MODELS as $modelInterface) {
1082
            $modelClass = DiHelper::getValidatedClassName($modelInterface);
1083
            if (!is_a($modelClass, Oauth2EncryptedStorageInterface::class, true)) {
1084
                throw new InvalidConfigException($modelInterface . ' must implement '
1085
                    . Oauth2EncryptedStorageInterface::class);
1086
            }
1087
1088
            $result[$modelClass] = $modelClass::getUsedStorageEncryptionKeys($cryptographer);
1089
        }
1090
1091
        return $result;
1092
    }
1093
1094 6
    /**
1095
     * @param Oauth2ClientInterface $client
1096 6
     * @param string[] $requestedScopeIdentifiers
1097
     * @throws Oauth2ServerException
1098
     */
1099
    public function validateAuthRequestScopes($client, $requestedScopeIdentifiers, $redirectUri = null)
1100
    {
1101
        if (!$client->validateAuthRequestScopes($requestedScopeIdentifiers, $unknownScopes, $unauthorizedScopes)) {
1102
            Yii::info('Invalid scope for client "' . $client->getIdentifier() . '": '
1103
                . VarDumper::export(['unauthorizedScopes' => $unauthorizedScopes, 'unknownScopes' => $unknownScopes]));
1104
1105
            if (
1106
                $client->getExceptionOnInvalidScope() === true
1107
                || (
1108
                    $client->getExceptionOnInvalidScope() === null
1109
                    && $this->exceptionOnInvalidScope === true
1110
                )
1111
            ) {
1112
                if ($unknownScopes) {
1113
                    throw Oauth2ServerException::unknownScope(array_shift($unknownScopes), $redirectUri);
1114
                }
1115
                throw Oauth2ServerException::scopeNotAllowedForClient(array_shift($unauthorizedScopes), $redirectUri);
1116
            }
1117
        }
1118
    }
1119
1120
    /**
1121
     * Generates a redirect Response to the Client Authorization page where the user is prompted to authorize the
1122 5
     * Client and requested Scope.
1123
     * @param Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest
1124 5
     * @return Response
1125 5
     * @since 1.0.0
1126 1
     */
1127
    public function generateClientAuthReqRedirectResponse($clientAuthorizationRequest)
1128 4
    {
1129 4
        $this->setClientAuthReqSession($clientAuthorizationRequest);
1130 4
        if (!empty($this->clientAuthorizationUrl)) {
1131
            $url = $this->clientAuthorizationUrl;
1132 5
        } else {
1133 5
            $url = $this->uniqueId
1134 5
                . '/' . Oauth2ConsentControllerInterface::CONTROLLER_NAME
1135 5
                . '/' . Oauth2ConsentControllerInterface::ACTION_NAME_AUTHORIZE_CLIENT;
1136
        }
1137
        return Yii::$app->response->redirect([
1138
            $url,
1139
            'clientAuthorizationRequestId' => $clientAuthorizationRequest->getRequestId(),
1140
        ]);
1141
    }
1142
1143
    /**
1144
     * Generates a redirect Response to the End Session Authorization page where the user is prompted to authorize the
1145
     * logout.
1146
     * @param Oauth2EndSessionAuthorizationRequestInterface $endSessionAuthorizationRequest
1147
     * @return Response
1148
     * @since 1.0.0
1149
     */
1150
    public function generateEndSessionAuthReqRedirectResponse($endSessionAuthorizationRequest)
1151
    {
1152
        $this->setEndSessionAuthReqSession($endSessionAuthorizationRequest);
1153
        if (!empty($this->openIdConnectLogoutConfirmationUrl)) {
1154
            $url = $this->openIdConnectLogoutConfirmationUrl;
1155
        } else {
1156
            $url = $this->uniqueId
1157
                . '/' . Oauth2ConsentControllerInterface::CONTROLLER_NAME
1158
                . '/' . Oauth2ConsentControllerInterface::ACTION_NAME_AUTHORIZE_END_SESSION;
1159
        }
1160
        return Yii::$app->response->redirect([
1161
            $url,
1162
            'endSessionAuthorizationRequestId' => $endSessionAuthorizationRequest->getRequestId(),
1163
        ]);
1164
    }
1165
1166
    /**
1167 5
     * Get a previously stored Client Authorization Request from the session.
1168
     * @param string $requestId
1169 5
     * @return Oauth2ClientAuthorizationRequestInterface|null
1170 5
     * @since 1.0.0
1171 5
     */
1172 5
    public function getClientAuthReqSession($requestId)
1173 5
    {
1174
        return $this->getAuthReqSession(
1175
            $requestId,
1176
            static::CLIENT_AUTHORIZATION_REQUEST_SESSION_PREFIX,
1177
            Oauth2ClientAuthorizationRequestInterface::class,
1178
        );
1179
    }
1180
1181
    /**
1182
     * Get a previously stored OIDC End Session Authorization Request from the session.
1183
     * @param string $requestId
1184
     * @return Oauth2EndSessionAuthorizationRequestInterface|null
1185
     * @since 1.0.0
1186
     */
1187
    public function getEndSessionAuthReqSession($requestId)
1188
    {
1189
        return $this->getAuthReqSession(
1190
            $requestId,
1191
            static::END_SESSION_AUTHORIZATION_REQUEST_SESSION_PREFIX,
1192
            Oauth2EndSessionAuthorizationRequestInterface::class,
1193
        );
1194
    }
1195
1196
    /**
1197
     * Get a previously stored Authorization Request from the session.
1198
     * @template T of Oauth2BaseAuthorizationRequestInterface
1199
     * @param string $requestId
1200 5
     * @param string $cachePrefix
1201
     * @param class-string<T> $expectedInterface
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
1202 5
     * @return T|null
1203
     * @since 1.0.0
1204
     */
1205 5
    protected function getAuthReqSession($requestId, $cachePrefix, $expectedInterface)
1206 5
    {
1207 5
        if (empty($requestId)) {
1208 2
            return null;
1209 1
        }
1210 1
        $key = $cachePrefix . $requestId;
1211 1
        $authorizationRequest = 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

1211
        /** @scrutinizer ignore-call */ 
1212
        $authorizationRequest = 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...
1212 1
        if (!($authorizationRequest instanceof $expectedInterface)) {
1213
            if (!empty($authorizationRequest)) {
1214 2
                Yii::warning(
1215
                    'Found a Authorization Request Session with key "' . $key
1216 5
                    . '", but it\'s not a ' . $expectedInterface
1217 1
                );
1218 1
            }
1219 1
            return null;
1220 1
        }
1221 1
        if ($authorizationRequest->getRequestId() !== $requestId) {
1222
            Yii::warning(
1223 5
                'Found a Authorization Request Session with key "' . $key
1224
                . '", but its request id does not match "' . $requestId . '".'
1225 5
            );
1226
            return null;
1227
        }
1228
        $authorizationRequest->setModule($this);
1229
1230
        return $authorizationRequest;
1231
    }
1232
1233 8
    /**
1234
     * Stores the Client Authorization Request in the session.
1235 8
     * @param Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest
1236 8
     * @since 1.0.0
1237 8
     */
1238 8
    public function setClientAuthReqSession($clientAuthorizationRequest)
1239
    {
1240
        $this->setAuthReqSession(
1241
            $clientAuthorizationRequest,
1242
            static::CLIENT_AUTHORIZATION_REQUEST_SESSION_PREFIX
1243
        );
1244
    }
1245
1246
    /**
1247
     * Stores the OIDC End Session Authorization Request in the session.
1248
     * @param Oauth2EndSessionAuthorizationRequestInterface $endSessionAuthorizationRequest
1249
     * @since 1.0.0
1250
     */
1251
    public function setEndSessionAuthReqSession($endSessionAuthorizationRequest)
1252
    {
1253
        $this->setAuthReqSession(
1254
            $endSessionAuthorizationRequest,
1255
            static::END_SESSION_AUTHORIZATION_REQUEST_SESSION_PREFIX
1256
        );
1257
    }
1258
1259
    /**
1260 8
     * Stores the Authorization Request in the session.
1261
     * @param Oauth2BaseAuthorizationRequestInterface $authorizationRequest
1262 8
     * @param string $cachePrefix
1263 8
     * @since 1.0.0
1264 1
     */
1265
    protected function setAuthReqSession($authorizationRequest, $cachePrefix)
1266 7
    {
1267 7
        $requestId = $authorizationRequest->getRequestId();
1268
        if (empty($requestId)) {
1269
            throw new InvalidArgumentException('$authorizationRequest must return a request id.');
1270
        }
1271
        $key = $cachePrefix . $requestId;
1272
        Yii::$app->session->set($key, $authorizationRequest);
1273
    }
1274
1275 2
    /**
1276
     * Clears a Client Authorization Request from the session storage.
1277 2
     * @param string $requestId
1278
     * @since 1.0.0
1279
     */
1280
    public function removeClientAuthReqSession($requestId)
1281
    {
1282
        $this->removeAuthReqSession($requestId, static::CLIENT_AUTHORIZATION_REQUEST_SESSION_PREFIX);
1283
    }
1284
1285
    /**
1286
     * Clears an End Session Authorization Request from the session storage.
1287
     * @param string $requestId
1288
     * @since 1.0.0
1289
     */
1290
    public function removeEndSessionAuthReqSession($requestId)
1291
    {
1292
        $this->removeAuthReqSession($requestId, static::END_SESSION_AUTHORIZATION_REQUEST_SESSION_PREFIX);
1293
    }
1294
1295
    /**
1296 2
     * Clears an Authorization Request from the session storage.
1297
     * @param string $requestId
1298 2
     * @param string $cachePrefix
1299 1
     * @since 1.0.0
1300
     */
1301 1
    public function removeAuthReqSession($requestId, $cachePrefix)
1302 1
    {
1303
        if (empty($requestId)) {
1304
            throw new InvalidArgumentException('$requestId can not be empty.');
1305
        }
1306
        $key = $cachePrefix . $requestId;
1307
        Yii::$app->session->remove($key);
1308
    }
1309
1310
    /**
1311
     * Stores whether the user was authenticated during the completion of the Client Authorization Request.
1312
     * @param string $clientAuthorizationRequestId
1313
     * @param bool $authenticatedDuringRequest
1314
     * @since 1.0.0
1315
     */
1316
    public function setUserAuthenticatedDuringClientAuthRequest(
1317
        $clientAuthorizationRequestId,
1318
        $authenticatedDuringRequest
1319
    ) {
1320
        $clientAuthorizationRequest = $this->getClientAuthReqSession($clientAuthorizationRequestId);
1321
        if ($clientAuthorizationRequest) {
1322
            $clientAuthorizationRequest->setUserAuthenticatedDuringRequest($authenticatedDuringRequest);
1323
            $this->setClientAuthReqSession($clientAuthorizationRequest);
1324
        }
1325
    }
1326
1327
    /**
1328
     * Stores the user identity selected during the completion of the Client Authorization Request.
1329
     * @param string $clientAuthorizationRequestId
1330
     * @param Oauth2UserInterface $userIdentity
1331
     * @since 1.0.0
1332
     */
1333
    public function setClientAuthRequestUserIdentity($clientAuthorizationRequestId, $userIdentity)
1334
    {
1335
        $clientAuthorizationRequest = $this->getClientAuthReqSession($clientAuthorizationRequestId);
1336
        if ($clientAuthorizationRequest) {
1337
            $clientAuthorizationRequest->setUserIdentity($userIdentity);
1338
            $this->setClientAuthReqSession($clientAuthorizationRequest);
1339
        }
1340
    }
1341
1342
    /**
1343 1
     * Generates a redirect Response when the Client Authorization Request is completed.
1344
     * @param Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest
1345 1
     * @return Response
1346 1
     * @since 1.0.0
1347 1
     */
1348
    public function generateClientAuthReqCompledRedirectResponse($clientAuthorizationRequest)
1349
    {
1350
        $clientAuthorizationRequest->processAuthorization();
1351
        $this->setClientAuthReqSession($clientAuthorizationRequest);
1352
        return Yii::$app->response->redirect($clientAuthorizationRequest->getAuthorizationRequestUrl());
1353
    }
1354
1355
    /**
1356
     * Generates a redirect Response when the End Session Authorization Request is completed.
1357
     * @param Oauth2EndSessionAuthorizationRequestInterface $endSessionAuthorizationRequest
1358
     * @return Response
1359
     * @since 1.0.0
1360
     */
1361
    public function generateEndSessionAuthReqCompledRedirectResponse($endSessionAuthorizationRequest)
1362
    {
1363
        $endSessionAuthorizationRequest->processAuthorization();
1364
        $this->setEndSessionAuthReqSession($endSessionAuthorizationRequest);
1365
        return Yii::$app->response->redirect($endSessionAuthorizationRequest->getEndSessionRequestUrl());
1366
    }
1367
1368 5
    /**
1369
     * @return IdentityInterface|Oauth2UserInterface|Oauth2OidcUserInterface|null
1370 5
     * @throws InvalidConfigException
1371 5
     * @since 1.0.0
1372 1
     */
1373 1
    public function getUserIdentity()
1374 1
    {
1375 1
        $user = Yii::$app->user->identity;
1376
        if (!empty($user) && !($user instanceof Oauth2UserInterface)) {
1377 4
            throw new InvalidConfigException(
1378
                'Yii::$app->user->identity (currently ' . get_class($user)
1379
                    . ') must implement ' . Oauth2UserInterface::class
1380
            );
1381
        }
1382
        return $user;
1383
    }
1384
1385
    /**
1386
     * Validates a bearer token authenticated request. Note: this method does not return a result but will throw
1387 3
     * an exception when the authentication fails.
1388
     * @throws InvalidConfigException
1389 3
     * @throws Oauth2ServerException
1390
     * @since 1.0.0
1391 3
     */
1392
    public function validateAuthenticatedRequest()
1393 3
    {
1394 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

1394
        $psr7Request = Psr7Helper::yiiToPsr7Request(/** @scrutinizer ignore-type */ Yii::$app->request);
Loading history...
1395
1396
        $psr7Request = $this->getResourceServer()->validateAuthenticatedRequest($psr7Request);
1397
1398
        $claims = $this->getAccessTokenClaims(Yii::$app->request->getBodyParam('token'));
0 ignored issues
show
Bug introduced by
It seems like Yii::app->request->getBodyParam('token') can also be of type null; however, parameter $token of rhertogh\Yii2Oauth2Serve...:getAccessTokenClaims() 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

1398
        $claims = $this->getAccessTokenClaims(/** @scrutinizer ignore-type */ Yii::$app->request->getBodyParam('token'));
Loading history...
1399
        foreach ($claims->all() as $claimKey => $claimValue) {
1400
            if (!$this->isDefaultClaimKey($claimKey)) {
1401
                $psr7Request = $psr7Request->withAttribute($claimKey, $claimValue);
1402
            }
1403
        }
1404
1405
        $this->_oauthClaims = $psr7Request->getAttributes();
1406
        $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...
1407
    }
1408 4
1409
    /**
1410 4
     * Find a user identity bases on an access token.
1411 1
     * Note: validateAuthenticatedRequest() must be called before this method is called.
1412
     * @param string $token
1413
     * @param string $type
1414
     * @return Oauth2UserInterface|null
1415 3
     * @throws InvalidConfigException
1416 3
     * @throws Oauth2ServerException
1417
     * @see validateAuthenticatedRequest()
1418 1
     * @since 1.0.0
1419 1
     */
1420 1
    public function findIdentityByAccessToken($token, $type)
1421
    {
1422
        if (!is_a($type, Oauth2HttpBearerAuthInterface::class, true)) {
1423 2
            throw new InvalidCallException($type . ' must implement ' . Oauth2HttpBearerAuthInterface::class);
1424 2
        }
1425 1
1426
        if (
1427
            !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

1427
            !preg_match('/^Bearer\s+(.*?)$/', /** @scrutinizer ignore-type */ $this->_oauthClaimsAuthorizationHeader, $matches)
Loading history...
1428 1
            || !Yii::$app->security->compareString($matches[1], $token)
1429
        ) {
1430
            throw new InvalidCallException(
1431
                'validateAuthenticatedRequest() must be called before findIdentityByAccessToken().'
1432
            );
1433
        }
1434
1435
        $userId = $this->getRequestOauthUserId();
1436
        if (empty($userId)) {
1437
            return null;
1438
        }
1439
1440
        return $this->identityClass::findIdentity($userId);
1441
    }
1442
1443
    /**
1444
     * Generate a "Personal Access Token" (PAT) which can be used as an alternative to using passwords
1445
     * for authentication (e.g. when using an API or command line).
1446
     *
1447 3
     * Note: Personal Access Tokens are intended to access resources on behalf users themselves.
1448
     *       To grant access to resources on behalf of an organization, or for long-lived integrations,
1449 3
     *       you most likely want to define an Oauth2 Client with the "Client Credentials" grant
1450 2
     *       (https://oauth.net/2/grant-types/client-credentials).
1451 2
     *
1452 2
     * @param string $clientIdentifier The Oauth2 client identifier for which the PAT should be generated.
1453 1
     * @param int|string $userIdentifier The identifier (primary key) of the user for which the PAT should be generated.
1454 1
     * @param Oauth2ScopeInterface[]|string[]|string|null $scope The Access Token scope.
1455 1
     * @param string|true|null $clientSecret If the client is a "confidential" client the secret is required.
1456
     *        If the boolean value `true` is passed, the client secret is automatically injected.
1457
     * @return Oauth2AccessTokenData
1458
     */
1459
    public function generatePersonalAccessToken($clientIdentifier, $userIdentifier, $scope = null, $clientSecret = null)
1460
    {
1461 2
        if (is_array($scope)) {
1462
            $scopeIdentifiers = [];
1463
            foreach ($scope as $scopeItem) {
1464 3
                if (is_string($scopeItem)) {
1465
                    $scopeIdentifiers[] = $scopeItem;
1466 3
                } elseif ($scopeItem instanceof Oauth2ScopeInterface) {
1467 3
                    $scopeIdentifiers[] = $scopeItem->getIdentifier();
1468 3
                } else {
1469
                    throw new InvalidArgumentException('If $scope is an array its elements must be either'
1470
                        . ' a string or an instance of ' . Oauth2ScopeInterface::class);
1471
                }
1472
            }
1473
            $scope = implode(' ', $scopeIdentifiers);
1474 3
        }
1475 3
1476 3
        if ($clientSecret === true) {
1477 3
            /** @var Oauth2ClientInterface $client */
1478 3
            $client = $this->getClientRepository()->findModelByIdentifier($clientIdentifier);
1479 3
            if ($client && $client->isConfidential()) {
1480 3
                $clientSecret = $client->getDecryptedSecret($this->getCryptographer());
1481
            } else {
1482 3
                $clientSecret = null;
1483 3
            }
1484 3
        }
1485 3
1486 3
        $request = (new Psr7ServerRequest('POST', ''))->withParsedBody([
1487 3
            'grant_type' => static::GRANT_TYPE_IDENTIFIER_PERSONAL_ACCESS_TOKEN,
1488 3
            'client_id' => $clientIdentifier,
1489 3
            'client_secret' => $clientSecret,
1490 3
            'user_id' => $userIdentifier,
1491
            'scope' => $scope,
1492
        ]);
1493
1494
        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

1494
        return new Oauth2AccessTokenData(/** @scrutinizer ignore-type */ Json::decode(
Loading history...
1495
            $this->getAuthorizationServer()
1496 5
                ->respondToAccessTokenRequest(
1497
                    $request,
1498 5
                    new Psr7Response()
1499
                )
1500 1
                ->getBody()
1501
                ->__toString()
1502 4
        ));
1503 1
    }
1504 1
1505 1
    /**
1506
     * @inheritDoc
1507 3
     */
1508
    public function getRequestOauthClaim($attribute, $default = null)
1509
    {
1510
        if (empty($this->_oauthClaimsAuthorizationHeader)) {
1511
            // User authorization was not processed by Oauth2Module.
1512
            return $default;
1513
        }
1514
        if (Yii::$app->request->getHeaders()->get('Authorization') !== $this->_oauthClaimsAuthorizationHeader) {
1515
            throw new InvalidCallException(
1516 32
                'App Request Authorization header does not match the processed Oauth header.'
1517
            );
1518 32
        }
1519 32
        return $this->_oauthClaims[$attribute] ?? $default;
1520 6
    }
1521
1522
    /**
1523
     * Helper function to ensure the required properties are configured for the module.
1524
     * @param string[] $properties
1525
     * @throws InvalidConfigException
1526
     * @since 1.0.0
1527
     */
1528
    protected function ensureProperties($properties)
1529
    {
1530
        foreach ($properties as $property) {
1531
            if (empty($this->$property)) {
1532
                throw new InvalidConfigException(__CLASS__ . '::$' . $property . ' must be set.');
1533
            }
1534
        }
1535
    }
1536
1537
    /**
1538
     * @throws InvalidConfigException
1539
     */
1540
    public function logoutUser($revokeTokens = true)
1541
    {
1542
        $identity = $this->getUserIdentity();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $identity is correct as $this->getUserIdentity() targeting rhertogh\Yii2Oauth2Serve...dule::getUserIdentity() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1543
1544
        if ($identity) {
0 ignored issues
show
introduced by
$identity is of type null, thus it always evaluated to false.
Loading history...
1545
            if ($revokeTokens) {
1546
                $this->revokeTokensByUserId($identity->getId());
1547
            }
1548 4
1549
            Yii::$app->user->logout();
1550 4
        }
1551 4
    }
1552 4
1553 4
    public function revokeTokensByUserId($userId)
1554 4
    {
1555 4
        $accessTokens = $this->getAccessTokenRepository()->revokeAccessTokensByUserId($userId);
1556
        $accessTokenIds = array_map(fn($accessToken) => $accessToken->getPrimaryKey(), $accessTokens);
1557 4
        $this->getRefreshTokenRepository()->revokeRefreshTokensByAccessTokenIds($accessTokenIds);
1558 4
    }
1559
1560
    public function getSupportedPromptValues()
1561 4
    {
1562
        $supportedPromptValues = [
1563
            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_NONE,
1564
            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_LOGIN,
1565
            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_CONSENT,
1566
            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_SELECT_ACCOUNT,
1567 2
        ];
1568
1569 2
        if (!empty($this->userAccountCreationUrl)) {
1570 1
            $supportedPromptValues[] = Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_CREATE;
1571
        }
1572
1573 1
        return $supportedPromptValues;
1574
    }
1575
1576
    /**
1577
     * @return int
1578
     */
1579
    public function getElaboratedHttpClientErrorsLogLevel()
1580
    {
1581
        if ($this->httpClientErrorsLogLevel === null) {
1582
            return YII_DEBUG ? Logger::LEVEL_ERROR : Logger::LEVEL_INFO;
1583
        }
1584
1585
        return $this->httpClientErrorsLogLevel;
1586
    }
1587
1588
1589
    public function getJwtConfiguration(): Configuration
1590
    {
1591
        // Based on \League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator::initJwtConfiguration().
1592
        $jwtConfiguration = Configuration::forSymmetricSigner(
1593
            new Sha256(),
1594
            InMemory::plainText('empty', 'empty')
1595
        );
1596
1597
        $publicKey = $this->getPublicKey();
1598
        $jwtConfiguration->setValidationConstraints(
1599
            new SignedWith(
1600
                new Sha256(),
1601
                InMemory::plainText($publicKey->getKeyContents(), $publicKey->getPassPhrase() ?? '')
1602
            )
1603
        );
1604
1605
        return $jwtConfiguration;
1606
    }
1607
1608
    public function getAccessToken(string $token): Token
1609
    {
1610
        $jwtConfiguration = $this->getJwtConfiguration();
1611
        $accessToken = $jwtConfiguration->parser()->parse($token);
1612
        $jwtConfiguration->validator()->assert($accessToken, ...$jwtConfiguration->validationConstraints());
1613
        Yii::debug('Found access token: ' . $token, __METHOD__);
1614
1615
        return $accessToken;
1616
    }
1617
1618
    public function getAccessTokenClaims(string $token): Token\DataSet
1619
    {
1620
        return $this->getAccessToken($token)->claims();
1621
    }
1622
1623
    private function isDefaultClaimKey(string $claimKey): bool
1624
    {
1625
        return in_array(
1626
            $claimKey,
1627
            [
1628
                'aud',
1629
                'jti',
1630
                'iat',
1631
                'nbf',
1632
                'exp',
1633
                'sub',
1634
                'scopes',
1635
                'client_id',
1636
            ]
1637
        );
1638
    }
1639
}
1640