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