Passed
Push — u2f-deprecation ( 289827 )
by Simon
28:23 queued 18:22
created

PageMultiFactor::disableU2F()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
dl 0
loc 10
rs 10
c 1
b 0
f 0
cc 1
nc 1
nop 0
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\SvgImageBackEnd;
12
use BaconQrCode\Renderer\ImageRenderer;
13
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
14
use BaconQrCode\Writer;
15
use Waca\DataObjects\User;
16
use Waca\Exceptions\ApplicationLogicException;
17
use Waca\PdoDatabase;
18
use Waca\Security\CredentialProviders\ICredentialProvider;
19
use Waca\Security\CredentialProviders\PasswordCredentialProvider;
20
use Waca\Security\CredentialProviders\ScratchTokenCredentialProvider;
21
use Waca\Security\CredentialProviders\TotpCredentialProvider;
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
        $scratchCredentialProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration());
47
        $this->assign('scratchEnrolled', $scratchCredentialProvider->userIsEnrolled($currentUser->getId()));
48
        $this->assign('scratchRemaining', $scratchCredentialProvider->getRemaining($currentUser->getId()));
49
50
        $this->assign('allowedTotp', $this->barrierTest('enableTotp', $currentUser));
51
        $this->assign('allowedYubikey', $this->barrierTest('enableYubikeyOtp', $currentUser));
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
                    if ($scratchProvider->getRemaining($currentUser->getId()) < 3) {
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
    protected function disableYubikeyOtp()
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 ImageRenderer(
150
                        new RendererStyle(256),
151
                        new SvgImageBackEnd()
152
                    );
153
154
                    $writer = new Writer($renderer);
155
                    $svg = $writer->writeString($provisioningUrl);
156
157
                    $this->assign('svg', $svg);
158
                    $this->assign('secret', $otpCredentialProvider->getSecret($currentUser));
159
160
                    $this->assignCSRFToken();
161
                    $this->setTemplate('mfa/enableTotpEnroll.tpl');
162
163
                    return;
164
                }
165
                else {
166
                    SessionAlert::error('Error enabling TOTP - invalid credentials.');
167
                    $this->redirect('multiFactor');
168
169
                    return;
170
                }
171
            }
172
173
            if ($stage === "enroll") {
174
                // we *must* have a defined credential already here,
175
                if ($otpCredentialProvider->isPartiallyEnrolled($currentUser)) {
176
                    $otp = WebRequest::postString('otp');
177
                    $result = $otpCredentialProvider->verifyEnable($currentUser, $otp);
178
179
                    if ($result) {
180
                        SessionAlert::success('Enabled TOTP.');
181
182
                        $scratchProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration());
183
                        if ($scratchProvider->getRemaining($currentUser->getId()) < 3) {
184
                            $scratchProvider->setCredential($currentUser, 2, null);
185
                            $tokens = $scratchProvider->getTokens();
186
                            $this->assign('tokens', $tokens);
187
                            $this->setTemplate('mfa/regenScratchTokens.tpl');
188
                            return;
189
                        }
190
                    }
191
                    else {
192
                        $otpCredentialProvider->deleteCredential($currentUser);
193
                        SessionAlert::error('Error enabling TOTP: invalid token provided');
194
                    }
195
196
197
                    $this->redirect('multiFactor');
198
                    return;
199
                }
200
                else {
201
                    SessionAlert::error('Error enabling TOTP - no enrollment found or enrollment expired.');
202
                    $this->redirect('multiFactor');
203
204
                    return;
205
                }
206
            }
207
208
            // urgh, dunno what happened, but it's not something expected.
209
            throw new ApplicationLogicException();
210
        }
