Completed
Push — develop ( d11ec8...35a198 )
by Wisoot
08:34
created

JwtGuard   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 427
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 15
Bugs 1 Features 5
Metric Value
wmc 45
c 15
b 1
f 5
lcom 1
cbo 11
dl 0
loc 427
rs 8.3673

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
B user() 0 26 5
A getUserByToken() 0 11 3
A validate() 0 4 1
B attempt() 0 23 4
A hasValidCredentials() 0 4 2
A fireAttemptEvent() 0 8 2
A attempting() 0 6 2
A login() 0 18 1
A refreshTokenForUser() 0 10 2
A fireLoginEvent() 0 6 2
A loginUsingId() 0 12 2
A logout() 0 14 3
A logoutAll() 0 17 3
A logoutCurrentUser() 0 13 2
A refreshToken() 0 10 2
A getDispatcher() 0 4 1
A setDispatcher() 0 4 1
A setToken() 0 4 1
A getToken() 0 4 1
A isTokenRefreshable() 0 4 1
A isTokenPresent() 0 4 1
A getBearerToken() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like JwtGuard 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 JwtGuard, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace WWON\JwtGuard;
4
5
use Illuminate\Auth\Events\Attempting;
6
use Illuminate\Auth\Events\Login;
7
use Illuminate\Auth\Events\Logout;
8
use Illuminate\Auth\GuardHelpers;
9
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
10
use Illuminate\Contracts\Auth\Guard;
11
use Illuminate\Contracts\Auth\UserProvider;
12
use Illuminate\Contracts\Events\Dispatcher;
13
use Illuminate\Http\Request;
14
use Illuminate\Support\Facades\Config;
15
use WWON\JwtGuard\Exceptions\Exception;
16
use WWON\JwtGuard\Exceptions\InaccessibleException;
17
use WWON\JwtGuard\Exceptions\InvalidTokenException;
18
use WWON\JwtGuard\Exceptions\MalformedException;
19
use WWON\JwtGuard\Exceptions\TokenExpiredException;
20
21
class JwtGuard implements Guard
22
{
23
24
    use GuardHelpers;
25
26
    /**
27
     * @var string
28
     */
29
    protected $token;
30
31
    /**
32
     * @var bool
33
     */
34
    protected $isTokenRefreshable = false;
35
36
    /**
37
     * @var bool
38
     */
39
    protected $isTokenPresent = false;
40
41
    /**
42
     * @var JwtService
43
     */
44
    protected $jwtService;
45
46
    /**
47
     * @var Request
48
     */
49
    protected $request;
50
51
    /**
52
     * The event dispatcher instance.
53
     *
54
     * @var Dispatcher
55
     */
56
    protected $events;
57
58
    /**
59
     * Indicates if the logout method has been called.
60
     *
61
     * @var bool
62
     */
63
    protected $loggedOut = false;
64
65
    /**
66
     * JwtGuard constructor
67
     *
68
     * @param UserProvider $provider
69
     * @param JwtService $jwtService
70
     * @param Request|null $request
71
     */
72
    public function __construct(
73
        UserProvider $provider,
74
        JwtService $jwtService,
75
        Request $request = null
76
    ) {
77
        $this->provider = $provider;
78
        $this->jwtService = $jwtService;
79
        $this->request = $request;
80
    }
81
82
    /**
83
     * Get the currently authenticated user.
84
     *
85
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
86
     */
87
    public function user()
88
    {
89
        // If we've already retrieved the user for the current request we can just
90
        // return it back immediately. We do not want to fetch the user data on
91
        // every call to this method because that would be tremendously slow.
92
        if ($this->user) {
93
            return $this->user;
94
        }
95
96
        if (!$token = $this->getBearerToken()) {
97
            return $this->user = null;
98
        }
99
100
        $this->isTokenPresent = true;
101
102
        try {
103
            $this->user = $this->getUserByToken($token);
104
        } catch (InaccessibleException $e) {
105
            $this->isTokenRefreshable = true;
106
            $this->user = null;
107
        } catch (Exception $e) {
108
            $this->user = null;
109
        }
110
111
        return $this->user;
112
    }
113
114
    /**
115
     * Retrieve the user by the given payload.
116
     *
117
     * @param string $token
118
     * @return AuthenticatableContract|null
119
     * @throws InaccessibleException
120
     * @throws MalformedException
121
     * @throws TokenExpiredException
122
     * @throws InvalidTokenException
123
     */
124
    protected function getUserByToken($token)
125
    {
126
        $claim = $this->jwtService->getClaimFromToken($token);
127
        $user = $this->provider->retrieveById($claim->sub);
128
129
        if (!empty($user) && get_class($user) !== $claim->aud) {
130
            throw new InvalidTokenException;
131
        }
132
133
        return $user;
134
    }
135
136
    /**
137
     * Validate a user's credentials.
138
     *
139
     * @param array $credentials
140
     * @return bool
141
     */
142
    public function validate(array $credentials = [])
143
    {
144
        return $this->attempt($credentials, false);
145
    }
146
147
    /**
148
     * Attempt to authenticate a user using the given credentials.
149
     *
150
     * @param array $credentials
151
     * @param bool $login
152
     * @return bool
153
     */
154
    public function attempt(array $credentials = [], $login = true)
155
    {
156
        $this->fireAttemptEvent($credentials, $login);
157
158
        $user = $this->provider->retrieveByCredentials($credentials);
159
160
        if (empty($user)) {
161
            return false;
162
        }
163
164
        // If an implementation of UserInterface was returned, we'll ask the provider
165
        // to validate the user against the given credentials, and if they are in
166
        // fact valid we'll log the users into the application and return true.
167
        if ($this->hasValidCredentials($user, $credentials)) {
168
            if ($login) {
169
                $this->login($user);
170
            }
171
172
            return true;
173
        }
174
175
        return false;
176
    }
177
178
    /**
179
     * Determine if the user matches the credentials.
180
     *
181
     * @param mixed $user
182
     * @param array $credentials
183
     * @return bool
184
     */
185
    protected function hasValidCredentials($user, $credentials)
186
    {
187
        return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
188
    }
189
190
    /**
191
     * Fire the attempt event with the arguments.
192
     *
193
     * @param array $credentials
194
     * @param bool $login
195
     * @return void
196
     */
197
    protected function fireAttemptEvent(array $credentials, $login)
198
    {
199
        if (isset($this->events)) {
200
            $this->events->fire(new Attempting(
201
                $credentials, false, $login
202
            ));
203
        }
204
    }
205
206
    /**
207
     * Register an authentication attempt event listener.
208
     *
209
     * @param mixed $callback
210
     * @return void
211
     */
212
    public function attempting($callback)
213
    {
214
        if (isset($this->events)) {
215
            $this->events->listen(Attempting::class, $callback);
216
        }
217
    }
218
219
    /**
220
     * Log a user into the application.
221
     *
222
     * @param \Illuminate\Contracts\Auth\Authenticatable $user
223
     * @return void
224
     */
225
    public function login(AuthenticatableContract $user)
226
    {
227
        $claim = new Claim([
228
            'sub' => $user->getAuthIdentifier(),
229
            'aud' => get_class($user),
230
            'refresh' => Config::get('jwt.refreshable')
231
        ]);
232
233
        $token = $this->jwtService->getTokenForClaim($claim);
234
235
        // If we have an event dispatcher instance set we will fire an event so that
236
        // any listeners will hook into the authentication events and run actions
237
        // based on the login and logout events fired from the guard instances.
238
        $this->fireLoginEvent($user);
239
240
        $this->setToken($token);
241
        $this->setUser($user);
242
    }
243
244
    /**
245
     * generateTokenForUser method
246
     *
247
     * @param string $token
248
     * @return string
249
     */
250
    protected function refreshTokenForUser($token)
251
    {
252
        try {
253
            $newToken = $this->jwtService->refreshToken($token);
254
        } catch (Exception $e) {
255
            $newToken = null;
256
        }
257
258
        return $newToken;
259
    }
260
261
    /**
262
     * Fire the login event if the dispatcher is set.
263
     *
264
     * @param \Illuminate\Contracts\Auth\Authenticatable $user
265
     * @param bool  $remember
266
     * @return void
267
     */
268
    protected function fireLoginEvent($user, $remember = false)
269
    {
270
        if (isset($this->events)) {
271
            $this->events->fire(new Login($user, $remember));
272
        }
273
    }
274
275
    /**
276
     * Log the given user ID into the application.
277
     *
278
     * @param mixed $id
279
     * @return \Illuminate\Contracts\Auth\Authenticatable
280
     */
281
    public function loginUsingId($id)
282
    {
283
        $user = $this->provider->retrieveById($id);
284
285
        if (empty($user)) {
286
            return null;
287
        }
288
289
        $this->login($user);
290
291
        return $user;
292
    }
293
294
    /**
295
     * Log the user out of the application.
296
     *
297
     * @return void
298
     */
299
    public function logout()
300
    {
301
        if (!$token = $this->getBearerToken()) {
302
            return;
303
        }
304
305
        try {
306
            $this->jwtService->invalidateToken($token);
307
        } catch (Exception $e) {
308
            // User has no valid token but fine with logout calling
309
        }
310
311
        $this->logoutCurrentUser();
312
    }
313
314
    /**
315
     * log this user out from every token
316
     *
317
     * @return void
318
     */
319
    public function logoutAll()
320
    {
321
        if (!$token = $this->getBearerToken()) {
322
            return;
323
        }
324
325
        try {
326
            $claim = $this->jwtService->getClaimFromToken($token);
327
328
            $this->jwtService->wipeUserTokens($claim);
329
330
        } catch (Exception $e) {
331
            // User has no valid token but fine with logout calling
332
        }
333
334
        $this->logoutCurrentUser();
335
    }
336
337
    /**
338
     * logoutCurrentUser method
339
     */
340
    protected function logoutCurrentUser()
341
    {
342
        if (isset($this->events)) {
343
            $this->events->fire(new Logout($this->user));
344
        }
345
346
        // Once we have fired the logout event we will clear the users out of memory
347
        // so they are no longer available as the user is no longer considered as
348
        // being signed into this application and should not be available here.
349
        $this->user = null;
350
        $this->token = null;
351
        $this->loggedOut = true;
352
    }
353
354
    /**
355
     * Refresh user token
356
     *
357
     * @return string|null
358
     */
359
    public function refreshToken()
360
    {
361
        if (!$token = $this->getBearerToken()) {
362
            return null;
363
        }
364
365
        $this->token = $this->refreshTokenForUser($token);
366
367
        return $this->token;
368
    }
369
370
    /**
371
     * Get the event dispatcher instance.
372
     *
373
     * @return Dispatcher
374
     */
375
    public function getDispatcher()
376
    {
377
        return $this->events;
378
    }
379
380
    /**
381
     * Set the event dispatcher instance.
382
     *
383
     * @param Dispatcher $events
384
     * @return void
385
     */
386
    public function setDispatcher(Dispatcher $events)
387
    {
388
        $this->events = $events;
389
    }
390
391
    /**
392
     * setToken method
393
     *
394
     * @param string $token
395
     */
396
    public function setToken($token)
397
    {
398
        $this->token = $token;
399
    }
400
401
    /**
402
     * getToken method
403
     *
404
     * @return null|string
405
     */
406
    public function getToken()
407
    {
408
        return $this->token;
409
    }
410
411
    /**
412
     * isTokenRefreshable method
413
     *
414
     * @return boolean
415
     */
416
    public function isTokenRefreshable()
417
    {
418
        return $this->isTokenRefreshable;
419
    }
420
421
    /**
422
     * isTokenPresent method
423
     *
424
     * @return boolean
425
     */
426
    public function isTokenPresent()
427
    {
428
        return $this->isTokenPresent;
429
    }
430
431
    /**
432
     * getBearerToken method
433
     *
434
     * @return string|null
435
     */
436
    protected function getBearerToken()
437
    {
438
        $header = $this->request->header('Authorization', '');
439
440
        if (starts_with(strtolower($header), 'bearer ')) {
441
            return mb_substr($header, 7, null, 'UTF-8');
442
        }
443
444
        return null;
445
    }
446
447
}