Passed
Push — master ( 1437aa...6e82fd )
by Rutger
13:37
created

Oauth2AuthorizeAction::run()   F

Complexity

Conditions 46
Paths > 20000

Size

Total Lines 305
Code Lines 184

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 172
CRAP Score 48.0844

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 184
c 2
b 0
f 0
dl 0
loc 305
ccs 172
cts 191
cp 0.9005
rs 0
cc 46
nc 26629
nop 1
crap 48.0844

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\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\models\external\user\Oauth2UserAuthenticatedAtInterface;
16
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2ClientInterface;
17
use rhertogh\Yii2Oauth2Server\Oauth2Module;
18
use Yii;
19
use yii\base\InvalidConfigException;
20
use yii\web\BadRequestHttpException;
21
use yii\web\HttpException;
22
use yii\web\UnauthorizedHttpException;
23
24
/**
25
 * @property Oauth2ServerController $controller
26
 */
27
class Oauth2AuthorizeAction extends Oauth2BaseServerAction
28
{
29 39
    public function run($clientAuthorizationRequestId = null)
30
    {
31
        try {
32 39
            $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...
33 39
            $user = Yii::$app->user;
34 39
            $module = $this->controller->module;
35
36 39
            if ($module->enableOpenIdConnect) {
37 39
                if (!($user instanceof Oauth2OidcUserComponentInterface)) {
38 1
                    throw new InvalidConfigException(
39 1
                        'OpenId Connect is enabled but user component does not implement '
40 1
                        . Oauth2OidcUserComponentInterface::class
41 1
                    );
42
                }
43
44 38
                $oidcRequest = $this->getRequestParam(
45 38
                    $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

45
                    /** @scrutinizer ignore-type */ $request,
Loading history...
46 38
                    Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_REQUEST
47 38
                );
48 38
                if ($oidcRequest !== null) {
49 1
                    throw Oauth2OidcServerException::requestParameterNotSupported();
50
                }
51
52 37
                $oidcRequestUri = $this->getRequestParam(
53 37
                    $request,
54 37
                    Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_REQUEST_URI
55 37
                );
56 37
                if ($oidcRequestUri !== null) {
57 1
                    throw Oauth2OidcServerException::requestUriParameterNotSupported();
58
                }
59
            }
60
61 36
            $server = $module->getAuthorizationServer();
62 34
            $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

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

201
                            'The authorization request max age is set, but ' . get_class(/** @scrutinizer ignore-type */ $appUserIdentity)
Loading history...
202
                            . ' does not implement ' . Oauth2UserAuthenticatedAtInterface::class
203
                        );
204
                    }
205 4
                    $latestAuthenticatedAt = $appUserIdentity->getLatestAuthenticatedAt();
206
                    if (
207 4
                        ($latestAuthenticatedAt === null)
208
                        || ( // if $latestAuthenticatedAt is not null, check if it's before the max time allowed.
209 4
                            (time() - $latestAuthenticatedAt->getTimestamp()) > $clientAuthorizationRequest->getMaxAge()
210
                        )
211
                    ) {
212 3
                        $reauthenticationRequired = true;
213
                    }
214
                }
215
            }
216
217 25
            if ($reauthenticationRequired) {
218
                // Prevent redirect loop.
219 4
                $redirectAttempt = (int)$request->get('redirectAttempt', 0);
220 4
                if ($redirectAttempt > 3) {
221
                    // This error most likely occurs if the User Controller does not correctly performs
222
                    // user reauthentication and redirect back to this action without calling
223
                    // `setUserAuthenticatedDuringRequest(true)` on the $clientAuthorizationRequest.
224
                    throw new HttpException(
225
                        501,
226
                        'Reauthentication not correctly implemented, aborting due to redirect loop.'
227
                    );
228
                }
229
230 4
                $module->setClientAuthReqSession($clientAuthorizationRequest);
231 4
                $user->setReturnUrl(
232 4
                    UrlHelper::addQueryParams($clientAuthorizationRequest->getAuthorizationRequestUrl(), [
233 4
                        'redirectAttempt' => $redirectAttempt + 1,
234 4
                    ])
235 4
                );
236 4
                return $user->reauthenticationRequired($clientAuthorizationRequest);
237
            }
238
239 22
            $userAccountSelection = $client->getUserAccountSelection() ?? $module->defaultUserAccountSelection;
