TYPO3 API  SVNRelease
class.tx_saltedpasswords_tasks_bulkupdate.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 2010-2011 Christian Kuhn <lolli@schwarzbu.ch>
00006 *       Marcus Krause <marcus#exp2010@t3sec.info>
00007 *
00008 *  All rights reserved
00009 *
00010 *  This script is part of the TYPO3 project. The TYPO3 project is
00011 *  free software; you can redistribute it and/or modify
00012 *  it under the terms of the GNU General Public License as published by
00013 *  the Free Software Foundation; either version 2 of the License, or
00014 *  (at your option) any later version.
00015 *
00016 *  The GNU General Public License can be found at
00017 *  http://www.gnu.org/copyleft/gpl.html.
00018 *
00019 *  This script is distributed in the hope that it will be useful,
00020 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00021 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00022 *  GNU General Public License for more details.
00023 *
00024 *  This copyright notice MUST APPEAR in all copies of the script!
00025 ***************************************************************/
00026 
00027 /**
00028  * Update plaintext and hashed passwords of existing users to salted passwords.
00029  *
00030  * @author Christian Kuhn <lolli@schwarzbu.ch>
00031  * @author Marcus Krause <marcus#exp2010@t3sec.info>
00032  * @package TYPO3
00033  * @subpackage saltedpasswords
00034  */
00035 class tx_saltedpasswords_Tasks_BulkUpdate extends tx_scheduler_Task {
00036     /**
00037      * @var boolean Whether or not the task is allowed to deactivate itself after processing all existing user records.
00038      * @TODO: This could be set with an additional field later on.
00039      *      The idea is to not disable the task after all initial users where handled.
00040      *      This could be handy for example if new users are imported regularily from some external source.
00041      */
00042     protected $canDeactivateSelf = TRUE;
00043 
00044     /**
00045      * Converting a password to a salted hash takes some milliseconds (~100ms on an entry system in 2010).
00046      * If all users are updated in one run, the task might run a long time if a lot of users must be handled.
00047      * Therefore only a small number of frontend and backend users are processed.
00048      * If saltedpasswords is enabled for both frontend and backend 2 * numberOfRecords will be handled.
00049      *
00050      * @var integer Number of records
00051      * @TODO: This could be set with an additional field later on
00052      */
00053     protected $numberOfRecords = 42; // 23 is too low ;)
00054 
00055     /**
00056      * @var integer Pointer to last handled frontend and backend user row
00057      */
00058     protected $userRecordPointer = array();
00059 
00060     /**
00061      * Constructor initializes user record pointer
00062      *
00063      * @return void
00064      */
00065     public function __construct() {
00066         parent::__construct();
00067 
00068         $this->userRecordPointer = array(
00069             'FE' => 0,
00070             'BE' => 0,
00071         );
00072     }
00073 
00074     /**
00075      * Execute task
00076      *
00077      * @return void
00078      */
00079     public function execute() {
00080         $processedAllRecords = TRUE;
00081 
00082             // For frontend and backend
00083         foreach ($this->userRecordPointer as $mode => $pointer) {
00084                 // If saltedpasswords is active for frontend / backend
00085             if (tx_saltedpasswords_div::isUsageEnabled($mode)) {
00086                 $usersToUpdate = $this->findUsersToUpdate($mode);
00087                 $numberOfRows = count($usersToUpdate);
00088                 if ($numberOfRows > 0) {
00089                     $processedAllRecords = FALSE;
00090                     $this->incrementUserRecordPointer($mode, $numberOfRows);
00091                     $this->convertPasswords($mode, $usersToUpdate);
00092                 }
00093             }
00094         }
00095 
00096             // Determine if task should disable itself
00097         if ($this->canDeactivateSelf && $processedAllRecords) {
00098             $this->deactivateSelf();
00099         }
00100 
00101             // Use save() of parent class tx_scheduler_Task to persist
00102             // changed task variables: $this->userRecordPointer and $this->disabled
00103         $this->save();
00104 
00105         return(TRUE);
00106     }
00107 
00108     /**
00109      * Find next set of frontend or backend users to update.
00110      *
00111      * @param string 'FE' for frontend, 'BE' for backend user records
00112      * @return array Rows with uid and password
00113      */
00114     protected function findUsersToUpdate($mode) {
00115         $usersToUpdate = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
00116             'uid, password',
00117             strtolower($mode) . '_users',
00118             '1 = 1',  // retrieve and update all records (also disabled/deleted) for security reasons
00119             '',
00120             'uid ASC',
00121             $this->userRecordPointer[$mode] . ', ' . $this->numberOfRecords
00122         );
00123 
00124         return $usersToUpdate;
00125     }
00126 
00127     /**
00128      * Iterate over given user records and update password if needed.
00129      *
00130      * @param string 'FE' for frontend, 'BE' for backend user records
00131      * @param array with user uids and passwords
00132      * @return void
00133      */
00134     protected function convertPasswords($mode, $users) {
00135         $updateUsers = array();
00136         foreach ($users as $user) {
00137                 // If a password is already a salted hash it must not be updated
00138             if ($this->isSaltedHash($user['password'])) {
00139                 continue;
00140             }
00141 
00142             $updateUsers[] = $user;
00143         }
00144 
00145         if (count($updateUsers) > 0) {
00146             $this->updatePasswords($mode, $updateUsers);
00147         }
00148     }
00149 
00150     /**
00151      * Update password and persist salted hash.
00152      *
00153      * @param string 'FE' for frontend, 'BE' for backend user records
00154      * @param array with user uids and passwords
00155      * @return void
00156      */
00157     protected function updatePasswords($mode, $users) {
00158             // Get a default saltedpasswords instance
00159         $saltedpasswordsInstance = tx_saltedpasswords_salts_factory::getSaltingInstance(NULL, $mode);
00160         
00161         foreach ($users as $user) {
00162             $newPassword = $saltedpasswordsInstance->getHashedPassword($user['password']);
00163 
00164                 // If a given password is a md5 hash (usually default be_users without saltedpasswords activated),
00165                 // result of getHasedPassword() is a salted hashed md5 hash.
00166                 // We prefix those with 'M', saltedpasswords will then update this password
00167                 // to a usual salted hash upon first login of the user.
00168             if ($this->isMd5Password($user['password'])) {
00169                 $newPassword = 'M' . $newPassword;
00170             }
00171 
00172                 // Persist updated password
00173             $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
00174                 strtolower($mode) . '_users',
00175                 'uid = ' . $user['uid'],
00176                 array(
00177                     'password' => $newPassword
00178                 )
00179             );
00180         }
00181     }
00182 
00183     /**
00184      * Passwords prefixed with M or C might be salted passwords:
00185      *  M means: originally a md5 hash before it was salted (eg. default be_users).
00186      *  C means: originally a cleartext password with lower hash looping count generated by t3sec_saltedpw.
00187      * Both M and C will be updated to usual salted hashes on first login of user.
00188      *
00189      * If a password does not start with M or C determine if a password is already a usual salted hash.
00190      *
00191      * @param string Password
00192      * @return boolean True if password is a salted hash
00193      */
00194     protected function isSaltedHash($password) {
00195         $isSaltedHash = FALSE;
00196         if (strlen($password) > 2 && (t3lib_div::isFirstPartOfStr($password, 'C$') || t3lib_div::isFirstPartOfStr($password, 'M$'))) {
00197                 // Cut off M or C and test if we have a salted hash
00198             $isSaltedHash = tx_saltedpasswords_salts_factory::determineSaltingHashingMethod(substr($password, 1));
00199         }
00200 
00201             // Test if given password is a already a usual salted hash
00202         if (!$isSaltedHash) {
00203             $isSaltedHash = tx_saltedpasswords_salts_factory::determineSaltingHashingMethod($password);
00204         }
00205 
00206         return $isSaltedHash;
00207     }
00208 
00209     /**
00210      * Check if a given password is a md5 hash, the default for be_user records before saltedpasswords.
00211      *
00212      * @return boolean TRUE if password is md5
00213      */
00214     protected function isMd5Password($password) {
00215         return (bool) preg_match('/[0-9abcdef]{32,32}/i', $password);
00216     }
00217 
00218     /**
00219      * Increment current user record counter by number of handled rows.
00220      *
00221      * @param string 'FE' for frontend, 'BE' for backend user records
00222      * @param integer Number of handled rows
00223      * @return void
00224      */
00225     protected function incrementUserRecordPointer($mode, $number) {
00226         $this->userRecordPointer[$mode] += $number;
00227     }
00228 
00229     /**
00230      * Deactivate this task instance.
00231      * Uses setDisabled() method of parent class tx_scheduler_Task.
00232      *
00233      * @return void
00234      */
00235     protected function deactivateSelf() {
00236         $this->setDisabled(TRUE);
00237     }
00238 } // End of class
00239 
00240 if (defined('TYPO3_MODE') && $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/saltedpasswords/classes/tasks/class.tx_saltedpasswords_tasks_bulkupdate.php']) {
00241     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/saltedpasswords/classes/tasks/class.tx_saltedpasswords_tasks_bulkupdate.php']);
00242 }
00243 
00244 ?>