Failed Conditions
Push — newinternal ( b66232...216d62 )
by Simon
16:33 queued 06:35
created

PageMultiFactor::enableYubikeyOtp()   B

Complexity

Conditions 6
Paths 13

Size

Total Lines 54

Duplication

Lines 7
Ratio 12.96 %

Importance

Changes 0
Metric Value
cc 6
nc 13
nop 0
dl 7
loc 54
rs 8.3814
c 0
b 0
f 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 u2flib_server\U2F;
14
use Waca\DataObjects\User;
15
use Waca\Exceptions\ApplicationLogicException;
16
use Waca\PdoDatabase;
17
use Waca\Security\CredentialProviders\ICredentialProvider;
18
use Waca\Security\CredentialProviders\PasswordCredentialProvider;
19
use Waca\Security\CredentialProviders\ScratchTokenCredentialProvider;
20
use Waca\Security\CredentialProviders\TotpCredentialProvider;
21
use Waca\Security\CredentialProviders\U2FCredentialProvider;
22
use Waca\Security\CredentialProviders\YubikeyOtpCredentialProvider;
23
use Waca\SessionAlert;
24
use Waca\Tasks\InternalPageBase;
25
use Waca\WebRequest;
26
27
class PageMultiFactor extends InternalPageBase
28
{
29
    /**
30
     * Main function for this page, when no specific actions are called.
31
     * @return void
32
     */
33
    protected function main()
34
    {
35
        $database = $this->getDatabase();
36
        $currentUser = User::getCurrent($database);
37
38
        $yubikeyOtpCredentialProvider = new YubikeyOtpCredentialProvider($database, $this->getSiteConfiguration(),
39
            $this->getHttpHelper());
40
        $this->assign('yubikeyOtpIdentity', $yubikeyOtpCredentialProvider->getYubikeyData($currentUser->getId()));
41
        $this->assign('yubikeyOtpEnrolled', $yubikeyOtpCredentialProvider->userIsEnrolled($currentUser->getId()));
42
43
        $totpCredentialProvider = new TotpCredentialProvider($database, $this->getSiteConfiguration());
44
        $this->assign('totpEnrolled', $totpCredentialProvider->userIsEnrolled($currentUser->getId()));
45
46
        $u2fCredentialProvider = new U2FCredentialProvider($database, $this->getSiteConfiguration());
47
        $this->assign('u2fEnrolled', $u2fCredentialProvider->userIsEnrolled($currentUser->getId()));
48
49
        $scratchCredentialProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration());
50
        $this->assign('scratchEnrolled', $scratchCredentialProvider->userIsEnrolled($currentUser->getId()));
51
        $this->assign('scratchRemaining', $scratchCredentialProvider->getRemaining($currentUser->getId()));
52
53
        $this->setTemplate('mfa/mfa.tpl');
54
    }
55
56
    protected function enableYubikeyOtp()
57
    {
58
        $database = $this->getDatabase();
59
        $currentUser = User::getCurrent($database);
60
61
        $otpCredentialProvider = new YubikeyOtpCredentialProvider($database,
62
            $this->getSiteConfiguration(), $this->getHttpHelper());
63
64
        if (WebRequest::wasPosted()) {
65
            $this->validateCSRFToken();
66
67
            $passwordCredentialProvider = new PasswordCredentialProvider($database,
68
                $this->getSiteConfiguration());
69
70
            $password = WebRequest::postString('password');
71
            $otp = WebRequest::postString('otp');
72
73
            $result = $passwordCredentialProvider->authenticate($currentUser, $password);
74
75
            if ($result) {
76
                try {
77
                    $otpCredentialProvider->setCredential($currentUser, 2, $otp);
78
                    SessionAlert::success('Enabled YubiKey OTP.');
79
80
                    $scratchProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration());
81 View Code Duplication
                    if($scratchProvider->getRemaining($currentUser->getId()) < 3) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
82
                        $scratchProvider->setCredential($currentUser, 2, null);
83
                        $tokens = $scratchProvider->getTokens();
84
                        $this->assign('tokens', $tokens);
85
                        $this->setTemplate('mfa/regenScratchTokens.tpl');
86
                        return;
87
                    }
88
                }
89
                catch (ApplicationLogicException $ex) {
90
                    SessionAlert::error('Error enabling YubiKey OTP: ' . $ex->getMessage());
91
                }
92
93
                $this->redirect('multiFactor');
94
            }
