Fixed issue #17289: Form protection tokens get lost because of a race condition when...
authorErnesto Baschny <ernst@cron-it.de>
Tue, 25 Jan 2011 10:17:36 +0000 (10:17 +0000)
committerErnesto Baschny <ernst@cron-it.de>
Tue, 25 Jan 2011 10:17:36 +0000 (10:17 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@10297 709f56b5-9817-0410-a4d7-c38de5d9e867

ChangeLog
t3lib/class.t3lib_lock.php
t3lib/formprotection/class.t3lib_formprotection_abstract.php
t3lib/formprotection/class.t3lib_formprotection_backendformprotection.php

index fc92629..1d7243a 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -4,6 +4,7 @@
 
 2011-01-25  Ernesto Baschny  <ernst@cron-it.de>
 
+       * Fixed issue #17289: Form protection tokens get lost because of a race condition when persisting tokens (Thanks to Helmut Hummel)
        * Fixed issue #17284: Formprotection persistToken method is called too often, causing unnecessary DB-load (Thanks to Helmut Hummel)
 
 2011-01-25  Jeff Segars  <jeff@webempoweredchurch>
index 99ef034..f734e9e 100644 (file)
@@ -54,6 +54,8 @@ class t3lib_lock {
 
        protected $loops = 150; // Number of times a locked resource is tried to be acquired. This is only used by manual locks like the "simple" method.
        protected $step = 200; // Milliseconds after lock acquire is retried. $loops * $step results in the maximum delay of a lock. Only used by manual locks like the "simple" method.
+       protected $syslogFacility = 'cms';
+       protected $isLoggingEnabled = TRUE;
 
 
        /**
@@ -269,6 +271,24 @@ class t3lib_lock {
        }
 
        /**
+        * Sets the facility (extension name) for the syslog entry.
+        *
+        * @param string $syslogFacility
+        */
+       public function setSyslogFacility($syslogFacility) {
+               $this->syslogFacility = $syslogFacility;
+       }
+
+       /**
+        * Enable/ disable logging
+        *
+        * @param boolean $isLoggingEnabled
+        */
+       public function setEnableLogging($isLoggingEnabled) {
+               $this->isLoggingEnabled = $isLoggingEnabled;
+       }
+
+       /**
         * Adds a common log entry for this locking API using t3lib_div::sysLog().
         * Example: 25-02-08 17:58 - cms: Locking [simple::0aeafd2a67a6bb8b9543fb9ea25ecbe2]: Acquired
         *
@@ -277,7 +297,9 @@ class t3lib_lock {
         * @return      void
         */
        public function sysLog($message, $severity = 0) {
-               t3lib_div::sysLog('Locking [' . $this->method . '::' . $this->id . ']: ' . trim($message), 'cms', $severity);
+               if ($this->isLoggingEnabled) {
+                       t3lib_div::sysLog('Locking [' . $this->method . '::' . $this->id . ']: ' . trim($message), $this->syslogFacility, $severity);
+               }
        }
 }
 
index 23f6ad3..c5d12ae 100644 (file)
@@ -60,7 +60,7 @@ abstract class t3lib_formprotection_Abstract {
         * checking.
         */
        public function __construct() {
-               $this->retrieveTokens();
+               $this->tokens = $this->retrieveTokens();
        }
 
        /**
@@ -245,4 +245,4 @@ abstract class t3lib_formprotection_Abstract {
        }
 }
 
-?>
\ No newline at end of file
+?>
index c5b9b39..92a7d4d 100644 (file)
@@ -169,7 +169,22 @@ class t3lib_formprotection_BackendFormProtection extends t3lib_formprotection_Ab
                        $tokens = array();
                }
 
-               $this->tokens = $tokens;
+               return $tokens;
+       }
+
+       /**
+        * It might be that two (or more) scripts are executed at the same time,
+        * which would lead to a race condition, where both (all) scripts retrieve
+        * the same tokens from the session, so the script that is executed
+        * last will overwrite the tokens generated in the first scripts.
+        * So before writing all tokens back to the session we need to get the
+        * current tokens from the session again.
+        *
+        */
+       protected function updateTokens() {
+               $this->backendUser->user = $this->backendUser->fetchUserSession(TRUE);
+               $tokens = $this->retrieveTokens();
+               $this->tokens = array_merge($this->tokens, $tokens);
        }
 
        /**
@@ -179,7 +194,47 @@ class t3lib_formprotection_BackendFormProtection extends t3lib_formprotection_Ab
         * @return void
         */
        public function persistTokens() {
+               $lockObject = $this->acquireLock();
+
+               $this->updateTokens();
                $this->backendUser->setAndSaveSessionData('formTokens', $this->tokens);
+
+               $this->releaseLock($lockObject);
+       }
+
+       /**
+        * Tries to acquire a lock to not allow a race condition.
+        *
+        * @return t3lib_lock|FALSE The lock object or FALSE
+        */
+       protected function acquireLock() {
+               $identifier = 'persistTokens' . $this->backendUser->id;
+               try {
+                       $lockObject = t3lib_div::makeInstance('t3lib_lock', $identifier, 'simple');
+                       $lockObject->setEnableLogging(FALSE);
+                       $success = $lockObject->acquire();
+               } catch (Exception $e) {
+                       t3lib_div::sysLog('Locking: Failed to acquire lock: '.$e->getMessage(), 't3lib_formprotection_BackendFormProtection', t3lib_div::SYSLOG_SEVERITY_ERROR);
+                       $success = FALSE;       // If locking fails, return with false and continue without locking
+               }
+
+               return $success ? $lockObject : FALSE;
+       }
+
+       /**
+        * Releases the lock if it was acquired before.
+        *
+        * @return boolean
+        */
+       protected function releaseLock(&$lockObject) {
+               $success = FALSE;
+                       // If lock object is set and was acquired, release it:
+               if (is_object($lockObject) && $lockObject instanceof t3lib_lock && $lockObject->getLockStatus()) {
+                       $success = $lockObject->release();
+                       $lockObject = NULL;
+               }
+
+               return $success;
        }
 }