‪TYPO3CMS  11.5
TotpProvider.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 BaconQrCode\Renderer\Image\SvgImageBackEnd;
21 use BaconQrCode\Renderer\ImageRenderer;
22 use BaconQrCode\Renderer\RendererStyle\RendererStyle;
23 use BaconQrCode\Writer;
24 use Psr\Http\Message\ResponseInterface;
25 use Psr\Http\Message\ServerRequestInterface;
33 
40 {
41  private const ‪MAX_ATTEMPTS = 3;
42 
44 
46  {
47  $this->context = ‪$context;
48  }
49 
56  public function ‪canProcess(ServerRequestInterface $request): bool
57  {
58  return $this->‪getTotp($request) !== '';
59  }
60 
68  public function ‪isActive(‪MfaProviderPropertyManager $propertyManager): bool
69  {
70  return (bool)$propertyManager->‪getProperty('active')
71  && $propertyManager->‪getProperty('secret', '') !== '';
72  }
73 
81  public function ‪isLocked(‪MfaProviderPropertyManager $propertyManager): bool
82  {
83  $attempts = (int)$propertyManager->‪getProperty('attempts', 0);
84 
85  // Assume the provider is locked in case the maximum attempts are exceeded.
86  // A provider however can only be locked if set up - an entry exists in database.
87  return $propertyManager->‪hasProviderEntry() && $attempts >= ‪self::MAX_ATTEMPTS;
88  }
89 
97  public function ‪verify(ServerRequestInterface $request, ‪MfaProviderPropertyManager $propertyManager): bool
98  {
99  if (!$this->‪isActive($propertyManager) || $this->‪isLocked($propertyManager)) {
100  // Can not verify an inactive or locked provider
101  return false;
102  }
103 
104  $totp = $this->‪getTotp($request);
105  $secret = $propertyManager->‪getProperty('secret', '');
106  $verified = GeneralUtility::makeInstance(Totp::class, $secret)->verifyTotp($totp, 2);
107  if (!$verified) {
108  $attempts = $propertyManager->‪getProperty('attempts', 0);
109  $propertyManager->‪updateProperties(['attempts' => ++$attempts]);
110  return false;
111  }
112  $propertyManager->‪updateProperties([
113  'attempts' => 0,
114  'lastUsed' => $this->context->getPropertyFromAspect('date', 'timestamp'),
115  ]);
116  return true;
117  }
118 
127  public function ‪activate(ServerRequestInterface $request, ‪MfaProviderPropertyManager $propertyManager): bool
128  {
129  if ($this->‪isActive($propertyManager)) {
130  // Can not activate an active provider
131  return false;
132  }
133 
134  if (!$this->‪canProcess($request)) {
135  // Return since the request can not be processed by this provider
136  return false;
137  }
138 
139  $secret = (string)($request->getParsedBody()['secret'] ?? '');
140  $checksum = (string)($request->getParsedBody()['checksum'] ?? '');
141  if ($secret === '' || !hash_equals(GeneralUtility::hmac($secret, 'totp-setup'), $checksum)) {
142  // Return since the request does not contain the initially created secret
143  return false;
144  }
145 
146  $totpInstance = GeneralUtility::makeInstance(Totp::class, $secret);
147  if (!$totpInstance->verifyTotp($this->getTotp($request), 2)) {
148  // Return since the given TOTP could not be verified
149  return false;
150  }
151 
152  // If valid, prepare the provider properties to be stored
153  $properties = ['secret' => $secret, 'active' => true];
154  if (($name = (string)($request->getParsedBody()['name'] ?? '')) !== '') {
155  $properties['name'] = $name;
156  }
157 
158  // Usually there should be no entry if the provider is not activated, but to prevent the
159  // provider from being unable to activate again, we update the existing entry in such case.
160  return $propertyManager->‪hasProviderEntry()
161  ? $propertyManager->‪updateProperties($properties)
162  : $propertyManager->‪createProviderEntry($properties);
163  }
164 
172  public function ‪update(ServerRequestInterface $request, ‪MfaProviderPropertyManager $propertyManager): bool
173  {
174  if (!$this->‪isActive($propertyManager) || $this->‪isLocked($propertyManager)) {
175  // Can not update an inactive or locked provider
176  return false;
177  }
178 
179  $name = (string)($request->getParsedBody()['name'] ?? '');
180  if ($name !== '') {
181  return $propertyManager->‪updateProperties(['name' => $name]);
182  }
183 
184  // Provider properties successfully updated
185  return true;
186  }
187 
195  public function ‪unlock(ServerRequestInterface $request, ‪MfaProviderPropertyManager $propertyManager): bool
196  {
197  if (!$this->‪isActive($propertyManager) || !$this->‪isLocked($propertyManager)) {
198  // Can not unlock an inactive or not locked provider
199  return false;
200  }
201 
202  // Reset the attempts
203  return $propertyManager->‪updateProperties(['attempts' => 0]);
204  }
205 
215  public function ‪deactivate(ServerRequestInterface $request, ‪MfaProviderPropertyManager $propertyManager): bool
216  {
217  if (!$this->‪isActive($propertyManager)) {
218  // Can not deactivate an inactive provider
219  return false;
220  }
221 
222  // Delete the provider entry
223  return $propertyManager->‪deleteProviderEntry();
224  }
225 
235  public function ‪handleRequest(
236  ServerRequestInterface $request,
237  ‪MfaProviderPropertyManager $propertyManager,
238  string $type
239  ): ResponseInterface {
240  $view = GeneralUtility::makeInstance(StandaloneView::class);
241  $view->setTemplateRootPaths(['EXT:core/Resources/Private/Templates/Authentication/MfaProvider/Totp']);
242  switch ($type) {
244  $this->‪prepareSetupView($view, $propertyManager);
245  break;
247  $this->‪prepareEditView($view, $propertyManager);
248  break;
250  $this->‪prepareAuthView($view, $propertyManager);
251  break;
252  }
253  return new ‪HtmlResponse($view->assign('providerIdentifier', $propertyManager->‪getIdentifier())->render());
254  }
255 
261  protected function ‪prepareSetupView(‪StandaloneView $view, ‪MfaProviderPropertyManager $propertyManager): void
262  {
263  $userData = $propertyManager->‪getUser()->user ?? [];
264  $secret = ‪Totp::generateEncodedSecret([(string)($userData['uid'] ?? ''), (string)($userData['username'] ?? '')]);
265  $totpInstance = GeneralUtility::makeInstance(Totp::class, $secret);
266  $totpAuthUrl = $totpInstance->getTotpAuthUrl(
267  (string)(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? 'TYPO3'),
268  (string)($userData['email'] ?? '') ?: (string)($userData['username'] ?? '')
269  );
270  $view->‪setTemplate('Setup');
271  $view->‪assignMultiple([
272  'secret' => $secret,
273  'totpAuthUrl' => $totpAuthUrl,
274  'qrCode' => $this->‪getSvgQrCode($totpAuthUrl),
275  // Generate hmac of the secret to prevent it from being changed in the setup from
276  'checksum' => GeneralUtility::hmac($secret, 'totp-setup'),
277  ]);
278  }
279 
283  protected function ‪prepareEditView(‪StandaloneView $view, ‪MfaProviderPropertyManager $propertyManager): void
284  {
285  $view->‪setTemplate('Edit');
286  $view->‪assignMultiple([
287  'name' => $propertyManager->‪getProperty('name'),
288  'lastUsed' => $this->getDateTime($propertyManager->‪getProperty('lastUsed', 0)),
289  'updated' => $this->getDateTime($propertyManager->‪getProperty('updated', 0)),
290  ]);
291  }
292 
296  protected function ‪prepareAuthView(‪StandaloneView $view, ‪MfaProviderPropertyManager $propertyManager): void
297  {
298  $view->‪setTemplate('Auth');
299  $view->‪assign('isLocked', $this->‪isLocked($propertyManager));
300  }
301 
308  protected function ‪getTotp(ServerRequestInterface $request): string
309  {
310  return trim((string)($request->getQueryParams()['totp'] ?? $request->getParsedBody()['totp'] ?? ''));
311  }
312 
319  protected function ‪getSvgQrCode(string $content): string
320  {
321  $qrCodeRenderer = new ImageRenderer(
322  new RendererStyle(225, 4),
323  new SvgImageBackEnd()
324  );
325 
326  return (new Writer($qrCodeRenderer))->writeString($content);
327  }
328 
335  protected function ‪getDateTime(int $timestamp): string
336  {
337  if ($timestamp === 0) {
338  return '';
339  }
340 
341  return date(
342  ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
343  $timestamp
344  ) ?: '';
345  }
346 }
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\__construct
‪__construct(Context $context)
Definition: TotpProvider.php:45
‪TYPO3\CMS\Core\Authentication\Mfa\MfaViewType\EDIT
‪const EDIT
Definition: MfaViewType.php:28
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\canProcess
‪bool canProcess(ServerRequestInterface $request)
Definition: TotpProvider.php:56
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\unlock
‪bool unlock(ServerRequestInterface $request, MfaProviderPropertyManager $propertyManager)
Definition: TotpProvider.php:195
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\prepareAuthView
‪prepareAuthView(StandaloneView $view, MfaProviderPropertyManager $propertyManager)
Definition: TotpProvider.php:296
‪TYPO3\CMS\Core\Authentication\Mfa\Provider
Definition: RecoveryCodes.php:18
‪TYPO3\CMS\Core\Authentication\Mfa\MfaViewType\AUTH
‪const AUTH
Definition: MfaViewType.php:29
‪TYPO3\CMS\Extbase\Mvc\View\ViewInterface\assign
‪self assign($key, $value)
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\verify
‪bool verify(ServerRequestInterface $request, MfaProviderPropertyManager $propertyManager)
Definition: TotpProvider.php:97
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\activate
‪bool activate(ServerRequestInterface $request, MfaProviderPropertyManager $propertyManager)
Definition: TotpProvider.php:127
‪TYPO3\CMS\Core\Authentication\Mfa\MfaViewType\SETUP
‪const SETUP
Definition: MfaViewType.php:27
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\isActive
‪bool isActive(MfaProviderPropertyManager $propertyManager)
Definition: TotpProvider.php:68
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager\getProperty
‪mixed null getProperty(string $key, $default=null)
Definition: MfaProviderPropertyManager.php:79
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\prepareSetupView
‪prepareSetupView(StandaloneView $view, MfaProviderPropertyManager $propertyManager)
Definition: TotpProvider.php:261
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\prepareEditView
‪prepareEditView(StandaloneView $view, MfaProviderPropertyManager $propertyManager)
Definition: TotpProvider.php:283
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\Totp\generateEncodedSecret
‪static string generateEncodedSecret(array $additionalAuthFactors=[])
Definition: Totp.php:202
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager\hasProviderEntry
‪bool hasProviderEntry()
Definition: MfaProviderPropertyManager.php:55
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager\deleteProviderEntry
‪bool deleteProviderEntry()
Definition: MfaProviderPropertyManager.php:158
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:53
‪TYPO3\CMS\Fluid\View\AbstractTemplateView\setTemplate
‪setTemplate($templateName)
Definition: AbstractTemplateView.php:102
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider
Definition: TotpProvider.php:40
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\getDateTime
‪string getDateTime(int $timestamp)
Definition: TotpProvider.php:335
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\$context
‪Context $context
Definition: TotpProvider.php:43
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\isLocked
‪bool isLocked(MfaProviderPropertyManager $propertyManager)
Definition: TotpProvider.php:81
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager\updateProperties
‪bool updateProperties(array $properties)
Definition: MfaProviderPropertyManager.php:102
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderInterface
Definition: MfaProviderInterface.php:27
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager\getUser
‪AbstractUserAuthentication getUser()
Definition: MfaProviderPropertyManager.php:202
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\getSvgQrCode
‪string getSvgQrCode(string $content)
Definition: TotpProvider.php:319
‪TYPO3\CMS\Extbase\Mvc\View\ViewInterface\assignMultiple
‪self assignMultiple(array $values)
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\getTotp
‪string getTotp(ServerRequestInterface $request)
Definition: TotpProvider.php:308
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\deactivate
‪bool deactivate(ServerRequestInterface $request, MfaProviderPropertyManager $propertyManager)
Definition: TotpProvider.php:215
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager
Definition: MfaProviderPropertyManager.php:33
‪TYPO3\CMS\Fluid\View\StandaloneView
Definition: StandaloneView.php:31
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\update
‪bool update(ServerRequestInterface $request, MfaProviderPropertyManager $propertyManager)
Definition: TotpProvider.php:172
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\MAX_ATTEMPTS
‪const MAX_ATTEMPTS
Definition: TotpProvider.php:41
‪TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider\handleRequest
‪ResponseInterface handleRequest(ServerRequestInterface $request, MfaProviderPropertyManager $propertyManager, string $type)
Definition: TotpProvider.php:235
‪TYPO3\CMS\Core\Authentication\Mfa\MfaViewType
Definition: MfaViewType.php:26
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager\createProviderEntry
‪bool createProviderEntry(array $properties)
Definition: MfaProviderPropertyManager.php:129
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager\getIdentifier
‪string getIdentifier()
Definition: MfaProviderPropertyManager.php:212
‪TYPO3\CMS\Core\Http\HtmlResponse
Definition: HtmlResponse.php:26