Oauth2Module::createClient()   A
last analyzed

Complexity

Conditions 4
Paths 8

Size

Total Lines 47
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 4

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 24
c 3
b 0
f 0
dl 0
loc 47
ccs 25
cts 25
cp 1
rs 9.536
cc 4
nc 8
nop 10
crap 4

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * @link http://www.yiiframework.com/
5
 * @copyright Copyright (c) 2008 Yii Software LLC
6
 * @license http://www.yiiframework.com/license/
7
 */
8
9
namespace rhertogh\Yii2Oauth2Server;
10
11
// 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
     * Offset of Bearer: in Authorization header
227
     */
228
    protected const BEARER_TOKEN_OFFSET = 7;
229
230
    /**
231
     * @inheritdoc
232
     */
233
    public $controllerNamespace = __NAMESPACE__ . '\-'; // Set explicitly via $controllerMap in `init()`.
234
235
    /**
236
     * @var string|null The application type. If `null` the type will be automatically detected.
237
     * @see APPLICATION_TYPES
238
     */
239
    public $appType = null;
240
241
    /**
242
     * @var int The Oauth 2.0 Server Roles the module will perform.
243
     * @since 1.0.0
244
     */
245
    public $serverRole = self::SERVER_ROLE_AUTHORIZATION_SERVER | self::SERVER_ROLE_RESOURCE_SERVER;
246
247
    /**
248
     * @var string|null The private key for the server. Can be a string containing the key itself or point to a file.
249
     * When pointing to a file it's recommended to use an absolute path prefixed with 'file://' or start with
250
     * '@' to use a Yii path alias.
251
     * @see $privateKeyPassphrase For setting a passphrase for the private key.
252
     * @since 1.0.0
253
     */
254
    public $privateKey = null;
255
256
    /**
257
     * @var string|null The passphrase for the private key.
258
     * @since 1.0.0
259
     */
260
    public $privateKeyPassphrase = null;
261
    /**
262
     * @var string|null The public key for the server. Can be a string containing the key itself or point to a file.
263
     * When pointing to a file it's recommended to use an absolute path prefixed with 'file://' or start with
264
     * '@' to use a Yii path alias.
265
     * @since 1.0.0
266
     */
267
    public $publicKey = null;
268
269
    /**
270
     * @var string|null The encryption key for authorization and refresh codes.
271
     * @since 1.0.0
272
     */
273
    public $codesEncryptionKey = null;
274
275
    /**
276
     * @var string[]|string|null The encryption keys for storage like client secrets.
277
     * Where the array key is the name of the key, and the value the key itself. E.g.
278
     * `['2022-01-01' => 'def00000cb36fd6ed6641e0ad70805b28d....']`
279
     * If a string (instead of an array of strings) is specified it will be JSON decoded
280
     * it should contain an object where each property name is the name of the key, its value the key itself. E.g.
281
     * `{"2022-01-01": "def00000cb36fd6ed6641e0ad70805b28d...."}`
282
     *
283
     * @since 1.0.0
284
     */
285
    public $storageEncryptionKeys = null;
286
287
    /**
288
     * @var string|null The index of the default key in storageEncryptionKeys. E.g. 'myKey'.
289
     * @since 1.0.0
290
     */
291
    public $defaultStorageEncryptionKey = null;
292
293
    /**
294
     * @var string|string[]|null IP addresses, CIDR ranges, or range aliases that are allowed to connect over a
295
     * non-TLS connection. If `null` or an empty array LTS is always required.
296
     *
297
     * Warning: Although you can use '*' or 'any' to allow a non-TLS connection from any ip address,
298
     * doing so would most likely introduce a security risk and should be done for debugging purposes only!
299
     *
300
     * @see \yii\validators\IpValidator::$networks for a list of available alliasses.
301
     */
302
    public $nonTlsAllowedRanges = 'localhost';
303
304
    /**
305
     * @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...
306
     * most likely the same as the 'identityClass' of your application's User Component.
307
     * @since 1.0.0
308
     */
309
    public $identityClass = null;
310
311
    /**
312
     * @var null|string Prefix used for url rules. When `null` the module's uniqueId will be used.
313
     * @since 1.0.0
314
     */