95
            else {
96
                SessionAlert::error('Error enabling YubiKey OTP - invalid credentials.');
97
                $this->redirect('multiFactor');
98
            }
99
        }
100
        else {
101
            if ($otpCredentialProvider->userIsEnrolled($currentUser->getId())) {
102
                // user is not enrolled, we shouldn't have got here.
103
                throw new ApplicationLogicException('User is already enrolled in the selected MFA mechanism');
104
            }
105
106
            $this->assignCSRFToken();
107
            $this->setTemplate('mfa/enableYubikey.tpl');
108
        }
109
    }
110
111 View Code Duplication
    protected function disableYubikeyOtp()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
112
    {
113
        $database = $this->getDatabase();
114
        $currentUser = User::getCurrent($database);
115
116
        $otpCredentialProvider = new YubikeyOtpCredentialProvider($database,
117
            $this->getSiteConfiguration(), $this->getHttpHelper());
118
119
        $factorType = 'YubiKey OTP';
120
121
        $this->deleteCredential($database, $currentUser, $otpCredentialProvider, $factorType);
122
    }
123
124
    protected function enableTotp()
125
    {
126
        $database = $this->getDatabase();
127
        $currentUser = User::getCurrent($database);
128
129
        $otpCredentialProvider = new TotpCredentialProvider($database, $this->getSiteConfiguration());
130
131
        if (WebRequest::wasPosted()) {
132
            $this->validateCSRFToken();
133
134
            // used for routing only, not security
135
            $stage = WebRequest::postString('stage');
136
137
            if ($stage === "auth") {
138
                $password = WebRequest::postString('password');
139
140
                $passwordCredentialProvider = new PasswordCredentialProvider($database,
141
                    $this->getSiteConfiguration());
142
                $result = $passwordCredentialProvider->authenticate($currentUser, $password);
143
144
                if ($result) {
145
                    $otpCredentialProvider->setCredential($currentUser, 2, null);
146
147
                    $provisioningUrl = $otpCredentialProvider->getProvisioningUrl($currentUser);
148
149
                    $renderer = new Svg();
150
                    $renderer->setHeight(256);
151
                    $renderer->setWidth(256);
152
                    $writer = new Writer($renderer);
153
                    $svg = $writer->writeString($provisioningUrl);
154
155
                    $this->assign('svg', $svg);
156
                    $this->assign('secret', $otpCredentialProvider->getSecret($currentUser));
157
158
                    $this->assignCSRFToken();
159
                    $this->setTemplate('mfa/enableTotpEnroll.tpl');
160
161
                    return;
162
                }
163
                else {
164
                    SessionAlert::error('Error enabling TOTP - invalid credentials.');
165
                    $this->redirect('multiFactor');
166
167
                    return;
168
                }
169
            }
170
171
            if ($stage === "enroll") {
172
                // we *must* have a defined credential already here,
173
                if ($otpCredentialProvider->isPartiallyEnrolled($currentUser)) {
174
                    $otp = WebRequest::postString('otp');
175
                    $result = $otpCredentialProvider->verifyEnable($currentUser, $otp);
176
177
                    if ($result) {
178
                        SessionAlert::success('Enabled TOTP.');
179
180
                        $scratchProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration());
181 View Code Duplication
                        if($scratchProvider->getRemaining($currentUser->getId()) < 3) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
182
                            $scratchProvider->setCredential($currentUser, 2, null);
183
                            $tokens = $scratchProvider->getTokens();
184
                            $this->assign('tokens', $tokens);
185
                            $this->setTemplate('mfa/regenScratchTokens.tpl');
186
                            return;
187
                        }
188
                    }
189
                    else {
190
                        $otpCredentialProvider->deleteCredential($currentUser);
191
                        SessionAlert::error('Error enabling TOTP: invalid token provided');
192
                    }
193
194
195
                    $this->redirect('multiFactor');
196
                    return;
197
                }
198
                else {
199
                    SessionAlert::error('Error enabling TOTP - no enrollment found or enrollment expired.');
200
                    $this->redirect('multiFactor');
201
202
                    return;
203
                }
204
            }
