Passed
Push — master ( f52d5c...0b49e9 )
by Rutger
03:13
created

setUserAuthenticatedDuringClientAuthRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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

659
            $privateKey = 'file://' . /** @scrutinizer ignore-type */ Yii::getAlias($privateKey);
Loading history...
660
        }
661 21
        return Yii::createObject(CryptKey::class, [$privateKey, $this->privateKeyPassphrase]);
662
    }
663
664
    /**
665
     * @return CryptKey The public key of the server.
666
     * @throws InvalidConfigException
667
     * @since 1.0.0
668
     */
669 9
    public function getPublicKey()
670
    {
671 9
        $publicKey = $this->publicKey;
672 9
        if (StringHelper::startsWith($publicKey, '@')) {
673 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

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

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

947
        /** @scrutinizer ignore-call */ 
948
        $clientAuthorizationRequest = Yii::$app->session->get($key);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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

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

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

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