315
    public $urlRulesPrefix = null;
316
317
    /**
318
     * @var string URL path for the access token endpoint (will be prefixed with $urlRulesPrefix).
319
     * @since 1.0.0
320
     */
321
    public $authorizePath = 'authorize';
322
323
    /**
324
     * @var string URL path for the access token endpoint (will be prefixed with $urlRulesPrefix).
325
     * @since 1.0.0
326
     */
327
    public $accessTokenPath = 'access-token';
328
329
    /**
330
     * @var string URL path for the token revocation endpoint (will be prefixed with $urlRulesPrefix).
331
     * @since 1.0.0
332
     */
333
    public $tokenRevocationPath = 'revoke';
334
335
    /**
336
     * @var string URL path for the certificates jwks endpoint (will be prefixed with $urlRulesPrefix).
337
     * @since 1.0.0
338
     */
339
    public $jwksPath = 'certs';
340
341
    /**
342
     * The URL to the page where the user can perform the Client/Scope authorization
343
     * (if `null` the build in page will be used).
344
     * @return string
345
     * @since 1.0.0
346
     * @see $clientAuthorizationPath
347
     */
348
    public $clientAuthorizationUrl = null;
349
350
    /**
351
     * @var string The URL path to the build in page where the user can authorize the Client for the requested Scopes
352
     * (will be prefixed with $urlRulesPrefix).
353
     * Note: This setting will only be used if $clientAuthorizationUrl is `null`.
354
     * @since 1.0.0
355
     * @see $clientAuthorizationView
356
     */
357
    public $clientAuthorizationPath = 'authorize-client';
358
359
    /**
360
     * @var string The view to use in the "Client Authorization" action for the page where the user can
361
     * authorize the Client for the requested Scopes.
362
     * Note: This setting will only be used if $clientAuthorizationUrl is `null`.
363
     * @since 1.0.0
364
     * @see $clientAuthorizationPath
365
     */
366
    public $clientAuthorizationView = 'authorize-client';
367
368
    /**
369
     * @var bool Allow clients to invoke token revocation (RFC 7009).
370
     * @see https://datatracker.ietf.org/doc/html/rfc7009
371
     */
372
    public $enableTokenRevocation = true;
373
374
    /**
375
     * @var bool Will the server throw an exception when a Client requests an unknown or unauthorized scope
376
     * (would be silently ignored otherwise).
377
     * Note: this setting can be overwritten per client.
378
     */
379
    public $exceptionOnInvalidScope = false;
380
381
    /**
382
     * Configuration for `Oauth2Client::getRedirectUrisEnvVarConfig()` fallback (the
383
     * Oauth2Client::$envVarConfig['redirectUris'] has precedence).
384
     * When configured, environment variables specified in the `Oauth2Client` redirect URI(s) will be substituted with
385
     * their values. Please see `EnvironmentHelper::parseEnvVars()` for more details.
386
     *
387
     * Warning: This setting applies to all clients, for security it's recommended to specify this configuration at the
388
     * individual client level via its `envVarConfig` setting.
389
     *
390
     * @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...
391
     *          allowList: array,
392
     *          denyList: array|null,
393
     *          parseNested: bool,
394
     *          exceptionWhenNotSet: bool,
395
     *          exceptionWhenNotAllowed: bool,
396
     *      }|null
397
     * @see Oauth2ClientInterface::setEnvVarConfig()
398
     * @see Oauth2ClientInterface::getRedirectUrisEnvVarConfig()
399
     * @see \rhertogh\Yii2Oauth2Server\helpers\EnvironmentHelper::parseEnvVars()
400
     */
401
    public $clientRedirectUrisEnvVarConfig = null;
402
403
    public $userAccountCreationUrl = null;
0 ignored issues
show
Coding Style Documentation introduced by
Missing member variable doc comment
Loading history...
404
405
    /**
406
     * @var string|null The URL path to the OpenID Connect Provider Configuration Information Action.
407
     * If set to `null` the endpoint will be disabled.
408
     * Note: This path is defined in the
409
     *       [OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.4)
410
     *       specification and should normally not be changed.
411
     * @since 1.0.0
412
     */
