‪TYPO3CMS  ‪main
MfaControllerTest.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;
21 use Psr\EventDispatcher\EventDispatcherInterface;
37 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
38 
39 final class ‪MfaControllerTest extends FunctionalTestCase
40 {
43 
53  'SYS' => [
54  'systemMaintainers' => [],
55  ],
56  ];
57 
58  protected function ‪setUp(): void
59  {
60  parent::setUp();
61  $this->importCSVDataSet(__DIR__ . '/../Fixtures/be_users.csv');
62  $backendUser = $this->setUpBackendUser(1);
63  ‪$GLOBALS['LANG'] = $this->get(LanguageServiceFactory::class)->createFromUserPreferences($backendUser);
64 
65  $this->subject = new ‪MfaController(
66  $this->get(UriBuilder::class),
67  $this->get(AuthenticationStyleInformation::class),
68  $this->get(PageRenderer::class),
69  $this->get(ExtensionConfiguration::class),
70  new ‪Logger('testing'),
71  $this->get(BackendViewFactory::class),
72  $this->get(EventDispatcherInterface::class)
73  );
74  $this->subject->injectMfaProviderRegistry($this->get(MfaProviderRegistry::class));
75 
76  $this->request = (new ‪ServerRequest('https://example.com/typo3/'))
77  ->withAttribute('applicationType', ‪SystemEnvironmentBuilder::REQUESTTYPE_BE)
78  ->withAttribute('route', new ‪Route('path', ['packageName' => 'typo3/cms-backend']));
79  }
80 
81  #[Test]
83  {
84  $this->expectException(\InvalidArgumentException::class);
85  $this->expectExceptionCode(1611879244);
86 
87  ‪$request = $this->request->‪withQueryParams(['action' => 'unknown']);
88  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
89  $this->subject->handleRequest(‪$request);
90  }
91 
92  #[Test]
94  {
95  $this->expectException(\InvalidArgumentException::class);
96  $this->expectExceptionCode(1611879242);
97 
98  ‪$request = $this->request->‪withQueryParams(['action' => 'auth']);
99  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
100  $this->subject->handleRequest(‪$request);
101  }
102 
103  #[Test]
105  {
106  $this->expectException(\InvalidArgumentException::class);
107  $this->expectExceptionCode(1611879242);
108 
109  $queryParams = [
110  'action' => 'auth',
111  'identifier' => 'totp',
112  ];
113 
114  ‪$request = $this->request->‪withQueryParams($queryParams);
115  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
116  $this->subject->handleRequest(‪$request);
117  }
118 
119  #[Test]
120  public function ‪handleRequestReturnsAuthViewTest(): void
121  {
122  ‪$GLOBALS['BE_USER']->user['mfa'] = json_encode([
123  'totp' => ['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB'],
124  ]);
125 
126  $queryParams = [
127  'action' => 'auth',
128  'identifier' => 'totp',
129  ];
130 
131  ‪$request = $this->request->‪withQueryParams($queryParams);
132  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
133  $response = $this->subject->handleRequest(‪$request);
134 
135  self::assertEquals(200, $response->getStatusCode());
136 
137  $responseContent = $response->getBody()->__toString();
138 
139  // Auth view for provider is renderer
140  self::assertStringContainsString('Time-based one-time password', $responseContent);
141  self::assertMatchesRegularExpression('/<form.*name="verify".*id="mfaController">/s', $responseContent);
142 
143  // Ensure provider specific content is added as well
144  self::assertMatchesRegularExpression('/<input.*id="totp"/s', $responseContent);
145  }
146 
147  #[Test]
149  {
150  ‪$GLOBALS['BE_USER']->user['mfa'] = json_encode([
151  'totp' => ['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB', 'attempts' => 3],
152  ]);
153 
154  $queryParams = [
155  'action' => 'auth',
156  'identifier' => 'totp',
157  ];
158 
159  ‪$request = $this->request->‪withQueryParams($queryParams);
160  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
161  $response = $this->subject->handleRequest(‪$request);
162 
163  self::assertEquals(200, $response->getStatusCode());
164  self::assertStringContainsString('This provider is temporarily locked!', $response->getBody()->__toString());
165  }
166 
167  #[Test]
169  {
170  ‪$GLOBALS['BE_USER']->user['mfa'] = json_encode([
171  'totp' => ['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB'],
172  'recovery-codes' => ['active' => true, 'codes' => ['some-code']],
173  ]);
174 
175  $queryParams = [
176  'action' => 'auth',
177  'identifier' => 'totp',
178  ];
179 
180  ‪$request = $this->request->‪withQueryParams($queryParams);
181  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
182  $response = $this->subject->handleRequest(‪$request);
183 
184  self::assertEquals(200, $response->getStatusCode());
185 
186  $responseContent = $response->getBody()->__toString();
187  self::assertStringContainsString('Alternative providers', $responseContent);
188  self::assertMatchesRegularExpression('/<a.*title="Use Recovery codes"/s', $responseContent);
189  }
190 
191  #[Test]
193  {
194  ‪$GLOBALS['BE_USER']->user['mfa'] = json_encode([
195  'totp' => ['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB'],
196  ]);
197 
198  $queryParams = [
199  'action' => 'verify',
200  'identifier' => 'totp',
201  ];
202 
203  // The "totp" parameter is missing, therefore the TotpProvider will return false on ->canProcess()
204  ‪$request = $this->request->‪withQueryParams($queryParams);
205  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
206  $response = $this->subject->handleRequest(‪$request);
207 
208  self::assertEquals(302, $response->getStatusCode());
209  self::assertEquals('/typo3/login', parse_url($response->getHeaderLine('location'))['path']);
210  }
211 
212  #[Test]
214  {
215  ‪$GLOBALS['BE_USER']->user['mfa'] = json_encode([
216  'totp' => ['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB', 'attempts' => 3],
217  ]);
218 
219  $queryParams = [
220  'action' => 'verify',
221  'identifier' => 'totp',
222  'totp' => '123456',
223  ];
224 
225  ‪$request = $this->request->‪withQueryParams($queryParams);
226  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
227  $response = $this->subject->handleRequest(‪$request);
228 
229  self::assertEquals(302, $response->getStatusCode());
230  self::assertEquals('/typo3/login', parse_url($response->getHeaderLine('location'))['path']);
231  }
232 
233  #[Test]
235  {
236  ‪$GLOBALS['BE_USER']->user['mfa'] = json_encode([
237  'totp' => ['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB'],
238  ]);
239 
240  $queryParams = [
241  'action' => 'verify',
242  'identifier' => 'totp',
243  'totp' => '123456',
244  ];
245 
246  ‪$request = $this->request->‪withQueryParams($queryParams);
247  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
248  $response = $this->subject->handleRequest(‪$request);
249 
250  self::assertEquals(302, $response->getStatusCode());
251  self::assertEquals('/typo3/auth/mfa', parse_url($response->getHeaderLine('location'))['path']);
252  }
253 
254  #[Test]
256  {
257  ‪$GLOBALS['BE_USER']->user['mfa'] = json_encode([
258  'totp' => ['active' => true, 'secret' => 'KRMVATZTJFZUC53FONXW2ZJB'],
259  ]);
260 
261  $timestamp = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('date', 'timestamp');
262  $totp = GeneralUtility::makeInstance(
263  Totp::class,
264  'KRMVATZTJFZUC53FONXW2ZJB'
265  )->generateTotp((int)floor($timestamp / 30));
266 
267  $queryParams = [
268  'action' => 'verify',
269  'identifier' => 'totp',
270  'totp' => $totp,
271  ];
272 
273  // Ensure mfa session key is not set
274  self::assertFalse((bool)‪$GLOBALS['BE_USER']->getSessionData('mfa'));
275 
276  ‪$request = $this->request->‪withQueryParams($queryParams);
277  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
278  $response = $this->subject->handleRequest(‪$request);
279 
280  // Session key is set - User is authenticated
281  self::assertTrue(‪$GLOBALS['BE_USER']->getSessionData('mfa'));
282 
283  // Redirect back to login
284  self::assertEquals(302, $response->getStatusCode());
285  self::assertEquals('/typo3/login', parse_url($response->getHeaderLine('location'))['path']);
286  }
287 
288  #[Test]
290  {
291  ‪$request = $this->request->‪withQueryParams(['action' => 'cancel']);
292  ‪$GLOBALS['TYPO3_REQUEST'] = ‪$request;
293  $response = $this->subject->handleRequest(‪$request);
294 
295  self::assertEquals(302, $response->getStatusCode());
296  self::assertEquals('/typo3/login', parse_url($response->getHeaderLine('location'))['path']);
297  }
298 }
‪TYPO3\CMS\Core\Localization\LanguageServiceFactory
Definition: LanguageServiceFactory.php:25
‪TYPO3\CMS\Backend\View\AuthenticationStyleInformation
Definition: AuthenticationStyleInformation.php:32
‪TYPO3\CMS\Core\Http\ServerRequest\withQueryParams
‪static withQueryParams(array $query)
Definition: ServerRequest.php:159
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\handleRequestRedirectsToLoginOnCancelTest
‪handleRequestRedirectsToLoginOnCancelTest()
Definition: MfaControllerTest.php:289
‪TYPO3\CMS\Backend\View\BackendViewFactory
Definition: BackendViewFactory.php:35
‪TYPO3\CMS\Core\Configuration\ExtensionConfiguration
Definition: ExtensionConfiguration.php:47
‪TYPO3\CMS\Core\Core\SystemEnvironmentBuilder
Definition: SystemEnvironmentBuilder.php:41
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\$request
‪ServerRequest $request
Definition: MfaControllerTest.php:42
‪TYPO3\CMS\Core\Core\SystemEnvironmentBuilder\REQUESTTYPE_BE
‪const REQUESTTYPE_BE
Definition: SystemEnvironmentBuilder.php:45
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\setUp
‪setUp()
Definition: MfaControllerTest.php:58
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\Totp
Definition: Totp.php:30
‪TYPO3\CMS\Backend\Routing\Route
Definition: Route.php:24
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\handleRequestReturnsLockedAuthViewTest
‪handleRequestReturnsLockedAuthViewTest()
Definition: MfaControllerTest.php:148
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:54
‪TYPO3\CMS\Core\Page\PageRenderer
Definition: PageRenderer.php:44
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:44
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest
Definition: MfaControllerTest.php:40
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\$configurationToUseInTestInstance
‪array $configurationToUseInTestInstance
Definition: MfaControllerTest.php:52
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\handleRequestRedirectsToLoginOnLockedProviderRequestTest
‪handleRequestRedirectsToLoginOnLockedProviderRequestTest()
Definition: MfaControllerTest.php:213
‪TYPO3\CMS\Core\Http\ServerRequest
Definition: ServerRequest.php:39
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\handleRequestRedirectsToAuthViewOnUnsuccessfulAuthenticationTest
‪handleRequestRedirectsToAuthViewOnUnsuccessfulAuthenticationTest()
Definition: MfaControllerTest.php:234
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\handleRequestThrowsExceptionOnInvalidActionTest
‪handleRequestThrowsExceptionOnInvalidActionTest()
Definition: MfaControllerTest.php:82
‪TYPO3\CMS\Backend\Controller\MfaController
Definition: MfaController.php:50
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\$subject
‪MfaController $subject
Definition: MfaControllerTest.php:41
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\handleRequestRedirectsToLoginOnInvalidRequestTest
‪handleRequestRedirectsToLoginOnInvalidRequestTest()
Definition: MfaControllerTest.php:192
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Log\Logger
Definition: Logger.php:28
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\handleRequestSetsSessionKeyOnSuccessfulAuthenticationTest
‪handleRequestSetsSessionKeyOnSuccessfulAuthenticationTest()
Definition: MfaControllerTest.php:255
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\handleRequestThrowsExceptionOnInactiveProviderTest
‪handleRequestThrowsExceptionOnInactiveProviderTest()
Definition: MfaControllerTest.php:104
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\handleRequestReturnsAuthViewTest
‪handleRequestReturnsAuthViewTest()
Definition: MfaControllerTest.php:120
‪TYPO3\CMS\Backend\Tests\Functional\Controller
Definition: BackendControllerTest.php:18
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry
Definition: MfaProviderRegistry.php:28
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\handleRequestThrowsExceptionOnMissingProviderTest
‪handleRequestThrowsExceptionOnMissingProviderTest()
Definition: MfaControllerTest.php:93
‪TYPO3\CMS\Backend\Tests\Functional\Controller\MfaControllerTest\handleRequestReturnsAlternativeProvidersInAuthViewTest
‪handleRequestReturnsAlternativeProvidersInAuthViewTest()
Definition: MfaControllerTest.php:168