‪TYPO3CMS  ‪main
PasswordReset.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 Doctrine\DBAL\Platforms\MySQLPlatform;
21 use Psr\EventDispatcher\EventDispatcherInterface;
22 use Psr\Http\Message\ServerRequestInterface;
23 use Psr\Http\Message\UriInterface;
24 use Psr\Log\LoggerAwareInterface;
25 use Psr\Log\LoggerAwareTrait;
26 use Symfony\Component\Mime\Address;
34 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
48 use ‪TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction;
49 use ‪TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
50 use ‪TYPO3\CMS\Core\SysLog\Type as SystemLogType;
52 
63 class ‪PasswordReset implements LoggerAwareInterface
64 {
65  use LoggerAwareTrait;
66 
67  protected const ‪TOKEN_VALID_UNTIL = '+2 hours';
68  protected const ‪MAXIMUM_RESET_ATTEMPTS = 3;
69  protected const ‪MAXIMUM_RESET_ATTEMPTS_SINCE = '-30 minutes';
70 
71  public function ‪__construct(private readonly ‪MailerInterface $mailer) {}
72 
76  public function ‪isEnabled(): bool
77  {
78  // Option not explicitly enabled
79  if (!(‪$GLOBALS['TYPO3_CONF_VARS']['BE']['passwordReset'] ?? false)) {
80  return false;
81  }
82  $queryBuilder = $this->‪getPreparedQueryBuilder();
83  $statement = $queryBuilder
84  ->select('uid')
85  ->from('be_users')
86  ->setMaxResults(1)
87  ->executeQuery();
88  return (int)$statement->fetchOne() > 0;
89  }
90 
94  public function ‪isEnabledForUser(int $userId): bool
95  {
96  $queryBuilder = $this->‪getPreparedQueryBuilder();
97  $statement = $queryBuilder
98  ->select('uid')
99  ->from('be_users')
100  ->andWhere(
101  $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($userId, ‪Connection::PARAM_INT))
102  )
103  ->setMaxResults(1)
104  ->executeQuery();
105  return $statement->fetchOne() > 0;
106  }
107 
120  public function ‪initiateReset(ServerRequestInterface $request, ‪Context $context, string $emailAddress): void
121  {
122  if (!GeneralUtility::validEmail($emailAddress)) {
123  return;
124  }
125  if ($this->‪hasExceededMaximumAttemptsForReset($context, $emailAddress)) {
126  $this->logger->alert('Password reset requested for email {email} but was requested too many times.', ['email' => $emailAddress]);
127  return;
128  }
129  $queryBuilder = $this->‪getPreparedQueryBuilder();
130  $users = $queryBuilder
131  ->select('uid', 'email', 'username', 'realName', 'lang')
132  ->from('be_users')
133  ->andWhere(
134  $queryBuilder->expr()->eq('email', $queryBuilder->createNamedParameter($emailAddress))
135  )
136  ->executeQuery()
137  ->fetchAllAssociative();
138  if (!is_array($users) || count($users) === 0) {
139  // No user found, do nothing, also no log to sys_log in order avoid log flooding
140  $this->logger->warning('Password reset requested for email but no valid users');
141  } elseif (count($users) > 1) {
142  // More than one user with the same email address found, send out the email that one cannot send out a reset link
143  $this->‪sendAmbiguousEmail($request, $context, $emailAddress);
144  } else {
145  $user = reset($users);
146  $this->‪sendResetEmail($request, $context, (array)$user, $emailAddress);
147  }
148  }
149 
154  protected function ‪sendAmbiguousEmail(ServerRequestInterface $request, ‪Context $context, string $emailAddress): void
155  {
156  $emailObject = GeneralUtility::makeInstance(FluidEmail::class);
157  $emailObject
158  ->to(new Address($emailAddress))
159  ->setRequest($request)
160  ->assign('email', $emailAddress)
161  ->setTemplate('PasswordReset/AmbiguousResetRequested');
162 
163  $this->mailer->send($emailObject);
164  $this->logger->warning('Password reset sent to email address {email} but multiple accounts found', ['email' => $emailAddress]);
165  $this->‪log(
166  'Sent password reset email to email address %s but with multiple accounts attached.',
167  SystemLogLoginAction::PASSWORD_RESET_REQUEST,
168  SystemLogErrorClassification::WARNING,
169  0,
170  [
171  'email' => $emailAddress,
172  ],
173  ‪NormalizedParams::createFromRequest($request)->getRemoteAddress(),
174  $context
175  );
176  }
177 
181  protected function ‪sendResetEmail(ServerRequestInterface $request, ‪Context $context, array $user, string $emailAddress): void
182  {
183  $resetLink = $this->‪generateResetLinkForUser($context, (int)$user['uid'], (string)$user['email']);
184  $emailObject = GeneralUtility::makeInstance(FluidEmail::class);
185  $emailObject
186  ->to(new Address((string)$user['email'], $user['realName']))
187  ->setRequest($request)
188  ->assign('name', $user['realName'])
189  ->assign('email', $user['email'])
190  ->assign('language', $user['lang'] ?: 'default')
191  ->assign('resetLink', $resetLink)
192  ->assign('username', $user['username'])
193  ->setTemplate('PasswordReset/ResetRequested');
194 
195  $this->mailer->send($emailObject);
196 
197  $this->logger->info('Sent password reset email to email address {email} for user {username}', [
198  'email' => $emailAddress,
199  'username' => $user['username'],
200  ]);
201  $this->‪log(
202  'Sent password reset email to email address %s',
203  SystemLogLoginAction::PASSWORD_RESET_REQUEST,
204  SystemLogErrorClassification::SECURITY_NOTICE,
205  (int)$user['uid'],
206  [
207  'email' => $user['email'],
208  ],
209  ‪NormalizedParams::createFromRequest($request)->getRemoteAddress(),
210  $context
211  );
212  }
213 
226  protected function ‪generateResetLinkForUser(‪Context $context, int $userId, string $emailAddress): UriInterface
227  {
228  $token = GeneralUtility::makeInstance(Random::class)->generateRandomHexString(96);
229  $currentTime = $context->‪getAspect('date')->getDateTime();
230  $expiresOn = $currentTime->modify(self::TOKEN_VALID_UNTIL);
231  // Create a hash ("one time password") out of the token including the timestamp of the expiration date
232  $hash = ‪GeneralUtility::hmac($token . '|' . (string)$expiresOn->getTimestamp() . '|' . $emailAddress . '|' . (string)$userId, 'password-reset');
233 
234  // Set the token in the database, which is hashed
235  GeneralUtility::makeInstance(ConnectionPool::class)
236  ->getConnectionForTable('be_users')
237  ->update('be_users', ['password_reset_token' => $this->‪getHasher()->getHashedPassword($hash)], ['uid' => $userId]);
238 
239  return GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute(
240  'password_reset_validate',
241  [
242  // "token"
243  't' => $token,
244  // "expiration date"
245  'e' => $expiresOn->getTimestamp(),
246  // "identity"
247  'i' => hash('sha1', $emailAddress . (string)$userId),
248  ],
250  );
251  }
252 
256  public function ‪isValidResetTokenFromRequest(ServerRequestInterface $request): bool
257  {
258  $user = $this->‪findValidUserForToken(
259  (string)($request->getQueryParams()['t'] ?? ''),
260  (string)($request->getQueryParams()['i'] ?? ''),
261  (int)($request->getQueryParams()['e'] ?? 0)
262  );
263  return $user !== null;
264  }
265 
271  protected function ‪findValidUserForToken(string $token, string $identity, int $expirationTimestamp): ?array
272  {
273  // Early return if token expired
274  if ($expirationTimestamp < time()) {
275  return null;
276  }
277 
278  $user = null;
279  // Find the token in the database
280  $queryBuilder = $this->‪getPreparedQueryBuilder();
281 
282  $queryBuilder
283  ->select('uid', 'username', 'realName', 'email', 'password_reset_token', 'password')
284  ->from('be_users');
285  if ($queryBuilder->getConnection()->getDatabasePlatform() instanceof MySQLPlatform) {
286  $queryBuilder->andWhere(
287  $queryBuilder->expr()->comparison('SHA1(CONCAT(' . $queryBuilder->quoteIdentifier('email') . ', ' . $queryBuilder->quoteIdentifier('uid') . '))', $queryBuilder->expr()::EQ, $queryBuilder->createNamedParameter($identity))
288  );
289  $user = $queryBuilder->executeQuery()->fetchAssociative();
290  } else {
291  // no native SHA1/ CONCAT functionality, has to be done in PHP
292  $stmt = $queryBuilder->executeQuery();
293  while ($row = $stmt->fetchAssociative()) {
294  if (hash_equals(hash('sha1', $row['email'] . (string)$row['uid']), $identity)) {
295  $user = $row;
296  break;
297  }
298  }
299  }
300 
301  if (!is_array($user) || empty($user)) {
302  return null;
303  }
304 
305  // Validate hash by rebuilding the hash from the parameters and the URL and see if this matches against the stored password_reset_token
306  $hash = ‪GeneralUtility::hmac($token . '|' . (string)$expirationTimestamp . '|' . $user['email'] . '|' . (string)$user['uid'], 'password-reset');
307  if (!$this->‪getHasher()->checkPassword($hash, $user['password_reset_token'] ?? '')) {
308  return null;
309  }
310  return $user;
311  }
312 
319  public function ‪resetPassword(ServerRequestInterface $request, ‪Context $context): bool
320  {
321  $expirationTimestamp = (int)($request->getQueryParams()['e'] ?? '');
322  $identityHash = (string)($request->getQueryParams()['i'] ?? '');
323  $token = (string)($request->getQueryParams()['t'] ?? '');
324  $newPassword = (string)($request->getParsedBody()['password'] ?? '');
325  $newPasswordRepeat = (string)($request->getParsedBody()['passwordrepeat'] ?? '');
326 
327  $user = $this->‪findValidUserForToken($token, $identityHash, $expirationTimestamp);
328  if ($user === null) {
329  $this->logger->warning('Password reset not possible. Valid user for token not found.');
330  return false;
331  }
332  $userId = (int)$user['uid'];
333 
334  if ($newPassword === '') {
335  $this->logger->debug('Password reset not possible because an empty password was provided.');
336  return false;
337  }
338 
339  if ($newPassword !== $newPasswordRepeat) {
340  $this->logger->debug('Password reset not possible because new password and new password repeat do not match.');
341  return false;
342  }
343 
344  if (!$this->‪isValidPassword($newPassword, $user)) {
345  $this->logger->debug('The new password does not match all requirements of the password policy.');
346  return false;
347  }
348 
349  GeneralUtility::makeInstance(ConnectionPool::class)
350  ->getConnectionForTable('be_users')
351  ->update('be_users', ['password_reset_token' => '', 'password' => $this->‪getHasher()->getHashedPassword($newPassword)], ['uid' => $userId]);
352 
353  $this->‪invalidateUserSessions($userId);
354 
355  $this->logger->info('Password reset successful for user \'{username}\'', ['username' => $user['username'], 'user_id' => $userId]);
356  $this->‪log(
357  'Password reset successful for user %s',
358  SystemLogLoginAction::PASSWORD_RESET_ACCOMPLISHED,
359  SystemLogErrorClassification::SECURITY_NOTICE,
360  $userId,
361  [
362  'email' => $user['email'],
363  'user' => $userId,
364  ],
365  ‪NormalizedParams::createFromRequest($request)->getRemoteAddress(),
366  $context
367  );
368  return true;
369  }
370 
380  protected function ‪getPreparedQueryBuilder(): QueryBuilder
381  {
382  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
383  $queryBuilder->getRestrictions()
384  ->removeAll()
385  ->add(GeneralUtility::makeInstance(RootLevelRestriction::class))
386  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
387  ->add(GeneralUtility::makeInstance(StartTimeRestriction::class))
388  ->add(GeneralUtility::makeInstance(EndTimeRestriction::class))
389  ->add(GeneralUtility::makeInstance(HiddenRestriction::class));
390  $queryBuilder->where(
391  $queryBuilder->expr()->neq('username', $queryBuilder->createNamedParameter('')),
392  $queryBuilder->expr()->neq('username', $queryBuilder->createNamedParameter('_cli_')),
393  $queryBuilder->expr()->neq('password', $queryBuilder->createNamedParameter('')),
394  $queryBuilder->expr()->neq('email', $queryBuilder->createNamedParameter(''))
395  );
396  if (!(‪$GLOBALS['TYPO3_CONF_VARS']['BE']['passwordResetForAdmins'] ?? false)) {
397  $queryBuilder->andWhere(
398  $queryBuilder->expr()->eq('admin', $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT))
399  );
400  }
401  return $queryBuilder;
402  }
403 
405  {
406  return GeneralUtility::makeInstance(PasswordHashFactory::class)->getDefaultHashInstance('BE');
407  }
408 
418  protected function ‪log(string $message, int $action, int $error, int $userId, array $data, $ipAddress, ‪Context $context): void
419  {
420  ‪$fields = [
421  'userid' => $userId,
422  'type' => SystemLogType::LOGIN,
423  'channel' => SystemLogType::toChannel(SystemLogType::LOGIN),
424  'level' => SystemLogType::toLevel(SystemLogType::LOGIN),
425  'action' => $action,
426  'error' => $error,
427  'details_nr' => 1,
428  'details' => $message,
429  'log_data' => json_encode($data),
430  'tablename' => 'be_users',
431  'recuid' => $userId,
432  'IP' => (string)$ipAddress,
433  'tstamp' => $context->‪getAspect('date')->get('timestamp'),
434  'event_pid' => 0,
435  'NEWid' => '',
436  'workspace' => 0,
437  ];
438 
439  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_log');
440  $connection->insert(
441  'sys_log',
442  ‪$fields,
443  [
460  ]
461  );
462  }
463 
468  protected function ‪hasExceededMaximumAttemptsForReset(‪Context $context, string $email): bool
469  {
470  $now = $context->‪getAspect('date')->getDateTime();
471  $numberOfAttempts = $this->‪getNumberOfInitiatedResetsForEmail($now->modify(self::MAXIMUM_RESET_ATTEMPTS_SINCE), $email);
472  return $numberOfAttempts > ‪self::MAXIMUM_RESET_ATTEMPTS;
473  }
474 
478  protected function ‪getNumberOfInitiatedResetsForEmail(\DateTimeInterface $since, string $email): int
479  {
480  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
481  return (int)$queryBuilder
482  ->count('uid')
483  ->from('sys_log')
484  ->where(
485  $queryBuilder->expr()->eq('type', $queryBuilder->createNamedParameter(SystemLogType::LOGIN)),
486  $queryBuilder->expr()->eq('action', $queryBuilder->createNamedParameter(SystemLogLoginAction::PASSWORD_RESET_REQUEST)),
487  $queryBuilder->expr()->eq('log_data', $queryBuilder->createNamedParameter(json_encode(['email' => $email]))),
488  $queryBuilder->expr()->gte('tstamp', $queryBuilder->createNamedParameter($since->getTimestamp(), ‪Connection::PARAM_INT))
489  )
490  ->executeQuery()
491  ->fetchOne();
492  }
493 
497  protected function ‪isValidPassword(string $password, array $user): bool
498  {
499  $passwordPolicy = ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['passwordPolicy'] ?? 'default';
500  $passwordPolicyValidator = GeneralUtility::makeInstance(
501  PasswordPolicyValidator::class,
502  PasswordPolicyAction::UPDATE_USER_PASSWORD,
503  is_string($passwordPolicy) ? $passwordPolicy : ''
504  );
505  $contextData = new ‪ContextData(currentPasswordHash: $user['password']);
506  $contextData->setData('currentUsername', $user['username']);
507  $contextData->setData('currentFullname', $user['realName']);
508  $event = GeneralUtility::makeInstance(EventDispatcherInterface::class)->dispatch(
510  $contextData,
511  $user,
512  self::class
513  )
514  );
515  $contextData = $event->getContextData();
516 
517  return $passwordPolicyValidator->isValidPassword($password, $contextData);
518  }
519 
523  protected function ‪invalidateUserSessions(int $userId): void
524  {
525  $sessionManager = GeneralUtility::makeInstance(SessionManager::class);
526  $sessionBackend = $sessionManager->getSessionBackend('BE');
527  $sessionManager->invalidateAllSessionsByUserId($sessionBackend, $userId);
528  }
529 }
‪TYPO3\CMS\Backend\Authentication\PasswordReset\log
‪log(string $message, int $action, int $error, int $userId, array $data, $ipAddress, Context $context)
Definition: PasswordReset.php:418
‪TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction
Definition: HiddenRestriction.php:27
‪TYPO3\CMS\Backend\Authentication\PasswordReset\sendResetEmail
‪sendResetEmail(ServerRequestInterface $request, Context $context, array $user, string $emailAddress)
Definition: PasswordReset.php:181
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory
Definition: PasswordHashFactory.php:27
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:50
‪TYPO3\CMS\Backend\Authentication\PasswordReset\resetPassword
‪bool resetPassword(ServerRequestInterface $request, Context $context)
Definition: PasswordReset.php:319
‪TYPO3\CMS\Backend\Authentication
‪TYPO3\CMS\Backend\Authentication\PasswordReset\getHasher
‪getHasher()
Definition: PasswordReset.php:404
‪TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction
Definition: EndTimeRestriction.php:27
‪TYPO3\CMS\Core\Context\Context\getAspect
‪getAspect(string $name)
Definition: Context.php:88
‪TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction
Definition: StartTimeRestriction.php:27
‪TYPO3\CMS\Backend\Authentication\PasswordReset
Definition: PasswordReset.php:64
‪TYPO3\CMS\Backend\Authentication\PasswordReset\initiateReset
‪initiateReset(ServerRequestInterface $request, Context $context, string $emailAddress)
Definition: PasswordReset.php:120
‪TYPO3\CMS\Backend\Authentication\PasswordReset\MAXIMUM_RESET_ATTEMPTS
‪const MAXIMUM_RESET_ATTEMPTS
Definition: PasswordReset.php:68
‪TYPO3\CMS\Core\Mail\MailerInterface
Definition: MailerInterface.php:28
‪TYPO3\CMS\Core\Session\SessionManager
Definition: SessionManager.php:41
‪TYPO3\CMS\Backend\Authentication\PasswordReset\isEnabledForUser
‪isEnabledForUser(int $userId)
Definition: PasswordReset.php:94
‪$fields
‪$fields
Definition: pages.php:5
‪TYPO3\CMS\Core\Database\Connection\PARAM_STR
‪const PARAM_STR
Definition: Connection.php:55
‪TYPO3\CMS\Core\SysLog\Action\Login
Definition: Login.php:24
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:54
‪TYPO3\CMS\Backend\Authentication\PasswordReset\TOKEN_VALID_UNTIL
‪const TOKEN_VALID_UNTIL
Definition: PasswordReset.php:67
‪TYPO3\CMS\Backend\Authentication\PasswordReset\isValidResetTokenFromRequest
‪isValidResetTokenFromRequest(ServerRequestInterface $request)
Definition: PasswordReset.php:256
‪TYPO3\CMS\Core\Mail\FluidEmail
Definition: FluidEmail.php:35
‪TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction
Definition: RootLevelRestriction.php:27
‪TYPO3\CMS\Backend\Authentication\PasswordReset\__construct
‪__construct(private readonly MailerInterface $mailer)
Definition: PasswordReset.php:71
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:44
‪TYPO3\CMS\Core\PasswordPolicy\PasswordPolicyValidator
Definition: PasswordPolicyValidator.php:27
‪TYPO3\CMS\Backend\Authentication\PasswordReset\hasExceededMaximumAttemptsForReset
‪hasExceededMaximumAttemptsForReset(Context $context, string $email)
Definition: PasswordReset.php:468
‪TYPO3\CMS\Core\Utility\GeneralUtility\hmac
‪static string hmac($input, $additionalSecret='')
Definition: GeneralUtility.php:473
‪TYPO3\CMS\Core\SysLog\Error
Definition: Error.php:24
‪TYPO3\CMS\Backend\Authentication\PasswordReset\generateResetLinkForUser
‪generateResetLinkForUser(Context $context, int $userId, string $emailAddress)
Definition: PasswordReset.php:226
‪TYPO3\CMS\Backend\Authentication\PasswordReset\findValidUserForToken
‪array null findValidUserForToken(string $token, string $identity, int $expirationTimestamp)
Definition: PasswordReset.php:271
‪TYPO3\CMS\Backend\Authentication\PasswordReset\isValidPassword
‪isValidPassword(string $password, array $user)
Definition: PasswordReset.php:497
‪TYPO3\CMS\Backend\Authentication\PasswordReset\sendAmbiguousEmail
‪sendAmbiguousEmail(ServerRequestInterface $request, Context $context, string $emailAddress)
Definition: PasswordReset.php:154
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:39
‪TYPO3\CMS\Backend\Authentication\PasswordReset\getPreparedQueryBuilder
‪getPreparedQueryBuilder()
Definition: PasswordReset.php:380
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\PasswordPolicy\Event\EnrichPasswordValidationContextDataEvent
Definition: EnrichPasswordValidationContextDataEvent.php:30
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Backend\Authentication\PasswordReset\invalidateUserSessions
‪invalidateUserSessions(int $userId)
Definition: PasswordReset.php:523
‪TYPO3\CMS\Backend\Authentication\PasswordReset\MAXIMUM_RESET_ATTEMPTS_SINCE
‪const MAXIMUM_RESET_ATTEMPTS_SINCE
Definition: PasswordReset.php:69
‪TYPO3\CMS\Core\PasswordPolicy\PasswordPolicyAction
‪PasswordPolicyAction
Definition: PasswordPolicyAction.php:24
‪TYPO3\CMS\Core\Crypto\Random
Definition: Random.php:27
‪TYPO3\CMS\Backend\Authentication\PasswordReset\getNumberOfInitiatedResetsForEmail
‪getNumberOfInitiatedResetsForEmail(\DateTimeInterface $since, string $email)
Definition: PasswordReset.php:478
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:48
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Backend\Routing\UriBuilder\ABSOLUTE_URL
‪const ABSOLUTE_URL
Definition: UriBuilder.php:48
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashInterface
Definition: PasswordHashInterface.php:25
‪TYPO3\CMS\Core\Http\NormalizedParams\createFromRequest
‪static static createFromRequest(ServerRequestInterface $request, array $systemConfiguration=null)
Definition: NormalizedParams.php:840
‪TYPO3\CMS\Backend\Authentication\PasswordReset\isEnabled
‪isEnabled()
Definition: PasswordReset.php:76
‪TYPO3\CMS\Core\Http\NormalizedParams
Definition: NormalizedParams.php:38
‪TYPO3\CMS\Core\SysLog\Type
Definition: Type.php:28
‪TYPO3\CMS\Core\PasswordPolicy\Validator\Dto\ContextData
Definition: ContextData.php:28