Test Failed
Push — newinternal ( 8c4587...b2f220 )
by Michael
15:41 queued 06:17
created

PageMultiFactor::enableU2F()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 108
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 56
c 1
b 0
f 0
dl 0
loc 108
rs 7.7155
cc 8
nc 8
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 *                                                                            *
5
 * All code in this file is released into the public domain by the ACC        *
6
 * Development Team. Please see team.json for a list of contributors.         *
7
 ******************************************************************************/
8
9
namespace Waca\Pages\UserAuth\MultiFactor;
10
11
use BaconQrCode\Renderer\Image\Svg;
12
use BaconQrCode\Writer;
13
use Waca\DataObjects\User;
14
use Waca\Exceptions\ApplicationLogicException;
15
use Waca\PdoDatabase;
16
use Waca\Security\CredentialProviders\ICredentialProvider;
17
use Waca\Security\CredentialProviders\PasswordCredentialProvider;
18
use Waca\Security\CredentialProviders\ScratchTokenCredentialProvider;
19
use Waca\Security\CredentialProviders\TotpCredentialProvider;
20
use Waca\Security\CredentialProviders\U2FCredentialProvider;
21
use Waca\Security\CredentialProviders\YubikeyOtpCredentialProvider;
22
use Waca\SessionAlert;
23
use Waca\Tasks\InternalPageBase;
24
use Waca\WebRequest;
25
26
class PageMultiFactor extends InternalPageBase
27
{
28
    /**
29
     * Main function for this page, when no specific actions are called.
30
     * @return void
31
     */
32
    protected function main()
33
    {
34
        $database = $this->getDatabase();
35
        $currentUser = User::getCurrent($database);
36
37
        $yubikeyOtpCredentialProvider = new YubikeyOtpCredentialProvider($database, $this->getSiteConfiguration(),
38
            $this->getHttpHelper());
39
        $this->assign('yubikeyOtpIdentity', $yubikeyOtpCredentialProvider->getYubikeyData($currentUser->getId()));
40
        $this->assign('yubikeyOtpEnrolled', $yubikeyOtpCredentialProvider->userIsEnrolled($currentUser->getId()));
41
42
        $totpCredentialProvider = new TotpCredentialProvider($database, $this->getSiteConfiguration());
43
        $this->assign('totpEnrolled', $totpCredentialProvider->userIsEnrolled($currentUser->getId()));
44
45
        $u2fCredentialProvider = new U2FCredentialProvider($database, $this->getSiteConfiguration());
46
        $this->assign('u2fEnrolled', $u2fCredentialProvider->userIsEnrolled($currentUser->getId()));
47
48
        $scratchCredentialProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration());
49
        $this->assign('scratchEnrolled', $scratchCredentialProvider->userIsEnrolled($currentUser->getId()));
50
        $this->assign('scratchRemaining', $scratchCredentialProvider->getRemaining($currentUser->getId()));
51
52
        $this->assign('allowedTotp', $this->barrierTest('enableTotp', $currentUser));
53
        $this->assign('allowedYubikey', $this->barrierTest('enableYubikeyOtp', $currentUser));
54
        $this->assign('allowedU2f', $this->barrierTest('enableU2F', $currentUser));
55
56
        $this->setTemplate('mfa/mfa.tpl');
57
    }
58
59
    protected function enableYubikeyOtp()
60
    {
61
        $database = $this->getDatabase();
62
        $currentUser = User::getCurrent($database);
63
64
        $otpCredentialProvider = new YubikeyOtpCredentialProvider($database,
65
            $this->getSiteConfiguration(), $this->getHttpHelper());
66
67
        if (WebRequest::wasPosted()) {
68
            $this->validateCSRFToken();
69
70
            $passwordCredentialProvider = new PasswordCredentialProvider($database,
71
                $this->getSiteConfiguration());
72
73
            $password = WebRequest::postString('password');
74
            $otp = WebRequest::postString('otp');
75
76
            $result = $passwordCredentialProvider->authenticate($currentUser, $password);
77
78
            if ($result) {
79
                try {
80
                    $otpCredentialProvider->setCredential($currentUser, 2, $otp);
81
                    SessionAlert::success('Enabled YubiKey OTP.');
82
83
                    $scratchProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration());
84
                    if($scratchProvider->getRemaining($currentUser->getId()) < 3) {
85
                        $scratchProvider->setCredential($currentUser, 2, null);
86
                        $tokens = $scratchProvider->getTokens();
87
                        $this->assign('tokens', $tokens);
88
                        $this->setTemplate('mfa/regenScratchTokens.tpl');
89
                        return;
90
                    }
91
                }
92
                catch (ApplicationLogicException $ex) {
93
                    SessionAlert::error('Error enabling YubiKey OTP: ' . $ex->getMessage());
94
                }
95
96
                $this->redirect('multiFactor');
97
            }
98
            else {
99
                SessionAlert::error('Error enabling YubiKey OTP - invalid credentials.');
100
                $this->redirect('multiFactor');
101
            }
102
        }
