Issues (8)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/JwtGuard.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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->getRequestToken()) {
97
            return $this->user = null;
98
        }
99
100
        $this->isTokenPresent = true;
101
102
        try {
103
            $this->user = $this->getUserByToken($token);
0 ignored issues
show
It seems like $token defined by $this->getRequestToken() on line 96 can also be of type array; however, WWON\JwtGuard\JwtGuard::getUserByToken() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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
0 ignored issues
show
The call to Attempting::__construct() has too many arguments starting with $login.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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->getRequestToken()) {
302
            return;
303
        }
304
305
        try {
306
            $this->jwtService->invalidateToken($token);
0 ignored issues
show
It seems like $token defined by $this->getRequestToken() on line 301 can also be of type array; however, WWON\JwtGuard\JwtService::invalidateToken() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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->getRequestToken()) {
322
            return;
323
        }
324
325
        try {
326
            $claim = $this->jwtService->getClaimFromToken($token);
0 ignored issues
show
It seems like $token defined by $this->getRequestToken() on line 321 can also be of type array; however, WWON\JwtGuard\JwtService::getClaimFromToken() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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->getRequestToken()) {
362
            return null;
363
        }
364
365
        $this->token = $this->refreshTokenForUser($token);
0 ignored issues
show
It seems like $token defined by $this->getRequestToken() on line 361 can also be of type array; however, WWON\JwtGuard\JwtGuard::refreshTokenForUser() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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
    /**
433
     * getRequestToken method
434
     *
435
     * @return string|null
436
     */
437
    protected function getRequestToken()
438
    {
439
        $token = $this->getBearerToken();
440
        if (!$token) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $token of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
441
            return $this->getCookieToken();
442
        }
443
444
        return $token;
445
    }
446
447
    /**
448
     * getBearerToken method
449
     *
450
     * @return string|null
451
     */
452
    protected function getBearerToken()
453
    {
454
        $header = $this->request->header('Authorization', '');
455
456
        if (starts_with(strtolower($header), 'bearer ')) {
457
            return mb_substr($header, 7, null, 'UTF-8');
458
        }
459
460
        return null;
461
    }
462
463
    /**
464
     * getCookieToken method
465
     *
466
     * @return string|null
467
     */
468
    protected function getCookieToken()
469
    {
470
        return $this->request->cookie('token');
471
    }
472
473
}