205
206
            // urgh, dunno what happened, but it's not something expected.
207
            throw new ApplicationLogicException();
208
        }
209
        else {
210
            if ($otpCredentialProvider->userIsEnrolled($currentUser->getId())) {
211
                // user is not enrolled, we shouldn't have got here.
212
                throw new ApplicationLogicException('User is already enrolled in the selected MFA mechanism');
213
            }
214
215
            $this->assignCSRFToken();
216
            $this->setTemplate('mfa/enableTotpAuth.tpl');
217
        }
218
    }
219
220 View Code Duplication
    protected function disableTotp()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
    {
222
        $database = $this->getDatabase();
223
        $currentUser = User::getCurrent($database);
224
225
        $otpCredentialProvider = new TotpCredentialProvider($database, $this->getSiteConfiguration());
226
227
        $factorType = 'TOTP';
228
229
        $this->deleteCredential($database, $currentUser, $otpCredentialProvider, $factorType);
230
    }
231
232
    protected function enableU2F() {
233
        $database = $this->getDatabase();
234
        $currentUser = User::getCurrent($database);
235
236
        $otpCredentialProvider = new U2FCredentialProvider($database, $this->getSiteConfiguration());
237
238
        if (WebRequest::wasPosted()) {
239
            $this->validateCSRFToken();
240
241
            // used for routing only, not security
242
            $stage = WebRequest::postString('stage');
243
244
            if ($stage === "auth") {
245
                $password = WebRequest::postString('password');
246
247
                $passwordCredentialProvider = new PasswordCredentialProvider($database,
248
                    $this->getSiteConfiguration());
249
                $result = $passwordCredentialProvider->authenticate($currentUser, $password);
250
251
                if ($result) {
252
                    $otpCredentialProvider->setCredential($currentUser, 2, null);
253
                    $this->assignCSRFToken();
254
255
                    list($data, $reqs) = $otpCredentialProvider->getRegistrationData();
256
257
                    $u2fRequest =json_encode($data);
258
                    $u2fSigns = json_encode($reqs);
259
260
                    $this->addJs('/vendor/yubico/u2flib-server/examples/assets/u2f-api.js');
261
                    $this->setTailScript(<<<JS
262
var request = ${u2fRequest};
263
var signs = ${u2fSigns};
264
265
u2f.register([request], signs, function(data) {
266
	var form = document.getElementById('u2fEnroll');
267
	var reg = document.getElementById('u2fData');
268
	var req = document.getElementById('u2fRequest');
269
270
	if(data.errorCode && data.errorCode != 0) {
271
		alert("registration failed with errror: " + data.errorCode);
272
		return;
273
	}
274
275
	reg.value=JSON.stringify(data);
276
	req.value=JSON.stringify(request);
277
	form.submit();
278
});
279
JS
280
                    );
281
282
                    $this->setTemplate('mfa/enableU2FEnroll.tpl');
283
284
                    return;
285
                }
286
                else {
287
                    SessionAlert::error('Error enabling TOTP - invalid credentials.');
288
                    $this->redirect('multiFactor');
289
290
                    return;
291
                }
292
            }
293
294
            if ($stage === "enroll") {
295
                // we *must* have a defined credential already here,
296
                if ($otpCredentialProvider->isPartiallyEnrolled($currentUser)) {
297
298
                    $request = json_decode(WebRequest::postString('u2fRequest'));
299
                    $u2fData = json_decode(WebRequest::postString('u2fData'));
300
301
                    $otpCredentialProvider->enable($currentUser, $request, $u2fData);
302
303
                    SessionAlert::success('Enabled TOTP.');
304
305
                    $scratchProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration());
306 View Code Duplication
                    if($scratchProvider->getRemaining($currentUser->getId()) < 3) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
307
                        $scratchProvider->setCredential($currentUser, 2, null);
308
                        $tokens = $scratchProvider->getTokens();
309
                        $this->assign('tokens', $tokens);
310
                        $this->setTemplate('mfa/regenScratchTokens.tpl');
311
                        return;
312
                    }
313
314
                    $this->redirect('multiFactor');
315
                    return;
316
                }
317
                else {
318
                    SessionAlert::error('Error enabling TOTP - no enrollment found or enrollment expired.');
319
                    $this->redirect('multiFactor');
320
321
                    return;
322
                }
323
            }