103
        else {
104
            if ($otpCredentialProvider->userIsEnrolled($currentUser->getId())) {
105
                // user is not enrolled, we shouldn't have got here.
106
                throw new ApplicationLogicException('User is already enrolled in the selected MFA mechanism');
107
            }
108
109
            $this->assignCSRFToken();
110
            $this->setTemplate('mfa/enableYubikey.tpl');
111
        }
112
    }
113
114
    protected function disableYubikeyOtp()
115
    {
116
        $database = $this->getDatabase();
117
        $currentUser = User::getCurrent($database);
118
119
        $otpCredentialProvider = new YubikeyOtpCredentialProvider($database,
120
            $this->getSiteConfiguration(), $this->getHttpHelper());
121
122
        $factorType = 'YubiKey OTP';
123
124
        $this->deleteCredential($database, $currentUser, $otpCredentialProvider, $factorType);
125
    }
126
127
    protected function enableTotp()
128
    {
129
        $database = $this->getDatabase();
130
        $currentUser = User::getCurrent($database);
131
132
        $otpCredentialProvider = new TotpCredentialProvider($database, $this->getSiteConfiguration());
133
134
        if (WebRequest::wasPosted()) {
135
            $this->validateCSRFToken();
136
137
            // used for routing only, not security
138
            $stage = WebRequest::postString('stage');
139
140
            if ($stage === "auth") {
141
                $password = WebRequest::postString('password');
142
143
                $passwordCredentialProvider = new PasswordCredentialProvider($database,
144
                    $this->getSiteConfiguration());
145
                $result = $passwordCredentialProvider->authenticate($currentUser, $password);
146
147
                if ($result) {
148
                    $otpCredentialProvider->setCredential($currentUser, 2, null);
149
150
                    $provisioningUrl = $otpCredentialProvider->getProvisioningUrl($currentUser);
151
152
                    $renderer = new Svg();
153
                    $renderer->setHeight(256);
154
                    $renderer->setWidth(256);
155
                    $writer = new Writer($renderer);
156
                    $svg = $writer->writeString($provisioningUrl);
157
158
                    $this->assign('svg', $svg);
159
                    $this->assign('secret', $otpCredentialProvider->getSecret($currentUser));
160
161
                    $this->assignCSRFToken();
162
                    $this->setTemplate('mfa/enableTotpEnroll.tpl');
163
164
                    return;
165
                }
166
                else {
167
                    SessionAlert::error('Error enabling TOTP - invalid credentials.');
168
                    $this->redirect('multiFactor');
169
170
                    return;
171
                }
172
            }
173
174
            if ($stage === "enroll") {
175
                // we *must* have a defined credential already here,
176
                if ($otpCredentialProvider->isPartiallyEnrolled($currentUser)) {
177
                    $otp = WebRequest::postString('otp');
178
                    $result = $otpCredentialProvider->verifyEnable($currentUser, $otp);
179
180
                    if ($result) {
181
                        SessionAlert::success('Enabled TOTP.');
182
183
                        $scratchProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration());
184
                        if($scratchProvider->getRemaining($currentUser->getId()) < 3) {
185
                            $scratchProvider->setCredential($currentUser, 2, null);
186
                            $tokens = $scratchProvider->getTokens();
187
                            $this->assign('tokens', $tokens);
188
                            $this->setTemplate('mfa/regenScratchTokens.tpl');
189
                            return;
190
                        }
191
                    }
192
                    else {
193
                        $otpCredentialProvider->deleteCredential($currentUser);
194
                        SessionAlert::error('Error enabling TOTP: invalid token provided');
195
                    }
196
197
198
                    $this->redirect('multiFactor');
199
                    return;
200
                }
201
                else {
202
                    SessionAlert::error('Error enabling TOTP - no enrollment found or enrollment expired.');
203
                    $this->redirect('multiFactor');
204
205
                    return;
206
                }
207
            }
208
209
            // urgh, dunno what happened, but it's not something expected.
210
            throw new ApplicationLogicException();
211
        }
