Completed
Push — develop ( 574df4...10ba76 )
by Wisoot
06:15
created

JwtGuard   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 330
Duplicated Lines 13.64 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 10
Bugs 1 Features 3
Metric Value
c 10
b 1
f 3
dl 45
loc 330
wmc 34
lcom 1
cbo 7
rs 9.2

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A user() 0 17 3
A getUserByToken() 0 6 1
A validate() 0 4 1
A attempt() 0 19 3
A hasValidCredentials() 0 4 2
A fireAttemptEvent() 0 8 2
A attempting() 0 6 2
A login() 0 12 1
A refreshTokenForUser() 0 6 1
A fireLoginEvent() 0 6 2
A loginUsingId() 0 6 1
A logout() 21 21 4
B logoutAll() 24 24 4
A refreshToken() 0 10 2
A setToken() 0 4 1
A getToken() 0 4 1
A getBearerToken() 0 8 2

How to fix   Duplicated Code   

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:

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\Http\Request;
13
use Illuminate\Support\Facades\Config;
14
use WWON\JwtGuard\Exceptions\Exception;
15
use WWON\JwtGuard\Exceptions\InaccessibleException;
16
use WWON\JwtGuard\Exceptions\InvalidTokenException;
17
use WWON\JwtGuard\Exceptions\MalformedException;
18
use WWON\JwtGuard\Exceptions\TokenExpiredException;
19
20
class JwtGuard implements Guard
21
{
22
23
    use GuardHelpers;
24
25
    /**
26
     * @var string
27
     */
28
    protected $token;
29
30
    /**
31
     * @var JwtService
32
     */
33
    protected $jwtService;
34
35
    /**
36
     * @var Request
37
     */
38
    protected $request;
39
40
    /**
41
     * Indicates if the logout method has been called.
42
     *
43
     * @var bool
44
     */
45
    protected $loggedOut = false;
46
47
    /**
48
     * JwtGuard constructor
49
     *
50
     * @param UserProvider $provider
51
     * @param JwtService $jwtService
52
     * @param Request|null $request
53
     */
54
    public function __construct(
55
        UserProvider $provider,
56
        JwtService $jwtService,
57
        Request $request = null
58
    ) {
59
        $this->provider = $provider;
60
        $this->jwtService = $jwtService;
61
        $this->request = $request;
62
    }
63
64
    /**
65
     * Get the currently authenticated user.
66
     *
67
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
68
     */
69
    public function user()
70
    {
71
        // If we've already retrieved the user for the current request we can just
72
        // return it back immediately. We do not want to fetch the user data on
73
        // every call to this method because that would be tremendously slow.
74
        if ($this->user) {
75
            return $this->user;
76
        }
77
78
        if (!$token = $this->getBearerToken()) {
79
            return $this->user = null;
80
        }
81
82
        $this->user = $this->getUserByToken($token);
83
84
        return $this->user;
85
    }
86
87
    /**
88
     * Retrieve the user by the given payload.
89
     *
90
     * @param string $token
91
     * @return AuthenticatableContract|null
92
     * @throws InaccessibleException
93
     * @throws MalformedException
94
     * @throws TokenExpiredException
95
     * @throws InvalidTokenException
96
     */
97
    protected function getUserByToken($token)
98
    {
99
        $userId = $this->jwtService->getUserIdFromToken($token);
100
101
        return $this->provider->retrieveById($userId);
102
    }
103
104
    /**
105
     * Validate a user's credentials.
106
     *
107
     * @param array $credentials
108
     * @return bool
109
     */
110
    public function validate(array $credentials = [])
111
    {
112
        return $this->attempt($credentials, false);
113
    }
114
115
    /**
116
     * Attempt to authenticate a user using the given credentials.
117
     *
118
     * @param array $credentials
119
     * @param bool $login
120
     * @return bool
121
     */
122
    public function attempt(array $credentials = [], $login = true)
123
    {
124
        $this->fireAttemptEvent($credentials, $login);
125
126
        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
0 ignored issues
show
Bug introduced by
The property lastAttempted does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
127
128
        // If an implementation of UserInterface was returned, we'll ask the provider
129
        // to validate the user against the given credentials, and if they are in
130
        // fact valid we'll log the users into the application and return true.
131
        if ($this->hasValidCredentials($user, $credentials)) {
132
            if ($login) {
133
                $this->login($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->provider->retriev...edentials($credentials) on line 126 can be null; however, WWON\JwtGuard\JwtGuard::login() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
134
            }
135
136
            return true;
137
        }
138
139
        return false;
140
    }
141
142
    /**
143
     * Determine if the user matches the credentials.
144
     *
145
     * @param mixed $user
146
     * @param array $credentials
147
     * @return bool
148
     */
149
    protected function hasValidCredentials($user, $credentials)
150
    {
151
        return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
152
    }
153
154
    /**
155
     * Fire the attempt event with the arguments.
156
     *
157
     * @param array $credentials
158
     * @param bool $login
159
     * @return void
160
     */
161
    protected function fireAttemptEvent(array $credentials, $login)
162
    {
163
        if (isset($this->events)) {
164
            $this->events->fire(new Attempting(
0 ignored issues
show
Bug introduced by
The property events does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
165
                $credentials, false, $login
166
            ));
167
        }
168
    }
169
170
    /**
171
     * Register an authentication attempt event listener.
172
     *
173
     * @param mixed $callback
174
     * @return void
175
     */
176
    public function attempting($callback)
177
    {
178
        if (isset($this->events)) {
179
            $this->events->listen(Attempting::class, $callback);
180
        }
181
    }
182
183
    /**
184
     * Log a user into the application.
185
     *
186
     * @param \Illuminate\Contracts\Auth\Authenticatable $user
187
     * @return void
188
     */
189
    public function login(AuthenticatableContract $user)
190
    {
191
        $token = $this->jwtService->getTokenForUser($user, Config::get('jwt.refreshable'));
192
193
        // If we have an event dispatcher instance set we will fire an event so that
194
        // any listeners will hook into the authentication events and run actions
195
        // based on the login and logout events fired from the guard instances.
196
        $this->fireLoginEvent($user);
197
198
        $this->setToken($token);
199
        $this->setUser($user);
200
    }
201
202
    /**
203
     * generateTokenForUser method
204
     *
205
     * @param string $token
206
     * @return string
207
     */
208
    protected function refreshTokenForUser($token)
209
    {
210
        $newToken = $this->jwtService->refreshToken($token);
211
212
        return $newToken;
213
    }
214
215
    /**
216
     * Fire the login event if the dispatcher is set.
217
     *
218
     * @param \Illuminate\Contracts\Auth\Authenticatable $user
219
     * @param bool  $remember
220
     * @return void
221
     */
222
    protected function fireLoginEvent($user, $remember = false)
223
    {
224
        if (isset($this->events)) {
225
            $this->events->fire(new Login($user, $remember));
226
        }
227
    }
228
229
    /**
230
     * Log the given user ID into the application.
231
     *
232
     * @param mixed $id
233
     * @return \Illuminate\Contracts\Auth\Authenticatable
234
     */
235
    public function loginUsingId($id)
236
    {
237
        $this->login($user = $this->provider->retrieveById($id));
0 ignored issues
show
Bug introduced by
It seems like $user = $this->provider->retrieveById($id) can be null; however, login() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
238
239
        return $user;
240
    }
241
242
    /**
243
     * Log the user out of the application.
244
     *
245
     * @return void
246
     */
247 View Code Duplication
    public function logout()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
248
    {
249
        if (!$token = $this->getBearerToken()) {
250
            return;
251
        }
252
253
        try {
254
            $this->jwtService->invalidateToken($token);
255
        } catch (Exception $e) { }
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
256
257
        if (isset($this->events)) {
258
            $this->events->fire(new Logout($this->user));
259
        }
260
261
        // Once we have fired the logout event we will clear the users out of memory
262
        // so they are no longer available as the user is no longer considered as
263
        // being signed into this application and should not be available here.
264
        $this->user = null;
265
        $this->token = null;
266
        $this->loggedOut = true;
267
    }
268
269
    /**
270
     * log this user out from every token
271
     *
272
     * @return void
273
     */
274 View Code Duplication
    public function logoutAll()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
275
    {
276
        if (!$token = $this->getBearerToken()) {
277
            return;
278
        }
279
280
        try {
281
            $user = $this->getUserByToken($token);
282
283
            $this->jwtService->wipeUserTokens($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->getUserByToken($token) on line 281 can be null; however, WWON\JwtGuard\JwtService::wipeUserTokens() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
284
285
        } catch (Exception $e) { }
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
286
287
        if (isset($this->events)) {
288
            $this->events->fire(new Logout($this->user));
289
        }
290
291
        // Once we have fired the logout event we will clear the users out of memory
292
        // so they are no longer available as the user is no longer considered as
293
        // being signed into this application and should not be available here.
294
        $this->user = null;
295
        $this->token = null;
296
        $this->loggedOut = true;
297
    }
298
299
    /**
300
     * Refresh user token
301
     *
302
     * @return string|null
303
     */
304
    public function refreshToken()
305
    {
306
        if (!$token = $this->getBearerToken()) {
307
            return null;
308
        }
309
310
        $this->token = $this->refreshTokenForUser($token);
311
312
        return $this->token;
313
    }
314
315
    /**
316
     * setToken method
317
     *
318
     * @param string $token
319
     */
320
    public function setToken($token)
321
    {
322
        $this->token = $token;
323
    }
324
325
    /**
326
     * getToken method
327
     *
328
     * @return null|string
329
     */
330
    public function getToken()
331
    {
332
        return $this->token;
333
    }
334
335
    /**
336
     * getBearerToken method
337
     *
338
     * @return string|null
339
     */
340
    protected function getBearerToken()
341
    {
342
        $header = $this->request->header('Authorization', '');
343
344
        if (starts_with(strtolower($header), 'bearer ')) {
345
            return mb_substr($header, 7, null, 'UTF-8');
346
        }
347
    }
348
349
}