JWTGuard::cycleRememberToken()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace AtlassianConnectCore;
4
5
use Illuminate\Auth\GuardHelpers;
6
use Illuminate\Contracts\Auth\Guard;
7
use Illuminate\Contracts\Auth\UserProvider;
8
use Illuminate\Contracts\Session\Session;
9
use Illuminate\Contracts\Cookie\QueueingFactory as CookieJar;
10
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
11
12
/**
13
 * Class JWTGuard
14
 *
15
 * @package AtlassianConnectCore
16
 */
17
class JWTGuard implements Guard
18
{
19
    use GuardHelpers;
20
21
    /**
22
     * The request instance.
23
     *
24
     * @var \Illuminate\Http\Request
25
     */
26
    protected $request;
27
28
    /**
29
     * The name of the token "column" in persistent storage.
30
     *
31
     * @var string
32
     */
33
    protected $storageKey = 'client_key';
34
35
    /**
36
     * The session used by the guard.
37
     *
38
     * @var \Illuminate\Contracts\Session\Session
39
     */
40
    protected $session;
41
42
    /**
43
     * The Illuminate cookie creator service.
44
     *
45
     * @var \Illuminate\Contracts\Cookie\QueueingFactory
46
     */
47
    protected $cookie;
48
49
    /**
50
     * Indicates if a token user retrieval has been attempted.
51
     *
52
     * @var bool
53
     */
54
    protected $recallAttempted = false;
55
56
    /**
57
     * Create a new authentication guard.
58
     *
59
     * @param UserProvider $provider
60
     * @param Session $session
61
     * @param CookieJar $cookie
62
     */
63
    public function __construct(UserProvider $provider, Session $session, CookieJar $cookie)
64
    {
65
        $this->provider = $provider;
66
        $this->session = $session;
67
        $this->cookie = $cookie;
68
    }
69
70
    /**
71
     * Get the currently authenticated user.
72
     *
73
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
74
     */
75
    public function user()
76
    {
77
        // If we've already retrieved the user for the current request we can just
78
        // return it back immediately. We do not want to fetch the user data on
79
        // every call to this method because that would be tremendously slow.
80
        if (! is_null($this->user)) {
81
            return $this->user;
82
        }
83
84
        $user = null;
85
86
        if ($token = request('jwt', request()->header('Authorization'))) {
87
            $token = last(explode(' ', $token));
88
89
            $user = $this->provider->retrieveByCredentials(
90
                [$this->storageKey => $this->getTokenKey($token)]
91
            );
92
        }
93
        else {
94
95
            $id = $this->session->get($this->getName());
96
97
            // First we will try to load the user using the identifier in the session if
98
            // one exists. Otherwise we will check for a "remember me" cookie in this
99
            // request, and if one exists, attempt to retrieve the user using that.
100
            $user = null;
101
102
            if (! is_null($id)) {
103
                $user = $this->provider->retrieveById($id);
104
            }
105
106
            // If the user is null, but we decrypt a "recaller" cookie we can attempt to
107
            // pull the user data on that cookie which serves as a remember cookie on
108
            // the application. Once we have a user we can return it to the caller.
109
            $recaller = $this->recaller();
110
111
            if (is_null($user) && ! is_null($recaller)) {
112
                $user = $this->userFromRecaller($recaller);
113
114
                if ($user) {
115
                    $this->updateSession($user->getAuthIdentifier());
116
                }
117
            }
118
        }
119
120
        return $this->user = $user;
121
    }
122
123
    /**
124
     * Validate a user's credentials.
125
     *
126
     * @param  array  $credentials
127
     * @return bool
128
     */
129
    public function validate(array $credentials = [])
130
    {
131
        if (empty($credentials['token'])) {
132
            return false;
133
        }
134
135
        $key = $this->getTokenKey($credentials['token']);
136
        $credentials = [$this->storageKey => $key];
137
138
        if ($this->provider->retrieveByCredentials($credentials)) {
139
            return true;
140
        }
141
142
        return false;
143
    }
144
145
    /**
146
     * Get JWT token key
147
     *
148
     * @param string $token
149
     *
150
     * @return mixed
151
     */
152
    protected function getTokenKey($token)
153
    {
154
        $decoded = \AtlassianConnectCore\Helpers\JWTHelper::decode($token);
155
156
        return array_get($decoded, 'body.iss');
157
    }
158
159
    /**
160
     * Attempt user authentication
161
     *
162
     * @return bool
163
     */
164
    public function attempt()
165
    {
166
        if(!$this->check()) {
167
            return false;
168
        }
169
170
        $user = $this->user();
171
172
        // Update user session items
173
        $this->updateSession($user->getAuthIdentifier());
174
175
        $this->ensureRememberTokenIsSet($user);
176
177
        $this->queueRecallerCookie($user);
178
179
        return true;
180
    }
181
182
    /**
183
     * Update the session with the given ID.
184
     *
185
     * @param  string  $id
186
     * @return void
187
     */
188
    protected function updateSession($id)
189
    {
190
        $this->session->put($this->getName(), $id);
191
192
        $this->session->migrate(true);
193
    }
194
195
    /**
196
     * Get a unique identifier for the auth session value.
197
     *
198
     * @return string
199
     */
200
    public function getName()
201
    {
202
        return 'jwt_session_' . sha1(static::class);
203
    }
204
205
    /**
206
     * Refresh the "remember me" token for the user.
207
     *
208
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
209
     * @return void
210
     */
211
    protected function cycleRememberToken(AuthenticatableContract $user)
212
    {
213
        $user->setRememberToken($token = str_random(60));
214
215
        $this->provider->updateRememberToken($user, $token);
216
    }
217
218
    /**
219
     * Create a new "remember me" token for the user if one doesn't already exist.
220
     *
221
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
222
     * @return void
223
     */
224
    protected function ensureRememberTokenIsSet(AuthenticatableContract $user)
225
    {
226
        if (empty($user->getRememberToken())) {
227
            $this->cycleRememberToken($user);
228
        }
229
    }
230
231
    /**
232
     * Queue the recaller cookie into the cookie jar.
233
     *
234
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
235
     * @return void
236
     */
237
    protected function queueRecallerCookie(AuthenticatableContract $user)
238
    {
239
        $this->getCookieJar()->queue($this->createRecaller(
0 ignored issues
show
Bug introduced by
$this->createRecaller($u...er->getRememberToken()) of type Symfony\Component\HttpFoundation\Cookie is incompatible with the type array expected by parameter $parameters of Illuminate\Cookie\CookieJar::queue(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

239
        $this->getCookieJar()->queue(/** @scrutinizer ignore-type */ $this->createRecaller(
Loading history...
240
            $user->getAuthIdentifier().'|'.$user->getRememberToken()
241
        ));
242
    }
243
244
    /**
245
     * Create a "remember me" cookie for a given ID.
246
     *
247
     * @param  string  $value
248
     * @return \Symfony\Component\HttpFoundation\Cookie
249
     */
250
    protected function createRecaller($value)
251
    {
252
        return $this->getCookieJar()->forever($this->getRecallerName(), $value);
253
    }
254
255
    /**
256
     * Get the cookie creator instance used by the guard.
257
     *
258
     * @return \Illuminate\Contracts\Cookie\QueueingFactory
259
     *
260
     * @throws \RuntimeException
261
     */
262
    public function getCookieJar()
263
    {
264
        if (! isset($this->cookie)) {
265
            throw new \RuntimeException('Cookie jar has not been set.');
266
        }
267
268
        return $this->cookie;
269
    }
270
271
    /**
272
     * Set the cookie creator instance used by the guard.
273
     *
274
     * @param  \Illuminate\Contracts\Cookie\QueueingFactory  $cookie
275
     * @return void
276
     */
277
    public function setCookieJar(CookieJar $cookie)
278
    {
279
        $this->cookie = $cookie;
280
    }
281
282
    /**
283
     * Get the name of the cookie used to store the "recaller".
284
     *
285
     * @return string
286
     */
287
    public function getRecallerName()
288
    {
289
        return 'remember_session_' . sha1(static::class);
290
    }
291
292
    /**
293
     * Get the decrypted recaller cookie for the request.
294
     *
295
     * @return \Illuminate\Auth\Recaller|null
296
     */
297
    protected function recaller()
298
    {
299
        if (is_null($this->request)) {
300
            return null;
301
        }
302
303
        if ($recaller = $this->request->cookies->get($this->getRecallerName())) {
304
            return new \Illuminate\Auth\Recaller($recaller);
305
        }
306
    }
307
308
    /**
309
     * Pull a user from the repository by its "remember me" cookie token.
310
     *
311
     * @param  \Illuminate\Auth\Recaller  $recaller
312
     * @return mixed
313
     */
314
    protected function userFromRecaller($recaller)
315
    {
316
        if (! $recaller->valid() || $this->recallAttempted) {
317
            return;
318
        }
319
320
        // If the user is null, but we decrypt a "recaller" cookie we can attempt to
321
        // pull the user data on that cookie which serves as a remember cookie on
322
        // the application. Once we have a user we can return it to the caller.
323
        $this->recallAttempted = true;
324
325
        $this->viaRemember = !is_null($user = $this->provider->retrieveByToken(
0 ignored issues
show
Bug Best Practice introduced by
The property viaRemember does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
326
            $recaller->id(), $recaller->token()
327
        ));
328
329
        return $user;
330
    }
331
}