[!!!][TASK] Make TimeTracker a singleton
[Packages/TYPO3.CMS.git] / typo3 / sysext / saltedpasswords / Classes / SaltedPasswordService.php
1 <?php
2 namespace TYPO3\CMS\Saltedpasswords;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18
19 /**
20 * Class implements salted-password hashes authentication service.
21 * Contains authentication service class for salted hashed passwords.
22 * @since 2009-06-14
23 */
24 class SaltedPasswordService extends \TYPO3\CMS\Sv\AbstractAuthenticationService
25 {
26 /**
27 * Keeps class name.
28 *
29 * @var string
30 */
31 public $prefixId = 'tx_saltedpasswords_sv1';
32
33 /**
34 * Keeps path to this script relative to the extension directory.
35 *
36 * @var string
37 */
38 public $scriptRelPath = 'sv1/class.tx_saltedpasswords_sv1.php';
39
40 /**
41 * Keeps extension key.
42 *
43 * @var string
44 */
45 public $extKey = 'saltedpasswords';
46
47 /**
48 * Keeps extension configuration.
49 *
50 * @var mixed
51 */
52 protected $extConf;
53
54 /**
55 * An instance of the salted hashing method.
56 * This member is set in the getSaltingInstance() function.
57 *
58 * @var \TYPO3\CMS\Saltedpasswords\Salt\AbstractSalt
59 */
60 protected $objInstanceSaltedPW = null;
61
62 /**
63 * Indicates whether the salted password authentication has failed.
64 *
65 * Prevents authentication bypass. See vulnerability report:
66 * { @link http://forge.typo3.org/issues/22030 }
67 *
68 * @var bool
69 */
70 protected $authenticationFailed = false;
71
72 /**
73 * Checks if service is available. In case of this service we check that
74 * following prerequesties are fulfilled:
75 * - loginSecurityLevel of according TYPO3_MODE is set to normal
76 *
77 * @return bool TRUE if service is available
78 */
79 public function init()
80 {
81 $available = false;
82 $mode = TYPO3_MODE;
83 if ($this->info['requestedServiceSubType'] === 'authUserBE') {
84 $mode = 'BE';
85 } elseif ($this->info['requestedServiceSubType'] === 'authUserFE') {
86 $mode = 'FE';
87 }
88 if (\TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility::isUsageEnabled($mode)) {
89 $available = true;
90 $this->extConf = \TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility::returnExtConf();
91 }
92 return $available ? parent::init() : false;
93 }
94
95 /**
96 * Checks the login data with the user record data for builtin login method.
97 *
98 * @param array $user User data array
99 * @param array $loginData Login data array
100 * @param string $passwordCompareStrategy Password compare strategy
101 * @return bool TRUE if login data matched
102 */
103 public function compareUident(array $user, array $loginData, $passwordCompareStrategy = '')
104 {
105 $validPasswd = false;
106 $password = $loginData['uident_text'];
107 // Determine method used for given salted hashed password
108 $this->objInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance($user['password']);
109 // Existing record is in format of Salted Hash password
110 if (is_object($this->objInstanceSaltedPW)) {
111 $validPasswd = $this->objInstanceSaltedPW->checkPassword($password, $user['password']);
112 // Record is in format of Salted Hash password but authentication failed
113 // skip further authentication methods
114 if (!$validPasswd) {
115 $this->authenticationFailed = true;
116 }
117 $defaultHashingClassName = \TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility::getDefaultSaltingHashingMethod();
118 $skip = false;
119 // Test for wrong salted hashing method
120 if ($validPasswd && !(get_class($this->objInstanceSaltedPW) == $defaultHashingClassName) || is_subclass_of($this->objInstanceSaltedPW, $defaultHashingClassName)) {
121 // Instantiate default method class
122 $this->objInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(null);
123 $this->updatePassword((int)$user['uid'], array('password' => $this->objInstanceSaltedPW->getHashedPassword($password)));
124 }
125 if ($validPasswd && !$skip && $this->objInstanceSaltedPW->isHashUpdateNeeded($user['password'])) {
126 $this->updatePassword((int)$user['uid'], array('password' => $this->objInstanceSaltedPW->getHashedPassword($password)));
127 }
128 } elseif (!(int)$this->extConf['forceSalted']) {
129 // Stored password is in deprecated salted hashing method
130 $hashingMethod = substr($user['password'], 0, 2);
131 if ($hashingMethod === 'C$' || $hashingMethod === 'M$') {
132 // Instantiate default method class
133 $this->objInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(substr($user['password'], 1));
134 // md5
135 if ($hashingMethod === 'M$') {
136 $validPasswd = $this->objInstanceSaltedPW->checkPassword(md5($password), substr($user['password'], 1));
137 } else {
138 $validPasswd = $this->objInstanceSaltedPW->checkPassword($password, substr($user['password'], 1));
139 }
140 // Skip further authentication methods
141 if (!$validPasswd) {
142 $this->authenticationFailed = true;
143 }
144 } elseif (preg_match('/[0-9abcdef]{32,32}/', $user['password'])) {
145 $validPasswd = md5($password) === (string)$user['password'];
146 // Skip further authentication methods
147 if (!$validPasswd) {
148 $this->authenticationFailed = true;
149 }
150 } else {
151 $validPasswd = (string)$password === (string)$user['password'];
152 }
153 // Should we store the new format value in DB?
154 if ($validPasswd && (int)$this->extConf['updatePasswd']) {
155 // Instantiate default method class
156 $this->objInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(null);
157 $this->updatePassword((int)$user['uid'], array('password' => $this->objInstanceSaltedPW->getHashedPassword($password)));
158 }
159 }
160 return $validPasswd;
161 }
162
163 /**
164 * Method adds a further authUser method.
165 *
166 * Will return one of following authentication status codes:
167 * - 0 - authentication failure
168 * - 100 - just go on. User is not authenticated but there is still no reason to stop
169 * - 200 - the service was able to authenticate the user
170 *
171 * @param array Array containing FE user data of the logged user.
172 * @return int Authentication statuscode, one of 0,100 and 200
173 */
174 public function authUser(array $user)
175 {
176 $OK = 100;
177 $validPasswd = false;
178 if ($this->login['uident'] && $this->login['uname']) {
179 if (!empty($this->login['uident_text'])) {
180 $validPasswd = $this->compareUident($user, $this->login);
181 }
182 if (!$validPasswd) {
183 // Failed login attempt (wrong password)
184 $errorMessage = 'Login-attempt from %s (%s), username \'%s\', password not accepted!';
185 // No delegation to further services
186 if ((int)$this->extConf['onlyAuthService'] || $this->authenticationFailed) {
187 $this->writeLogMessage(TYPO3_MODE . ' Authentication failed - wrong password for username \'%s\'', $this->login['uname']);
188 $OK = 0;
189 } else {
190 $this->writeLogMessage($errorMessage, $this->authInfo['REMOTE_ADDR'], $this->authInfo['REMOTE_HOST'], $this->login['uname']);
191 }
192 $this->writelog(255, 3, 3, 1, $errorMessage, array(
193 $this->authInfo['REMOTE_ADDR'],
194 $this->authInfo['REMOTE_HOST'],
195 $this->login['uname']
196 ));
197 GeneralUtility::sysLog(sprintf($errorMessage, $this->authInfo['REMOTE_ADDR'], $this->authInfo['REMOTE_HOST'], $this->login['uname']), 'core', GeneralUtility::SYSLOG_SEVERITY_INFO);
198 } elseif ($validPasswd && $user['lockToDomain'] && strcasecmp($user['lockToDomain'], $this->authInfo['HTTP_HOST'])) {
199 // Lock domain didn't match, so error:
200 $errorMessage = 'Login-attempt from %s (%s), username \'%s\', locked domain \'%s\' did not match \'%s\'!';
201 $this->writeLogMessage($errorMessage, $this->authInfo['REMOTE_ADDR'], $this->authInfo['REMOTE_HOST'], $this->login['uname'], $user['lockToDomain'], $this->authInfo['HTTP_HOST']);
202 $this->writelog(255, 3, 3, 1, $errorMessage, array(
203 $this->authInfo['REMOTE_ADDR'],
204 $this->authInfo['REMOTE_HOST'],
205 $user[$this->db_user['username_column']],
206 $user['lockToDomain'],
207 $this->authInfo['HTTP_HOST']
208 ));
209 GeneralUtility::sysLog(sprintf($errorMessage, $this->authInfo['REMOTE_ADDR'], $this->authInfo['REMOTE_HOST'], $user[$this->db_user['username_column']], $user['lockToDomain'], $this->authInfo['HTTP_HOST']), 'core', GeneralUtility::SYSLOG_SEVERITY_INFO);
210 $OK = 0;
211 } elseif ($validPasswd) {
212 $this->writeLogMessage(TYPO3_MODE . ' Authentication successful for username \'%s\'', $this->login['uname']);
213 $OK = 200;
214 }
215 }
216 return $OK;
217 }
218
219 /**
220 * Method updates a FE/BE user record - in this case a new password string will be set.
221 *
222 * @param int $uid uid of user record that will be updated
223 * @param mixed $updateFields Field values as key=>value pairs to be updated in database
224 * @return void
225 */
226 protected function updatePassword($uid, $updateFields)
227 {
228 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($this->pObj->user_table, sprintf('uid = %u', $uid), $updateFields);
229 GeneralUtility::devLog(sprintf('Automatic password update for user record in %s with uid %u', $this->pObj->user_table, $uid), $this->extKey, 1);
230 }
231
232 /**
233 * Writes log message. Destination log depends on the current system mode.
234 * For FE the function writes to the admin panel log. For BE messages are
235 * sent to the system log. If developer log is enabled, messages are also
236 * sent there.
237 *
238 * This function accepts variable number of arguments and can format
239 * parameters. The syntax is the same as for sprintf()
240 *
241 * @param string $message Message to output
242 * @return void
243 * @see \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog()
244 */
245 public function writeLogMessage($message)
246 {
247 if (func_num_args() > 1) {
248 $params = func_get_args();
249 array_shift($params);
250 $message = vsprintf($message, $params);
251 }
252 if (TYPO3_MODE === 'BE') {
253 GeneralUtility::sysLog($message, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_NOTICE);
254 } else {
255 /** @var TimeTracker $timeTracker */
256 $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
257 $timeTracker->setTSlogMessage($message);
258 }
259 if (TYPO3_DLOG) {
260 GeneralUtility::devLog($message, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_NOTICE);
261 }
262 }
263 }