Token   B
last analyzed

Complexity

Total Complexity 44

Size/Duplication

Total Lines 462
Duplicated Lines 16.02 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
dl 74
loc 462
rs 8.3396
c 0
b 0
f 0
wmc 44
lcom 1
cbo 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
B token() 0 21 5
C code() 17 77 7
B credentials() 26 67 6
C password() 31 102 12
C revoke() 0 62 8
B refresh() 0 67 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Token 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Token, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace FFCMS\Controllers\API;
4
5
use FFMVC\Helpers;
6
use FFCMS\{Traits, Models, Mappers};
7
8
/**
9
 * Api Token Controller Class.
10
 *
11
 * @author Vijay Mahrra <[email protected]>
12
 * @copyright Vijay Mahrra
13
 * @license GPLv3 (http://www.gnu.org/licenses/gpl-3.0.html)
14
 */
15
class Token extends API
16
{
17
    /**
18
     * Handle incoming access token request and process
19
     * with appropriate method based on grant_type.parameter
20
     *
21
     * requires input of client_id and client_secret
22
     *
23
     * @param \Base $f3
24
     * @return void
25
     */
26
    public function token(\Base $f3)
27
    {
28
        switch ($f3->get('REQUEST.grant_type')) {
29
30
            case 'authorization_code': // exchange auth code for access token
31
                return $this->code($f3);
32
33
            case 'client_credentials': // client app gets a token for itself
34
                return $this->credentials($f3);
35
36
            case 'password': // client app passes end-user username and password to get token
37
                return $this->password($f3);
38
39
            case 'refresh_token': // refresh access token using refresh token
40
                return $this->refresh($f3);
41
42
            default:
43
                $this->failure('api_connnection_error', "Grant type should be one of: (authorization_code, client_credentials, password, refresh_token)", 400);
44
                $this->setOAuthError('unsupported_grant_type');
45
        }
46
    }
47
48
49
    /**
50
     * Handle grant_type=authorization_code.
51
     *
52
     * requires input of 'code'
53
     *
54
     * @param \Base $f3
55
     * @return void
56
     * @link http://tools.ietf.org/html/rfc6749
57
     */
58
    protected function code(\Base $f3)
59
    {
60
        // this requires a valid client_id/secret
61
        if (empty($this->validateAccess())) {
62
            return;
63
        }
64
65
        // not a valid app
66
        $app = $f3->get('api_app');
67
        if (empty($app)) {
68
            return;
69
        }
70
71
        // check valid code
72
        $code = (int) $f3->get('REQUEST.code');
73
        if ($code < 9999999) {
74
            $this->failure('authentication_error', "The code was was invalid.", 401);
75
            $this->setOAuthError('invalid_grant');
76
            return;
77
        }
78
79
        // fetch models
80
        $oAuth2Model = Models\OAuth2::instance();
81
        $appsMapper = $oAuth2Model->getAppsMapper();
82
        $tokensMapper = $oAuth2Model->getTokensMapper();
83
84
        // get the app's authorized app token
85
        $tokensMapper->load(['client_id = ? AND users_uuid = ? AND token = ?',
86
                $appsMapper->client_id, $appsMapper->users_uuid, $code]);
87
        if (null == $tokensMapper->users_uuid) {
88
            $this->failure('authentication_error', "Could not find the user for the token.", 401);
89
            $this->setOAuthError('invalid_grant');
90
            return;
91
        }
92
93
        // code expired!
94 View Code Duplication
        if (time() > strtotime($tokensMapper->expires)) {
95
            $this->failure('authentication_error', "The token expired.", 401);
96
            $this->setOAuthError('invalid_grant');
97
            return;
98
        }
99
100
        // now set a token into the the same token object
101
        $tokensMapper->setUUID('token');
102
        $tokensMapper->type = 'access_token';
103
        $tokensMapper->expires = Helpers\Time::database(time() + $f3->get('oauth2.expires_token'));
104
        $tokensMapper->save();
105
106
        // check if there's already a refresh token for the client/user combination
107
        $rTokensMapper = clone $tokensMapper;
108
        $rTokensMapper->load(['client_id = ? AND users_uuid = ? AND '.$rTokensMapper->quotekey('type').' = "refresh_token"',
109
                $appsMapper->client_id, $appsMapper->users_uuid]);
110
111
        // make a new refresh token if one doesn't exist
112 View Code Duplication
        if (null == $rTokensMapper->token) {
113
             // if not, create one
114
            $data = $tokensMapper->cast();
115
            unset($data['id']);
116
            unset($data['uuid']);
117
            unset($data['token']);
118
            $rTokensMapper->copyfrom($data);
119
            $rTokensMapper->setUUID('token');
120
            $rTokensMapper->type = 'refresh_token';
121
            $rTokensMapper->expires = null;
122
            $rTokensMapper->save();
123
        }
124
125
        // all good - return the token!
126
        $this->params['headers']['Service'] = 'OAuth2 Client Access Token';
127
        $this->data += [
128
            'access_token' => $tokensMapper->token,
129
            'refresh_token' => $rTokensMapper->token,
130
            'scope' => $tokensMapper->scope,
131
            'token_type' => 'bearer',
132
            'expires_in' => $f3->get('oauth2.expires_token')
133
        ];
134
    }
135
136
137
    /**
138
     * Handle grant_type=client_credentials.
139
     *
140
     * As per RFC6749 - must NOT return a refresh_token!
141
     *
142
     *
143
     * @param \Base $f3
144
     * @return void
145
     * @link http://tools.ietf.org/html/rfc6749
146
     */
147
    protected function credentials(\Base $f3)
148
    {
149
        // this requires a valid client_id/secret
150
        if (empty($this->validateAccess())) {
151
            return;
152
        }
153
154
        // not a valid app
155
        $app = $f3->get('api_app');
156
        if (empty($app)) {
157
            $this->failure('authentication_error', "Invalid client credentials!", 401);
158
            $this->setOAuthError('invalid_grant');
159
            return;
160
        }
161
162
        $oAuth2Model = Models\OAuth2::instance();
163
        $appsMapper = $oAuth2Model->getAppsMapper();
164
        $tokensMapper = $oAuth2Model->getTokensMapper();
165
166
        // get the app's authorized app token if it exists
167
        $tokensMapper->load(['client_id = ? AND users_uuid = ? AND '.$tokensMapper->quotekey('type').' = "access_token"',
168
                $appsMapper->client_id, $appsMapper->users_uuid]);
169 View Code Duplication
        if (null == $tokensMapper->users_uuid) {
170
                // make a new token (and refresh token)
171
            $tokensMapper->users_uuid = $appsMapper->users_uuid;
172
            $tokensMapper->client_id = $appsMapper->client_id;
173
            $tokensMapper->type = 'access_token';
174
            $tokensMapper->scope = 'read write';
175
            $tokensMapper->setUUID('token');
176
            $tokensMapper->expires = Helpers\Time::database(time() + $f3->get('oauth2.expires_token'));
177
            $tokensMapper->save();
178
        }
179
180
        // token expired!
181 View Code Duplication
        if (time() > strtotime($tokensMapper->expires)) {
182
            // as it's a client credentials login we can extend it though
183
            $tokensMapper->expires = Helpers\Time::database(time() + $f3->get('oauth2.expires_token'));
184
            $tokensMapper->save();
185
        }
186
187
        // check if there's already a refresh token for the client/user combination
188
        $rTokensMapper = clone $tokensMapper;
189
        $rTokensMapper->load(['client_id = ? AND users_uuid = ? AND '.$rTokensMapper->quotekey('type').' = "refresh_token"',
190
                $appsMapper->client_id, $appsMapper->users_uuid]);
191
        // if not, create one
192 View Code Duplication
        if (null == $rTokensMapper->token) {
193
            $data = $tokensMapper->cast();
194
            unset($data['id']);
195
            unset($data['uuid']);
196
            unset($data['token']);
197
            $rTokensMapper->copyfrom($data);
198
            $rTokensMapper->setUUID('token');
199
            $rTokensMapper->type = 'refresh_token';
200
            $rTokensMapper->expires = Helpers\Time::database(time() + 3600);
201
            $rTokensMapper->save();
202
        }
203
204
        // all good - return the access token only
205
        $this->params['headers']['Service'] = 'OAuth2 Client Credentials Access Token';
206
        $this->data += [
207
            'access_token' => $tokensMapper->token,
208
            'refresh_token' => $rTokensMapper->token,
209
            'scope' => $tokensMapper->scope,
210
            'token_type' => 'bearer',
211
            'expires_in' => $f3->get('oauth2.expires_token')
212
        ];
213
    }
214
215
216
    /**
217
     * Handle grant_type=password.
218
     *
219
     * requires input of 'username' and 'password'
220
     *
221
     * @param \Base $f3
222
     * @return void
223
     * @link http://tools.ietf.org/html/rfc6749
224
     */
225
    protected function password(\Base $f3)
226
    {
227 View Code Duplication
        if ('http' == $f3->get('SCHEME') && !empty($f3->get('api.https'))) {
228
            $this->failure('api_connection_failure', "Connection only allowed via HTTPS!", 400);
229
            $this->setOAuthError('unauthorized_client');
230
            return;
231
        }
232
233
        // fetch models now
234
        $oAuth2Model = Models\OAuth2::instance();
235
        $appsMapper = $oAuth2Model->getAppsMapper();
236
        $tokensMapper = $oAuth2Model->getTokensMapper();
237
        $usersModel = Models\Users::instance();
238
239
        $clientId = $f3->get('REQUEST.client_id');
240
        if (empty($clientId)) {
241
            $this->failure('authentication_error', "Missing client_id.", 400);
242
            $this->setOAuthError('invalid_request');
243
            return;
244
        }
245
        $appsMapper->load(['client_id = ?', $clientId]);
246
        if (null == $appsMapper->client_id) {
247
            $this->failure('authentication_error', "Invalid client_id.", 401);
248
            $this->setOAuthError('invalid_credentials');
249
            return;
250
        }
251
        $f3->set('api_app', $appsMapper->cast());
252
253
        // not a valid app
254
        $app = $f3->get('api_app');
255
        if (empty($app)) {
256
            $this->failure('authentication_error', "Invalid client credentials!", 401);
257
            $this->setOAuthError('invalid_grant');
258
            return;
259
        }
260
261
        // check required params
262
        $username = $f3->get('REQUEST.username');
263
        $password = $f3->get('REQUEST.password');
264
        if (empty($username) || empty($password)) {
265
            $this->failure('authentication_error', "Missing either username or password.", 401);
266
            $this->setOAuthError('invalid_request');
267
            return;
268
        }
269
270
        // check user exists
271
        $usersMapper = $usersModel->getMapper();
272
        $passwordHash = Helpers\Str::password($password);
273
        $usersMapper->load(['email = ? AND password =?', $username, $passwordHash]);
274
        if (null == $usersMapper->uuid) {
275
            $this->failure('authentication_error', "No user matching those credentials.", 401);
276
            $this->setOAuthError('invalid_grant');
277
            return;
278
        }
279
280
        // get the app users authorized app token if it exists
281
        $tokensMapper->load(['client_id = ? AND users_uuid = ? AND '.$tokensMapper->quotekey('type').' = "access_token"',
282
                $appsMapper->client_id, $usersMapper->uuid]);
283 View Code Duplication
        if (null == $tokensMapper->users_uuid) {
284
                // make a new token (and refresh token)
285
            $tokensMapper->users_uuid = $usersMapper->uuid;
286
            $tokensMapper->client_id = $appsMapper->client_id;
287
            $tokensMapper->type = 'access_token';
288
            $tokensMapper->scope = 'read write';
289
            $tokensMapper->setUUID('token');
290
            $tokensMapper->expires = Helpers\Time::database(time() + $f3->get('oauth2.expires_token'));
291
            $tokensMapper->save();
292
        }
293
        // token expired!
294 View Code Duplication
        if (time() > strtotime($tokensMapper->expires)) {
295
            // as it's a password login we can extend it though
296
            $tokensMapper->expires = Helpers\Time::database(time() + $f3->get('oauth2.expires_token'));
297
            $tokensMapper->save();
298
        }
299
300
        // check if there's already a refresh token for the client/user combination
301
        $rTokensMapper = clone $tokensMapper;
302
        $rTokensMapper->load(['client_id = ? AND users_uuid = ? AND '.$rTokensMapper->quotekey('type').' = "refresh_token"',
303
                $appsMapper->client_id, $usersMapper->uuid]);
304
305
        // create one otherwise
306 View Code Duplication
        if (null == $rTokensMapper->token) {
307
            $data = $tokensMapper->cast();
308
            unset($data['id']);
309
            unset($data['uuid']);
310
            unset($data['token']);
311
            $rTokensMapper->copyfrom($data);
312
            $rTokensMapper->setUUID('token');
313
            $rTokensMapper->type = 'refresh_token';
314
            $rTokensMapper->expires = null;
315
            $rTokensMapper->save();
316
        }
317
318
        // all good - return the access token only because client_secret absent
319
        $this->params['headers']['Service'] = 'OAuth2 Password Access Token';
320
        $this->data += [
321
            'access_token' => $tokensMapper->token,
322
            'scope' => $tokensMapper->scope,
323
            'token_type' => 'bearer',
324
            'expires_in' => $f3->get('oauth2.expires_token')
325
        ];
326
    }
327
328
329
    /**
330
     * Revoke an access token, revoke?token=TOKEN GET.
331
     *
332
     * @param \Base $f3
333
     * @return void
334
     */
335
    public function revoke(\Base $f3)
336
    {
337
        if (empty($this->validateAccess())) {
338
            return;
339
        }
340
341
        // not a valid app
342
        $app = $f3->get('api_app');
343
        if (empty($app)) {
344
            $this->failure('authentication_error', "Invalid client credentials!", 401);
345
            $this->setOAuthError('invalid_grant');
346
            return;
347
        }
348
349
        // check required params
350
        $token = $f3->get('REQUEST.token');
351
        if (empty($token)) {
352
            $this->failure('authentication_error', "Missing access token!", 401);
353
            $this->setOAuthError('invalid_request');
354
            return;
355
        }
356
357
        // fetch models now
358
        $oAuth2Model = Models\OAuth2::instance();
359
360
        // check refresh token exists
361
        $tokensMapper = $oAuth2Model->getTokensMapper();
362
        $tokensMapper->load(['client_id = ? AND token = ?',
363
                $app['client_id'], $token]);
364
365
        // get the token type and user
366
        $usersUUID = $tokensMapper->users_uuid;
367
        $isAccessToken = $tokensMapper->type == 'access_token';
368
        $isRefreshToken = $tokensMapper->type == 'refresh_token';
369
370
        // no matching token found, error
371
        if (!$isAccessToken && !$isRefreshToken) {
372
            $this->failure('authentication_error', "Not matching token(s) found!", 401);
373
            $this->setOAuthError('invalid_request');
374
            return;
375
        }
376
377
        // revoke the token, delete it
378
        $revoked = [];
379
        $revoked[$tokensMapper->type][] = $tokensMapper->token;
380
        $tokensMapper->erase();
381
382
            // erase other matching tokens for app and user id
383
        if ($isRefreshToken) {
384
            $tokens = $tokensMapper->find(['client_id = ? AND users_uuid = ?',
385
                    $app['client_id'], $usersUUID]);
386
            foreach ($tokens as $t) {
387
                $revoked[$t->type][] = $t->token;
388
                $t->erase();
389
            }
390
        }
391
392
        $this->params['headers']['Service'] = 'OAuth2 Revoke Token';
393
        $this->data += [
394
            'revoked' => $revoked
395
        ];
396
    }
397
398
399
    /**
400
     * Handle grant_type=refresh_token.
401
     *
402
     * Request a new access_token using the refresh_token
403
     *
404
     * @param \Base $f3
405
     * @return void
406
     * @link http://tools.ietf.org/html/rfc6749
407
     */
408
    protected function refresh(\Base $f3)
409
    {
410
        // fetch models now
411
        $oAuth2Model = Models\OAuth2::instance();
412
        $tokensMapper = $oAuth2Model->getTokensMapper();
413
414
        // check required params
415
        $refreshToken = $f3->get('REQUEST.refresh_token');
416
        if (empty($refreshToken)) {
417
            $this->failure('authentication_error', "Missing refresh token!",400);
418
            $this->setOAuthError('invalid_request');
419
            return;
420
        }
421
422
        // if no client_id/secret,, fetch app information
423
        if (empty($this->validateAccess())) {
424
            // the spec says we need client_id and client_password to do this
425
            $this->failure('authentication_error', "Unable to authenticate client!", 401);
426
            $this->setOAuthError('invalid_grant');
427
            return;
428
        }
429
430
        // not a valid app
431
        $app = $f3->get('api_app');
432
        if (empty($app)) {
433
            $this->failure('authentication_error', "Invalid client credentials!", 401);
434
            $this->setOAuthError('invalid_grant');
435
            return;
436
        }
437
438
        // get the app users authorized app
439
        $app = $f3->get('api_app');
440
441
        // check refresh token exists
442
        $tokensMapper->load(['client_id = ? AND token =? AND '.$tokensMapper->quotekey('type').' = "refresh_token"',
443
                $app['client_id'], $refreshToken]);
444
        if (null == $tokensMapper->client_id) {
445
            $this->failure('authentication_error', "Could not find that refresh token!", 401);
446
            $this->setOAuthError('invalid_grant');
447
            return;
448
        }
449
        $refreshToken = $tokensMapper->cast();
450
451
        $tokensMapper->load(['client_id = ? AND users_uuid =? AND '.$tokensMapper->quotekey('type').' = "access_token"',
452
                $app['client_id'], $refreshToken['users_uuid']]);
453
454
        // no-pre existing token, make one
455
        if (null == $tokensMapper->client_id) {
456
            $tokensMapper->users_uuid = $refreshToken['users_uuid'];
457
            $tokensMapper->client_id = $app['client_id'];
458
            $tokensMapper->type = 'access_token';
459
            $tokensMapper->scope = $refreshToken['scope'];
460
        }
461
462
        // create a new token value
463
        $tokensMapper->setUUID('token');
464
        $tokensMapper->expires = Helpers\Time::database(time() + $f3->get('oauth2.expires_token'));
465
        $tokensMapper->save();
466
467
        $this->params['headers']['Service'] = 'OAuth2 Refresh Access Token';
468
        $this->data += [
469
            'access_token' => $tokensMapper->token,
470
            'scope' => $tokensMapper->scope,
471
            'token_type' => 'bearer',
472
            'expires_in' => $f3->get('oauth2.expires_token')
473
        ];
474
    }
475
476
}
477