413
    public $openIdConnectProviderConfigurationInformationPath = '.well-known/openid-configuration';
414
415
    /**
416
     * @var string The URL path to the OpenID Connect Userinfo Action (will be prefixed with $urlRulesPrefix).
417
     * Note: This setting will only be used if $enableOpenIdConnect and $openIdConnectUserinfoEndpoint are `true`.
418
     * @since 1.0.0
419
     * @see $openIdConnectUserinfoEndpoint
420
     */
421
    public $openIdConnectUserinfoPath = 'oidc/userinfo';
422
423
    /**
424
     * @var string The URL path to the OpenID Connect End Session Action (will be prefixed with $urlRulesPrefix).
425
     * Note: This setting will only be used if $enableOpenIdConnect and
426
     * $openIdConnectRpInitiatedLogoutEndpoint are `true`.
427
     * @since 1.0.0
428
     * @see $openIdConnectRpInitiatedLogoutEndpoint
429
     * @see https://openid.net/specs/openid-connect-rpinitiated-1_0.html
430
     */
431
    public $openIdConnectRpInitiatedLogoutPath = 'oidc/end-session';
432
433
    /**
434
     * The URL to the page where the user can perform the End Session (logout) confirmation
435
     * (if `null` the build in page will be used).
436
     * @return string
437
     * @since 1.0.0
438
     * @see $openIdConnectLogoutConfirmationPath
439
     */
440
    public $openIdConnectLogoutConfirmationUrl = null;
441
442
    /**
443
     * @var string The URL path to the build in page where the user can confirm the End Session (logout) request
444
     * (will be prefixed with $urlRulesPrefix).
445
     * Note: This setting will only be used if $openIdConnectLogoutConfirmationUrl is `null`.
446
     * @since 1.0.0
447
     * @see $openIdConnectLogoutConfirmationView
448
     */
449
    public $openIdConnectLogoutConfirmationPath = 'confirm-logout';
450
451
    /**
452
     * @var string The view to use in the "End Session Authorization" action for the page where the user can
453
     * authorize the End Session (logout) request.
454
     * Note: This setting will only be used if $openIdConnectLogoutConfirmationUrl is `null`.
455
     * @since 1.0.0
456
     * @see $openIdConnectLogoutConfirmationPath
457
     */
458
    public $openIdConnectLogoutConfirmationView = 'confirm-logout';
459
460
461
    /**
462
     * @var Oauth2GrantTypeFactoryInterface[]|GrantTypeInterface[]|string[]|Oauth2GrantTypeFactoryInterface|GrantTypeInterface|string|callable
463
     * The Oauth 2.0 Grant Types that the module will serve.
464
     * @since 1.0.0
465
     */
466
    public $grantTypes = [];
467
468
    /**
469
     * @var bool Should the resource server check for revocation of the access token.
470
     * @since 1.0.0
471
     */
472
    public $resourceServerAccessTokenRevocationValidation = true;
473
474
    /**
475
     * @var bool Enable support for OpenIdvConnect.
476
     * @since 1.0.0
477
     */
478
    public $enableOpenIdConnect = false;
479
480
    /**
481
     * @var bool Enable the .well-known/openid-configuration discovery endpoint.
482
     * @since 1.0.0
483
     */
484
    public $enableOpenIdConnectDiscovery = true;
485
486
    /**
487
     * @var bool include `grant_types_supported` in the OpenIdConnect Discovery.
488
     * Note: Since grant types can be specified per client not all clients might support all enabled grant types.
489
     * @since 1.0.0
490
     */
491
    public $openIdConnectDiscoveryIncludeSupportedGrantTypes = true;
492
493
    /**
494
     * @var string URL to include in the OpenID Connect Discovery Service of a page containing
495
     * human-readable information that developers might want or need to know when using the OpenID Provider.
496
     * @see 'service_documentation' in https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3
497
     * @since 1.0.0
498
     */
499
    public $openIdConnectDiscoveryServiceDocumentationUrl = null;
500
501
    /**
502
     * @var string|bool A string to a custom userinfo endpoint or `true` to enable the build in endpoint.
503
     * @since 1.0.0
504
     * @see $openIdConnectUserinfoPath
505
     */
