Passed
Push — master ( f3b330...57677b )
by Rutger
04:01
created

Oauth2AuthorizeAction   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 297
Duplicated Lines 0 %

Test Coverage

Coverage 95.97%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 42
eloc 167
c 1
b 0
f 0
dl 0
loc 297
rs 9.0399
ccs 143
cts 149
cp 0.9597

2 Methods

Rating   Name   Duplication   Size   Complexity  
A getRequestParam() 0 3 1
F run() 0 282 41

How to fix   Complexity   

Complex Class

Complex classes like Oauth2AuthorizeAction often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Oauth2AuthorizeAction, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace rhertogh\Yii2Oauth2Server\controllers\web\server;
4
5
use GuzzleHttp\Psr7\Response as Psr7Response;
6
use League\OAuth2\Server\Exception\OAuthServerException;
7
use rhertogh\Yii2Oauth2Server\controllers\web\Oauth2ServerController;
8
use rhertogh\Yii2Oauth2Server\controllers\web\server\base\Oauth2BaseServerAction;
9
use rhertogh\Yii2Oauth2Server\exceptions\Oauth2OidcServerException;
10
use rhertogh\Yii2Oauth2Server\helpers\Psr7Helper;
11
use rhertogh\Yii2Oauth2Server\helpers\UrlHelper;
12
use rhertogh\Yii2Oauth2Server\interfaces\components\authorization\Oauth2ClientAuthorizationRequestInterface;
13
use rhertogh\Yii2Oauth2Server\interfaces\components\openidconnect\request\Oauth2OidcAuthenticationRequestInterface;
14
use rhertogh\Yii2Oauth2Server\interfaces\components\openidconnect\scope\Oauth2OidcScopeInterface;
15
use rhertogh\Yii2Oauth2Server\interfaces\components\openidconnect\user\Oauth2OidcUserComponentInterface;
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\Request;
23
use yii\web\ServerErrorHttpException;
24
use yii\web\UnauthorizedHttpException;
25
26
/**
27
 * @property Oauth2ServerController $controller
28
 */