212
        else {
213
            if ($otpCredentialProvider->userIsEnrolled($currentUser->getId())) {
214
                // user is not enrolled, we shouldn't have got here.
215
                throw new ApplicationLogicException('User is already enrolled in the selected MFA mechanism');
216
            }
217
218
            $this->assignCSRFToken();
219
220
            $this->assign('alertmessage', 'To enable your multi-factor credentials, please prove you are who you say you are by providing the information below.');
221
            $this->assign('alertheader', 'Provide credentials');
222
            $this->assign('continueText', 'Verify password');
223
            $this->setTemplate('mfa/enableAuth.tpl');
224
        }
225
    }
226
227
    protected function disableTotp()
228
    {
229
        $database = $this->getDatabase();
230
        $currentUser = User::getCurrent($database);
231
232
        $otpCredentialProvider = new TotpCredentialProvider($database, $this->getSiteConfiguration());
233
234
        $factorType = 'TOTP';
235
236
        $this->deleteCredential($database, $currentUser, $otpCredentialProvider, $factorType);
237
    }
238
239
    protected function enableU2F() {
240
        $database = $this->getDatabase();
241
        $currentUser = User::getCurrent($database);
242
243
        $otpCredentialProvider = new U2FCredentialProvider($database, $this->getSiteConfiguration());
244
245
        if (WebRequest::wasPosted()) {
246
            $this->validateCSRFToken();
247
248
            // used for routing only, not security
249
            $stage = WebRequest::postString('stage');
250
251
            if ($stage === "auth") {
252
                $password = WebRequest::postString('password');
253
254
                $passwordCredentialProvider = new PasswordCredentialProvider($database,
255
                    $this->getSiteConfiguration());
256
                $result = $passwordCredentialProvider->authenticate($currentUser, $password);
257
258
                if ($result) {
259
                    $otpCredentialProvider->setCredential($currentUser, 2, null);
260
                    $this->assignCSRFToken();
261
262
                    list($data, $reqs) = $otpCredentialProvider->getRegistrationData();
263
264
                    $u2fRequest =json_encode($data);
265
                    $u2fSigns = json_encode($reqs);
266
267
                    $this->addJs('/vendor/yubico/u2flib-server/examples/assets/u2f-api.js');
268
                    $this->setTailScript($this->getCspManager()->getNonce(), <<<JS
269
var request = ${u2fRequest};
270
var signs = ${u2fSigns};
271
272
u2f.register([request], signs, function(data) {
273
	var form = document.getElementById('u2fEnroll');
274
	var reg = document.getElementById('u2fData');
275
	var req = document.getElementById('u2fRequest');
276
277
	if(data.errorCode && data.errorCode !== 0) {
278
		alert("registration failed with errror: " + data.errorCode);
279
		return;
280
	}
281
282
	reg.value=JSON.stringify(data);
283
	req.value=JSON.stringify(request);
284
	form.submit();
285
});
286
JS
287
                    );
288
289
                    $this->setTemplate('mfa/enableU2FEnroll.tpl');
290
291
                    return;
292
                }
293
                else {
294
                    SessionAlert::error('Error enabling TOTP - invalid credentials.');
295
                    $this->redirect('multiFactor');
296
297
                    return;
298
                }
299
            }
300
301
            if ($stage === "enroll") {
302
                // we *must* have a defined credential already here,
303
                if ($otpCredentialProvider->isPartiallyEnrolled($currentUser)) {
304
305
                    $request = json_decode(WebRequest::postString('u2fRequest'));
306
                    $u2fData = json_decode(WebRequest::postString('u2fData'));
307
308
                    $otpCredentialProvider->enable($currentUser, $request, $u2fData);
309
310
                    SessionAlert::success('Enabled U2F.');
311
312
                    $scratchProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration());
313
                    if($scratchProvider->getRemaining($currentUser->getId()) < 3) {
314
                        $scratchProvider->setCredential($currentUser, 2, null);
315
                        $tokens = $scratchProvider->getTokens();
316
                        $this->assign('tokens', $tokens);
317
                        $this->setTemplate('mfa/regenScratchTokens.tpl');
318
                        return;
319
                    }
320
321
                    $this->redirect('multiFactor');
322
                    return;
323
                }
324
                else {
325
                    SessionAlert::error('Error enabling TOTP - no enrollment found or enrollment expired.');
326
                    $this->redirect('multiFactor');
327
328
                    return;
329
                }
330
            }
331
332
            // urgh, dunno what happened, but it's not something expected.
333
            throw new ApplicationLogicException();
334
        }