506
    public $openIdConnectUserinfoEndpoint = true;
507
508
    /**
509
     * @var string|bool A string to a custom logout endpoint or `true` to enable the build in endpoint.
510
     * @since 1.0.0
511
     * @see $openIdConnectRpInitiatedLogoutPath
512
     * @see https://openid.net/specs/openid-connect-rpinitiated-1_0.html
513
     */
514
    public $openIdConnectRpInitiatedLogoutEndpoint = false;
515
516
    /**
517
     * @var bool Allow access to the "end session" endpoint without user authentication (in the form of the
518
     * `id_token_hint` parameter). If enabled the "end session" endpoint will always prompt the user to verify the
519
     * logout if no `id_token_hint` is provided and no redirect after logout will be performed.
520
     * Note: If disabled the client's `oidc_rp_initiated_logout` will be used
521
     * to determine whether to prompt the end-user for logout validation.
522
     * @since 1.0.0
523
     * @see $openIdConnectRpInitiatedLogoutPath
524
     * @see https://openid.net/specs/openid-connect-rpinitiated-1_0.html
525
     */
526
    public $openIdConnectAllowAnonymousRpInitiatedLogout = false;
527
528
    /**
529
     * Warning! Enabling this setting might introduce privacy concerns since the client could poll for the
530
     * online status of a user.
531
     *
532
     * @var bool If this setting is disabled in case of OpenID Connect Context the Access Token won't include a
533
     * Refresh Token when the 'offline_access' scope is not included in the authorization request.
534
     * In some cases it might be needed to always include a Refresh Token, in that case enable this setting and
535
     * implement the `Oauth2OidcUserSessionStatusInterface` on the User Identity model.
536
     * @since 1.0.0
537
     */
538
    public $openIdConnectIssueRefreshTokenWithoutOfflineAccessScope = false;
539
540
    /**
541
     * @var int The default option for "User Account Selection' when not specified for a client.
542
     * @since 1.0.0
543
     */
544
    public $defaultUserAccountSelection = self::USER_ACCOUNT_SELECTION_DISABLED;
545
546
    /**
547
     * @var bool|null Display exception messages that might leak server details. This could be useful for debugging.
548
     * In case of `null` (default) the YII_DEBUG constant will be used.
549
     * Warning: Should NOT be enabled in production!
550
     * @since 1.0.0
551
     */
552
    public $displayConfidentialExceptionMessages = null;
553
554
    /**
555
     * @var string|null The namespace with which migrations will be created (and by which they will be located).
556
     * Note: The specified namespace must be defined as a Yii alias (e.g. '@app').
557
     * @since 1.0.0
558
     */
559
    public $migrationsNamespace = null;
560
    /**
561
     * @var string|null Optional prefix used in the name of generated migrations
562
     * @since 1.0.0
563
     */
564
    public $migrationsPrefix = null;
565
    /**
566
     * @var string|array|int|null Sets the file ownership of generated migrations
567
     * @see \yii\helpers\BaseFileHelper::changeOwnership()
568
     * @since 1.0.0
569
     */
570
    public $migrationsFileOwnership = null;
571
    /**
572
     * @var int|null Sets the file mode of generated migrations
573
     * @see \yii\helpers\BaseFileHelper::changeOwnership()
574
     * @since 1.0.0
575
     */
576
    public $migrationsFileMode = null;
577
578
    /**
579
     * The log level for HTTP Client Errors (HTTP status code 400 - 499). Can be one of the following:
580
     *  - A log level of `\yii\log\Logger` => LEVEL_ERROR, LEVEL_WARNING, LEVEL_INFO, LEVEL_TRACE.
581
     *  - `0` => disable logging for HTTP Client Errors
582
     *  - null => The `YII_DEBUG` constant will be used to determine the log level.
583
     *            If `true` LEVEL_ERROR will be used, LEVEL_INFO otherwise.
584
     * @var int|null
585
     * @see \yii\log\Logger
586
     */
587
    public $httpClientErrorsLogLevel = null;
588
589
    /**
590
     * @var Oauth2AuthorizationServerInterface|null Cache for the authorization server
591
     * @since 1.0.0
592
     */