29
class Oauth2AuthorizeAction extends Oauth2BaseServerAction
30
{
31 38
    public function run($clientAuthorizationRequestId = null)
32
    {
33
        try {
34 38
            $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...
35
            /** @var yii\web\User $user */
36 38
            $user = Yii::$app->user;
37 38
            $module = $this->controller->module;
38
39 38
            if ($module->enableOpenIdConnect) {
40 38
                if (!($user instanceof Oauth2OidcUserComponentInterface)) {
41 1
                    throw new InvalidConfigException(
42
                        'OpenId Connect is enabled but user component does not implement '
43 1
                        . Oauth2OidcUserComponentInterface::class
44
                    );
45
                }
46
47 37
                $oidcRequest = $this->getRequestParam(
48 37
                    $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

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

65
            $psr7Request = Psr7Helper::yiiToPsr7Request(/** @scrutinizer ignore-type */ $request);
Loading history...
66 33
            $authRequest = $server->validateAuthorizationRequest($psr7Request);
67
68 33
            $requestedScopeIdentifiers = array_map(fn($scope) => $scope->getIdentifier(), $authRequest->getScopes());
69
70
            /** @var Oauth2ClientInterface $client */
71 33
            $client = $authRequest->getClient();
72
73 33
            if (!$client->validateAuthRequestScopes($requestedScopeIdentifiers, $unauthorizedScopes)) {
74 1
                throw OAuthServerException::invalidScope(
75 1
                    array_shift($unauthorizedScopes),
76 1
                    $authRequest->getRedirectUri()
77
                );
78
            }
79
80
            //Orig $openIdConnectActive, if ($openIdConnectActive) {
81
82
83
84
            if (
85 32
                !$client->isAuthCodeWithoutPkceAllowed()
86
                // PKCE is not supported in the implicit flow:
87 32
                && $authRequest->getGrantTypeId() != Oauth2Module::GRANT_TYPE_IDENTIFIER_IMPLICIT
88
            ) {
89 31
                if (empty($request->get('code_challenge'))) {
90 2
                    throw new BadRequestHttpException(
91
                        'PKCE is required for this client when using grant type "'
92 2
                            . $authRequest->getGrantTypeId() . '".'
93
                    );
94 29
                } elseif ($request->get('code_challenge_method', 'plain') === 'plain') {
95 1
                    throw new BadRequestHttpException('PKCE code challenge mode "plain" is not allowed.');
96
                }
97
            }
98
99 29
            if ($clientAuthorizationRequestId) {
100 11
                $clientAuthorizationRequest = $module->getClientAuthReqSession($clientAuthorizationRequestId);
101
                if (
102 11
                    $clientAuthorizationRequest
103 11
                    && $clientAuthorizationRequest->getState()
104 10
                    && !Yii::$app->security->compareString(
105 10
                        $clientAuthorizationRequest->getState(),
106 11
                        $authRequest->getState()
107
                    )
108
                ) {
109 1
                    throw new UnauthorizedHttpException('Invalid state.');
110
                }
111
            }
112
113 28
            if (empty($clientAuthorizationRequest)) {
114 24
                $prompts = explode(
115 24
                    ' ',
116 24
                    $this->getRequestParam(
0 ignored issues
show
Bug introduced by
It seems like $this->getRequestParam($...QUEST_PARAMETER_PROMPT) can also be of type array; however, parameter $string of explode() 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

116
                    /** @scrutinizer ignore-type */ $this->getRequestParam(
Loading history...
117 24
                        $request,
118 24
                        Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT
119
                    )
120
                )
121 24
                    ?? [];
122
123
                if (
124 24
                    in_array(Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_NONE, $prompts)
125 24
                    && (count($prompts) > 1)
126
                ) {
127 1
                    throw new BadRequestHttpException(
128 1
                        'When the "prompt" parameter contains "none" other values are not allowed.'
129
                    );
130
                }
131
132
                // Ignore `offline_access` scope if prompt doesn't contain 'consent' (or pre-approved via config)
133
                // https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
134
                if (
135 23
                    in_array(Oauth2OidcScopeInterface::OPENID_CONNECT_SCOPE_OFFLINE_ACCESS, $requestedScopeIdentifiers)
136 23
                    && !in_array(Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_CONSENT, $prompts)
137 23
                    && !$client->getOpenIdConnectAllowOfflineAccessWithoutConsent()
138
                ) {
139 1
                    $requestedScopeIdentifiers = array_diff(
140 1
                        $requestedScopeIdentifiers,
141 1
                        [Oauth2OidcScopeInterface::OPENID_CONNECT_SCOPE_OFFLINE_ACCESS]
142
                    );
143
                }
144
145 23
                $maxAge = $this->getRequestParam(
146 23
                    $request,
147 23
                    Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_MAX_AGE
148
                );
149 23
                if ($maxAge === '') {
150 1
                    $maxAge = null;
151 22
                } elseif ($maxAge !== null) {
152 4
                    $maxAge = (int)$maxAge;
153
                }
154
155
                /** @var Oauth2ClientAuthorizationRequestInterface $clientAuthorizationRequest */
156 23
                $clientAuthorizationRequest = Yii::createObject([
157 23
                    'class' => Oauth2ClientAuthorizationRequestInterface::class,
158 23
                    'module' => $module,
159 23
                    'userAuthenticatedBeforeRequest' => !$user->isGuest,
160 23
                    'clientIdentifier' => $authRequest->getClient()->getIdentifier(),
161 23
                    'state' => $authRequest->getState(),
162 23
                    'requestedScopeIdentifiers' => $requestedScopeIdentifiers,
163 23
                    'grantType' => $authRequest->getGrantTypeId(),
164 23
                    'authorizeUrl' => $request->absoluteUrl,
165 23
                    'redirectUri' => $authRequest->getRedirectUri(),
166 23
                    'prompts' => $prompts,
167 23
                    'maxAge' => $maxAge,
168
                ]);
169
            }
170
171 27
            if ($user->isGuest) {
172
                if (
173 4
                    in_array(
174 4
                        Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_NONE,
175 4
                        $clientAuthorizationRequest->getPrompts()
176
                    )
177
                ) {
178
                    // User authentication disallowed by OpenID Connect
179 1
                    throw Oauth2OidcServerException::loginRequired($authRequest->getRedirectUri());
180
                }
181
182 3
                $module->setClientAuthReqSession($clientAuthorizationRequest);
183 3
                $user->setReturnUrl($clientAuthorizationRequest->getAuthorizationRequestUrl());
184
//                $user->setReturnUrl(UrlHelper::addQueryParams($request->absoluteUrl, [
185
//                    'clientAuthorizationRequestId' => $clientAuthorizationRequest->getRequestId(),
186
//                ]));
187
188 3
                return Yii::$app->response->redirect($user->loginUrl);
189
            }
190
191
            // Check if reauthentication is required
192
            if (
193
                (
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (in_array(rhertogh\Yii2O...nticatedDuringRequest(), Probably Intended Meaning: in_array(rhertogh\Yii2Oa...ticatedDuringRequest())
Loading history...
194
                    ( // true in case user was authenticated before request and oidc prompt requires login
195 25
                        in_array(
196 25
                            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_LOGIN,
197 25
                            $clientAuthorizationRequest->getPrompts()
198
                        )
199 2
                        && $clientAuthorizationRequest->wasUserAuthenticatedBeforeRequest()
200
                    )
201
                    ||
202
                    ( // true in case oidc max_age is set and the user was authenticated before the maximum time allowed
203 24
                        $clientAuthorizationRequest->getMaxAge() !== null
204
                        && (
205 4
                            (time() - $module->getUserIdentity()->getLatestAuthenticatedAt()->getTimestamp())
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...
206 25
                            > $clientAuthorizationRequest->getMaxAge()
207
                        )
208
                    )
209
                )
210 25
                && !$clientAuthorizationRequest->wasUserAthenticatedDuringRequest()
211
            ) {
212
                // Prevent redirect loop
213 4
                $redirectAttempt = (int)$request->get('redirectAttempt', 0);
214 4
                if ($redirectAttempt > 3) {
215
                    // This error most likely occurs if the User Controller does not correctly performs
216
                    // user reauthentication and redirect back to this action without calling
217
                    // `setUserAuthenticatedDuringRequest(true)` on the $clientAuthorizationRequest.
218
                    throw new HttpException(
219
                        501,
220
                        'Reauthentication not correctly implemented, aborting due to redirect loop.'
221
                    );
222
                }
223
224 4
                $module->setClientAuthReqSession($clientAuthorizationRequest);
225 4
                $user->setReturnUrl(
226 4
                    UrlHelper::addQueryParams($clientAuthorizationRequest->getAuthorizationRequestUrl(), [
227 4
                        'redirectAttempt' => $redirectAttempt + 1,
228
                    ])
229
                );
230 4
                return $user->reauthenticationRequired($clientAuthorizationRequest);
231
            }
232
233 22
            $userAccountSelection = $client->getUserAccountSelection() ?? $module->defaultUserAccountSelection;
234
235 22
            if (empty($clientAuthorizationRequest->getUserIdentity())) {
236
                if (
237 18
                    ($userAccountSelection === Oauth2Module::USER_ACCOUNT_SELECTION_ALWAYS)
238
                    || (
239 16
                        $userAccountSelection === Oauth2Module::USER_ACCOUNT_SELECTION_UPON_CLIENT_REQUEST
240 16
                        && in_array(
241 16
                            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_SELECT_ACCOUNT,
242 18
                            $clientAuthorizationRequest->getPrompts()
243
                        )
244
                    )
245
                ) {
246
                    if (
247 4
                        in_array(
248 4
                            Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_NONE,
249 4
                            $clientAuthorizationRequest->getPrompts()
250
                        )
251
                    ) {
252 2
                        throw Oauth2OidcServerException::accountSelectionRequired(
253 2
                            'User account selection is required but the "prompt" parameter is set to "none".',
254 2
                            $authRequest->getRedirectUri()
255
                        );
256
                    }
257 2
                    $accountSelectionRequiredResponse = $user->accountSelectionRequired($clientAuthorizationRequest);
258 2
                    if ($accountSelectionRequiredResponse === false) {
259
                        throw Oauth2OidcServerException::accountSelectionRequired(
260
                            'User account selection is not supported by the server.',
261
                            $authRequest->getRedirectUri(),
262
                        );
263
                    }
264 2
                    $module->setClientAuthReqSession($clientAuthorizationRequest);
265 2
                    $user->setReturnUrl($clientAuthorizationRequest->getAuthorizationRequestUrl());
266 2
                    return $accountSelectionRequiredResponse;
267
                } else {
268 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...
269
                }
270
            }
271
272
            if (
273 19
                $clientAuthorizationRequest->isAuthorizationNeeded()
274 2
                || in_array(
275 2
                    Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_CONSENT,
276 19
                    $clientAuthorizationRequest->getPrompts()
277
                )
278
            ) {
279
                if (
280 18
                    in_array(
281 18
                        Oauth2OidcAuthenticationRequestInterface::REQUEST_PARAMETER_PROMPT_NONE,
282 18
                        $clientAuthorizationRequest->getPrompts()
283
                    )
284
                ) {
285
                    // User consent is disallowed by OpenID Connect
286 1
                    throw Oauth2OidcServerException::consentRequired($authRequest->getRedirectUri());
287
                }
288 17
                if ($clientAuthorizationRequest->isCompleted()) {
289 6
                    $authorizationApproved = $clientAuthorizationRequest->isApproved();
290
                    // Cleanup session data
291 6
                    $module->removeClientAuthReqSession($clientAuthorizationRequest->getRequestId());
292
                } else {
293 17
                    return $module->generateClientAuthReqRedirectResponse($clientAuthorizationRequest);
294
                }
295
            } else {
296
                // All scopes are already approved (or are default).
297 2
                $authorizationApproved = true;
298
            }
299
300 8
            $authRequest->setUser($clientAuthorizationRequest->getUserIdentity());
301 8
            $authRequest->setAuthorizationApproved($authorizationApproved);
302
303 8
            $psr7Response = Yii::createObject(Psr7Response::class);
304 8
            $psr7Response = $server->completeAuthorizationRequest($authRequest, $psr7Response);
305
306 7
            return Psr7Helper::psr7ToYiiResponse($psr7Response);
307
308
//        } catch (OAuthServerException $e) {
309
//            return $this->processOAuthServerException($e);
310 16
        } catch (\Exception $e) {
311 16
            Yii::error((string)$e, __METHOD__);
312 16
            return $this->processException($e);
313
//            $message = Yii::t('oauth2', 'Unable to respond to authorization request.');
314
//            throw Oauth2ServerHttpException::createFromException($message, $e);
315
        }
316
    }
317
318
    /**
319
     * @param Request $request
320
     * @param string $name
321
     * @return mixed
322
     */
323 37
    protected function getRequestParam($request, $name, $defaultValue = null)
324
    {
325 37
        return $request->post($name) ?? $request->get($name, $defaultValue);
326
    }
327
}
328