Oauth2AuthorizeAction::run()   F
last analyzed

Complexity

Conditions 51
Paths > 20000

Size

Total Lines 331
Code Lines 202

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 189
CRAP Score 53.2796

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 202
c 2
b 0
f 0
dl 0
loc 331
rs 0
ccs 189
cts 209
cp 0.9043
cc 51
nc 53677
nop 1
crap 53.2796

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace rhertogh\Yii2Oauth2Server\controllers\web\server;
4
5
use rhertogh\Yii2Oauth2Server\controllers\web\Oauth2ServerController;
6
use rhertogh\Yii2Oauth2Server\controllers\web\server\base\Oauth2BaseServerAction;
7
use rhertogh\Yii2Oauth2Server\exceptions\Oauth2OidcServerException;
8
use rhertogh\Yii2Oauth2Server\exceptions\Oauth2ServerException;
9
use rhertogh\Yii2Oauth2Server\helpers\Psr7Helper;
10
use rhertogh\Yii2Oauth2Server\helpers\UrlHelper;
11
use rhertogh\Yii2Oauth2Server\interfaces\components\authorization\client\Oauth2ClientAuthorizationRequestInterface;
12
use rhertogh\Yii2Oauth2Server\interfaces\components\openidconnect\request\Oauth2OidcAuthenticationRequestInterface;
13
use rhertogh\Yii2Oauth2Server\interfaces\components\openidconnect\scope\Oauth2OidcScopeInterface;
14
use rhertogh\Yii2Oauth2Server\interfaces\components\openidconnect\user\Oauth2OidcUserComponentInterface;
15
use rhertogh\Yii2Oauth2Server\interfaces\controllers\web\server\Oauth2AuthorizeActionInterface;
16
use rhertogh\Yii2Oauth2Server\interfaces\models\external\user\Oauth2UserAuthenticatedAtInterface;
17
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2ClientInterface;
18
use rhertogh\Yii2Oauth2Server\Oauth2Module;
19
use Yii;
20
use yii\base\InvalidConfigException;
21
use yii\web\BadRequestHttpException;
22
use yii\web\HttpException;
23
use yii\web\UnauthorizedHttpException;
24
25
/**
26
 * @property Oauth2ServerController $controller
27
 */