593
    protected $_authorizationServer = null;
594
595
    /**
596
     * @var Oauth2ResourceServerInterface|null Cache for the resource server
597
     * @since 1.0.0
598
     */
599
    protected $_resourceServer = null;
600
601
    /**
602
     * @var Oauth2CryptographerInterface|null Cache for the Oauth2Cryptographer
603
     * @since 1.0.0
604
     */
605
    protected $_cryptographer = null;
606
607
    /**
608
     * @var string|null The authorization header used when the authorization request was validated.
609
     * @since 1.0.0
610
     */
611
    protected $_oauthClaimsAuthorizationHeader = null;
612
613
    /**
614
     * @inheritDoc
615
     * @throws InvalidConfigException
616
     */
617 161
    public function init()
618
    {
619 161
        parent::init();
620
621 161
        $app = Yii::$app;
622
623 161
        if ($app instanceof WebApplication || $this->appType == static::APPLICATION_TYPE_WEB) {
624 31
            $controllerMap = static::CONTROLLER_MAP[static::APPLICATION_TYPE_WEB];
625 161
        } 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...
626 161
            $controllerMap = static::CONTROLLER_MAP[static::APPLICATION_TYPE_CONSOLE];
627 161
            $this->defaultRoute = 'debug';
628
        } else {
629 1
            throw new InvalidConfigException(
630 1
                'Unable to detect application type, configure it manually by setting `$appType`.'
631 1
            );
632
        }
633 161
        $controllerMap = array_filter(
634 161
            $controllerMap,
635 161
            fn($controllerSettings) => $controllerSettings['serverRole'] & $this->serverRole
636 161
        );
637 161
        $this->controllerMap = ArrayHelper::getColumn($controllerMap, 'controller');
638
639 161
        if (empty($this->identityClass)) {
640 1
            throw new InvalidConfigException('$identityClass must be set.');
641 161
        } elseif (!is_a($this->identityClass, Oauth2UserInterface::class, true)) {
642 1
            throw new InvalidConfigException(
643 1
                $this->identityClass . ' must implement ' . Oauth2UserInterface::class
644 1
            );
645
        }
646
647 161
        foreach (static::DEFAULT_INTERFACE_IMPLEMENTATIONS as $interface => $implementation) {
648 161
            if (!Yii::$container->has($interface)) {
649 161
                Yii::$container->set($interface, $implementation);
650
            }
651
        }
652
653 161
        if (empty($this->urlRulesPrefix)) {
654 161
            $this->urlRulesPrefix = $this->uniqueId;
655
        }
656
657 161
        $this->registerTranslations();
658
    }
659
660
    /**
661
     * @inheritdoc
662
     * @throws InvalidConfigException
663
     */
664 161
    public function bootstrap($app)
