Completed
Push — develop ( 0e4d6e...a935b5 )
by Wisoot
02:16
created

JwtGuard::attempt()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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