‪TYPO3CMS  ‪main
TotpProviderTest.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use PHPUnit\Framework\Attributes\Test;
32 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
33 
34 final class ‪TotpProviderTest extends FunctionalTestCase
35 {
39 
40  protected function ‪setUp(): void
41  {
42  parent::setUp();
43  $this->importCSVDataSet(__DIR__ . '/../../Fixtures/be_users.csv');
44  $this->user = $this->setUpBackendUser(1);
45  ‪$GLOBALS['LANG'] = $this->get(LanguageServiceFactory::class)->createFromUserPreferences($this->user);
46  $this->hashService = GeneralUtility::makeInstance(HashService::class);
47  $this->subject = $this->get(MfaProviderRegistry::class)->getProvider('totp');
48  }
49 
50  #[Test]
51  public function ‪canProcessTest(): void
52  {
53  self::assertFalse($this->subject->canProcess(new ‪ServerRequest('https://example.com', 'POST')));
54 
55  // Add necessary query parameter
56  self::assertTrue($this->subject->canProcess(
57  (new ‪ServerRequest('https://example.com', 'POST'))
58  ->withQueryParams(['totp' => '123456'])
59  ));
60  }
61 
62  #[Test]
63  public function ‪isActiveTest(): void
64  {
65  // No provider entry exists
66  self::assertFalse($this->subject->isActive(‪MfaProviderPropertyManager::create($this->subject, $this->user)));
67 
68  // Active state missing
69  $this->‪setupUser(['secret' => 'KRMVATZTJFZUC53FONXW2ZJB']);
70  self::assertFalse($this->subject->isActive(‪MfaProviderPropertyManager::create($this->subject, $this->user)));
71 
72  // Secret missing
73  $this->‪setupUser(['active' => true]);
74  self::assertFalse($this->subject->isActive(‪MfaProviderPropertyManager::create($this->subject, $this->user)));
75 
76  // Active provider
77  $this->‪setupUser(['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB']);
78  self::assertTrue($this->subject->isActive(‪MfaProviderPropertyManager::create($this->subject, $this->user)));
79  }
80 
81  #[Test]
82  public function ‪isLockedTest(): void
83  {
84  // No provider entry exists
85  self::assertFalse($this->subject->isLocked(‪MfaProviderPropertyManager::create($this->subject, $this->user)));
86 
87  // Provider is not locked
88  $this->‪setupUser(['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB', 'attempts' => 0]);
89  self::assertFalse($this->subject->isLocked(‪MfaProviderPropertyManager::create($this->subject, $this->user)));
90 
91  // Lock provider by setting attempts=3
92  $this->‪setupUser(['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB', 'attempts' => 3]);
93  self::assertTrue($this->subject->isLocked(‪MfaProviderPropertyManager::create($this->subject, $this->user)));
94  }
95 
96  #[Test]
97  public function ‪verifyTest(): void
98  {
99  $request = (new ‪ServerRequest('https://example.com', 'POST'));
100  $timestamp = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('date', 'timestamp');
101  $totp = GeneralUtility::makeInstance(
102  Totp::class,
103  'KRMVATZTJFZUC53FONXW2ZJB'
104  )->generateTotp((int)floor($timestamp / 30));
105 
106  // Provider is inactive (secret missing)
107  $this->‪setupUser(['active' => true]);
108  self::assertFalse(
109  $this->subject->verify(
110  $request->withQueryParams(['totp' => $totp]),
111  ‪MfaProviderPropertyManager::create($this->subject, $this->user)
112  )
113  );
114 
115  // Provider is locked (attempts=3)
116  $this->‪setupUser(['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB', 'attempts' => 3]);
117  self::assertFalse(
118  $this->subject->verify(
119  $request->withQueryParams(['totp' => $totp]),
120  ‪MfaProviderPropertyManager::create($this->subject, $this->user)
121  )
122  );
123 
124  // Wrong totp
125  $this->‪setupUser(['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB', 'attempts' => 0]);
126  self::assertFalse(
127  $this->subject->verify(
128  $request->withQueryParams(['totp' => '123456']),
129  ‪MfaProviderPropertyManager::create($this->subject, $this->user)
130  )
131  );
132 
133  // Correct totp
134  self::assertTrue(
135  $this->subject->verify(
136  $request->withQueryParams(['totp' => $totp]),
137  ‪MfaProviderPropertyManager::create($this->subject, $this->user)
138  )
139  );
140  }
141 
142  #[Test]
143  public function ‪activateTest(): void
144  {
145  $request = (new ‪ServerRequest('https://example.com', 'POST'));
146  $propertyManager = ‪MfaProviderPropertyManager::create($this->subject, $this->user);
147 
148  // Wrong totp
149  self::assertFalse($this->subject->activate($request->withParsedBody(['totp' => '123456']), $propertyManager));
150 
151  // Setup form data to activate provider
152  $secret = 'KRMVATZTJFZUC53FONXW2ZJB';
153  $timestamp = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('date', 'timestamp');
154  $parsedBody = [
155  'totp' => GeneralUtility::makeInstance(Totp::class, $secret)->generateTotp((int)floor($timestamp / 30)),
156  'secret' => $secret,
157  'checksum' => $this->hashService->hmac($secret, 'totp-setup'),
158 
159  ];
160  self::assertTrue($this->subject->activate($request->withParsedBody($parsedBody), $propertyManager));
161  self::assertTrue($propertyManager->getProperty('active'));
162  self::assertEquals('KRMVATZTJFZUC53FONXW2ZJB', $propertyManager->getProperty('secret'));
163  }
164 
165  #[Test]
166  public function ‪deactivateTest(): void
167  {
168  $request = (new ‪ServerRequest('https://example.com', 'POST'));
169 
170  // No provider entry exists
171  self::assertFalse($this->subject->deactivate($request, ‪MfaProviderPropertyManager::create($this->subject, $this->user)));
172 
173  // Only an active provider can be deactivated
174  $this->‪setupUser(['active' => false, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB']);
175  self::assertFalse($this->subject->deactivate($request, ‪MfaProviderPropertyManager::create($this->subject, $this->user)));
176 
177  // Active provider is deactivated
178  $this->‪setupUser(['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB']);
179  self::assertTrue($this->subject->deactivate($request, ‪MfaProviderPropertyManager::create($this->subject, $this->user)));
180  }
181 
182  #[Test]
183  public function ‪unlockTest(): void
184  {
185  $request = (new ‪ServerRequest('https://example.com', 'POST'));
186 
187  // No provider entry exists
188  self::assertFalse($this->subject->unlock($request, ‪MfaProviderPropertyManager::create($this->subject, $this->user)));
189 
190  // Provider is inactive (missing secret)
191  $this->‪setupUser(['active' => true, 'attempts' => 3]);
192  self::assertFalse($this->subject->unlock($request, ‪MfaProviderPropertyManager::create($this->subject, $this->user)));
193 
194  // Provider is not locked
195  $this->‪setupUser(['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB', 'attempts' => 0]);
196  self::assertFalse($this->subject->unlock($request, ‪MfaProviderPropertyManager::create($this->subject, $this->user)));
197 
198  // Active and locked provider is unlocked
199  $this->‪setupUser(['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB', 'attempts' => 3]);
200  self::assertTrue($this->subject->unlock($request, ‪MfaProviderPropertyManager::create($this->subject, $this->user)));
201  }
202 
203  #[Test]
204  public function ‪updateTest(): void
205  {
206  $request = (new ‪ServerRequest('https://example.com', 'POST'));
207 
208  // No provider entry exists
209  self::assertFalse($this->subject->update($request, ‪MfaProviderPropertyManager::create($this->subject, $this->user)));
210 
211  // Provider is inactive (missing secret)
212  $this->‪setupUser(['active' => true, 'attempts' => 0]);
213  self::assertFalse($this->subject->update($request, ‪MfaProviderPropertyManager::create($this->subject, $this->user)));
214 
215  // Provider is locked (attempts=3)
216  $this->‪setupUser(['active' => true, 'attempts' => 3]);
217  self::assertFalse($this->subject->update($request, ‪MfaProviderPropertyManager::create($this->subject, $this->user)));
218 
219  // Active and unlocked provider is updated
220  $this->‪setupUser(['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB', 'attempts' => 0]);
221  $request = $request->withParsedBody(['name' => 'some name']);
222  self::assertTrue($this->subject->update($request, ‪MfaProviderPropertyManager::create($this->subject, $this->user)));
223  }
224 
225  #[Test]
226  public function ‪setupViewTest(): void
227  {
228  $request = (new ‪ServerRequest('https://example.com', 'POST'));
229  $propertyManager = ‪MfaProviderPropertyManager::create($this->subject, $this->user);
230  $response = $this->subject->handleRequest($request, $propertyManager, MfaViewType::SETUP)->getBody()->getContents();
231 
232  self::assertMatchesRegularExpression('/<input.*id="totp"/s', $response);
233  self::assertMatchesRegularExpression('/<input.*id="secret"/s', $response);
234  self::assertMatchesRegularExpression('/<div.*id="qr-code"/s', $response);
235  self::assertMatchesRegularExpression('/<typo3-mfa-totp-url-info-button.*url="otpauth:\/\//s', $response);
236  }
237 
238  #[Test]
239  public function ‪editViewTest(): void
240  {
241  $request = (new ‪ServerRequest('https://example.com', 'POST'));
242  $this->‪setupUser(['name' => 'some name', 'updated' => 1616099471, 'lastUsed' => 1616099472]);
243  $propertyManager = ‪MfaProviderPropertyManager::create($this->subject, $this->user);
244  $response = $this->subject->handleRequest($request, $propertyManager, MfaViewType::EDIT)->getBody()->getContents();
245 
246  self::assertMatchesRegularExpression('/<td>.*Name.*<td>.*some name/s', $response);
247  self::assertMatchesRegularExpression('/<td>.*Last updated.*<td>.*2021-03-18/s', $response);
248  self::assertMatchesRegularExpression('/<td>.*Last used.*<td>.*2021-03-18/s', $response);
249  }
250 
251  #[Test]
252  public function ‪authViewTest(): void
253  {
254  $request = (new ‪ServerRequest('https://example.com', 'POST'));
255  $this->‪setupUser(['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB', 'attempts' => 0]);
256  $propertyManager = ‪MfaProviderPropertyManager::create($this->subject, $this->user);
257  $response = $this->subject->handleRequest($request, $propertyManager, ‪MfaViewType::AUTH)->getBody()->getContents();
258 
259  self::assertMatchesRegularExpression('/<input.*id="totp"/s', $response);
260 
261  // Lock the provider by setting attempts=3
262  $this->‪setupUser(['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB', 'attempts' => 3]);
263  $propertyManager = ‪MfaProviderPropertyManager::create($this->subject, $this->user);
264  $response = $this->subject->handleRequest($request, $propertyManager, ‪MfaViewType::AUTH)->getBody()->getContents();
265 
266  self::assertStringContainsString('The maximum attempts for this provider are exceeded.', $response);
267  }
268 
269  protected function ‪setupUser(array $properties = []): void
270  {
271  $this->user->user['mfa'] = json_encode(['totp' => $properties]);
272  }
273 }
‪TYPO3\CMS\Core\Localization\LanguageServiceFactory
Definition: LanguageServiceFactory.php:25
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\deactivateTest
‪deactivateTest()
Definition: TotpProviderTest.php:166
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\updateTest
‪updateTest()
Definition: TotpProviderTest.php:204
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\$hashService
‪HashService $hashService
Definition: TotpProviderTest.php:37
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\setupUser
‪setupUser(array $properties=[])
Definition: TotpProviderTest.php:269
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\isActiveTest
‪isActiveTest()
Definition: TotpProviderTest.php:63
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest
Definition: TotpProviderTest.php:35
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider
Definition: RecoveryCodesProviderTest.php:18
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderManifestInterface
Definition: MfaProviderManifestInterface.php:26
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\Totp
Definition: Totp.php:30
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:54
‪TYPO3\CMS\Core\Authentication\Mfa\AUTH
‪@ AUTH
Definition: MfaViewType.php:27
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\activateTest
‪activateTest()
Definition: TotpProviderTest.php:143
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager\create
‪static create(MfaProviderManifestInterface $provider, AbstractUserAuthentication $user)
Definition: MfaProviderPropertyManager.php:193
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\editViewTest
‪editViewTest()
Definition: TotpProviderTest.php:239
‪TYPO3\CMS\Core\Http\ServerRequest
Definition: ServerRequest.php:39
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\$subject
‪MfaProviderManifestInterface $subject
Definition: TotpProviderTest.php:38
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager
Definition: MfaProviderPropertyManager.php:33
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\verifyTest
‪verifyTest()
Definition: TotpProviderTest.php:97
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\authViewTest
‪authViewTest()
Definition: TotpProviderTest.php:252
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\unlockTest
‪unlockTest()
Definition: TotpProviderTest.php:183
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\$user
‪BackendUserAuthentication $user
Definition: TotpProviderTest.php:36
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\canProcessTest
‪canProcessTest()
Definition: TotpProviderTest.php:51
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\isLockedTest
‪isLockedTest()
Definition: TotpProviderTest.php:82
‪TYPO3\CMS\Core\Authentication\Mfa\MfaViewType
‪MfaViewType
Definition: MfaViewType.php:24
‪TYPO3\CMS\Core\Crypto\HashService
Definition: HashService.php:27
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\setupViewTest
‪setupViewTest()
Definition: TotpProviderTest.php:226
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry
Definition: MfaProviderRegistry.php:28
‪TYPO3\CMS\Core\Tests\Functional\Authentication\Mfa\Provider\TotpProviderTest\setUp
‪setUp()
Definition: TotpProviderTest.php:40