240
241 22
            if (empty($clientAuthorizationRequest->getUserIdentity())) {
242
                if (
243 18
                    ($userAccountSelection === Oauth2Module::USER_ACCOUNT_SELECTION_ALWAYS)
244
                    || (
245 18
                        $userAccountSelection === Oauth2Module::USER_ACCOUNT_SELECTION_UPON_CLIENT_REQUEST
246 18
                        && in_array(
247 18
                            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_SELECT_ACCOUNT,
248 18
                            $clientAuthorizationRequest->getPrompts()
249 18
                        )
250
                    )
251
                ) {
252
                    if (
253 4
                        in_array(
254 4
                            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_NONE,
255 4
                            $clientAuthorizationRequest->getPrompts()
256 4
                        )
257
                    ) {
258 2
                        throw Oauth2OidcServerException::accountSelectionRequired(
259 2
                            'User account selection is required but the "prompt" parameter is set to "none".',
260 2
                            $authRequest->getRedirectUri()
261 2
                        );
262
                    }
263 2
                    $accountSelectionRequiredResponse = $user->accountSelectionRequired($clientAuthorizationRequest);
264 2
                    if ($accountSelectionRequiredResponse === false) {
265
                        throw Oauth2OidcServerException::accountSelectionRequired(
266
                            'User account selection is not supported by the server.',
267
                            $authRequest->getRedirectUri(),
268
                        );
269
                    }
270 2
                    $module->setClientAuthReqSession($clientAuthorizationRequest);
271 2
                    $user->setReturnUrl($clientAuthorizationRequest->getAuthorizationRequestUrl());
272 2
                    return $accountSelectionRequiredResponse;
273
                } else {
274 14
                    $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...
275
                }
276
            }
277
278
            if (
279 19
                $clientAuthorizationRequest->getUserIdentity()->isOauth2ClientAllowed(
280 19
                    $client,
281 19
                    $clientAuthorizationRequest->getGrantType()
282 19
                ) !== true
283
            ) {
284
                throw Oauth2ServerException::accessDenied(
285
                    Yii::t('oauth2', 'User {user_id} is not allowed to use client {client_identifier}.', [
286
                        'user_id' => $user->getId(),
287
                        'client_identifier' => $client->getIdentifier(),
288
                    ])
289
                );
290
            }
291
292
            if (
293 19
                $clientAuthorizationRequest->isAuthorizationNeeded()
294 19
                || in_array(
295 19
                    Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_CONSENT,
296 19
                    $clientAuthorizationRequest->getPrompts()
297 19
                )
298
            ) {
299 18
                if (!$clientAuthorizationRequest->isAuthorizationAllowed()) {
300
                    throw Oauth2ServerException::authorizationNotAllowed();
301
                }
302
303
                if (
304 18
                    in_array(
305 18
                        Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_NONE,
306 18
                        $clientAuthorizationRequest->getPrompts()
307 18
                    )
308
                ) {
309
                    // User consent is disallowed by OpenID Connect.
310 1
                    throw Oauth2OidcServerException::consentRequired($authRequest->getRedirectUri());
311
                }
312
313 17
                if ($clientAuthorizationRequest->isCompleted()) {
314 6
                    $authorizationApproved = $clientAuthorizationRequest->isApproved();
315
                    // Cleanup session data.
316 6
                    $module->removeClientAuthReqSession($clientAuthorizationRequest->getRequestId());
317
                } else {
318 17
                    return $module->generateClientAuthReqRedirectResponse($clientAuthorizationRequest);
319
                }
320
            } else {
321
                // All scopes are already approved (or are default).
322 2
                $authorizationApproved = true;
323
            }
324
325 8
            $authRequest->setUser($clientAuthorizationRequest->getUserIdentity());
326 8
            $authRequest->setAuthorizationApproved($authorizationApproved);
327
328 8
            $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

328
            $psr7Response = Psr7Helper::yiiToPsr7Response(/** @scrutinizer ignore-type */ Yii::$app->response);
Loading history...
329 8
            $psr7Response = $server->completeAuthorizationRequest($authRequest, $psr7Response);
330
331 7
            return Psr7Helper::psr7ToYiiResponse($psr7Response);
332 17
        } catch (\Exception $e) {
333 17
            return $this->processException($e, __METHOD__);
334
        }
335
    }
336
}
337