665
    {
666
        if (
667 161
            $app instanceof WebApplication
668 161
            && $this->serverRole & static::SERVER_ROLE_AUTHORIZATION_SERVER
669
        ) {
670 31
            $rules = [
671 31
                $this->accessTokenPath => Oauth2ServerControllerInterface::CONTROLLER_NAME
672 31
                    . '/' . Oauth2ServerControllerInterface::ACTION_NAME_ACCESS_TOKEN,
673 31
                $this->authorizePath => Oauth2ServerControllerInterface::CONTROLLER_NAME
674 31
                    . '/' . Oauth2ServerControllerInterface::ACTION_NAME_AUTHORIZE,
675 31
                $this->jwksPath => Oauth2CertificatesControllerInterface::CONTROLLER_NAME
676 31
                    . '/' . Oauth2CertificatesControllerInterface::ACTION_NAME_JWKS,
677 31
            ];
678
679 31
            if ($this->enableTokenRevocation) {
680 31
                $rules[$this->tokenRevocationPath] = Oauth2ServerControllerInterface::CONTROLLER_NAME
681 31
                . '/' . Oauth2ServerControllerInterface::ACTION_NAME_REVOKE;
682
            }
683
684 31
            if (empty($this->clientAuthorizationUrl)) {
685 30
                $rules[$this->clientAuthorizationPath] = Oauth2ConsentControllerInterface::CONTROLLER_NAME
686 30
                    . '/' . Oauth2ConsentControllerInterface::ACTION_NAME_AUTHORIZE_CLIENT;
687
            }
688
689 31
            if ($this->enableOpenIdConnect && $this->openIdConnectUserinfoEndpoint === true) {
690 31
                $rules[$this->openIdConnectUserinfoPath] =
691 31
                    Oauth2OidcControllerInterface::CONTROLLER_NAME
692 31
                    . '/' . Oauth2OidcControllerInterface::ACTION_NAME_USERINFO;
693
            }
694
695 31
            if ($this->enableOpenIdConnect && $this->openIdConnectRpInitiatedLogoutEndpoint === true) {
696 31
                $rules[$this->openIdConnectRpInitiatedLogoutPath] =
697 31
                    Oauth2OidcControllerInterface::CONTROLLER_NAME
698 31
                    . '/' . Oauth2OidcControllerInterface::ACTION_END_SESSION;
699
700 31
                if (empty($this->openIdConnectLogoutConfirmationUrl)) {
701 31
                    $rules[$this->openIdConnectLogoutConfirmationPath] =
702 31
                        Oauth2ConsentControllerInterface::CONTROLLER_NAME
703 31
                        . '/' . Oauth2ConsentControllerInterface::ACTION_NAME_AUTHORIZE_END_SESSION;
704
                }
705
            }
706
707 31
            $urlManager = $app->getUrlManager();
708 31
            $urlManager->addRules([
709 31
                Yii::createObject([
710 31
                    'class' => GroupUrlRule::class,
711 31
                    'prefix' => $this->urlRulesPrefix,
712 31
                    'routePrefix' => $this->id,
713 31
                    'rules' => $rules,
714 31
                ]),
715 31
            ]);
716
717
            if (
718 31
                $this->enableOpenIdConnect
719 31
                && $this->enableOpenIdConnectDiscovery
720 31
                && $this->openIdConnectProviderConfigurationInformationPath
721
            ) {
722 31
                $urlManager->addRules([
723 31
                    Yii::createObject([
724 31
                        'class' => UrlRule::class,
725 31
                        'pattern' => $this->openIdConnectProviderConfigurationInformationPath,
726 31
                        'route' => $this->id
727 31
                            . '/' . Oauth2WellKnownControllerInterface::CONTROLLER_NAME
728 31
                            . '/' . Oauth2WellKnownControllerInterface::ACTION_NAME_OPENID_CONFIGURATION,
729 31
                    ]),
730 31
                ]);
731
            }
732
        }
733
    }
734
735
    /**
736
     * Registers the translations for the module
737
     * @param bool $force Force the setting of the translations (even if they are already defined).
738
     * @since 1.0.0
739
     */
740 161
    public function registerTranslations($force = false)
741
    {
742 161
        if ($force || !array_key_exists('oauth2', Yii::$app->i18n->translations)) {
743 161
            Yii::$app->i18n->translations['oauth2'] = [
744 161
                'class' => PhpMessageSource::class,
745 161
                'sourceLanguage' => 'en-US',
746 161
                'basePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages',
747 161
                'fileMap' => [
748 161
                    'oauth2' => 'oauth2.php',
749 161
                ],
750 161
            ];
751
        }
752
    }
753
754
    /**
755
     * @param string $identifier The client identifier
756
     * @param string $name The (user-friendly) name of the client
757
     * @param int $grantTypes The grant types enabled for this client.
758
     *        Use bitwise `OR` to combine multiple types,
759
     *        e.g. `Oauth2Module::GRANT_TYPE_AUTH_CODE | Oauth2Module::GRANT_TYPE_REFRESH_TOKEN`
760
     * @param string|string[] $redirectURIs One or multiple redirect URIs for the client
761
     * @param int $type The client type (e.g. Confidential or Public)
762
     *        See `\rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2ClientInterface::TYPES` for possible values
763
     * @param string|null $secret The client secret in case the client `type` is `confidential`.
764
     * @param string|string[]|array[]|Oauth2ScopeInterface[]|null $scopes
765
     * @param int|null $userId
766
     * @return Oauth2ClientInterface
767
     * @throws InvalidConfigException
768
     * @throws \yii\db\Exception
769
     */
