[TASK] Change "true" into "TRUE"
[Packages/TYPO3.CMS.git] / t3lib / formprotection / class.t3lib_formprotection_backendformprotection.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2010-2011 Oliver Klee <typo3-coding@oliverklee.de>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
24
25 /**
26 * Class t3lib_formprotection_BackendFormProtection.
27 *
28 * This class provides protection against cross-site request forgery (XSRF/CSRF)
29 * for forms in the BE.
30 *
31 * How to use:
32 *
33 * For each form in the BE (or link that changes some data), create a token and
34 * insert is as a hidden form element. The name of the form element does not
35 * matter; you only need it to get the form token for verifying it.
36 *
37 * <pre>
38 * $formToken = t3lib_formprotection_Factory::get(
39 * t3lib_formprotection_Factory::TYPE_BACK_END
40 * )->generateToken(
41 * 'BE user setup', 'edit'
42 * );
43 * $this->content .= '<input type="hidden" name="formToken" value="' .
44 * $formToken . '" />';
45 * </pre>
46 *
47 * The three parameters $formName, $action and $formInstanceName can be
48 * arbitrary strings, but they should make the form token as specific as
49 * possible. For different forms (e.g. BE user setup and editing a tt_content
50 * record) or different records (with different UIDs) from the same table,
51 * those values should be different.
52 *
53 * For editing a tt_content record, the call could look like this:
54 *
55 * <pre>
56 * $formToken = t3lib_formprotection_Factory::get(
57 * t3lib_formprotection_Factory::TYPE_BACK_END
58 * )->getFormProtection()->generateToken(
59 * 'tt_content', 'edit', $uid
60 * );
61 * </pre>
62 *
63 * At the end of the form, you need to persist the tokens. This makes sure that
64 * generated tokens get saved, and also that removed tokens stay removed:
65 *
66 * <pre>
67 * t3lib_formprotection_Factory::get(
68 * t3lib_formprotection_Factory::TYPE_BACK_END
69 * )->persistTokens();
70 * </pre>
71 *
72 * In BE lists, it might be necessary to generate hundreds of tokens. So the
73 * tokens do not get automatically persisted after creation for performance
74 * reasons.
75 *
76 *
77 * When processing the data that has been submitted by the form, you can check
78 * that the form token is valid like this:
79 *
80 * <pre>
81 * if ($dataHasBeenSubmitted && t3lib_formprotection_Factory::get(
82 * t3lib_formprotection_Factory::TYPE_BACK_END
83 * )->validateToken(
84 * (string) t3lib_div::_POST('formToken'),
85 * 'BE user setup', 'edit
86 * )
87 * ) {
88 * // processes the data
89 * } else {
90 * // no need to do anything here as the BE form protection will create a
91 * // flash message for an invalid token
92 * }
93 * </pre>
94 *
95 * Note that validateToken invalidates the token with the token ID. So calling
96 * validate with the same parameters two times in a row will always return FALSE
97 * for the second call.
98 *
99 * It is important that the tokens get validated <em>before</em> the tokens are
100 * persisted. This makes sure that the tokens that get invalidated by
101 * validateToken cannot be used again.
102 *
103 * @package TYPO3
104 * @subpackage t3lib
105 *
106 * @author Oliver Klee <typo3-coding@oliverklee.de>
107 */
108 class t3lib_formprotection_BackendFormProtection extends t3lib_formprotection_Abstract {
109 /**
110 * the maximum number of tokens that can exist at the same time
111 *
112 * @var integer
113 */
114 protected $maximumNumberOfTokens = 20000;
115
116 /**
117 * Keeps the instance of the user which existed during creation
118 * of the object.
119 *
120 * @var t3lib_beUserAuth
121 */
122 protected $backendUser;
123
124 /**
125 * Only allow construction if we have a backend session
126 */
127 public function __construct() {
128 if (!$this->isAuthorizedBackendSession()) {
129 throw new t3lib_error_Exception(
130 'A back-end form protection may only be instantiated if there' .
131 ' is an active back-end session.',
132 1285067843
133 );
134 }
135 $this->backendUser = $GLOBALS['BE_USER'];
136 parent::__construct();
137 }
138
139 /**
140 * Overrule the method in the absract class, because we can drop the
141 * whole locking procedure, which is done in persistTokens, if we
142 * simply want to delete all tokens.
143 *
144 * @see t3lib/formprotection/t3lib_formprotection_Abstract::clean()
145 */
146 public function clean() {
147 $this->tokens = array();
148 $this->backendUser->setAndSaveSessionData('formTokens', $this->tokens);
149 $this->resetPersistingRequiredStatus();
150 }
151
152 /**
153 * Override the abstract class to be able to strip out
154 * the token id from the POST variable.
155 *
156 * @see t3lib/formprotection/t3lib_formprotection_Abstract::validateToken()
157 */
158 public function validateToken(
159 $token, $formName, $action = '', $formInstanceName = ''
160 ) {
161 list($tokenId, $_) = explode('-', (string)$token);
162
163 return parent::validateToken($tokenId, $formName, $action, $formInstanceName);
164 }
165
166 /**
167 * Creates or displayes an error message telling the user that the submitted
168 * form token is invalid.
169 *
170 * @return void
171 */
172 protected function createValidationErrorMessage() {
173 $message = t3lib_div::makeInstance(
174 't3lib_FlashMessage',
175 $GLOBALS['LANG']->sL(
176 'LLL:EXT:lang/locallang_core.xml:error.formProtection.tokenInvalid'
177 ),
178 '',
179 t3lib_FlashMessage::ERROR,
180 TRUE
181 );
182 t3lib_FlashMessageQueue::addMessage($message);
183 }
184
185 /**
186 * Retrieves all saved tokens.
187 *
188 * @return array<array>
189 * the saved tokens as, will be empty if no tokens have been saved
190 */
191 protected function retrieveTokens() {
192 $tokens = $this->backendUser->getSessionData('formTokens');
193 if (!is_array($tokens)) {
194 $tokens = array();
195 }
196
197 return $tokens;
198 }
199
200 /**
201 * It might be that two (or more) scripts are executed at the same time,
202 * which would lead to a race condition, where both (all) scripts retrieve
203 * the same tokens from the session, so the script that is executed
204 * last will overwrite the tokens generated in the first scripts.
205 * So before writing all tokens back to the session we need to get the
206 * current tokens from the session again.
207 *
208 */
209 protected function updateTokens() {
210 $this->backendUser->user = $this->backendUser->fetchUserSession(TRUE);
211 $tokens = $this->retrieveTokens();
212 $this->tokens = array_merge($tokens, $this->addedTokens);
213 foreach ($this->droppedTokenIds as $tokenId) {
214 unset($this->tokens[$tokenId]);
215 }
216 }
217
218 /**
219 * Saves the tokens so that they can be used by a later incarnation of this
220 * class.
221 *
222 * @return void
223 */
224 public function persistTokens() {
225 if ($this->isPersistingRequired()) {
226 $lockObject = $this->acquireLock();
227
228 $this->updateTokens();
229 $this->backendUser->setAndSaveSessionData('formTokens', $this->tokens);
230 $this->resetPersistingRequiredStatus();
231
232 $this->releaseLock($lockObject);
233 }
234 }
235
236 /**
237 * Tries to acquire a lock to not allow a race condition.
238 *
239 * @return t3lib_lock|FALSE The lock object or FALSE
240 */
241 protected function acquireLock() {
242 $identifier = 'persistTokens' . $this->backendUser->id;
243 try {
244 /** @var t3lib_lock $lockObject */
245 $lockObject = t3lib_div::makeInstance('t3lib_lock', $identifier, 'simple');
246 $lockObject->setEnableLogging(FALSE);
247 $success = $lockObject->acquire();
248 } catch (Exception $e) {
249 t3lib_div::sysLog('Locking: Failed to acquire lock: '.$e->getMessage(), 't3lib_formprotection_BackendFormProtection', t3lib_div::SYSLOG_SEVERITY_ERROR);
250 $success = FALSE; // If locking fails, return with false and continue without locking
251 }
252
253 return $success ? $lockObject : FALSE;
254 }
255
256 /**
257 * Releases the lock if it was acquired before.
258 *
259 * @return boolean
260 */
261 protected function releaseLock(&$lockObject) {
262 $success = FALSE;
263 // If lock object is set and was acquired, release it:
264 if (is_object($lockObject) && $lockObject instanceof t3lib_lock && $lockObject->getLockStatus()) {
265 $success = $lockObject->release();
266 $lockObject = NULL;
267 }
268
269 return $success;
270 }
271
272 /**
273 * Checks if a user is logged in and the session is active.
274 *
275 * @return boolean
276 */
277 protected function isAuthorizedBackendSession() {
278 return (isset($GLOBALS['BE_USER']) && $GLOBALS['BE_USER'] instanceof t3lib_beUserAuth && isset($GLOBALS['BE_USER']->user['uid']));
279 }
280 }
281
282 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/formprotection/class.t3lib_formprotection_backendformprotection.php'])) {
283 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/formprotection/class.t3lib_formprotection_backendformprotection.php']);
284 }
285 ?>