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