770 5
    public function createClient(
771
        $identifier,
772
        $name,
773
        $grantTypes,
774
        $redirectURIs,
775
        $type,
776
        $secret = null,
777
        $scopes = null,
778
        $userId = null,
779
        $endUsersMayAuthorizeClient = null,
780
        $skipAuthorizationIfScopeIsAllowed = null
781
    ) {
782 5
        if (!($this->serverRole & static::SERVER_ROLE_AUTHORIZATION_SERVER)) {
783 1
            throw new InvalidCallException('Oauth2 server role does not include authorization server.');
784
        }
785
786
        /** @var Oauth2ClientInterface $client */
787 4
        $client = Yii::createObject([
788 4
            'class' => Oauth2ClientInterface::class,
789 4
            'identifier' => $identifier,
790 4
            'type' => $type,
791 4
            'name' => $name,
792 4
            'redirectUri' => $redirectURIs,
793 4
            'grantTypes' => $grantTypes,
794 4
            'endUsersMayAuthorizeClient' => $endUsersMayAuthorizeClient,
795 4
            'skip_authorization_if_scope_is_allowed' => $skipAuthorizationIfScopeIsAllowed,
796 4
            'clientCredentialsGrantUserId' => $userId
797 4
        ]);
798
799 4
        $transaction = $client::getDb()->beginTransaction();
800
801
        try {
802 4
            if ($type == Oauth2ClientInterface::TYPE_CONFIDENTIAL) {
803 4
                $client->setSecret($secret, $this->getCryptographer());
804
            }
805
806 3
            $client
807 3
                ->persist()
808 3
                ->syncClientScopes($scopes, $this->getScopeRepository());
809
810 3
            $transaction->commit();
811 1
        } catch (\Exception $e) {
812 1
            $transaction->rollBack();
813 1
            throw $e;
814
        }
815
816 3
        return $client;
817
    }
818
819
    /**
820
     * @return CryptKey The private key of the server.
821
     * @throws InvalidConfigException
822
     * @since 1.0.0
823
     */
824 22
    public function getPrivateKey()
