Completed
Push — master ( 671383...5c8721 )
by Antonio Carlos
04:32 queued 02:15
created

Authenticator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace PragmaRX\Google2FALaravel\Support;
4
5
use Carbon\Carbon;
6
use Google2FA;
7
use Illuminate\Http\Request;
8
use PragmaRX\Google2FALaravel\Exceptions\InvalidOneTimePassword;
9
use PragmaRX\Google2FALaravel\Exceptions\InvalidSecretKey;
10
11
class Authenticator
12
{
13
    use Config, ErrorBag, Response, Session;
14
15
    /**
16
     * The auth instance.
17
     *
18
     * @var
19
     */
20
    protected $auth;
21
22
    /**
23
     * The request instance.
24
     *
25
     * @var
26
     */
27
    protected $request;
28
29
    /**
30
     * The current password.
31
     *
32
     * @var
33
     */
34
    protected $password;
35
36
    /**
37
     * Authenticator constructor.
38
     *
39
     * @param Request $request
40
     */
41
    public function __construct(Request $request)
42
    {
43
        $this->setRequest($request);
44
    }
45
46
    /**
47
     * Authenticator boot.
48
     *
49
     * @param $request
50
     *
51
     * @return Authenticator
52
     */
53
    public function boot($request)
54
    {
55
        return $this->setRequest($request);
56
    }
57
58
    /**
59
     * Check if it is already logged in or passable without checking for an OTP.
60
     *
61
     * @return bool
62
     */
63
    protected function canPassWithoutCheckingOTP()
64
    {
65
        return
66
            !$this->isEnabled() ||
67
            $this->noUserIsAuthenticated() ||
68
            $this->twoFactorAuthStillValid();
69
    }
70
71
    /**
72
     * Get the user Google2FA secret.
73
     *
74
     * @throws InvalidSecretKey
75
     *
76
     * @return mixed
77
     */
78
    protected function getGoogle2FASecretKey()
79
    {
80
        $secret = $this->getUser()->{$this->config('otp_secret_column')};
81
82
        if (is_null($secret) || empty($secret)) {
83
            throw new InvalidSecretKey('Secret key cannot be empty.');
84
        }
85
86
        return $secret;
87
    }
88
89
    /**
90
     * Get the previous OTP.
91
     *
92
     * @return null|void
93
     */
94
    protected function getOldOneTimePassword()
95
    {
96
        $oldPassword = $this->config('forbid_old_passwords') === true
97
            ? $this->sessionGet(Constants::SESSION_OTP_TIMESTAMP)
98
            : null;
99
100
        return $oldPassword;
101
    }
102
103
    /**
104
     * Get the OTP from user input.
105
     *
106
     * @throws InvalidOneTimePassword
107
     *
108
     * @return mixed
109
     */
110
    protected function getOneTimePassword()
111
    {
112
        if (!is_null($this->password)) {
113
            return $this->password;
114
        }
115
116
        $this->password = $this->request->input($this->config('otp_input'));
117
118
        if (is_null($this->password) || empty($this->password)) {
119
            throw new InvalidOneTimePassword('One Time Password cannot be empty.');
120
        }
121
122
        return $this->password;
123
    }
124
125
    /**
126
     * Get the request instance.
127
     *
128
     * @return mixed
129
     */
130
    public function getRequest()
131
    {
132
        return $this->request;
133
    }
134
135
    /**
136
     * Get the OTP view.
137
     *
138
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
139
     */
140
    private function getView()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
141
    {
142
        return view($this->config('view'));
143
    }
144
145
    /**
146
     * Keep this OTP session alive.
147
     */
148
    protected function keepAlive()
149
    {
150
        if ($this->config('keep_alive')) {
151
            $this->updateCurrentAuthTime();
152
        }
153
    }
154
155
    /**
156
     * Check if the request input has the OTP.
157
     *
158
     * @return mixed
159
     */
160
    protected function inputHasOneTimePassword()
161
    {
162
        return $this->request->has($this->config('otp_input'));
163
    }
164
165
    /**
166
     * Get minutes since last activity.
167
     *
168
     * @return int
169
     */
170
    protected function minutesSinceLastActivity()
171
    {
172
        return Carbon::now()->diffInMinutes(
173
            $this->sessionGet(Constants::SESSION_AUTH_TIME)
174
        );
175
    }
176
177
    /**
178
     * Check if no user is authenticated using OTP.
179
     *
180
     * @return bool
181
     */
182
    protected function noUserIsAuthenticated()
183
    {
184
        return is_null($this->getUser());
185
    }
186
187
    /**
188
     * Check if OTP has expired.
189
     *
190
     * @return bool
191
     */
192
    protected function passwordExpired()
193
    {
194
        if (($minutes = $this->config('lifetime')) == 0 && $this->minutesSinceLastActivity() > $minutes) {
195
            $this->logout();
196
197
            return true;
198
        }
199
200
        $this->keepAlive();
201
202
        return false;
203
    }
204
205
    /**
206
     * Put a var value to the current session.
207
     *
208
     * @param $var
209
     * @param $value
210
     *
211
     * @return mixed
212
     */
213
    protected function sessionPut($var, $value)
214
    {
215
        $this->request->session()->put(
216
            $this->makeSessionVarName($var),
217
            $value
218
        );
219
220
        return $value;
221
    }
222
223
    /**
224
     * Forget a session var.
225
     *
226
     * @param null $var
227
     */
228
    protected function sessionForget($var = null)
229
    {
230
        $this->request->session()->forget(
231
            $this->makeSessionVarName($var)
232
        );
233
    }
234
235
    /**
236
     * Set the request property.
237
     *
238
     * @param mixed $request
239
     *
240
     * @return $this
241
     */
242
    public function setRequest($request)
243
    {
244
        $this->request = $request;
245
246
        return $this;
247
    }
248
249
    /**
250
     * Set current auth as valid.
251
     */
252
    protected function storeAuthPassed()
253
    {
254
        $this->sessionPut(Constants::SESSION_AUTH_PASSED, true);
255
256
        $this->updateCurrentAuthTime();
257
    }
258
259
    /**
260
     * Store the old OTP.
261
     *
262
     * @param $key
263
     *
264
     * @return mixed
265
     */
266
    protected function storeOldOneTimePassord($key)
267
    {
268
        return $this->sessionPut(Constants::SESSION_OTP_TIMESTAMP, $key);
269
    }
270
271
    /**
272
     * Verifies, in the current session, if a 2fa check has already passed.
273
     *
274
     * @return bool
275
     */
276
    protected function twoFactorAuthStillValid()
277
    {
278
        return
279
            (bool) $this->sessionGet(Constants::SESSION_AUTH_PASSED, false) &&
0 ignored issues
show
Unused Code introduced by
The call to Authenticator::sessionGet() has too many arguments starting with false.

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...
280
            !$this->passwordExpired();
281
    }
282
283
    /**
284
     * Get the current user.
285
     *
286
     * @return mixed
287
     */
288
    protected function getUser()
289
    {
290
        return $this->getAuth()->user();
291
    }
292
293
    /**
294
     * Check if the current use is authenticated via OTP.
295
     *
296
     * @return bool
297
     */
298
    public function isAuthenticated()
299
    {
300
        return
301
            $this->canPassWithoutCheckingOTP()
302
                ? true
303
                : $this->checkOTP();
304
    }
305
306
    /**
307
     * Check if the module is enabled.
308
     *
309
     * @return mixed
310
     */
311
    protected function isEnabled()
312
    {
313
        return $this->config('enabled');
314
    }
315
316
    /**
317
     * Check if the input OTP is valid.
318
     *
319
     * @return bool
320
     */
321
    protected function checkOTP()
322
    {
323
        if (!$this->inputHasOneTimePassword()) {
324
            return false;
325
        }
326
327
        if ($isValid = $this->verifyGoogle2FA()) {
328
            $this->storeAuthPassed();
329
        }
330
331
        return $isValid;
332
    }
333
334
    /**
335
     * OTP logout.
336
     */
337
    public function logout()
338
    {
339
        $this->sessionForget();
340
    }
341
342
    /**
343
     * Update the current auth time.
344
     */
345
    protected function updateCurrentAuthTime()
346
    {
347
        $this->sessionPut(Constants::SESSION_AUTH_TIME, Carbon::now());
348
    }
349
350
    /**
351
     * Verify the OTP.
352
     *
353
     * @return mixed
354
     */
355
    protected function verifyGoogle2FA()
356
    {
357
        return $this->storeOldOneTimePassord(
358
            Google2Fa::verifyKey(
359
                $this->getGoogle2FASecretKey(),
360
                $this->getOneTimePassword(),
361
                $this->config('window'),
362
                null, // $timestamp
363
                $this->getOldOneTimePassword()
364
            )
365
        );
366
    }
367
}
368