211
        else {
212
            if ($otpCredentialProvider->userIsEnrolled($currentUser->getId())) {
213
                // user is not enrolled, we shouldn't have got here.
214
                throw new ApplicationLogicException('User is already enrolled in the selected MFA mechanism');
215
            }
216
217
            $this->assignCSRFToken();
218
219
            $this->assign('alertmessage', 'To enable your multi-factor credentials, please prove you are who you say you are by providing the information below.');
220
            $this->assign('alertheader', 'Provide credentials');
221
            $this->assign('continueText', 'Verify password');
222
            $this->setTemplate('mfa/enableAuth.tpl');
223
        }
224
    }
225
226
    protected function disableTotp()
227
    {
228
        $database = $this->getDatabase();
229
        $currentUser = User::getCurrent($database);
230
231
        $otpCredentialProvider = new TotpCredentialProvider($database, $this->getSiteConfiguration());
232
233
        $factorType = 'TOTP';
234
235
        $this->deleteCredential($database, $currentUser, $otpCredentialProvider, $factorType);
236
    }
237
238
    protected function scratch()
239
    {
240
        $database = $this->getDatabase();
241
        $currentUser = User::getCurrent($database);
242
243
        if (WebRequest::wasPosted()) {
244
            $this->validateCSRFToken();
245
246
            $passwordCredentialProvider = new PasswordCredentialProvider($database,
247
                $this->getSiteConfiguration());
248
249
            $otpCredentialProvider = new ScratchTokenCredentialProvider($database,
250
                $this->getSiteConfiguration());
251
252
            $password = WebRequest::postString('password');
253
254
            $result = $passwordCredentialProvider->authenticate($currentUser, $password);
255
256
            if ($result) {
257
                $otpCredentialProvider->setCredential($currentUser, 2, null);
258
                $tokens = $otpCredentialProvider->getTokens();
259
                $this->assign('tokens', $tokens);
260
                $this->setTemplate('mfa/regenScratchTokens.tpl');
261
            }
262
            else {
263
                SessionAlert::error('Error refreshing scratch tokens - invalid credentials.');
264
                $this->redirect('multiFactor');
265
            }
266
        }
267
        else {
268
            $this->assignCSRFToken();
269
270
            $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.');
271
            $this->assign('alertheader', 'Re-generate scratch tokens');
272
            $this->assign('continueText', 'Regenerate Scratch Tokens');
273
274
            $this->setTemplate('mfa/enableAuth.tpl');
275
        }
276
    }
277
278
    /**
279
     * @param PdoDatabase         $database
280
     * @param User                $currentUser
281
     * @param ICredentialProvider $otpCredentialProvider
282
     * @param string              $factorType
283
     *
284
     * @throws ApplicationLogicException
285
     */
286
    private function deleteCredential(
287
        PdoDatabase $database,
288
        User $currentUser,
289
        ICredentialProvider $otpCredentialProvider,
290
        $factorType
291
    ) {
292
        if (WebRequest::wasPosted()) {
293
            $passwordCredentialProvider = new PasswordCredentialProvider($database,
294
                $this->getSiteConfiguration());
295
296
            $this->validateCSRFToken();
297
298
            $password = WebRequest::postString('password');
299
            $result = $passwordCredentialProvider->authenticate($currentUser, $password);
300
301
            if ($result) {
302
                $otpCredentialProvider->deleteCredential($currentUser);
303
                SessionAlert::success('Disabled ' . $factorType . '.');
304
                $this->redirect('multiFactor');
305
            }
306
            else {
307
                SessionAlert::error('Error disabling ' . $factorType . ' - invalid credentials.');
308
                $this->redirect('multiFactor');
309
            }
310
        }
311
        else {
312
            if (!$otpCredentialProvider->userIsEnrolled($currentUser->getId())) {
313
                // user is not enrolled, we shouldn't have got here.
314
                throw new ApplicationLogicException('User is not enrolled in the selected MFA mechanism');
315
            }
316
317
            $this->assignCSRFToken();
318
            $this->assign('otpType', $factorType);
319
            $this->setTemplate('mfa/disableOtp.tpl');
320
        }
321
    }
322
}
323