‪TYPO3CMS  ‪main
AuthenticationService.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use Psr\Log\LogLevel;
22 use ‪TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction;
23 use ‪TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
24 use ‪TYPO3\CMS\Core\SysLog\Type as SystemLogType;
27 
32 {
41  public function ‪processLoginData(array &‪$loginData, $passwordTransmissionStrategy)
42  {
43  $isProcessed = false;
44  if ($passwordTransmissionStrategy === 'normal') {
45  ‪$loginData = array_map(trim(...), ‪$loginData);
46  ‪$loginData['uident_text'] = ‪$loginData['uident'];
47  $isProcessed = true;
48  }
49  return $isProcessed;
50  }
51 
57  public function getUser()
58  {
59  if (LoginType::tryFrom($this->login['status'] ?? '') !== LoginType::LOGIN) {
60  return false;
61  }
62  if ((string)$this->login['uident_text'] === '') {
63  // Failed Login attempt (no password given)
64  $this->‪writelog(SystemLogType::LOGIN, SystemLogLoginAction::ATTEMPT, SystemLogErrorClassification::SECURITY_NOTICE, 2, 'Login-attempt from ###IP### for username \'%s\' with an empty password!', [
65  $this->login['uname'],
66  ]);
67  $this->logger->warning('Login-attempt from {ip}, for username "{username}" with an empty password!', [
68  'ip' => $this->authInfo['REMOTE_ADDR'],
69  'username' => $this->login['uname'],
70  ]);
71  return false;
72  }
73 
74  ‪$user = $this->fetchUserRecord($this->login['uname']);
75  if (!is_array(‪$user)) {
76  // Failed login attempt (no username found)
77  $this->‪writelog(SystemLogType::LOGIN, SystemLogLoginAction::ATTEMPT, SystemLogErrorClassification::SECURITY_NOTICE, 2, 'Login-attempt from ###IP###, username \'%s\' not found!', [$this->login['uname']]);
78  $this->logger->info('Login-attempt from username "{username}" not found!', [
79  'username' => $this->login['uname'],
80  'REMOTE_ADDR' => $this->authInfo['REMOTE_ADDR'],
81  ]);
82  } else {
83  $this->logger->debug('User found', [
84  $this->‪db_user['userid_column'] => ‪$user[$this->‪db_user['userid_column']],
85  $this->db_user['username_column'] => ‪$user[$this->db_user['username_column']],
86  ]);
87  }
88  return ‪$user;
89  }
90 
103  public function ‪authUser(array ‪$user): int
104  {
105  // Early 100 "not responsible, check other services" if username or password is empty
106  if (!isset($this->login['uident_text']) || (string)$this->login['uident_text'] === ''
107  || !isset($this->login['uname']) || (string)$this->login['uname'] === '') {
108  return 100;
109  }
110 
111  if (empty($this->‪db_user['table'])) {
112  throw new \RuntimeException('User database table not set', 1533159150);
113  }
114 
115  $submittedUsername = (string)$this->login['uname'];
116  $submittedPassword = (string)$this->login['uident_text'];
117  $passwordHashInDatabase = ‪$user['password'];
118  $userDatabaseTable = $this->‪db_user['table'];
119 
120  $isReHashNeeded = false;
121 
122  $saltFactory = GeneralUtility::makeInstance(PasswordHashFactory::class);
123 
124  // Get a hashed password instance for the hash stored in db of this user
125  try {
126  $hashInstance = $saltFactory->get($passwordHashInDatabase, $this->pObj->loginType);
127  } catch (‪InvalidPasswordHashException $exception) {
128  // Could not find a responsible hash algorithm for given password. This is unusual since other
129  // authentication services would usually be called before this one with higher priority. We thus log
130  // the failed login but still return '100' to proceed with other services that may follow.
131  $message = 'Login-attempt from ###IP###, username \'%s\', no suitable hash method found!';
132  $this->‪writeLogMessage($message, $submittedUsername);
133  $this->‪writelog(SystemLogType::LOGIN, SystemLogLoginAction::ATTEMPT, SystemLogErrorClassification::SECURITY_NOTICE, 1, $message, [$submittedUsername]);
134  // Not responsible, check other services
135  return 100;
136  }
137 
138  // An instance of the currently configured salted password mechanism
139  // Don't catch InvalidPasswordHashException here: Only install tool should handle those configuration failures
140  $defaultHashInstance = $saltFactory->getDefaultHashInstance($this->pObj->loginType);
141 
142  // We found a hash class that can handle this type of hash
143  $isValidPassword = $hashInstance->checkPassword($submittedPassword, $passwordHashInDatabase);
144  if ($isValidPassword) {
145  if ($hashInstance->isHashUpdateNeeded($passwordHashInDatabase)
146  || $defaultHashInstance != $hashInstance
147  ) {
148  // Lax object comparison intended: Rehash if old and new salt objects are not
149  // instances of the same class.
150  $isReHashNeeded = true;
151  }
152  }
153 
154  if (!$isValidPassword) {
155  // Failed login attempt - wrong password
156  $message = 'Login-attempt from ###IP###, username \'%s\', password not accepted!';
157  $this->‪writeLogMessage($message, $submittedUsername);
158  $this->‪writelog(SystemLogType::LOGIN, SystemLogLoginAction::ATTEMPT, SystemLogErrorClassification::SECURITY_NOTICE, 1, $message, [$submittedUsername]);
159  // Responsible, authentication failed, do NOT check other services
160  return 0;
161  }
162 
163  if ($isReHashNeeded) {
164  // Given password validated but a re-hash is needed. Do so.
166  $userDatabaseTable,
167  (int)‪$user['uid'],
168  $defaultHashInstance->getHashedPassword($submittedPassword)
169  );
170  }
171 
172  // Responsible, authentication ok. Log successful login and return 'auth ok, do NOT check other services'
173  $this->‪writeLogMessage($this->pObj->loginType . ' Authentication successful for username \'%s\'', $submittedUsername);
174  return 200;
175  }
176 
181  public function ‪mimicAuthUser(): bool
182  {
183  try {
184  $hashFactory = GeneralUtility::makeInstance(PasswordHashFactory::class);
185  $defaultHashInstance = $hashFactory->getDefaultHashInstance($this->pObj->loginType);
186  $defaultHashInstance->getHashedPassword(random_bytes(10));
187  } catch (\‪Exception) {
188  // no further processing here
189  }
190  return false;
191  }
192 
200  protected function ‪updatePasswordHashInDatabase(string $table, int ‪$uid, string $newPassword): void
201  {
202  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
203  $connection->update(
204  $table,
205  ['password' => $newPassword],
206  ['uid' => ‪$uid]
207  );
208  $this->logger->notice('Automatic password update for user record in {table} with uid {uid}', [
209  'table' => $table,
210  'uid' => ‪$uid,
211  ]);
212  }
213 
224  protected function ‪writeLogMessage(string $message, ...$params): void
225  {
226  if (!empty($params)) {
227  $message = vsprintf($message, $params);
228  }
229  $message = str_replace('###IP###', (string)GeneralUtility::getIndpEnv('REMOTE_ADDR'), $message);
230  if ($this->pObj->loginType === 'FE') {
231  $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
232  $timeTracker->setTSlogMessage($message, LogLevel::INFO);
233  }
234  $this->logger->notice($message);
235  }
236 }
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory
Definition: PasswordHashFactory.php:27
‪TYPO3\CMS\Core\Authentication\MimicServiceInterface
Definition: MimicServiceInterface.php:21
‪TYPO3\CMS\Core\Authentication\AuthenticationService\writeLogMessage
‪writeLogMessage(string $message,... $params)
Definition: AuthenticationService.php:224
‪TYPO3\CMS\Core\Exception
Definition: Exception.php:21
‪TYPO3\CMS\Core\Authentication
Definition: AbstractAuthenticationService.php:16
‪TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException
Definition: InvalidPasswordHashException.php:25
‪TYPO3\CMS\Core\Authentication\AuthenticationService\mimicAuthUser
‪mimicAuthUser()
Definition: AuthenticationService.php:181
‪TYPO3\CMS\Core\Authentication\AbstractAuthenticationService
Definition: AbstractAuthenticationService.php:29
‪TYPO3\CMS\Core\SysLog\Action\Login
Definition: Login.php:24
‪TYPO3\CMS\Webhooks\Message\$loginData
‪identifier readonly UriInterface readonly array $loginData
Definition: LoginErrorOccurredMessage.php:37
‪TYPO3\CMS\Core\SysLog\Error
Definition: Error.php:24
‪TYPO3\CMS\Core\Authentication\AuthenticationService\writelog
‪array< string, getUser() { if(LoginType::tryFrom( $this->login[ 'status'] ?? '') !==LoginType::LOGIN) { return false;} if((string) $this->login[ 'uident_text']==='') { $this-> writelog(SystemLogType::LOGIN, SystemLogLoginAction::ATTEMPT, SystemLogErrorClassification::SECURITY_NOTICE, 2, 'Login-attempt from ###IP### for username \'%s\' with an empty password!', [ $this->login['uname'],])
‪TYPO3\CMS\Core\Authentication\AuthenticationService
Definition: AuthenticationService.php:32
‪TYPO3\CMS\Core\Authentication\AbstractAuthenticationService\db_user
‪array< string, function fetchUserRecord( $username, $extraWhere='', $dbUserSetup='') { $dbUser=is_array( $dbUserSetup) ? $dbUserSetup :$this-> db_user
Definition: AbstractAuthenticationService.php:121
‪TYPO3\CMS\Core\Authentication\AuthenticationService\updatePasswordHashInDatabase
‪updatePasswordHashInDatabase(string $table, int $uid, string $newPassword)
Definition: AuthenticationService.php:200
‪TYPO3\CMS\Webhooks\Message\$uid
‪identifier readonly int $uid
Definition: PageModificationMessage.php:35
‪TYPO3\CMS\Core\Authentication\AuthenticationService\authUser
‪int authUser(array $user)
Definition: AuthenticationService.php:103
‪TYPO3\CMS\Core\Authentication\AuthenticationService\processLoginData
‪bool processLoginData(array &$loginData, $passwordTransmissionStrategy)
Definition: AuthenticationService.php:41
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\TimeTracker\TimeTracker
Definition: TimeTracker.php:34
‪TYPO3\CMS\Core\Authentication\AuthenticationService\$user
‪$user
Definition: AuthenticationService.php:74
‪TYPO3\CMS\Core\SysLog\Type
Definition: Type.php:28
‪TYPO3\CMS\Core\Authentication\AuthenticationService\$user
‪return $user
Definition: AuthenticationService.php:88