‪TYPO3CMS  ‪main
MfaConfigurationControllerTest.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 
35 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
36 
37 final class ‪MfaConfigurationControllerTest extends FunctionalTestCase
38 {
42 
44  'BE' => [
45  'recommendedMfaProvider' => 'totp',
46  'requireMfa' => 1,
47  ],
48  ];
49 
50  protected function ‪setUp(): void
51  {
52  parent::setUp();
53  $this->importCSVDataSet(__DIR__ . '/../Fixtures/be_users.csv');
54  $this->setUpBackendUser(1);
56 
57  $this->subject = new ‪MfaConfigurationController(
58  $this->get(IconFactory::class),
59  $this->get(UriBuilder::class),
60  $this->get(ModuleTemplateFactory::class),
61  );
62  $this->subject->injectMfaProviderRegistry($this->get(MfaProviderRegistry::class));
63 
64  $this->request = (new ‪ServerRequest('https://example.com/typo3/'))
65  ->withAttribute('applicationType', ‪SystemEnvironmentBuilder::REQUESTTYPE_BE)
66  ->withAttribute('route', new ‪Route('path', ['packageName' => 'typo3/cms-backend']));
67  $this->normalizedParams = new ‪NormalizedParams([], [], '', '');
68  }
69 
74  {
75  $queryParams = [
76  'action' => 'unknown',
77  ];
78 
79  ‪$request = $this->request
80  ->‪withAttribute('normalizedParams', $this->normalizedParams)
81  ->withQueryParams($queryParams);
82  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
83  $response = $this->subject->handleRequest(‪$request);
84 
85  self::assertEquals(400, $response->getStatusCode());
86  self::assertEquals('Action not allowed', $response->getBody()->getContents());
87  }
88 
93  {
94  ‪$request = $this->request->‪withAttribute('normalizedParams', $this->normalizedParams);
95  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
96  $response = $this->subject->handleRequest(‪$request);
97 
98  self::assertEquals(200, $response->getStatusCode());
99  $response->getBody()->rewind();
100  self::assertStringContainsString('Multi-factor Authentication Overview', $response->getBody()->getContents());
101  }
102 
107  {
108  ‪$request = $this->request->‪withAttribute('normalizedParams', $this->normalizedParams);
109  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
110  $response = $this->subject->handleRequest(‪$request);
111 
112  self::assertEquals(200, $response->getStatusCode());
113 
114  $response->getBody()->rewind();
115  $responseContent = $response->getBody()->getContents();
116  foreach (GeneralUtility::makeInstance(MfaProviderRegistry::class)->getProviders() as $provider) {
117  self::assertStringContainsString('id="' . $provider->getIdentifier() . '-provider"', $responseContent);
118  }
119  }
120 
125  {
126  ‪$request = $this->request->‪withAttribute('normalizedParams', $this->normalizedParams);
127  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
128  $response = $this->subject->handleRequest(‪$request);
129 
130  self::assertEquals(200, $response->getStatusCode());
131  $response->getBody()->rewind();
132  $responseContent = $response->getBody()->getContents();
133  self::assertStringContainsString('Multi-factor authentication required', $responseContent);
134  self::assertMatchesRegularExpression('/<div.*class="card card-size-fixed-small card-success".*id="totp-provider"/s', $responseContent);
135  }
136 
141  {
142  ‪$GLOBALS['BE_USER']->user['mfa'] = json_encode(['totp' => ['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB']]);
143  ‪$GLOBALS['BE_USER']->uc['mfa']['defaultProvider'] = 'totp';
144 
145  ‪$request = $this->request->‪withAttribute('normalizedParams', $this->normalizedParams);
146  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
147  $response = $this->subject->handleRequest(‪$request);
148 
149  self::assertEquals(200, $response->getStatusCode());
150  $response->getBody()->rewind();
151  self::assertMatchesRegularExpression('/<span.*title="Default provider">/s', $response->getBody()->getContents());
152  }
153 
158  {
159  $returnUrl = ‪Environment::getPublicPath() . '/typo3/some/module?token=123';
160 
161  $queryParams = [
162  'action' => 'overview',
163  'returnUrl' => $returnUrl,
164  ];
165 
166  ‪$request = $this->request
167  ->‪withAttribute('normalizedParams', $this->normalizedParams)
168  ->withQueryParams($queryParams);
169  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
170  $response = $this->subject->handleRequest(‪$request);
171 
172  self::assertEquals(200, $response->getStatusCode());
173  $response->getBody()->rewind();
174  self::assertStringContainsString('href="' . $returnUrl . '" class="btn btn-sm btn-default " title="Go back"', $response->getBody()->getContents());
175  }
176 
182  string $action,
183  string $provider,
184  bool $providerActive,
185  string $flashMessage
186  ): void {
187  $queryParams = [
188  'action' => $action,
189  'identifier' => $provider,
190  ];
191 
192  if ($providerActive) {
193  ‪$GLOBALS['BE_USER']->user['mfa'] = json_encode(['totp' => ['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB']]);
194  }
195 
196  ‪$request = $this->request
197  ->‪withAttribute('normalizedParams', $this->normalizedParams)
198  ->withQueryParams($queryParams);
199  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
200  $response = $this->subject->handleRequest(‪$request);
201 
202  $redirect = parse_url($response->getHeaderLine('location'));
203  $query = [];
204  parse_str($redirect['query'] ?? '', $query);
205  $message = GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier()->getAllMessages()[0];
206 
207  self::assertEquals(302, $response->getStatusCode());
208  self::assertEquals('/typo3/mfa', $redirect['path']);
209  self::assertEquals('overview', $query['action']);
210  self::assertEquals($flashMessage, $message->getMessage());
211  }
212 
214  {
215  yield 'Empty provider' => [
216  'setup',
217  '',
218  false,
219  'Selected MFA provider was not found!',
220  ];
221  yield 'Invalid provider' => [
222  'setup',
223  'unknown',
224  false,
225  'Selected MFA provider was not found!',
226  ];
227  yield 'Inactive provider on edit' => [
228  'edit',
229  'totp',
230  false,
231  'Selected MFA provider has to be active to perform this action!',
232  ];
233  yield 'Inactive provider on update' => [
234  'save',
235  'totp',
236  false,
237  'Selected MFA provider has to be active to perform this action!',
238  ];
239  yield 'Inactive provider on deactivate' => [
240  'deactivate',
241  'totp',
242  false,
243  'Selected MFA provider has to be active to perform this action!',
244  ];
245  yield 'Inactive provider on unlock' => [
246  'unlock',
247  'totp',
248  false,
249  'Selected MFA provider has to be active to perform this action!',
250  ];
251  yield 'Active provider on setup' => [
252  'setup',
253  'totp',
254  true,
255  'Selected MFA provider has to be inactive to perform this action!',
256  ];
257  yield 'Active provider on activate' => [
258  'activate',
259  'totp',
260  true,
261  'Selected MFA provider has to be inactive to perform this action!',
262  ];
263  }
264 
271  string $action,
272  string $provider,
273  bool $providerActive,
274  bool $redirect,
275  string $searchString
276  ): void {
277  $parsedBody = [];
278  $queryParams = [
279  'action' => $action,
280  'identifier' => $provider,
281  ];
282 
283  if ($providerActive) {
284  ‪$GLOBALS['BE_USER']->user['mfa'] = json_encode([
285  'totp' => [
286  'active' => true,
287  'secret' => 'KRMVATZTJFZUC53FONXW2ZJB',
288  'attempts' => ($action === 'unlock' ? 3 : 0),
289  ],
290  ]);
291  }
292 
293  if ($action === 'activate') {
294  $timestamp = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('date', 'timestamp');
295  $parsedBody['totp'] = GeneralUtility::makeInstance(
296  Totp::class,
297  'KRMVATZTJFZUC53FONXW2ZJB'
298  )->generateTotp((int)floor($timestamp / 30));
299  $parsedBody['secret'] = 'KRMVATZTJFZUC53FONXW2ZJB';
300  $parsedBody['checksum'] = ‪GeneralUtility::hmac('KRMVATZTJFZUC53FONXW2ZJB', 'totp-setup');
301  }
302 
303  ‪$request = $this->request
304  ->‪withAttribute('normalizedParams', $this->normalizedParams)
305  ->withQueryParams($queryParams)
306  ->withParsedBody($parsedBody);
307  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
308  $response = $this->subject->handleRequest(‪$request);
309 
310  if ($redirect) {
311  self::assertEquals(302, $response->getStatusCode());
312  $messages = GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier()->getAllMessages()[0];
313  self::assertEquals($searchString, $messages->getMessage());
314  } else {
315  self::assertEquals(200, $response->getStatusCode());
316  $response->getBody()->rewind();
317  self::assertStringContainsString($searchString, $response->getBody()->getContents());
318  }
319  }
320 
321  public static function ‪handleRequestForwardsToCorrectActionTestDataProvider(): \Generator
322  {
323  yield 'Edit provider' => [
324  'edit',
325  'totp',
326  true,
327  false,
328  'Edit Time-based one-time password',
329  ];
330  yield 'Save provider' => [
331  'save',
332  'totp',
333  true,
334  true,
335  'Successfully updated MFA provider Time-based one-time password.',
336  ];
337  yield 'Deactivate provider' => [
338  'deactivate',
339  'totp',
340  true,
341  true,
342  'Successfully deactivated MFA provider Time-based one-time password.',
343  ];
344  yield 'Unlock provider' => [
345  'unlock',
346  'totp',
347  true,
348  true,
349  'Successfully unlocked MFA provider Time-based one-time password.',
350  ];
351  yield 'Setup provider' => [
352  'setup',
353  'totp',
354  false,
355  false,
356  'Set up Time-based one-time password',
357  ];
358  yield 'Activate provider' => [
359  'activate',
360  'totp',
361  false,
362  true,
363  'Successfully activated MFA provider Time-based one-time password.',
364  ];
365  }
366 
372  string $action,
373  bool $providerActive,
374  string $providerContent
375  ): void {
376  $queryParams = [
377  'action' => $action,
378  'identifier' => 'totp',
379  ];
380 
381  if ($providerActive) {
382  ‪$GLOBALS['BE_USER']->user['mfa'] = json_encode(['totp' => ['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB']]);
383  }
384 
385  ‪$request = $this->request
386  ->‪withAttribute('normalizedParams', $this->normalizedParams)
387  ->withQueryParams($queryParams);
388  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
389  $response = $this->subject->handleRequest(‪$request);
390 
391  $response->‪getBody()->rewind();
392  $responseContent = $response->getBody()->getContents();
393 
394  self::assertEquals(200, $response->getStatusCode());
395  self::assertMatchesRegularExpression('/<a.*href="\/typo3\/mfa.*title="Close">/s', $responseContent);
396  self::assertMatchesRegularExpression('/<button.*name="save".*form="mfaConfigurationController">/s', $responseContent);
397  self::assertMatchesRegularExpression('/<form.*name="' . $action . '".*id="mfaConfigurationController">/s', $responseContent);
398 
399  // Ensure provider specific content is added as well
400  self::assertMatchesRegularExpression($providerContent, $responseContent);
401  }
402 
404  {
405  yield 'Edit provider' => ['edit', true, '/<input.*id="name"/s'];
406  yield 'Setup provider' => ['setup', false, '/<input.*id="totp"/s'];
407  }
408 }
‪TYPO3\CMS\Core\Http\Message\getBody
‪StreamInterface getBody()
Definition: Message.php:287
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest
Definition: MfaConfigurationControllerTest.php:38
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\setUp
‪setUp()
Definition: MfaConfigurationControllerTest.php:50
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\handleRequestAddsInformationAboutMfaBeingRequiredAndRecommendedTest
‪handleRequestAddsInformationAboutMfaBeingRequiredAndRecommendedTest()
Definition: MfaConfigurationControllerTest.php:124
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\handleRequestRedirectsToOverviewOnActionProviderMismatchTest
‪handleRequestRedirectsToOverviewOnActionProviderMismatchTest(string $action, string $provider, bool $providerActive, string $flashMessage)
Definition: MfaConfigurationControllerTest.php:181
‪TYPO3\CMS\Backend\Template\ModuleTemplateFactory
Definition: ModuleTemplateFactory.php:33
‪TYPO3\CMS\Core\Core\SystemEnvironmentBuilder
Definition: SystemEnvironmentBuilder.php:41
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\$normalizedParams
‪NormalizedParams $normalizedParams
Definition: MfaConfigurationControllerTest.php:41
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static getPublicPath()
Definition: Environment.php:187
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\handleRequestShowsAllRegisteredProvidersTest
‪handleRequestShowsAllRegisteredProvidersTest()
Definition: MfaConfigurationControllerTest.php:106
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\handleRequestReturnsBadRequestForInvalidActionTest
‪handleRequestReturnsBadRequestForInvalidActionTest()
Definition: MfaConfigurationControllerTest.php:73
‪TYPO3\CMS\Core\Core\SystemEnvironmentBuilder\REQUESTTYPE_BE
‪const REQUESTTYPE_BE
Definition: SystemEnvironmentBuilder.php:45
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\$request
‪ServerRequest $request
Definition: MfaConfigurationControllerTest.php:40
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\Totp
Definition: Totp.php:30
‪TYPO3\CMS\Backend\Routing\Route
Definition: Route.php:24
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:55
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\handleRequestRespectsReturnUrlTest
‪handleRequestRespectsReturnUrlTest()
Definition: MfaConfigurationControllerTest.php:157
‪TYPO3\CMS\Core\Core\Bootstrap\initializeLanguageObject
‪static initializeLanguageObject()
Definition: Bootstrap.php:562
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\handleRequestAddsFormOnInteractionViewsTest
‪handleRequestAddsFormOnInteractionViewsTest(string $action, bool $providerActive, string $providerContent)
Definition: MfaConfigurationControllerTest.php:371
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\handleRequestForwardsToCorrectActionTestDataProvider
‪static handleRequestForwardsToCorrectActionTestDataProvider()
Definition: MfaConfigurationControllerTest.php:321
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\handleRequestAddsFormOnInteractionViewsTestTestDataProvider
‪static handleRequestAddsFormOnInteractionViewsTestTestDataProvider()
Definition: MfaConfigurationControllerTest.php:403
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\handleRequestRedirectsToOverviewOnActionProviderMismatchTestDataProvider
‪static handleRequestRedirectsToOverviewOnActionProviderMismatchTestDataProvider()
Definition: MfaConfigurationControllerTest.php:213
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:44
‪TYPO3\CMS\Core\Utility\GeneralUtility\hmac
‪static string hmac($input, $additionalSecret='')
Definition: GeneralUtility.php:475
‪TYPO3\CMS\Core\Http\ServerRequest
Definition: ServerRequest.php:39
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\handleRequestForwardsToCorrectActionTest
‪handleRequestForwardsToCorrectActionTest(string $action, string $provider, bool $providerActive, bool $redirect, string $searchString)
Definition: MfaConfigurationControllerTest.php:270
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Core\Core\Bootstrap
Definition: Bootstrap.php:61
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\handleRequestFallsBackToOverviewActionIfNoActionGivenTest
‪handleRequestFallsBackToOverviewActionIfNoActionGivenTest()
Definition: MfaConfigurationControllerTest.php:92
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\$configurationToUseInTestInstance
‪array $configurationToUseInTestInstance
Definition: MfaConfigurationControllerTest.php:43
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Backend\Controller\MfaConfigurationController
Definition: MfaConfigurationController.php:48
‪TYPO3\CMS\Core\Messaging\FlashMessageService
Definition: FlashMessageService.php:27
‪TYPO3\CMS\Backend\Tests\Functional\Controller
Definition: BackendControllerTest.php:18
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\handleRequestIndicatesDefaultProviderTest
‪handleRequestIndicatesDefaultProviderTest()
Definition: MfaConfigurationControllerTest.php:140
‪TYPO3\CMS\Core\Http\NormalizedParams
Definition: NormalizedParams.php:38
‪TYPO3\CMS\Core\Http\ServerRequest\withAttribute
‪static withAttribute(string $name, $value)
Definition: ServerRequest.php:310
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaConfigurationControllerTest\$subject
‪MfaConfigurationController $subject
Definition: MfaConfigurationControllerTest.php:39
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry
Definition: MfaProviderRegistry.php:28