324
325
            // urgh, dunno what happened, but it's not something expected.
326
            throw new ApplicationLogicException();
327
        }
328
        else {
329
            if ($otpCredentialProvider->userIsEnrolled($currentUser->getId())) {
330
                // user is not enrolled, we shouldn't have got here.
331
                throw new ApplicationLogicException('User is already enrolled in the selected MFA mechanism');
332
            }
333
334
            $this->assignCSRFToken();
335
            $this->setTemplate('mfa/enableU2FAuth.tpl');
336
        }
337
    }
338
339 View Code Duplication
    protected function disableU2F() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
340
        $database = $this->getDatabase();
341
        $currentUser = User::getCurrent($database);
342
343
        $otpCredentialProvider = new U2FCredentialProvider($database, $this->getSiteConfiguration());
344
345
        $factorType = 'U2F';
346
347
        $this->deleteCredential($database, $currentUser, $otpCredentialProvider, $factorType);
348
    }
349
350
    protected function scratch()
351
    {
352
        $database = $this->getDatabase();
353
        $currentUser = User::getCurrent($database);
354
355
        if (WebRequest::wasPosted()) {
356
            $this->validateCSRFToken();
357
358
            $passwordCredentialProvider = new PasswordCredentialProvider($database,
359
                $this->getSiteConfiguration());
360
361
            $otpCredentialProvider = new ScratchTokenCredentialProvider($database,
362
                $this->getSiteConfiguration());
363
364
            $password = WebRequest::postString('password');
365
366
            $result = $passwordCredentialProvider->authenticate($currentUser, $password);
367
368 View Code Duplication
            if ($result) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
369
                $otpCredentialProvider->setCredential($currentUser, 2, null);
370
                $tokens = $otpCredentialProvider->getTokens();
371
                $this->assign('tokens', $tokens);
372
                $this->setTemplate('mfa/regenScratchTokens.tpl');
373
            }
374
            else {
375
                SessionAlert::error('Error refreshing scratch tokens - invalid credentials.');
376
                $this->redirect('multiFactor');
377
            }
378
        }
379
        else {
380
            $this->assignCSRFToken();
381
            $this->setTemplate('mfa/regenScratchAuth.tpl');
382
        }
383
    }
384
385
    /**
386
     * @param PdoDatabase         $database
387
     * @param User                $currentUser
388
     * @param ICredentialProvider $otpCredentialProvider
389
     * @param string              $factorType
390
     *
391
     * @throws ApplicationLogicException
392
     */
393
    private function deleteCredential(
394
        PdoDatabase $database,
395
        User $currentUser,
396
        ICredentialProvider $otpCredentialProvider,
397
        $factorType
398
    ) {
399
        if (WebRequest::wasPosted()) {
400
            $passwordCredentialProvider = new PasswordCredentialProvider($database,
401
                $this->getSiteConfiguration());
402
403
            $this->validateCSRFToken();
404
405
            $password = WebRequest::postString('password');
406
            $result = $passwordCredentialProvider->authenticate($currentUser, $password);
407
408
            if ($result) {
409
                $otpCredentialProvider->deleteCredential($currentUser);
410
                SessionAlert::success('Disabled ' . $factorType . '.');
411
                $this->redirect('multiFactor');
412
            }
413
            else {
414
                SessionAlert::error('Error disabling ' . $factorType . ' - invalid credentials.');
415
                $this->redirect('multiFactor');
416
            }
417
        }
418
        else {
419
            if (!$otpCredentialProvider->userIsEnrolled($currentUser->getId())) {
420
                // user is not enrolled, we shouldn't have got here.
421
                throw new ApplicationLogicException('User is not enrolled in the selected MFA mechanism');
422
            }
423
424
            $this->assignCSRFToken();
425
            $this->assign('otpType', $factorType);
426
            $this->setTemplate('mfa/disableOtp.tpl');
427
        }
428
    }
429
}
430