Completed
Pull Request — devel (#21)
by Philippe
52:25
created

AuthorizeController::handleResponse()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 0
cts 0
cp 0
rs 9.552
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 12
1
<?php
2
/**
3
 * DefaultController.php
4
 *
5
 * PHP version 5.6+
6
 *
7
 * @author pgaultier
8
 * @copyright 2010-2017 Philippe Gaultier
9
 * @license http://www.sweelix.net/license license
10
 * @version 1.2.0
11
 * @link http://www.sweelix.net
12
 * @package sweelix\oauth2\server\controllers
13
 */
14
15
namespace sweelix\oauth2\server\controllers;
16
17
use OAuth2\Request as OAuth2Request;
18
use OAuth2\Response as OAuth2Response;
19
use sweelix\oauth2\server\models\Client;
20
use sweelix\oauth2\server\models\Scope;
21
use sweelix\oauth2\server\Module;
22
use yii\filters\AccessControl;
23
use yii\web\Controller;
24
use yii\web\Response;
25
use Yii;
26
27
/**
28
 * Oauth2 main controller
29
 *
30
 * @author pgaultier
31
 * @copyright 2010-2017 Philippe Gaultier
32
 * @license http://www.sweelix.net/license license
33
 * @version 1.2.0
34
 * @link http://www.sweelix.net
35
 * @package sweelix\oauth2\server\controllers
36
 * @since 1.0.0
37
 */
38
class AuthorizeController extends Controller
39
{
40
    /**
41
     * @inheritdoc
42
     */
43
    public function init()
44 10
    {
45
        $module = Module::getInstance();
46 10
47
        if ($module->overrideLayout !== null) {
0 ignored issues
show
Bug introduced by
The property overrideLayout does not seem to exist. Did you mean layout?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
48 10
            $this->layout = $module->overrideLayout;
0 ignored issues
show
Bug introduced by
The property overrideLayout does not seem to exist. Did you mean layout?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
49
        }
50
51
        if ($module->overrideViewPath !== null) {
0 ignored issues
show
Bug introduced by
The property overrideViewPath does not seem to exist. Did you mean viewPath?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
52 10
            $this->setViewPath($module->overrideViewPath);
0 ignored issues
show
Bug introduced by
The property overrideViewPath does not seem to exist. Did you mean viewPath?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
53
        }
54
55
        parent::init();
56 10
    }
57 10
58
    /**
59
     * @inheritdoc
60
     */
61
    public function behaviors()
62 10
    {
63
        $behaviors = parent::behaviors();
64 10
        $behaviors['access'] = [
65 10
            'class' => AccessControl::class,
66 10
            'only' => ['authorize'],
67 10
            'rules' => [
68
                [
69
                    'allow' => true,
70 10
                    'actions' => ['authorize'],
71 10
                    'roles' => ['@'],
72 10
                ],
73 10
            ],
74 10
        ];
75
        return $behaviors;
76 10
    }
77
78
    /**
79
     * Send back an oauth token
80
     * @return Response
81
     * @throws \yii\base\InvalidConfigException
82
     * @since 1.0.0
83
     */
84 9
    public function actionIndex()
85
    {
86 9
        Yii::$app->response->headers->add('Content-Security-Policy', 'frame-ancestors \'none\';');
87 9
88
        $status = false;
89 9
90 9
        /** @var \OAuth2\Server $oauthServer */
91 9
        $oauthServer = Yii::createObject('OAuth2\Server');
92 9
93
        /** @var \OAuth2\Request $oauthRequest */
94
        $oauthRequest = OAuth2Request::createFromGlobals();
95 9
96 5
        $oauthResponse = new OAuth2Response();
97 4
98
        $grantType = Yii::$app->request->getQueryParam('response_type');
99 4
        $promptValues = key_exists('prompt', $oauthRequest->query) ? explode(" ", $oauthRequest->query['prompt']) : [];
100 4
101 4
        switch ($grantType) {
102 4
            // Authorization Code
103 2
            case 'code':
104
                if (Module::getInstance()->allowAuthorizationCode === true) {
105 2
                    $oauthGrantType = Yii::createObject('OAuth2\GrantType\AuthorizationCode');
106 4
                    $oauthServer->addGrantType($oauthGrantType);
107 1
108 1
                    $status = $oauthServer->validateAuthorizeRequest($oauthRequest, $oauthResponse);
109
                } else {
110 5
                    $status = false;
111
                    $oauthResponse->setError(400, 'invalid_grant', 'Authorization code grant is not supported');
112 4
                }
113 4
                break;
114 4
115 4
            // Implicit
116 2
            case 'token':
117
                $status = $oauthServer->validateAuthorizeRequest($oauthRequest, $oauthResponse);
118 2
                break;
119 4
        }
120
121
        if ($status === false) {
122 9
            $this->handleResponse($oauthResponse);
123 4
        } else {
124 4
            Yii::$app->session->set('oauthServer', $oauthServer);
125 4
            if (isset($oauthRequest) === true) {
126 4
                Yii::$app->session->set('oauthRequest', $oauthRequest);
127 4
            }
128 4
129 4
            if (Yii::$app->user->isGuest === true || in_array('login', $promptValues, true)) {
130 1
                /** @var \Oauth2\Controller\AuthorizeController $authController */
131
                $authController = $oauthServer->getAuthorizeController();
132 4
133
                //TODO: check if the user should get logged out
134 5
                if(in_array('none', $promptValues, true)) {
135
                    $response = new OAuth2Response();
136 9
                    $response->setRedirect(
137
                        302,
138
                        $authController->getRedirectUri(),
139
                        $authController->getState(),
140
                        'login_required',
141
                        'Authentication Request cannot be completed without user authentication.',
142
                        null
143
                    );
144 5
145
                    $this->handleResponse($response);
146 5
                } else {
147
                    return $this->redirect(['login']);
148 5
                }
149
            } else {
150 5
                return $this->redirect(['authorize']);
151 1
            }
152 1
        }
153 1
154 1
        return null;
155 1
    }
156
157
    /**
158 4
     * Display login page
159 4
     * @return Response|string
160
     * @throws \yii\base\InvalidConfigException
161 4
     * @since 1.0.0
162
     */
163 4
    public function actionLogin()
164 4
    {
165 4
        Yii::$app->response->headers->add('Content-Security-Policy', 'frame-ancestors \'none\';');
166 4
        $oauthServer = Yii::$app->session->get('oauthServer');
167
168 4
        /* @var \Oauth2\Server $oauthServer */
169 4
        if ($oauthServer === null) {
170 4
            $response = new OAuth2Response();
171 4
            $response->setError(400, 'invalid_request', 'The request was not performed as expected.');
172 2
173
            $this->handleResponse($response);
174 4
        }
175 4
176 4
        $oauthRequest = Yii::$app->session->get('oauthRequest');
177
        $promptValues = $oauthRequest && key_exists('prompt', $oauthRequest->query) ? explode(" ", $oauthRequest->query['prompt']) : [];
178 4
179 4
        $userForm = Yii::createObject('sweelix\oauth2\server\forms\User');
180 4
        $response = null;
181 4
        /* @var \sweelix\oauth2\server\forms\User $userForm */
182 4
        if (Yii::$app->request->isPost === true) {
183 4
            //TODO: handle case when user decline the grants
184
            $userForm->load(Yii::$app->request->bodyParams);
185
            if ($userForm->validate() === true) {
186
                $userClass = $this->getUserClass();
187
                $realUser = $userClass::findByUsernameAndPassword($userForm->username, $userForm->password);
188
                /* @var \sweelix\oauth2\server\interfaces\UserModelInterface $realUser */
189
                if ($realUser !== null) {
190
                    Yii::$app->user->login($realUser, Module::getInstance()->loginDuration);
191 4
192
                    $response = $this->redirect(['authorize']);
193 4
                } else {
194 4
                    $userForm->addError('username');
195
                }
196 4
            }
197
        }
198
        if ($response === null) {
199
            if(in_array('none', $promptValues, true)) {
200
                /** @var \Oauth2\Controller\AuthorizeController $authController */
201
                $authController = $oauthServer->getAuthorizeController();
202 1
                $oauthResponse = $oauthServer->getResponse();
203 4
                $oauthResponse->setRedirect(
204 4
                    302,
205
                    $authController->getRedirectUri(),
206 4
                    $authController->getState(),
207
                    'login_required',
208 1
                    'Authentication Request cannot be completed without user authentication.',
209 1
                    null
210 1
                );
211
212 1
                $this->handleResponse($oauthResponse);
0 ignored issues
show
Compatibility introduced by
$oauthResponse of type object<OAuth2\ResponseInterface> is not a sub-type of object<OAuth2\Response>. It seems like you assume a concrete implementation of the interface OAuth2\ResponseInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
213 1
            }
214 1
            // force empty password
215 1
            $userForm->password = '';
216
            $response = $this->render('login', [
217
                'user' => $userForm,
218
            ]);
219 1
        }
220
221
        return $response;
222
    }
223 4
224 4
    /**
225 4
     * Display authorize page
226
     * @return string|Response
227
     * @throws \yii\base\UnknownClassException
228
     * @since 1.0.0
229
     */
230
    public function actionAuthorize()
231
    {
232
        Yii::$app->response->headers->add('Content-Security-Policy', 'frame-ancestors \'none\';');
233
        /* @var \Oauth2\Server $oauthServer */
234
        $oauthServer = Yii::$app->session->get('oauthServer');
235
        if ($oauthServer === null) {
236
            $response = new OAuth2Response();
237
            $response->setError(400, 'invalid_request', 'The request was not performed as expected.');
238
239
            $this->handleResponse($response);
240 4
        }
241 4
        /** @var \OAuth2\Controller\AuthorizeController $authController */
242 4
        $authController = $oauthServer->getAuthorizeController();
243 4
        $client = Client::findOne($authController->getClientId());
244
        $oauthRequest = Yii::$app->session->get('oauthRequest');
245
        $promptValues = $oauthRequest && key_exists('prompt', $oauthRequest->query) ? explode(" ", $oauthRequest->query['prompt']) : [];
246 4
247 2
        if ($client->hasUser(Yii::$app->user->id) === true && !in_array('consent', $promptValues, true)) {
248 2
            //TODO: check if all consents should be removed
249
            // already logged
250 2
            /** @var OAuth2Response $oauthResponse */
251 2
            $oauthResponse = new OAuth2Response();
252 2
            $oauthResponse = $oauthServer->handleAuthorizeRequest($oauthRequest, $oauthResponse, true, Yii::$app->user->id);
253
254
            Yii::$app->session->remove('oauthServer');
255
            Yii::$app->session->remove('oauthRequest');
256 4
            if ($oauthResponse->getParameter('error') === null) {
257 4
                Yii::$app->session->remove('oauthServer');
258 4
                Yii::$app->session->remove('oauthRequest');
259 4
260 4
                return $this->redirect($oauthResponse->getHttpHeader('Location'));
261
            } else {
262
263
                $this->handleResponse($oauthResponse);
0 ignored issues
show
Compatibility introduced by
$oauthResponse of type object<OAuth2\ResponseInterface> is not a sub-type of object<OAuth2\Response>. It seems like you assume a concrete implementation of the interface OAuth2\ResponseInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
264 4
            }
265
        } else {
266
            // perform regular authorization
267
            if(in_array('none', $promptValues, true)) {
268
                $oauthResponse = $oauthServer->getResponse();
269 4
                $oauthResponse->setRedirect(
270 4
                    302,
271 4
                    $authController->getRedirectUri(),
272 4
                    $authController->getState(),
273
                    'consent_required',
274
                    'Authentication Request cannot be completed without End-User consent.',
275
                    null
276
                );
277
278
                $this->handleResponse($oauthResponse);
0 ignored issues
show
Compatibility introduced by
$oauthResponse of type object<OAuth2\ResponseInterface> is not a sub-type of object<OAuth2\Response>. It seems like you assume a concrete implementation of the interface OAuth2\ResponseInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
279
            }
280 6
281
            $additionalScopes = $authController->getScope();
282 6
            $requestedScopes = [];
283 6
            if (empty($additionalScopes) === false) {
284 6
                $additionalScopes = explode(' ', $additionalScopes);
285 6
                foreach ($additionalScopes as $scope) {
286 6
                    $dbScope = Scope::findOne($scope);
287 6
                    if ($dbScope !== null) {
288
                        $requestedScopes[] = $dbScope;
289
                    } else {
290
                        $response = new OAuth2Response();
291
                        $response->setError(400, 'invalid_scope', 'Scope ' . $scope . ' does not exist.');
292
293
                        $this->handleResponse($response);
294
                    }
295
                }
296
            }
297
            if (Yii::$app->request->isPost === true) {
298
                $accept = Yii::$app->request->getBodyParam('accept', null);
299 4
                $oauthRequest = Yii::$app->session->get('oauthRequest');
300
                /* @var OAuth2Response $oauthResponse */
301 4
                $oauthResponse = new OAuth2Response();
302 4
303 4
                if ($accept !== null) {
304 4
                    // authorize
305 4
                    $oauthResponse = $oauthServer->handleAuthorizeRequest($oauthRequest, $oauthResponse, true, Yii::$app->user->id);
306
                    if($client->hasUser(Yii::$app->user->id) === false) {
307
                        $client->addUser(Yii::$app->user->id);
308
                    }
309
310
                } else {
311
                    // decline
312
                    $oauthResponse = $oauthServer->handleAuthorizeRequest($oauthRequest, $oauthResponse, false, Yii::$app->user->id);
313
                    $client->removeUser(Yii::$app->user->id);
314
                }
315
316
                Yii::$app->session->remove('oauthServer');
317
                Yii::$app->session->remove('oauthRequest');
318
                $error = $oauthResponse->getParameters();
0 ignored issues
show
Bug introduced by
The method getParameters() does not exist on OAuth2\ResponseInterface. Did you maybe mean getParameter()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
319
                $redirect = $oauthResponse->getHttpHeader('Location');
320
                if ((empty($error) === false) && ($redirect === null)) {
321
                    Yii::$app->session->setFlash('error', $error, false);
322
323
                    return $this->redirect(['error']);
324
                } else {
325
326
                    return $this->redirect($redirect);
327
                }
328
            }
329
        }
330
331
        return $this->render('authorize', [
332
            'client' => $client,
333
            'requestedScopes' => $requestedScopes,
0 ignored issues
show
Bug introduced by
The variable $requestedScopes does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
334
        ]);
335
    }
336
337
    /**
338
     * Display an error page
339
     * @return Response|string
340
     * @since 1.0.0
341
     */
342
    public function actionError()
343
    {
344
        Yii::$app->response->headers->add('Content-Security-Policy', 'frame-ancestors \'none\';');
345
        $errorData = Yii::$app->session->getFlash('error');
346
        $code = isset($errorData['code']) && is_numeric($errorData['code']) ? $errorData['code'] : 400;
347
        Yii::$app->response->setStatusCode($code);
348
349
        return $this->render('error', [
350
            'type' => (isset($errorData['error']) ? $errorData['error'] : null),
351
            'description' => (isset($errorData['error_description']) ? $errorData['error_description'] : null),
352
        ]);
353
    }
354
355
    /**
356
     * @var string
357
     */
358
    private $userClass;
359
360
    /**
361
     * @return string classname for selected interface
362
     * @throws \yii\base\InvalidConfigException
363
     * @since 1.0.0
364
     */
365
    public function getUserClass()
366
    {
367
        if ($this->userClass === null) {
368
            $scope = Yii::createObject('sweelix\oauth2\server\interfaces\UserModelInterface');
369
            $this->userClass = get_class($scope);
370
        }
371
        return $this->userClass;
372
    }
373
374
    /**
375
     * @param \Oauth2\Response $response
376
     */
377
    public function handleResponse($response)
378
    {
379
380
        if ($response->getParameter('error') !== null) {
381
382
            Yii::$app->session->remove('oauthServer');
383
            Yii::$app->session->remove('oauthRequest');
384
385
            if ($response->isRedirection()) {
386
387
                return $this->redirect($response->getHttpHeader('Location'));
388
            } else {
389
390
                $code = $response->getStatusCode();
391
                $error = $response->getParameter('error', 'Unkown error');
392
                $description = $response->getParameter('error_description', 'Please check your request.');
393
                Yii::$app->session->setFlash('error', ['code' => $code, 'error' => $error, 'error_description' => $description], false);
394
395
                return $this->redirect(['error']);
396
            }
397
        }
398
399
    }
400
}
401