]> BookStack Code Mirror - bookstack/blob - tests/Auth/MfaConfigurationTest.php
Extracted text to translation files
[bookstack] / tests / Auth / MfaConfigurationTest.php
1 <?php
2
3 namespace Tests\Auth;
4
5 use BookStack\Actions\ActivityType;
6 use BookStack\Auth\Access\Mfa\MfaValue;
7 use BookStack\Auth\User;
8 use PragmaRX\Google2FA\Google2FA;
9 use Tests\TestCase;
10
11 class MfaConfigurationTest extends TestCase
12 {
13
14     public function test_totp_setup()
15     {
16         $editor = $this->getEditor();
17         $this->assertDatabaseMissing('mfa_values', ['user_id' => $editor->id]);
18
19         // Setup page state
20         $resp = $this->actingAs($editor)->get('/mfa/setup');
21         $resp->assertElementContains('a[href$="/mfa/totp/generate"]', 'Setup');
22
23         // Generate page access
24         $resp = $this->get('/mfa/totp/generate');
25         $resp->assertSee('Mobile App Setup');
26         $resp->assertSee('Verify Setup');
27         $resp->assertElementExists('form[action$="/mfa/totp/confirm"] button');
28         $this->assertSessionHas('mfa-setup-totp-secret');
29         $svg = $resp->getElementHtml('#main-content .card svg');
30
31         // Validation error, code should remain the same
32         $resp = $this->post('/mfa/totp/confirm', [
33             'code' => 'abc123',
34         ]);
35         $resp->assertRedirect('/mfa/totp/generate');
36         $resp = $this->followRedirects($resp);
37         $resp->assertSee('The provided code is not valid or has expired.');
38         $revisitSvg = $resp->getElementHtml('#main-content .card svg');
39         $this->assertTrue($svg === $revisitSvg);
40
41         // Successful confirmation
42         $google2fa = new Google2FA();
43         $secret = decrypt(session()->get('mfa-setup-totp-secret'));
44         $otp = $google2fa->getCurrentOtp($secret);
45         $resp = $this->post('/mfa/totp/confirm', [
46             'code' => $otp,
47         ]);
48         $resp->assertRedirect('/mfa/setup');
49
50         // Confirmation of setup
51         $resp = $this->followRedirects($resp);
52         $resp->assertSee('Multi-factor method successfully configured');
53         $resp->assertElementContains('a[href$="/mfa/totp/generate"]', 'Reconfigure');
54
55         $this->assertDatabaseHas('mfa_values', [
56             'user_id' => $editor->id,
57             'method' => 'totp',
58         ]);
59         $this->assertFalse(session()->has('mfa-setup-totp-secret'));
60         $value = MfaValue::query()->where('user_id', '=', $editor->id)
61             ->where('method', '=', 'totp')->first();
62         $this->assertEquals($secret, decrypt($value->value));
63     }
64
65     public function test_backup_codes_setup()
66     {
67         $editor = $this->getEditor();
68         $this->assertDatabaseMissing('mfa_values', ['user_id' => $editor->id]);
69
70         // Setup page state
71         $resp = $this->actingAs($editor)->get('/mfa/setup');
72         $resp->assertElementContains('a[href$="/mfa/backup_codes/generate"]', 'Setup');
73
74         // Generate page access
75         $resp = $this->get('/mfa/backup_codes/generate');
76         $resp->assertSee('Backup Codes');
77         $resp->assertElementContains('form[action$="/mfa/backup_codes/confirm"]', 'Confirm and Enable');
78         $this->assertSessionHas('mfa-setup-backup-codes');
79         $codes = decrypt(session()->get('mfa-setup-backup-codes'));
80         // Check code format
81         $this->assertCount(16, $codes);
82         $this->assertEquals(16*11, strlen(implode('', $codes)));
83         // Check download link
84         $resp->assertSee(base64_encode(implode("\n\n", $codes)));
85
86         // Confirm submit
87         $resp = $this->post('/mfa/backup_codes/confirm');
88         $resp->assertRedirect('/mfa/setup');
89
90         // Confirmation of setup
91         $resp = $this->followRedirects($resp);
92         $resp->assertSee('Multi-factor method successfully configured');
93         $resp->assertElementContains('a[href$="/mfa/backup_codes/generate"]', 'Reconfigure');
94
95         $this->assertDatabaseHas('mfa_values', [
96             'user_id' => $editor->id,
97             'method' => 'backup_codes',
98         ]);
99         $this->assertFalse(session()->has('mfa-setup-backup-codes'));
100         $value = MfaValue::query()->where('user_id', '=', $editor->id)
101             ->where('method', '=', 'backup_codes')->first();
102         $this->assertEquals($codes, json_decode(decrypt($value->value)));
103     }
104
105     public function test_backup_codes_cannot_be_confirmed_if_not_previously_generated()
106     {
107         $resp = $this->asEditor()->post('/mfa/backup_codes/confirm');
108         $resp->assertStatus(500);
109     }
110
111     public function test_mfa_method_count_is_visible_on_user_edit_page()
112     {
113         $user = $this->getEditor();
114         $resp = $this->actingAs($this->getAdmin())->get($user->getEditUrl());
115         $resp->assertSee('0 methods configured');
116
117         MfaValue::upsertWithValue($user, MfaValue::METHOD_TOTP, 'test');
118         $resp = $this->get($user->getEditUrl());
119         $resp->assertSee('1 method configured');
120
121         MfaValue::upsertWithValue($user, MfaValue::METHOD_BACKUP_CODES, 'test');
122         $resp = $this->get($user->getEditUrl());
123         $resp->assertSee('2 methods configured');
124     }
125
126     public function test_mfa_setup_link_only_shown_when_viewing_own_user_edit_page()
127     {
128         $admin = $this->getAdmin();
129         $resp = $this->actingAs($admin)->get($admin->getEditUrl());
130         $resp->assertElementExists('a[href$="/mfa/setup"]');
131
132         $resp = $this->actingAs($admin)->get($this->getEditor()->getEditUrl());
133         $resp->assertElementNotExists('a[href$="/mfa/setup"]');
134     }
135
136     public function test_mfa_indicator_shows_in_user_list()
137     {
138         $admin = $this->getAdmin();
139         User::query()->where('id', '!=', $admin->id)->delete();
140
141         $resp = $this->actingAs($admin)->get('/settings/users');
142         $resp->assertElementNotExists('[title="MFA Configured"] svg');
143
144         MfaValue::upsertWithValue($admin, MfaValue::METHOD_TOTP, 'test');
145         $resp = $this->actingAs($admin)->get('/settings/users');
146         $resp->assertElementExists('[title="MFA Configured"] svg');
147     }
148
149     public function test_remove_mfa_method()
150     {
151         $admin = $this->getAdmin();
152
153         MfaValue::upsertWithValue($admin, MfaValue::METHOD_TOTP, 'test');
154         $this->assertEquals(1, $admin->mfaValues()->count());
155         $resp = $this->actingAs($admin)->get('/mfa/setup');
156         $resp->assertElementExists('form[action$="/mfa/totp/remove"]');
157
158         $resp = $this->delete("/mfa/totp/remove");
159         $resp->assertRedirect("/mfa/setup");
160         $resp = $this->followRedirects($resp);
161         $resp->assertSee('Multi-factor method successfully removed');
162
163         $this->assertActivityExists(ActivityType::MFA_REMOVE_METHOD);
164         $this->assertEquals(0, $admin->mfaValues()->count());
165     }
166
167 }