825
    {
826 22
        $privateKey = $this->privateKey;
827 22
        if (StringHelper::startsWith($privateKey, '@')) {
828 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

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

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

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

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

1399
        $psr7Request = Psr7Helper::yiiToPsr7Request(/** @scrutinizer ignore-type */ Yii::$app->request);
Loading history...
1400
1401 3
        $psr7Request = $this->getResourceServer()->validateAuthenticatedRequest($psr7Request);
1402
1403 3
        $token = substr(Yii::$app->request->headers->get('Authorization'), self::BEARER_TOKEN_OFFSET);
0 ignored issues
show
Bug introduced by
It seems like Yii::app->request->headers->get('Authorization') can also be of type array and null; however, parameter $string of substr() 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

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

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

1504
        return new Oauth2AccessTokenData(/** @scrutinizer ignore-type */ Json::decode(
Loading history...
1505 3
            $this->getAuthorizationServer()
1506 3
                ->respondToAccessTokenRequest(
1507 3
                    $request,
1508 3
                    new Psr7Response()
1509 3
                )
1510 3
                ->getBody()
1511 3
                ->__toString()
1512 3
        ));
1513
    }
1514
1515
    /**
1516
     * @inheritDoc
1517
     */
1518 5
    public function getRequestOauthClaim($attribute, $default = null)
1519
    {
1520 5
        if (empty($this->_oauthClaimsAuthorizationHeader)) {
1521
            // User authorization was not processed by Oauth2Module.
1522 1
            return $default;
1523
        }
1524 4
        if (Yii::$app->request->getHeaders()->get('Authorization') !== $this->_oauthClaimsAuthorizationHeader) {
1525 1
            throw new InvalidCallException(
1526 1
                'App Request Authorization header does not match the processed Oauth header.'
1527 1
            );
1528
        }
1529 3
        return $this->_oauthClaims[$attribute] ?? $default;
1530
    }
1531
1532
    /**
1533
     * Helper function to ensure the required properties are configured for the module.
1534
     * @param string[] $properties
1535
     * @throws InvalidConfigException
1536
     * @since 1.0.0
1537
     */
1538 32
    protected function ensureProperties($properties)
1539
    {
1540 32
        foreach ($properties as $property) {
1541 32
            if (empty($this->$property)) {
1542 6
                throw new InvalidConfigException(__CLASS__ . '::$' . $property . ' must be set.');
1543
            }
1544
        }
1545
    }
1546
1547
    /**
1548
     * @throws InvalidConfigException
1549
     */
1550
    public function logoutUser($revokeTokens = true)
1551
    {
1552
        $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...
1553
1554
        if ($identity) {
0 ignored issues
show
introduced by
$identity is of type null, thus it always evaluated to false.
Loading history...
1555
            if ($revokeTokens) {
1556
                $this->revokeTokensByUserId($identity->getId());
1557
            }
1558
1559
            Yii::$app->user->logout();
1560
        }
1561
    }
1562
1563
    public function revokeTokensByUserId($userId)
1564
    {
1565
        $accessTokens = $this->getAccessTokenRepository()->revokeAccessTokensByUserId($userId);
1566
        $accessTokenIds = array_map(fn($accessToken) => $accessToken->getPrimaryKey(), $accessTokens);
1567
        $this->getRefreshTokenRepository()->revokeRefreshTokensByAccessTokenIds($accessTokenIds);
1568
    }
1569
1570 4
    public function getSupportedPromptValues()
1571
    {
1572 4
        $supportedPromptValues = [
1573 4
            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_NONE,
1574 4
            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_LOGIN,
1575 4
            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_CONSENT,
1576 4
            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_SELECT_ACCOUNT,
1577 4
        ];
1578
1579 4
        if (!empty($this->userAccountCreationUrl)) {
1580 4
            $supportedPromptValues[] = Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_CREATE;
1581
        }
1582
1583 4
        return $supportedPromptValues;
1584
    }
1585
1586
    /**
1587
     * @return int
1588
     */
1589 2
    public function getElaboratedHttpClientErrorsLogLevel()
1590
    {
1591 2
        if ($this->httpClientErrorsLogLevel === null) {
1592 1
            return YII_DEBUG ? Logger::LEVEL_ERROR : Logger::LEVEL_INFO;
1593
        }
1594
1595 1
        return $this->httpClientErrorsLogLevel;
1596
    }
1597
1598
1599 3
    public function getJwtConfiguration(): Configuration
1600
    {
1601
        // Based on \League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator::initJwtConfiguration().
1602 3
        $jwtConfiguration = Configuration::forSymmetricSigner(
1603 3
            new Sha256(),
1604 3
            InMemory::plainText('empty', 'empty')
1605 3
        );
1606
1607 3
        $publicKey = $this->getPublicKey();
1608 3
        $jwtConfiguration->setValidationConstraints(
1609 3
            new SignedWith(
1610 3
                new Sha256(),
1611 3
                InMemory::plainText($publicKey->getKeyContents(), $publicKey->getPassPhrase() ?? '')
1612 3
            )
1613 3
        );
1614
1615 3
        return $jwtConfiguration;
1616
    }
1617
1618 3
    public function getAccessToken(string $token): Token
1619
    {
1620 3
        $jwtConfiguration = $this->getJwtConfiguration();
1621 3
        $accessToken = $jwtConfiguration->parser()->parse($token);
1622 3
        $jwtConfiguration->validator()->assert($accessToken, ...$jwtConfiguration->validationConstraints());
1623 3
        Yii::debug('Found access token: ' . $token, __METHOD__);
1624
1625 3
        return $accessToken;
1626
    }
1627
1628 3
    protected function isDefaultClaimKey(string $claimKey): bool
1629
    {
1630 3
        return in_array(
1631 3
            $claimKey,
1632 3
            [
1633 3
                'aud',
1634 3
                'jti',
1635 3
                'iat',
1636 3
                'nbf',
1637 3
                'exp',
1638 3
                'sub',
1639 3
                'scopes',
1640 3
                'client_id',
1641 3
            ]
1642 3
        );
1643
    }
1644
}
1645