|
TYPO3 API
SVNRelease
|
00001 <?php 00002 /*************************************************************** 00003 * Copyright notice 00004 * 00005 * (c) 2010-2011 Oliver Klee <typo3-coding@oliverklee.de> 00006 * All rights reserved 00007 * 00008 * This script is part of the TYPO3 project. The TYPO3 project is 00009 * free software; you can redistribute it and/or modify 00010 * it under the terms of the GNU General Public License as published by 00011 * the Free Software Foundation; either version 2 of the License, or 00012 * (at your option) any later version. 00013 * 00014 * The GNU General Public License can be found at 00015 * http://www.gnu.org/copyleft/gpl.html. 00016 * 00017 * This script is distributed in the hope that it will be useful, 00018 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00020 * GNU General Public License for more details. 00021 * 00022 * This copyright notice MUST APPEAR in all copies of the script! 00023 ***************************************************************/ 00024 00025 /** 00026 * Class t3lib_formprotection_BackendFormProtection. 00027 * 00028 * This class provides protection against cross-site request forgery (XSRF/CSRF) 00029 * for forms in the BE. 00030 * 00031 * How to use: 00032 * 00033 * For each form in the BE (or link that changes some data), create a token and 00034 * insert is as a hidden form element. The name of the form element does not 00035 * matter; you only need it to get the form token for verifying it. 00036 * 00037 * <pre> 00038 * $formToken = t3lib_formprotection_Factory::get( 00039 * t3lib_formprotection_Factory::TYPE_BACK_END 00040 * )->generateToken( 00041 * 'BE user setup', 'edit' 00042 * ); 00043 * $this->content .= '<input type="hidden" name="formToken" value="' . 00044 * $formToken . '" />'; 00045 * </pre> 00046 * 00047 * The three parameters $formName, $action and $formInstanceName can be 00048 * arbitrary strings, but they should make the form token as specific as 00049 * possible. For different forms (e.g. BE user setup and editing a tt_content 00050 * record) or different records (with different UIDs) from the same table, 00051 * those values should be different. 00052 * 00053 * For editing a tt_content record, the call could look like this: 00054 * 00055 * <pre> 00056 * $formToken = t3lib_formprotection_Factory::get( 00057 * t3lib_formprotection_Factory::TYPE_BACK_END 00058 * )->getFormProtection()->generateToken( 00059 * 'tt_content', 'edit', $uid 00060 * ); 00061 * </pre> 00062 * 00063 * At the end of the form, you need to persist the tokens. This makes sure that 00064 * generated tokens get saved, and also that removed tokens stay removed: 00065 * 00066 * <pre> 00067 * t3lib_formprotection_Factory::get( 00068 * t3lib_formprotection_Factory::TYPE_BACK_END 00069 * )->persistTokens(); 00070 * </pre> 00071 * 00072 * In BE lists, it might be necessary to generate hundreds of tokens. So the 00073 * tokens do not get automatically persisted after creation for performance 00074 * reasons. 00075 * 00076 * 00077 * When processing the data that has been submitted by the form, you can check 00078 * that the form token is valid like this: 00079 * 00080 * <pre> 00081 * if ($dataHasBeenSubmitted && t3lib_formprotection_Factory::get( 00082 * t3lib_formprotection_Factory::TYPE_BACK_END 00083 * )->validateToken( 00084 * (string) t3lib_div::_POST('formToken'), 00085 * 'BE user setup', 'edit 00086 * ) 00087 * ) { 00088 * // processes the data 00089 * } else { 00090 * // no need to do anything here as the BE form protection will create a 00091 * // flash message for an invalid token 00092 * } 00093 * </pre> 00094 * 00095 * Note that validateToken invalidates the token with the token ID. So calling 00096 * validate with the same parameters two times in a row will always return FALSE 00097 * for the second call. 00098 * 00099 * It is important that the tokens get validated <em>before</em> the tokens are 00100 * persisted. This makes sure that the tokens that get invalidated by 00101 * validateToken cannot be used again. 00102 * 00103 * $Id$ 00104 * 00105 * @package TYPO3 00106 * @subpackage t3lib 00107 * 00108 * @author Oliver Klee <typo3-coding@oliverklee.de> 00109 */ 00110 class t3lib_formprotection_BackendFormProtection extends t3lib_formprotection_Abstract { 00111 /** 00112 * the maximum number of tokens that can exist at the same time 00113 * 00114 * @var integer 00115 */ 00116 protected $maximumNumberOfTokens = 20000; 00117 00118 /** 00119 * Keeps the instance of the user which existed during creation 00120 * of the object. 00121 * 00122 * @var t3lib_beUserAuth 00123 */ 00124 protected $backendUser; 00125 00126 /** 00127 * Only allow construction if we have a backend session 00128 */ 00129 public function __construct() { 00130 if (!$this->isAuthorizedBackendSession()) { 00131 throw new t3lib_error_Exception( 00132 'A back-end form protection may only be instantiated if there' . 00133 ' is an active back-end session.', 00134 1285067843 00135 ); 00136 } 00137 $this->backendUser = $GLOBALS['BE_USER']; 00138 parent::__construct(); 00139 } 00140 00141 /** 00142 * Overrule the method in the absract class, because we can drop the 00143 * whole locking procedure, which is done in persistTokens, if we 00144 * simply want to delete all tokens. 00145 * 00146 * @see t3lib/formprotection/t3lib_formprotection_Abstract::clean() 00147 */ 00148 public function clean() { 00149 $this->tokens = array(); 00150 $this->backendUser->setAndSaveSessionData('formTokens', $this->tokens); 00151 $this->resetPersistingRequiredStatus(); 00152 } 00153 00154 /** 00155 * Override the abstract class to be able to strip out 00156 * the token id from the POST variable. 00157 * 00158 * @see t3lib/formprotection/t3lib_formprotection_Abstract::validateToken() 00159 */ 00160 public function validateToken( 00161 $token, $formName, $action = '', $formInstanceName = '' 00162 ) { 00163 list($tokenId, $_) = explode('-', (string)$token); 00164 00165 return parent::validateToken($tokenId, $formName, $action, $formInstanceName); 00166 } 00167 00168 /** 00169 * Creates or displayes an error message telling the user that the submitted 00170 * form token is invalid. 00171 * 00172 * @return void 00173 */ 00174 protected function createValidationErrorMessage() { 00175 $message = t3lib_div::makeInstance( 00176 't3lib_FlashMessage', 00177 $GLOBALS['LANG']->sL( 00178 'LLL:EXT:lang/locallang_core.xml:error.formProtection.tokenInvalid' 00179 ), 00180 '', 00181 t3lib_FlashMessage::ERROR, 00182 TRUE 00183 ); 00184 t3lib_FlashMessageQueue::addMessage($message); 00185 } 00186 00187 /** 00188 * Retrieves all saved tokens. 00189 * 00190 * @return array<array> 00191 * the saved tokens as, will be empty if no tokens have been saved 00192 */ 00193 protected function retrieveTokens() { 00194 $tokens = $this->backendUser->getSessionData('formTokens'); 00195 if (!is_array($tokens)) { 00196 $tokens = array(); 00197 } 00198 00199 return $tokens; 00200 } 00201 00202 /** 00203 * It might be that two (or more) scripts are executed at the same time, 00204 * which would lead to a race condition, where both (all) scripts retrieve 00205 * the same tokens from the session, so the script that is executed 00206 * last will overwrite the tokens generated in the first scripts. 00207 * So before writing all tokens back to the session we need to get the 00208 * current tokens from the session again. 00209 * 00210 */ 00211 protected function updateTokens() { 00212 $this->backendUser->user = $this->backendUser->fetchUserSession(TRUE); 00213 $tokens = $this->retrieveTokens(); 00214 $this->tokens = array_merge($tokens, $this->addedTokens); 00215 foreach ($this->droppedTokenIds as $tokenId) { 00216 unset($this->tokens[$tokenId]); 00217 } 00218 } 00219 00220 /** 00221 * Saves the tokens so that they can be used by a later incarnation of this 00222 * class. 00223 * 00224 * @return void 00225 */ 00226 public function persistTokens() { 00227 if ($this->isPersistingRequired()) { 00228 $lockObject = $this->acquireLock(); 00229 00230 $this->updateTokens(); 00231 $this->backendUser->setAndSaveSessionData('formTokens', $this->tokens); 00232 $this->resetPersistingRequiredStatus(); 00233 00234 $this->releaseLock($lockObject); 00235 } 00236 } 00237 00238 /** 00239 * Tries to acquire a lock to not allow a race condition. 00240 * 00241 * @return t3lib_lock|FALSE The lock object or FALSE 00242 */ 00243 protected function acquireLock() { 00244 $identifier = 'persistTokens' . $this->backendUser->id; 00245 try { 00246 /** @var t3lib_lock $lockObject */ 00247 $lockObject = t3lib_div::makeInstance('t3lib_lock', $identifier, 'simple'); 00248 $lockObject->setEnableLogging(FALSE); 00249 $success = $lockObject->acquire(); 00250 } catch (Exception $e) { 00251 t3lib_div::sysLog('Locking: Failed to acquire lock: '.$e->getMessage(), 't3lib_formprotection_BackendFormProtection', t3lib_div::SYSLOG_SEVERITY_ERROR); 00252 $success = FALSE; // If locking fails, return with false and continue without locking 00253 } 00254 00255 return $success ? $lockObject : FALSE; 00256 } 00257 00258 /** 00259 * Releases the lock if it was acquired before. 00260 * 00261 * @return boolean 00262 */ 00263 protected function releaseLock(&$lockObject) { 00264 $success = FALSE; 00265 // If lock object is set and was acquired, release it: 00266 if (is_object($lockObject) && $lockObject instanceof t3lib_lock && $lockObject->getLockStatus()) { 00267 $success = $lockObject->release(); 00268 $lockObject = NULL; 00269 } 00270 00271 return $success; 00272 } 00273 00274 /** 00275 * Checks if a user is logged in and the session is active. 00276 * 00277 * @return boolean 00278 */ 00279 protected function isAuthorizedBackendSession() { 00280 return (isset($GLOBALS['BE_USER']) && $GLOBALS['BE_USER'] instanceof t3lib_beUserAuth && isset($GLOBALS['BE_USER']->user['uid'])); 00281 } 00282 } 00283 00284 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/formprotection/class.t3lib_formprotection_backendformprotection.php'])) { 00285 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/formprotection/class.t3lib_formprotection_backendformprotection.php']); 00286 } 00287 ?>
1.8.0