Cleanup: Updated copyright comments
[Packages/TYPO3.CMS.git] / typo3 / sysext / saltedpasswords / classes / tasks / class.tx_saltedpasswords_tasks_bulkupdate.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2010-2011 Christian Kuhn <lolli@schwarzbu.ch>
6 * Marcus Krause <marcus#exp2010@t3sec.info>
7 *
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26
27 /**
28 * Update plaintext and hashed passwords of existing users to salted passwords.
29 *
30 * @author Christian Kuhn <lolli@schwarzbu.ch>
31 * @author Marcus Krause <marcus#exp2010@t3sec.info>
32 * @package TYPO3
33 * @subpackage saltedpasswords
34 */
35 class tx_saltedpasswords_Tasks_BulkUpdate extends tx_scheduler_Task {
36 /**
37 * @var boolean Whether or not the task is allowed to deactivate itself after processing all existing user records.
38 * @TODO: This could be set with an additional field later on.
39 * The idea is to not disable the task after all initial users where handled.
40 * This could be handy for example if new users are imported regularily from some external source.
41 */
42 protected $canDeactivateSelf = TRUE;
43
44 /**
45 * Converting a password to a salted hash takes some milliseconds (~100ms on an entry system in 2010).
46 * If all users are updated in one run, the task might run a long time if a lot of users must be handled.
47 * Therefore only a small number of frontend and backend users are processed.
48 * If saltedpasswords is enabled for both frontend and backend 2 * numberOfRecords will be handled.
49 *
50 * @var integer Number of records
51 * @TODO: This could be set with an additional field later on
52 */
53 protected $numberOfRecords = 42; // 23 is too low ;)
54
55 /**
56 * @var integer Pointer to last handled frontend and backend user row
57 */
58 protected $userRecordPointer = array();
59
60 /**
61 * Constructor initializes user record pointer
62 *
63 * @return void
64 */
65 public function __construct() {
66 parent::__construct();
67
68 $this->userRecordPointer = array(
69 'FE' => 0,
70 'BE' => 0,
71 );
72 }
73
74 /**
75 * Execute task
76 *
77 * @return void
78 */
79 public function execute() {
80 $processedAllRecords = TRUE;
81
82 // For frontend and backend
83 foreach ($this->userRecordPointer as $mode => $pointer) {
84 // If saltedpasswords is active for frontend / backend
85 if (tx_saltedpasswords_div::isUsageEnabled($mode)) {
86 $usersToUpdate = $this->findUsersToUpdate($mode);
87 $numberOfRows = count($usersToUpdate);
88 if ($numberOfRows > 0) {
89 $processedAllRecords = FALSE;
90 $this->incrementUserRecordPointer($mode, $numberOfRows);
91 $this->convertPasswords($mode, $usersToUpdate);
92 }
93 }
94 }
95
96 // Determine if task should disable itself
97 if ($this->canDeactivateSelf && $processedAllRecords) {
98 $this->deactivateSelf();
99 }
100
101 // Use save() of parent class tx_scheduler_Task to persist
102 // changed task variables: $this->userRecordPointer and $this->disabled
103 $this->save();
104
105 return(TRUE);
106 }
107
108 /**
109 * Find next set of frontend or backend users to update.
110 *
111 * @param string 'FE' for frontend, 'BE' for backend user records
112 * @return array Rows with uid and password
113 */
114 protected function findUsersToUpdate($mode) {
115 $usersToUpdate = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
116 'uid, password',
117 strtolower($mode) . '_users',
118 '1 = 1', // retrieve and update all records (also disabled/deleted) for security reasons
119 '',
120 'uid ASC',
121 $this->userRecordPointer[$mode] . ', ' . $this->numberOfRecords
122 );
123
124 return $usersToUpdate;
125 }
126
127 /**
128 * Iterate over given user records and update password if needed.
129 *
130 * @param string 'FE' for frontend, 'BE' for backend user records
131 * @param array with user uids and passwords
132 * @return void
133 */
134 protected function convertPasswords($mode, $users) {
135 $updateUsers = array();
136 foreach ($users as $user) {
137 // If a password is already a salted hash it must not be updated
138 if ($this->isSaltedHash($user['password'])) {
139 continue;
140 }
141
142 $updateUsers[] = $user;
143 }
144
145 if (count($updateUsers) > 0) {
146 $this->updatePasswords($mode, $updateUsers);
147 }
148 }
149
150 /**
151 * Update password and persist salted hash.
152 *
153 * @param string 'FE' for frontend, 'BE' for backend user records
154 * @param array with user uids and passwords
155 * @return void
156 */
157 protected function updatePasswords($mode, $users) {
158 // Get a default saltedpasswords instance
159 $saltedpasswordsInstance = tx_saltedpasswords_salts_factory::getSaltingInstance(NULL, $mode);
160
161 foreach ($users as $user) {
162 $newPassword = $saltedpasswordsInstance->getHashedPassword($user['password']);
163
164 // If a given password is a md5 hash (usually default be_users without saltedpasswords activated),
165 // result of getHasedPassword() is a salted hashed md5 hash.
166 // We prefix those with 'M', saltedpasswords will then update this password
167 // to a usual salted hash upon first login of the user.
168 if ($this->isMd5Password($user['password'])) {
169 $newPassword = 'M' . $newPassword;
170 }
171
172 // Persist updated password
173 $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
174 strtolower($mode) . '_users',
175 'uid = ' . $user['uid'],
176 array(
177 'password' => $newPassword
178 )
179 );
180 }
181 }
182
183 /**
184 * Passwords prefixed with M or C might be salted passwords:
185 * M means: originally a md5 hash before it was salted (eg. default be_users).
186 * C means: originally a cleartext password with lower hash looping count generated by t3sec_saltedpw.
187 * Both M and C will be updated to usual salted hashes on first login of user.
188 *
189 * If a password does not start with M or C determine if a password is already a usual salted hash.
190 *
191 * @param string Password
192 * @return boolean True if password is a salted hash
193 */
194 protected function isSaltedHash($password) {
195 $isSaltedHash = FALSE;
196 if (strlen($password) > 2 && (t3lib_div::isFirstPartOfStr($password, 'C$') || t3lib_div::isFirstPartOfStr($password, 'M$'))) {
197 // Cut off M or C and test if we have a salted hash
198 $isSaltedHash = tx_saltedpasswords_salts_factory::determineSaltingHashingMethod(substr($password, 1));
199 }
200
201 // Test if given password is a already a usual salted hash
202 if (!$isSaltedHash) {
203 $isSaltedHash = tx_saltedpasswords_salts_factory::determineSaltingHashingMethod($password);
204 }
205
206 return $isSaltedHash;
207 }
208
209 /**
210 * Check if a given password is a md5 hash, the default for be_user records before saltedpasswords.
211 *
212 * @return boolean TRUE if password is md5
213 */
214 protected function isMd5Password($password) {
215 return (bool) preg_match('/[0-9abcdef]{32,32}/i', $password);
216 }
217
218 /**
219 * Increment current user record counter by number of handled rows.
220 *
221 * @param string 'FE' for frontend, 'BE' for backend user records
222 * @param integer Number of handled rows
223 * @return void
224 */
225 protected function incrementUserRecordPointer($mode, $number) {
226 $this->userRecordPointer[$mode] += $number;
227 }
228
229 /**
230 * Deactivate this task instance.
231 * Uses setDisabled() method of parent class tx_scheduler_Task.
232 *
233 * @return void
234 */
235 protected function deactivateSelf() {
236 $this->setDisabled(TRUE);
237 }
238 } // End of class
239
240 if (defined('TYPO3_MODE') && $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/saltedpasswords/classes/tasks/class.tx_saltedpasswords_tasks_bulkupdate.php']) {
241 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/saltedpasswords/classes/tasks/class.tx_saltedpasswords_tasks_bulkupdate.php']);
242 }
243
244 ?>