335
        else {
336
            if ($otpCredentialProvider->userIsEnrolled($currentUser->getId())) {
337
                // user is not enrolled, we shouldn't have got here.
338
                throw new ApplicationLogicException('User is already enrolled in the selected MFA mechanism');
339
            }
340
341
            $this->assignCSRFToken();
342
343
            $this->assign('alertmessage', 'To enable your multi-factor credentials, please prove you are who you say you are by providing the information below.');
344
            $this->assign('alertheader', 'Provide credentials');
345
            $this->assign('continueText', 'Verify password');
346
            $this->setTemplate('mfa/enableAuth.tpl');
347
        }
348
    }
349
350
    protected function disableU2F() {
351
        $database = $this->getDatabase();
352
        $currentUser = User::getCurrent($database);
353
354
        $otpCredentialProvider = new U2FCredentialProvider($database, $this->getSiteConfiguration());
355
356
        $factorType = 'U2F';
357
358
        $this->deleteCredential($database, $currentUser, $otpCredentialProvider, $factorType);
359
    }
360
361
    protected function scratch()
362
    {
363
        $database = $this->getDatabase();
364
        $currentUser = User::getCurrent($database);
365
366
        if (WebRequest::wasPosted()) {
367
            $this->validateCSRFToken();
368
369
            $passwordCredentialProvider = new PasswordCredentialProvider($database,
370
                $this->getSiteConfiguration());
371
372
            $otpCredentialProvider = new ScratchTokenCredentialProvider($database,
373
                $this->getSiteConfiguration());
374
375
            $password = WebRequest::postString('password');
376
377
            $result = $passwordCredentialProvider->authenticate($currentUser, $password);
378
379
            if ($result) {
380
                $otpCredentialProvider->setCredential($currentUser, 2, null);
381
                $tokens = $otpCredentialProvider->getTokens();
382
                $this->assign('tokens', $tokens);
383
                $this->setTemplate('mfa/regenScratchTokens.tpl');
384
            }
385
            else {
386
                SessionAlert::error('Error refreshing scratch tokens - invalid credentials.');
387
                $this->redirect('multiFactor');
388
            }
389
        }
390
        else {
391
            $this->assignCSRFToken();
392
393
            $this->assign('alertmessage', 'To regenerate your emergency scratch tokens, please prove you are who you say you are by providing the information below. Note that continuing will invalidate all remaining scratch tokens, and provide a set of new ones.');
394
            $this->assign('alertheader', 'Re-generate scratch tokens');
395
            $this->assign('continueText', 'Regenerate Scratch Tokens');
396
397
            $this->setTemplate('mfa/enableAuth.tpl');
398
        }
399
    }
400
401
    /**
402
     * @param PdoDatabase         $database
403
     * @param User                $currentUser
404
     * @param ICredentialProvider $otpCredentialProvider
405
     * @param string              $factorType
406
     *
407
     * @throws ApplicationLogicException
408
     */
409
    private function deleteCredential(
410
        PdoDatabase $database,
411
        User $currentUser,
412
        ICredentialProvider $otpCredentialProvider,
413
        $factorType
414
    ) {
415
        if (WebRequest::wasPosted()) {
416
            $passwordCredentialProvider = new PasswordCredentialProvider($database,
417
                $this->getSiteConfiguration());
418
419
            $this->validateCSRFToken();
420
421
            $password = WebRequest::postString('password');
422
            $result = $passwordCredentialProvider->authenticate($currentUser, $password);
423
424
            if ($result) {
425
                $otpCredentialProvider->deleteCredential($currentUser);
426
                SessionAlert::success('Disabled ' . $factorType . '.');
427
                $this->redirect('multiFactor');
428
            }
429
            else {
430
                SessionAlert::error('Error disabling ' . $factorType . ' - invalid credentials.');
431
                $this->redirect('multiFactor');
432
            }
433
        }
434
        else {
435
            if (!$otpCredentialProvider->userIsEnrolled($currentUser->getId())) {
436
                // user is not enrolled, we shouldn't have got here.
437
                throw new ApplicationLogicException('User is not enrolled in the selected MFA mechanism');
438
            }
439
440
            $this->assignCSRFToken();
441
            $this->assign('otpType', $factorType);
442
            $this->setTemplate('mfa/disableOtp.tpl');
443
        }
444
    }
445
}
446