Passed
Push — master ( fb60c6...a721e7 )
by Rutger
03:14
created

Oauth2Module::getCryptographer()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

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

808
            $privateKey = 'file://' . /** @scrutinizer ignore-type */ Yii::getAlias($privateKey);
Loading history...
809
        }
810 22
        return Yii::createObject(CryptKey::class, [$privateKey, $this->privateKeyPassphrase]);
811
    }
812
813
    /**
814
     * @return CryptKey The public key of the server.
815
     * @throws InvalidConfigException
816
     * @since 1.0.0
817
     */
818 9
    public function getPublicKey()
819
    {
820 9
        $publicKey = $this->publicKey;
821 9
        if (StringHelper::startsWith($publicKey, '@')) {
822 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

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

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

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

1373
        $psr7Request = Psr7Helper::yiiToPsr7Request(/** @scrutinizer ignore-type */ Yii::$app->request);
Loading history...
1374
1375 3
        $psr7Request = $this->getResourceServer()->validateAuthenticatedRequest($psr7Request);
1376
1377 3
        $this->_oauthClaims = $psr7Request->getAttributes();
1378 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...
1379
    }
1380
1381
    /**
1382
     * Find a user identity bases on an access token.
1383
     * Note: validateAuthenticatedRequest() must be called before this method is called.
1384
     * @param string $token
1385
     * @param string $type
1386
     * @return Oauth2UserInterface|null
1387
     * @throws InvalidConfigException
1388
     * @throws Oauth2ServerException
1389
     * @see validateAuthenticatedRequest()
1390
     * @since 1.0.0
1391
     */
1392 4
    public function findIdentityByAccessToken($token, $type)
1393
    {
1394 4
        if (!is_a($type, Oauth2HttpBearerAuthInterface::class, true)) {
1395 1
            throw new InvalidCallException($type . ' must implement ' . Oauth2HttpBearerAuthInterface::class);
1396
        }
1397
1398
        if (
1399 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

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

1466
        return new Oauth2AccessTokenData(/** @scrutinizer ignore-type */ Json::decode(
Loading history...
1467 3
            $this->getAuthorizationServer()
1468 3
                ->respondToAccessTokenRequest(
1469 3
                    $request,
1470 3
                    new Psr7Response()
1471 3
                )
1472 3
                ->getBody()
1473 3
                ->__toString()
1474 3
        ));
1475
    }
1476
1477
    /**
1478
     * @inheritDoc
1479
     */
1480 5
    protected function getRequestOauthClaim($attribute, $default = null)
1481
    {
1482 5
        if (empty($this->_oauthClaimsAuthorizationHeader)) {
1483
            // User authorization was not processed by Oauth2Module.
1484 1
            return $default;
1485
        }
1486 4
        if (Yii::$app->request->getHeaders()->get('Authorization') !== $this->_oauthClaimsAuthorizationHeader) {
1487 1
            throw new InvalidCallException(
1488 1
                'App Request Authorization header does not match the processed Oauth header.'
1489 1
            );
1490
        }
1491 3
        return $this->_oauthClaims[$attribute] ?? $default;
1492
    }
1493
1494
    /**
1495
     * Helper function to ensure the required properties are configured for the module.
1496
     * @param string[] $properties
1497
     * @throws InvalidConfigException
1498
     * @since 1.0.0
1499
     */
1500 32
    protected function ensureProperties($properties)
1501
    {
1502 32
        foreach ($properties as $property) {
1503 32
            if (empty($this->$property)) {
1504 6
                throw new InvalidConfigException(__CLASS__ . '::$' . $property . ' must be set.');
1505
            }
1506
        }
1507
    }
1508
1509
    /**
1510
     * @throws InvalidConfigException
1511
     */
1512
    public function logoutUser($revokeTokens = true)
1513
    {
1514
        $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...
1515
1516
        if ($identity) {
0 ignored issues
show
introduced by
$identity is of type null, thus it always evaluated to false.
Loading history...
1517
            if ($revokeTokens) {
1518
                $this->revokeTokensByUserId($identity->getId());
1519
            }
1520
1521
            Yii::$app->user->logout();
1522
        }
1523
    }
1524
1525
    public function revokeTokensByUserId($userId)
1526
    {
1527
        $accessTokens = $this->getAccessTokenRepository()->revokeAccessTokensByUserId($userId);
1528
        $accessTokenIds = array_map(fn($accessToken) => $accessToken->getPrimaryKey(), $accessTokens);
1529
        $this->getRefreshTokenRepository()->revokeRefreshTokensByAccessTokenIds($accessTokenIds);
1530
    }
1531
1532
    /**
1533
     * @return int
1534
     */
1535 2
    public function getElaboratedHttpClientErrorsLogLevel()
1536
    {
1537 2
        if ($this->httpClientErrorsLogLevel === null) {
1538 1
            return YII_DEBUG ? Logger::LEVEL_ERROR : Logger::LEVEL_INFO;
1539
        }
1540
1541 1
        return $this->httpClientErrorsLogLevel;
1542
    }
1543
}
1544