OAuth2::Authenticate()   F
last analyzed

Complexity

Conditions 15
Paths 290

Size

Total Lines 137
Code Lines 80

Duplication

Lines 24
Ratio 17.52 %

Importance

Changes 0
Metric Value
cc 15
eloc 80
nc 290
nop 2
dl 24
loc 137
rs 3.7313
c 0
b 0
f 0

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 FFCMS\Controllers\OAuth2;
4
5
use FFMVC\Helpers;
6
use FFCMS\{Traits, Models, Mappers};
7
8
9
/**
10
 * OAuth2 Controller Class.
11
 *
12
 * OAuth2 WWW Handler
13
 *
14
 * @author Vijay Mahrra <[email protected]>
15
 * @copyright (c) Copyright 2016 Vijay Mahrra
16
 * @license GPLv3 (http://www.gnu.org/licenses/gpl-3.0.html)
17
 */
18
class OAuth2 extends Controllers\User\Base
19
{
20
    /**
21
     * OAuth2 Callback for testing app callback
22
     *
23
     * @param \Base $f3
24
     * @param array $params
25
     * @return void
26
     */
27
    public function Callback(\Base $f3, array $params)
28
    {
29
        $oAuth2Model = Models\OAuth2::instance();
30
        $appsMapper = $oAuth2Model->getAppsMapper();
31
32
        // verify client_id is acceptable
33
        $clientId = $f3->get('REQUEST.client_id');
34
        if (empty($clientId)) {
35
            $this->notify(_('Invalid client id in request.'), 'warning');
36
            return $f3->reroute('@index');
37
        }
38
39
        // verify state - application must decide how really
40
        $state = $f3->get('REQUEST.state');
41
        if (empty($state)) {
42
            $this->notify(_('Invalid state in request.'), 'warning');
43
            return $f3->reroute('@index');
44
        }
45
46
        $state = $f3->get('REQUEST.state');
47
        if (empty($state)) {
48
            $this->notify(_('Invalid state in request.'), 'warning');
49
            return $f3->reroute('@index');
50
        }
51
52
        // there should be some scope
53
        $scope = $f3->get('REQUEST.scope');
54
        if (empty($scope)) {
55
            $this->notify(_('Invalid scope in request.'), 'warning');
56
            return $f3->reroute('@index');
57
        }
58
59
60
        // finally we need a code (8 digits) to swap for a token
61
        $code = (int) $f3->get('REQUEST.code');
62
        if (empty($code)) {
63
            $token = $f3->get('REQUEST.token');
64
        }
65
        if ($code < 9999999 && empty($token)) {
66
            $this->notify(_('Invalid code/token in request.'), 'warning');
67
            return $f3->reroute('@index');
68
        }
69
70
        // load the app to get the client_secret for the token call
71
        $appsMapper->load(['client_id = ?', $clientId]);
72
        if (empty($appsMapper->client_id)) {
73
            $this->notify(_('Unknown client_id in request.'), 'warning');
74
            return $f3->reroute('@index');
75
        }
76
77
        // get the url to retrieve the token
78
        if (!empty($code)) {
79
            $this->notify(_('Below is the URL to retrieve your token.'), 'info');
80
            // by now we have valid data from the request
81
            $url = $this->xurl(sprintf("http://%s/api/oauth2/token", $f3->get('HOST')), [
82
                'client_id' => $clientId,
83
                'client_secret' => $appsMapper->client_secret,
84
                'grant_type' => 'authorization_code',
85
                'code' => $code
86
            ]);
87
            $f3->set('retrieveTokenURL', $url);
88
        } elseif (!empty($token)) {
89
            $this->notify(_('Your access token is:') . ' ' . $token, 'info');
90
        }
91
92
        $f3->set('form', $f3->get('REQUEST'));
93
        echo \View::instance()->render('oauth2/callback.phtml');
94
    }
95
96
97
    /**
98
     * Authenticate an incoming OAuth2 request for user access
99
     *
100
     * @param \Base $f3
101
     * @param array $params
102
     * @return void
103
     */
104
    public function Authenticate(\Base $f3, array $params)
105
    {
106
        $this->csrf('@api_apps');
107
108
        $view = 'oauth2/authenticate.phtml';
109
110
        // redirect to user login if user not logged in
111
        $redirect_uri = $this->url($params[0], $f3->get('REQUEST'));
112
113
        $this->redirectLoggedOutUser('@login', [
114
            'redirect_uri' => $redirect_uri
115
        ]);
116
117
        $oAuth2Model = Models\OAuth2::instance();
118
        $appsMapper = $oAuth2Model->getAppsMapper();
119
        $permissions = [];
120
121
        // assume there's problems!
122
        $f3->set('errors', true);
123
124
        // check valid fields
125
        $this->filterRules([
126
            'client_id' => 'trim|sanitize_string',
127
            'scope' => 'trim|sanitize_string',
128
            'state' => 'trim|sanitize_string',
129
            'response_type' => 'trim|sanitize_string',
130
            'redirect_uri' => 'trim|sanitize_string',
131
        ]);
132
        $request = $this->filter($f3->get('REQUEST'));
133
        foreach ($request as $k => $v) {
134
            $f3->set('REQUEST.' . $k, $v);
135
        }
136
137
        // check valid fields
138
        $this->validationRules([
139
            'client_id' => 'required|alpha_dash|exact_len,36',
140
            'scope' => 'required|min_len,3|max_len,4096',
141
            'state' => 'required|min_len,1|max_len,255',
142
            'response_type' => 'required|min_len,1|max_len,16',
143
            'redirect_uri' => 'valid_url',
144
        ]);
145
        $errors = $this->validate(false, $f3->get('REQUEST'));
146
147
        // if errors display form
148 View Code Duplication
        if (is_array($errors)) {
149
            $this->notify(['info' => $oAuth2Model->validationErrors($errors)]);
150
            $f3->set('form', $f3->get('REQUEST'));
151
            echo \View::instance()->render($view);
152
            return;
153
        }
154
155
            // validate response_type - only one type is allowed anyway
156
        if ('code' !== $request['response_type']) {
157
            $request['response_type'] = 'token';
158
        }
159
        $f3->set('REQUEST.response_type', $request['response_type']);
160
161
            // validate scope(s)
162
        $allScopes = $oAuth2Model->SCOPES;
163
        $scopes = empty($request['scope']) ? [] : preg_split("/[\s,]+/", $request['scope']);
164
165
        foreach ($scopes as $k => $scope) {
166
167
            if (!array_key_exists($scope, $allScopes)) {
168
                $this->notify(_('Unknown scope specified ') . $scope, 'warning');
169
                unset($scopes[$k]);
170
            } else {
171
                $permissions[$scope] = $allScopes[$scope];
172
            }
173
174
        }
175
176
        // no valid scopes
177 View Code Duplication
        if (empty($scopes)) {
178
            $this->notify(_('No valid scope(s) specified'), 'error');
179
            $f3->set('form', $f3->get('REQUEST'));
180
            echo \View::instance()->render($view);
181
            return;
182
        }
183
184
        // verify client id is valid
185
        $appsMapper->load(['client_id = ?', $request['client_id']]);
186 View Code Duplication
        if (empty($appsMapper->client_id)) {
187
            $this->notify(_('Unknown client id!'), 'error');
188
            $f3->set('form', $f3->get('REQUEST'));
189
            echo \View::instance()->render($view);
190
            return;
191
        }
192
193
        // verify client app status
194 View Code Duplication
        if ('approved' !== $appsMapper->status) {
195
            $this->notify(sprintf(_('Application status %s currently forbids access.'), $appsMapper->status), 'error');
196
            $f3->set('form', $f3->get('REQUEST'));
197
            echo \View::instance()->render($view);
198
            return;
199
        }
200
201
        if (empty($request['redirect_uri'])) {
202
            $request['redirect_uri'] = $appsMapper->callback_uri;
203
        } elseif ($appsMapper->callback_uri !== $request['redirect_uri']) {
204
            $redirect_uris = empty($appsMapper->redirect_uris) ? [] : preg_split("/[\s,]+/", $appsMapper->redirect_uris);
205
            if (!in_array($request['redirect_uri'], $redirect_uris)) {
206
                $this->notify(_('Unregistered redirect_uri!'), $appsMapper->status, 'error');
207
                $f3->set('form', $f3->get('REQUEST'));
208
                echo \View::instance()->render($view);
209
                return;
210
            }
211
        }
212
        $f3->set('REQUEST.redirect_uri', $request['redirect_uri']);
213
214
        // verify client_id from session on accept/deny click
215
        $f3->set('SESSION.client_id', $appsMapper->client_id);
216
217
        // allowed scopes
218
        $f3->set('SESSION.scope', join(',', array_keys($permissions)));
219
220
        // validate client_id
221
        $client = true;
222
223
        if (!empty($client)) {
224
            // get client permissions requested
225
            // if valid, create code and access token for it
226
227
            $f3->set('confirmUrl',
228
                $this->url('@oauth_confirm', $f3->get('REQUEST')));
229
230
            $f3->set('denyUrl',
231
                $this->url('@oauth_deny',  $f3->get('REQUEST')));
232
233
            $f3->set('errors', false);
234
        }
235
236
        $f3->set('permissions', $permissions);
237
238
        $f3->set('form', $f3->get('REQUEST'));
239
        echo \View::instance()->render($view);
240
    }
241
242
243
    /**
244
     * Accept incoming OAuth2 request for user access
245
     *
246
     * @param \Base $f3
247
     * @param array $params
248
     * @return void
249
     */
250
    public function ConfirmPost(\Base $f3, array $params)
251
    {
252
        $this->csrf('@api_apps');
253
        $request = $f3->get('REQUEST');
254
255
        $oAuth2Model = Models\OAuth2::instance();
256
        $appsMapper = $oAuth2Model->getAppsMapper();
257
        $tokensMapper = $oAuth2Model->getTokensMapper();
258
259
        // verify client_id is acceptable
260
        $sessionClientId = $f3->get('SESSION.client_id');
261
        $clientId = $f3->get('REQUEST.client_id');
262 View Code Duplication
        if (empty($sessionClientId) || empty($clientId) || $clientId !== $sessionClientId) {
263
            $this->notify(_('Invalid client id in request.'), 'warning');
264
            return $f3->reroute('@index');
265
        }
266
        $f3->clear('SESSION.client_id');
267
268
        // verify client id is valid
269
        $appsMapper->load(['client_id = ?', $clientId]);
270
        if (empty($appsMapper->client_id)) {
271
            $this->notify(_('Unknown client id!'), 'error');
272
            return $f3->reroute('@index');
273
        }
274
275
        // get scopes
276
        $scope = $f3->get('SESSION.scope');
277
        if (empty($scope)) {
278
            $this->notify(_('Unknown scope!'), 'error');
279
            return $f3->reroute('@index');
280
        }
281
        $f3->clear('SESSION.scope');
282
        // generate a  new app user token
283
        // load pre-existing value
284
        $tokensMapper->load(['client_id = ? AND users_uuid = ?', $clientId, $f3->get('uuid')]);
285
286
        $response_type = $f3->get('REQUEST.response_type');
287
        switch ($response_type) {
288
289
            case 'token':
290
                // we already have a token, and the token is not a uuid
291
                if (null !== $tokensMapper->token && !is_numeric($tokensMapper->token)) {
292
                    break;
293
                }
294
                $data = [
295
                    'users_uuid' => $f3->get('uuid'),
296
                    'client_id' => $clientId,
297
                    'token' => null,
298
                    'type' => 'access_token',
299
                    'scope' => $scope
300
                ];
301
                $tokensMapper->copyfrom($data);
302
                $tokensMapper->token = $tokensMapper->setUUID('token');
303
                $tokensMapper->expires = Helpers\Time::database(time() + $f3->get('oauth2.expires_token'));
304
                break;
305
306
            case 'code':
307
                $tokensMapper->expires = Helpers\Time::database(time() + $f3->get('oauth2.expires_code'));
308
                break;
309
310
            default:
311
                $this->notify(_('Unknown response type!'), 'error');
312
                return $f3->reroute('@index');
313
314
        }
315
316
        if (!$tokensMapper->save()) {
317
            $this->notify(_('Could not create app access.'), 'error');
318
        } else {
319
            $this->notify(_('Access granted to') . ' ' . $appsMapper->name, 'success');
320
321
            $url = $request['redirect_uri'];
322
            $data = [
323
                'state' => $request['state'],
324
                'client_id' => $tokensMapper->client_id,
325
                'scope' => $tokensMapper->scope,
326
                $response_type => $tokensMapper->token
327
            ];
328
329
            // token must be sent in url fragment #
330
            if ($response_type == 'token') {
331
                unset($data[$response_type]);
332
            }
333
            $url = Helpers\Url::external($url, $data);
334
            if ($response_type == 'token') {
335
                $url .= '#token=' . $tokensMapper->token;
336
            }
337
338
            $f3->set('returnUrl', $url);
339
        }
340
341
        $f3->set('form', $f3->get('REQUEST'));
342
        echo \View::instance()->render('oauth2/confirm.phtml');
343
    }
344
345
346
    /**
347
     * Deny incoming OAuth2 request for user access
348
     *
349
     * @param \Base $f3
350
     * @param array $params
351
     * @return void
352
     */
353
    public function Deny(\Base $f3, array $params)
354
    {
355
        $this->csrf('@api_apps');
356
        $request = $f3->get('REQUEST');
357
        $oAuth2Model = Models\OAuth2::instance();
358
        $appsMapper = $oAuth2Model->getAppsMapper();
359
360
        // verify client_id is acceptable
361
        $sessionClientId = $f3->get('SESSION.client_id');
362
        $clientId = $f3->get('REQUEST.client_id');
363 View Code Duplication
        if (empty($sessionClientId) || empty($clientId) || $clientId !== $sessionClientId) {
364
            $this->notify(_('Invalid client id in request.'), 'warning');
365
            return $f3->reroute('@index');
366
        }
367
        $f3->clear('SESSION.client_id');
368
369
        // get scopes
370
        $scope = $f3->get('SESSION.scope');
371
        if (empty($scope)) {
372
            $this->notify(_('Unknown scope!'), 'error');
373
            return $f3->reroute('@index');
374
        }
375
        $f3->clear('SESSION.scope');
376
377
        // verify client id is valid
378
        $appsMapper->load(['client_id = ?', $clientId]);
379
        if (empty($appsMapper->client_id)) {
380
            $this->notify(_('Unknown client id!'), 'error');
381
            return $f3->reroute('@index');
382
        }
383
384
        // link to redirect the user back with error description in the querystring
385
        $url = Helpers\Url::external($request['redirect_uri'], [
386
            'error' => 'access_denied',
387
            'error_description' => _('The user denied access to the application'),
388
            'state' => $request['state'],
389
            'client_id' => $appsMapper->client_id,
390
            'scope' => $scope,
391
        ]);
392
393
        $f3->set('denyUrl', $url);
394
395
        $f3->set('form', $f3->get('REQUEST'));
396
        echo \View::instance()->render('oauth2/deny.phtml');
397
    }
398
399
}
400