28
class Oauth2AuthorizeAction extends Oauth2BaseServerAction implements Oauth2AuthorizeActionInterface
29
{
30 40
    public function run($clientAuthorizationRequestId = null)
31
    {
32
        try {
33 40
            $request = Yii::$app->request;
0 ignored issues
show
Documentation Bug introduced by
It seems like Yii::app->request can also be of type yii\web\Request. However, the property $request is declared as type yii\console\Request. 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...
34 40
            $user = Yii::$app->user;
35 40
            $module = $this->controller->module;
36
37 40
            if ($module->enableOpenIdConnect) {
38 40
                if (!($user instanceof Oauth2OidcUserComponentInterface)) {
39 1
                    throw new InvalidConfigException(
40 1
                        'OpenId Connect is enabled but user component does not implement '
41 1
                        . Oauth2OidcUserComponentInterface::class
42 1
                    );
43
                }
44
45 39
                $oidcRequest = $this->getRequestParam(
46 39
                    $request,
0 ignored issues
show
Bug introduced by
It seems like $request can also be of type yii\console\Request; however, parameter $request of rhertogh\Yii2Oauth2Serve...tion::getRequestParam() 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

46
                    /** @scrutinizer ignore-type */ $request,
Loading history...
47 39
                    Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_REQUEST
48 39
                );
49 39
                if ($oidcRequest !== null) {
50 1
                    throw Oauth2OidcServerException::requestParameterNotSupported();
51
                }
52
53 38
                $oidcRequestUri = $this->getRequestParam(
54 38
                    $request,
55 38
                    Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_REQUEST_URI
56 38
                );
57 38
                if ($oidcRequestUri !== null) {
58 1
                    throw Oauth2OidcServerException::requestUriParameterNotSupported();
59
                }
60
            }
61
62 37
            $server = $module->getAuthorizationServer();
63 35
            $psr7Request = Psr7Helper::yiiToPsr7Request($request);
0 ignored issues
show
Bug introduced by
It seems like $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

63
            $psr7Request = Psr7Helper::yiiToPsr7Request(/** @scrutinizer ignore-type */ $request);
Loading history...
64 35
            $authRequest = $server->validateAuthorizationRequest($psr7Request);
65
66 34
            $requestedScopeIdentifiers = array_map(fn($scope) => $scope->getIdentifier(), $authRequest->getScopes());
67
68
            /** @var Oauth2ClientInterface $client */
69 34
            $client = $authRequest->getClient();
70
71 34
            $module->validateAuthRequestScopes($client, $requestedScopeIdentifiers, $authRequest->getRedirectUri());
72
73
            if (
74 33
                !$client->isAuthCodeWithoutPkceAllowed()
75
                // PKCE is not supported in the implicit flow.
76 33
                && $authRequest->getGrantTypeId() != Oauth2Module::GRANT_TYPE_IDENTIFIER_IMPLICIT
77
            ) {
78 32
                if (empty($request->get('code_challenge'))) {
79 2
                    throw new BadRequestHttpException(
80 2
                        'PKCE is required for this client when using grant type "'
81 2
                            . $authRequest->getGrantTypeId() . '".'
82 2
                    );
83 30
                } elseif ($request->get('code_challenge_method', 'plain') === 'plain') {
84 1
                    throw new BadRequestHttpException('PKCE code challenge mode "plain" is not allowed.');
85
                }
86
            }
87
88 30
            if ($clientAuthorizationRequestId) {
89 12
                $clientAuthorizationRequest = $module->getClientAuthReqSession($clientAuthorizationRequestId);
90
                if (
91 12
                    $clientAuthorizationRequest
92 12
                    && $clientAuthorizationRequest->getState()
93 12
                    && !Yii::$app->security->compareString(
94 12
                        $clientAuthorizationRequest->getState(),
95 12
                        $authRequest->getState()
96 12
                    )
97
                ) {
98 1
                    throw new UnauthorizedHttpException('Invalid state.');
99
                }
100
            }
101
102 29
            if (empty($clientAuthorizationRequest)) {
103 25
                $prompts = explode(
104 25
                    ' ',
105 25
                    (string)$this->getRequestParam(
106 25
                        $request,
107 25
                        Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT
108 25
                    )
109 25
                )
110 25
                    ?? [];
111
112
                if (
113 25
                    in_array(Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_NONE, $prompts)
114 25
                    && (count($prompts) > 1)
115
                ) {
116 1
                    throw new BadRequestHttpException(
117 1
                        'When the "prompt" parameter contains "none" other values are not allowed.'
118 1
                    );
119
                }
120
121
                // Ignore `offline_access` scope if prompt doesn't contain 'consent' (or pre-approved via config).
122
                // See https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess.
123
                if (
124 24
                    in_array(Oauth2OidcScopeInterface::OPENID_CONNECT_SCOPE_OFFLINE_ACCESS, $requestedScopeIdentifiers)
125 24
                    && !in_array(Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_CONSENT, $prompts)
126 24
                    && !$client->getOpenIdConnectAllowOfflineAccessWithoutConsent()
127
                ) {
128 1
                    $requestedScopeIdentifiers = array_diff(
129 1
                        $requestedScopeIdentifiers,
130 1
                        [Oauth2OidcScopeInterface::OPENID_CONNECT_SCOPE_OFFLINE_ACCESS]
131 1
                    );
132
                }
133
134 24
                $maxAge = $this->getRequestParam(
135 24
                    $request,
136 24
                    Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_MAX_AGE
137 24
                );
138 24
                if ($maxAge === '') {
139 1
                    $maxAge = null;
140 23
                } elseif ($maxAge !== null) {
141 4
                    $maxAge = (int)$maxAge;
142
                }
143
144
                /** @var Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest */
145 24
                $clientAuthorizationRequest = Yii::createObject([
146 24
                    'class' => Oauth2ClientAuthorizationRequestInterface::class,
147 24
                    'module' => $module,
148 24
                    'userAuthenticatedBeforeRequest' => !$user->isGuest,
149 24
                    'clientIdentifier' => $authRequest->getClient()->getIdentifier(),
150 24
                    'state' => $authRequest->getState(),
151 24
                    'requestedScopeIdentifiers' => $requestedScopeIdentifiers,
152 24
                    'grantType' => $authRequest->getGrantTypeId(),
153 24
                    'authorizeUrl' => $request->absoluteUrl,
154 24
                    'redirectUri' => $authRequest->getRedirectUri(),
155 24
                    'prompts' => $prompts,
156 24
                    'maxAge' => $maxAge,
157 24
                ]);
158
            }
159
160
            if (
161 28
                in_array(
162 28
                    Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_CREATE,
163 28
                    $module->getSupportedPromptValues()
164 28
                )
165 28
                && in_array(
166 28
                    Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_CREATE,
167 28
                    $clientAuthorizationRequest->getPrompts()
168 28
                )
169 28
                && !$clientAuthorizationRequest->getCreateUserPromptProcessed()
170
            ) {
171 1
                if (!$request->get('createUserPromtProcessed')) {
172 1
                    $module->setClientAuthReqSession($clientAuthorizationRequest);
173 1
                    $returnUrl = $clientAuthorizationRequest->getAuthorizationRequestUrl();
174 1
                    $returnUrl = UrlHelper::addQueryParams($returnUrl, ['createUserPromtProcessed' => 'true']);
175 1
                    $user->setReturnUrl($returnUrl);
176
177 1
                    return Yii::$app->response->redirect($module->userAccountCreationUrl);
178
                } else {
179 1
                    if ($user->isGuest) {
180
                        throw new BadRequestHttpException('The `createUserPromtProcessed` parameter is set, but no user is logged in.');
181
                    }
182 1
                    $clientAuthorizationRequest->setCreateUserPromptProcessed(true);
183
                }
184
            }
185
186 28
            if ($user->isGuest) {
187
                if (
188 4
                    in_array(
189 4
                        Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_NONE,
190 4
                        $clientAuthorizationRequest->getPrompts()
191 4
                    )
192
                ) {
193
                    // User authentication disallowed by OpenID Connect.
194 1
                    throw Oauth2OidcServerException::loginRequired($authRequest->getRedirectUri());
195
                }
196
197 3
                $module->setClientAuthReqSession($clientAuthorizationRequest);
198 3
                $user->setReturnUrl($clientAuthorizationRequest->getAuthorizationRequestUrl());
199
//                $user->setReturnUrl(UrlHelper::addQueryParams($request->absoluteUrl, [
200
//                    'clientAuthorizationRequestId' => $clientAuthorizationRequest->getRequestId(),
201
//                ]));
202
203 3
                return Yii::$app->response->redirect($user->loginUrl);
204
            }
205
206
            // Check if reauthentication is required.
207 26
            $reauthenticationRequired = false;
208 26
            if (!$clientAuthorizationRequest->wasUserAthenticatedDuringRequest()) {
209
                if (
210
                    (// true in case user was authenticated before request and oidc prompt requires login.
211 26
                        in_array(
212 26
                            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_LOGIN,
213 26
                            $clientAuthorizationRequest->getPrompts()
214 26
                        )
215 26
                        && $clientAuthorizationRequest->wasUserAuthenticatedBeforeRequest()
216
                    )
217
                ) {
218 2
                    $reauthenticationRequired = true;
219
                }
220
221
                if (
222 26
                    !$reauthenticationRequired // Prevent unnecessary checking.
223 26
                    && $clientAuthorizationRequest->getMaxAge() !== null
224
                ) {
225 4
                    $appUserIdentity = $module->getUserIdentity();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $appUserIdentity is correct as $module->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...
226 4
                    if (!($appUserIdentity instanceof Oauth2UserAuthenticatedAtInterface)) {
227
                        throw new InvalidConfigException(
228
                            'The authorization request max age is set, but ' . get_class($appUserIdentity)
0 ignored issues
show
Bug introduced by
$appUserIdentity of type null is incompatible with the type object expected by parameter $object of get_class(). ( Ignorable by Annotation )

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

228
                            'The authorization request max age is set, but ' . get_class(/** @scrutinizer ignore-type */ $appUserIdentity)
Loading history...
229
                            . ' does not implement ' . Oauth2UserAuthenticatedAtInterface::class
230
                        );
231
                    }
232 4
                    $latestAuthenticatedAt = $appUserIdentity->getLatestAuthenticatedAt();
233
                    if (
234 4
                        ($latestAuthenticatedAt === null)
235
                        || ( // if $latestAuthenticatedAt is not null, check if it's before the max time allowed.
236 4
                            (time() - $latestAuthenticatedAt->getTimestamp()) > $clientAuthorizationRequest->getMaxAge()
237
                        )
238
                    ) {
239 3
                        $reauthenticationRequired = true;
240
                    }
241
                }
242
            }
243
244 26
            if ($reauthenticationRequired) {
245
                // Prevent redirect loop.
246 4
                $redirectAttempt = (int)$request->get('redirectAttempt', 0);
247 4
                if ($redirectAttempt > 3) {
248
                    // This error most likely occurs if the User Controller does not correctly performs
249
                    // user reauthentication and redirect back to this action without calling
250
                    // `setUserAuthenticatedDuringRequest(true)` on the $clientAuthorizationRequest.
251
                    throw new HttpException(
252
                        501,
253
                        'Reauthentication not correctly implemented, aborting due to redirect loop.'
254
                    );
255
                }
256
257 4
                $module->setClientAuthReqSession($clientAuthorizationRequest);
258 4
                $user->setReturnUrl(
259 4
                    UrlHelper::addQueryParams($clientAuthorizationRequest->getAuthorizationRequestUrl(), [
260 4
                        'redirectAttempt' => $redirectAttempt + 1,
261 4
                    ])
262 4
                );
263 4
                return $user->reauthenticationRequired($clientAuthorizationRequest);
264
            }
265
266 23
            $userAccountSelection = $client->getUserAccountSelection() ?? $module->defaultUserAccountSelection;
267
268 23
            if (empty($clientAuthorizationRequest->getUserIdentity())) {
269
                if (
270 19
                    ($userAccountSelection === Oauth2Module::USER_ACCOUNT_SELECTION_ALWAYS)
271
                    || (
272 19
                        $userAccountSelection === Oauth2Module::USER_ACCOUNT_SELECTION_UPON_CLIENT_REQUEST
273 19
                        && in_array(
274 19
                            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_SELECT_ACCOUNT,
275 19
                            $clientAuthorizationRequest->getPrompts()
276 19
                        )
277
                    )
278
                ) {
279
                    if (
280 4
                        in_array(
281 4
                            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_NONE,
282 4
                            $clientAuthorizationRequest->getPrompts()
283 4
                        )
284
                    ) {
285 2
                        throw Oauth2OidcServerException::accountSelectionRequired(
286 2
                            'User account selection is required but the "prompt" parameter is set to "none".',
287 2
                            $authRequest->getRedirectUri()
288 2
                        );
289
                    }
290 2
                    $accountSelectionRequiredResponse = $user->accountSelectionRequired($clientAuthorizationRequest);
291 2
                    if ($accountSelectionRequiredResponse === false) {
292
                        throw Oauth2OidcServerException::accountSelectionRequired(
293
                            'User account selection is not supported by the server.',
294
                            $authRequest->getRedirectUri(),
295
                        );
296
                    }
297 2
                    $module->setClientAuthReqSession($clientAuthorizationRequest);
298 2
                    $user->setReturnUrl($clientAuthorizationRequest->getAuthorizationRequestUrl());
299 2
                    return $accountSelectionRequiredResponse;
300
                } else {
301 15
                    $clientAuthorizationRequest->setUserIdentity($module->getUserIdentity());
0 ignored issues
show
Bug introduced by
Are you sure the usage of $module->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 used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
302
                }
303
            }
304
305
            if (
306 20
                $clientAuthorizationRequest->getUserIdentity()->isOauth2ClientAllowed(
307 20
                    $client,
308 20
                    $clientAuthorizationRequest->getGrantType()
309 20
                ) !== true
310
            ) {
311
                throw Oauth2ServerException::accessDenied(
312
                    Yii::t('oauth2', 'User {user_id} is not allowed to use client {client_identifier}.', [
313
                        'user_id' => $user->getId(),
314
                        'client_identifier' => $client->getIdentifier(),
315
                    ])
316
                );
317
            }
318
319
            if (
320 20
                $clientAuthorizationRequest->isAuthorizationNeeded()
321 20
                || in_array(
322 20
                    Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_CONSENT,
323 20
                    $clientAuthorizationRequest->getPrompts()
324 20
                )
325
            ) {
326 19
                if (!$clientAuthorizationRequest->isAuthorizationAllowed()) {
327
                    throw Oauth2ServerException::authorizationNotAllowed();
328
                }
329
330
                if (
331 19
                    in_array(
332 19
                        Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_NONE,
333 19
                        $clientAuthorizationRequest->getPrompts()
334 19
                    )
335
                ) {
336
                    // User consent is disallowed by OpenID Connect.
337 1
                    throw Oauth2OidcServerException::consentRequired($authRequest->getRedirectUri());
338
                }
339
340 18
                if ($clientAuthorizationRequest->isCompleted()) {
341 7
                    $authorizationApproved = $clientAuthorizationRequest->isApproved();
342
                    // Cleanup session data.
343 7
                    $module->removeClientAuthReqSession($clientAuthorizationRequest->getRequestId());
344
                } else {
345 18
                    return $module->generateClientAuthReqRedirectResponse($clientAuthorizationRequest);
346
                }
347
            } else {
348
                // All scopes are already approved (or are default).
349 2
                $authorizationApproved = true;
350
            }
351
352 9
            $authRequest->setUser($clientAuthorizationRequest->getUserIdentity());
353 9
            $authRequest->setAuthorizationApproved($authorizationApproved);
354
355 9
            $psr7Response = Psr7Helper::yiiToPsr7Response(Yii::$app->response);
0 ignored issues
show
Bug introduced by
It seems like Yii::app->response can also be of type yii\console\Response; however, parameter $response of rhertogh\Yii2Oauth2Serve...er::yiiToPsr7Response() does only seem to accept yii\web\Response, 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

355
            $psr7Response = Psr7Helper::yiiToPsr7Response(/** @scrutinizer ignore-type */ Yii::$app->response);
Loading history...
356 9
            $psr7Response = $server->completeAuthorizationRequest($authRequest, $psr7Response);
357
358 8
            return Psr7Helper::psr7ToYiiResponse($psr7Response);
359 17
        } catch (\Exception $e) {
360 17
            return $this->processException($e, __METHOD__);
361
        }
362
    }
363
}
364