Added feature #16437: Introduce a form protection API (Thanks to the Security Team...
authorErnesto Baschny <ernst@cron-it.de>
Wed, 17 Nov 2010 11:11:41 +0000 (11:11 +0000)
committerErnesto Baschny <ernst@cron-it.de>
Wed, 17 Nov 2010 11:11:41 +0000 (11:11 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@9439 709f56b5-9817-0410-a4d7-c38de5d9e867

12 files changed:
ChangeLog
t3lib/core_autoload.php
t3lib/formprotection/class.t3lib_formprotection_abstract.php [new file with mode: 0644]
t3lib/formprotection/class.t3lib_formprotection_backendformprotection.php [new file with mode: 0644]
t3lib/formprotection/class.t3lib_formprotection_factory.php [new file with mode: 0644]
t3lib/formprotection/class.t3lib_formprotection_installtoolformprotection.php [new file with mode: 0644]
tests/t3lib/formprotection/fixtures/class.t3lib_formprotection_testing.php [new file with mode: 0644]
tests/t3lib/formprotection/t3lib_formprotection_AbstractTest.php [new file with mode: 0644]
tests/t3lib/formprotection/t3lib_formprotection_BackendFormProtectionTest.php [new file with mode: 0644]
tests/t3lib/formprotection/t3lib_formprotection_FactoryTest.php [new file with mode: 0644]
tests/t3lib/formprotection/t3lib_formprotection_InstallToolFormProtectionTest.php [new file with mode: 0644]
typo3/sysext/lang/locallang_core.xml

index b45f769..94c4c39 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -9,6 +9,7 @@
 2010-11-17  Ernesto Baschny  <ernst@cron-it.de>
 
        * Added feature #16027: Let typolink honour secure filelink configuration
+       * Added feature #16437: Introduce a form protection API (Thanks to the Security Team: Oliver Klee, Helmut Hummel)
 
 2010-11-17  Benjamin Mack  <benni@typo3.org>
 
index f6bb09e..9ee9048 100644 (file)
@@ -119,6 +119,10 @@ $t3libClasses = array(
        't3lib_error_exceptionhandlerinterface' => PATH_t3lib . 'error/interface.t3lib_error_exceptionhandlerinterface.php',
        't3lib_browselinkshook' => PATH_t3lib . 'interfaces/interface.t3lib_browselinkshook.php',
        't3lib_extfilefunctions_processdatahook' => PATH_t3lib . 'interfaces/interface.t3lib_extfilefunctions_processdatahook.php',
+       't3lib_formprotection_factory' => PATH_t3lib . 'formprotection/class.t3lib_formprotection_factory.php',
+       't3lib_formprotection_abstract' => PATH_t3lib . 'formprotection/class.t3lib_formprotection_abstract.php',
+       't3lib_formprotection_backendformprotection' => PATH_t3lib . 'formprotection/class.t3lib_formprotection_backendformprotection.php',
+       't3lib_formprotection_installtoolformprotection' => PATH_t3lib . 'formprotection/class.t3lib_formprotection_installtoolformprotection.php',
        't3lib_localrecordlistgettablehook' => PATH_t3lib . 'interfaces/interface.t3lib_localrecordlistgettablehook.php',
        't3lib_pageselect_getpagehook' => PATH_t3lib . 'interfaces/interface.t3lib_pageselect_getpagehook.php',
        't3lib_pageselect_getrecordoverlayhook' => PATH_t3lib . 'interfaces/interface.t3lib_pageselect_getrecordoverlayhook.php',
diff --git a/t3lib/formprotection/class.t3lib_formprotection_abstract.php b/t3lib/formprotection/class.t3lib_formprotection_abstract.php
new file mode 100644 (file)
index 0000000..a7b92eb
--- /dev/null
@@ -0,0 +1,247 @@
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2010 Oliver Klee <typo3-coding@oliverklee.de>
+* All rights reserved
+*
+* This script is part of the TYPO3 project. The TYPO3 project is
+* free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* The GNU General Public License can be found at
+* http://www.gnu.org/copyleft/gpl.html.
+*
+* This script is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+/**
+ * Class t3lib_formprotection_Abstract.
+ *
+ * This class provides protection against cross-site request forgery (XSRF/CSRF)
+ * for forms.
+ *
+ * For documentation on how to use this class, please see the documentation of
+ * the corresponding subclasses, e.g. t3lib_formprotection_BackendFormProtection.
+ *
+ * $Id$
+ *
+ * @package TYPO3
+ * @subpackage t3lib
+ *
+ * @author Oliver Klee <typo3-coding@oliverklee.de>
+ */
+abstract class t3lib_formprotection_Abstract {
+       /**
+        * the maximum number of tokens that can exist at the same time
+        *
+        * @var integer
+        */
+       protected $maximumNumberOfTokens = 0;
+
+       /**
+        * Valid tokens sorted from oldest to newest.
+        *
+        * [tokenId] => array(formName, formInstanceName)
+        *
+        * @var array<array>
+        */
+       protected $tokens = array();
+
+       /**
+        * Constructor. Makes sure existing tokens are read and available for
+        * checking.
+        */
+       public function __construct() {
+               $this->retrieveTokens();
+       }
+
+       /**
+        * Frees as much memory as possible.
+        */
+       public function __destruct() {
+               $this->tokens = array();
+       }
+
+       /**
+        * Deletes all existing tokens and persists the (empty) token table.
+        *
+        * This function is intended to be called when a user logs on or off.
+        *
+        * @return void
+        */
+       public function clean() {
+               $this->tokens = array();
+               $this->persistTokens();
+       }
+
+       /**
+        * Generates and stores a token for a form.
+        *
+        * Calling this function two times with the same parameters will create
+        * two valid, different tokens.
+        *
+        * Generating more tokens than $maximumNumberOfEntries will cause the oldest
+        * tokens to get dropped.
+        *
+        * Note: This function does not persist the tokens.
+        *
+        * @param string $formName
+        *        the name of the form, for example a table name like "tt_content",
+        *        or some other identifier like "install_tool_password", must not be
+        *        empty
+        * @param string $action
+        *        the name of the action of the form, for example "new", "delete" or
+        *        "edit", may also be empty
+        * @param string $formInstanceName
+        *        a string used to differentiate two instances of the same form,
+        *        form example a record UID or a comma-separated list of UIDs,
+        *        may also be empty
+        *
+        * @return string the 32-character hex ID of the generated token
+        */
+       public function generateToken(
+               $formName, $action = '', $formInstanceName = ''
+       ) {
+               if ($formName == '') {
+                       throw new InvalidArgumentException('$formName must not be empty.');
+               }
+
+               do {
+                       $tokenId = bin2hex(t3lib_div::generateRandomBytes(16));
+               } while (isset($this->tokens[$tokenId]));
+
+               $this->tokens[$tokenId] = array(
+                       'formName' => $formName,
+                       'action' => $action,
+                       'formInstanceName' => $formInstanceName,
+               );
+               $this->preventOverflow();
+
+               return $tokenId;
+       }
+
+       /**
+        * Checks whether the token $tokenId is valid in the form $formName with
+        * $formInstanceName.
+        *
+        * A token is valid if $tokenId, $formName and $formInstanceName match and
+        * the token has not been used yet.
+        *
+        * Calling this function will mark the token $tokenId as invalud (if it
+        * exists).
+        *
+        * So calling this function with the same parameters two times will return
+        * FALSE the second time.
+        *
+        * @param string $tokenId
+        *        a form token to check, may also be empty or utterly misformed
+        * @param string $formName
+        *        the name of the form to check, for example "tt_content",
+        *        may also be empty or utterly misformed
+        * @param string $action
+        *        the action of the form to check, for example "edit",
+        *        may also be empty or utterly misformed
+        * @param string $formInstanceName
+        *        the instance name of the form to check, for example "42" or "foo"
+        *        or "31,42", may also be empty or utterly misformed
+        *
+        * @return boolean
+        *         TRUE if $tokenId, $formName, $action and $formInstanceName match
+        *         and the token has not been used yet, FALSE otherwise
+        */
+       public function validateToken(
+               $tokenId, $formName, $action = '', $formInstanceName = ''
+       ) {
+               if (isset($this->tokens[$tokenId])) {
+                       $token = $this->tokens[$tokenId];
+                       $isValid = ($token['formName'] == $formName)
+                               && ($token['action'] == $action)
+                               && ($token['formInstanceName'] == $formInstanceName);
+                       $this->dropToken($tokenId);
+               } else {
+                       $isValid = FALSE;
+               }
+
+               if (!$isValid) {
+                       $this->createValidationErrorMessage();
+               }
+
+               return $isValid;
+       }
+
+       /**
+        * Creates or displayes an error message telling the user that the submitted
+        * form token is invalid.
+        *
+        * This function may also be empty if the validation error should be handled
+        * silently.
+        *
+        * @return void
+        */
+       abstract protected function createValidationErrorMessage();
+
+       /**
+        * Retrieves all saved tokens.
+        *
+        * @return array<arrray>
+        *         the saved tokens, will be empty if no tokens have been saved
+        */
+       abstract protected function retrieveTokens();
+
+       /**
+        * Saves the tokens so that they can be used by a later incarnation of this
+        * class.
+        *
+        * @return void
+        */
+       abstract public function persistTokens();
+
+       /**
+        * Drops the token with the ID $tokenId.
+        *
+        * If there is no token with that ID, this function is a no-op.
+        *
+        * Note: This function does not persist the tokens.
+        *
+        * @param string $tokenId
+        *        the 32-character ID of an existing token, must not be empty
+        *
+        * @return void
+        */
+       protected function dropToken($tokenId) {
+               if (isset($this->tokens[$tokenId])) {
+                       unset($this->tokens[$tokenId]);
+               }
+       }
+
+       /**
+        * Checks whether the number of current tokens still is at most
+        * $this->maximumNumberOfTokens.
+        *
+        * If there are more tokens, the oldest tokens are removed until the number
+        * of tokens is low enough.
+        *
+        * Note: This function does not persist the tokens.
+        *
+        * @return void
+        */
+       protected function preventOverflow() {
+               if (empty($this->tokens)) {
+                       return;
+               }
+
+               while (count($this->tokens) > $this->maximumNumberOfTokens) {
+                       reset($this->tokens);
+                       $this->dropToken(key($this->tokens));
+               }
+       }
+}
+?>
\ No newline at end of file
diff --git a/t3lib/formprotection/class.t3lib_formprotection_backendformprotection.php b/t3lib/formprotection/class.t3lib_formprotection_backendformprotection.php
new file mode 100644 (file)
index 0000000..046c53a
--- /dev/null
@@ -0,0 +1,179 @@
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2010 Oliver Klee <typo3-coding@oliverklee.de>
+* All rights reserved
+*
+* This script is part of the TYPO3 project. The TYPO3 project is
+* free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* The GNU General Public License can be found at
+* http://www.gnu.org/copyleft/gpl.html.
+*
+* This script is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+/**
+ * Class t3lib_formprotection_BackendFormProtection.
+ *
+ * This class provides protection against cross-site request forgery (XSRF/CSRF)
+ * for forms in the BE.
+ *
+ * How to use:
+ *
+ * For each form in the BE (or link that changes some data), create a token and
+ * insert is as a hidden form element. The name of the form element does not
+ * matter; you only need it to get the form token for verifying it.
+ *
+ * <pre>
+ * $formToken = t3lib_formprotection_Factory::get(
+ *     t3lib_formprotection_Factory::TYPE_BACK_END
+ * )->generateToken(
+ *     'BE user setup', 'edit'
+ * );
+ * $this->content .= '<input type="hidden" name="formToken" value="' .
+ *     $formToken . '" />';
+ * </pre>
+ *
+ * The three parameters $formName, $action and $formInstanceName can be
+ * arbitrary strings, but they should make the form token as specific as
+ * possible. For different forms (e.g. BE user setup and editing a tt_content
+ * record) or different records (with different UIDs) from the same table,
+ * those values should be different.
+ *
+ * For editing a tt_content record, the call could look like this:
+ *
+ * <pre>
+ * $formToken = t3lib_formprotection_Factory::get(
+ *     t3lib_formprotection_Factory::TYPE_BACK_END
+ * )->getFormProtection()->generateToken(
+ *    'tt_content', 'edit', $uid
+ * );
+ * </pre>
+ *
+ * At the end of the form, you need to persist the tokens. This makes sure that
+ * generated tokens get saved, and also that removed tokens stay removed:
+ *
+ * <pre>
+ * t3lib_formprotection_Factory::get(
+ *     t3lib_formprotection_Factory::TYPE_BACK_END
+ * )->persistTokens();
+ * </pre>
+ *
+ * In BE lists, it might be necessary to generate hundreds of tokens. So the
+ * tokens do not get automatically persisted after creation for performance
+ * reasons.
+ *
+ *
+ * When processing the data that has been submitted by the form, you can check
+ * that the form token is valid like this:
+ *
+ * <pre>
+ * if ($dataHasBeenSubmitted && t3lib_formprotection_Factory::get(
+ *         t3lib_formprotection_Factory::TYPE_BACK_END
+ *     )->validateToken(
+ *         (string) t3lib_div::_POST('formToken'),
+ *         'BE user setup', 'edit
+ *     )
+ * ) {
+ *     // processes the data
+ * } else {
+ *     // no need to do anything here as the BE form protection will create a
+ *     // flash message for an invalid token
+ * }
+ * </pre>
+ *
+ * Note that validateToken invalidates the token with the token ID. So calling
+ * validate with the same parameters two times in a row will always return FALSE
+ * for the second call.
+ *
+ * It is important that the tokens get validated <em>before</em> the tokens are
+ * persisted. This makes sure that the tokens that get invalidated by
+ * validateToken cannot be used again.
+ *
+ * $Id$
+ *
+ * @package TYPO3
+ * @subpackage t3lib
+ *
+ * @author Oliver Klee <typo3-coding@oliverklee.de>
+ */
+class t3lib_formprotection_BackendFormProtection extends t3lib_formprotection_Abstract {
+       /**
+        * the maximum number of tokens that can exist at the same time
+        *
+        * @var integer
+        */
+       protected $maximumNumberOfTokens = 20000;
+
+       /**
+        * Only allow construction if we have a backend session
+        */
+       public function __construct() {
+               if (!isset($GLOBALS['BE_USER'])) {
+                       throw new t3lib_error_Exception(
+                               'A back-end form protection may only be instantiated if there' .
+                                       ' is an active back-end session.',
+                               1285067843
+                       );
+               }
+               parent::__construct();
+       }
+
+       /**
+        * Creates or displayes an error message telling the user that the submitted
+        * form token is invalid.
+        *
+        * @return void
+        */
+       protected function createValidationErrorMessage() {
+               $message = t3lib_div::makeInstance(
+                       't3lib_FlashMessage',
+                       $GLOBALS['LANG']->sL(
+                               'LLL:EXT:lang/locallang_core.xml:error.formProtection.tokenInvalid'
+                       ),
+                       '',
+                       t3lib_FlashMessage::ERROR
+               );
+               t3lib_FlashMessageQueue::addMessage($message);
+       }
+
+       /**
+        * Retrieves all saved tokens.
+        *
+        * @return array<array>
+        *         the saved tokens as, will be empty if no tokens have been saved
+        */
+       protected function retrieveTokens() {
+               $tokens = $GLOBALS['BE_USER']->getSessionData('formTokens');
+               if (!is_array($tokens)) {
+                       $tokens = array();
+               }
+
+               $this->tokens = $tokens;
+       }
+
+       /**
+        * Saves the tokens so that they can be used by a later incarnation of this
+        * class.
+        *
+        * @return void
+        */
+       public function persistTokens() {
+               $GLOBALS['BE_USER']->setAndSaveSessionData('formTokens', $this->tokens);
+       }
+}
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/formprotection/class.t3lib_formprotection_backendformprotection.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/formprotection/class.t3lib_formprotection_backendformprotection.php']);
+}
+?>
\ No newline at end of file
diff --git a/t3lib/formprotection/class.t3lib_formprotection_factory.php b/t3lib/formprotection/class.t3lib_formprotection_factory.php
new file mode 100644 (file)
index 0000000..a731c5e
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2010 Oliver Klee <typo3-coding@oliverklee.de>
+* All rights reserved
+*
+* This script is part of the TYPO3 project. The TYPO3 project is
+* free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* The GNU General Public License can be found at
+* http://www.gnu.org/copyleft/gpl.html.
+*
+* This script is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+/**
+ * Class t3lib_formprotection_Factory.
+ *
+ * This class creates and manages instances of the various form protection
+ * classes.
+ *
+ * This class provides only static methods. It can not be instantiated.
+ *
+ * Usage for the back-end form protection:
+ *
+ * <pre>
+ * $formProtection = t3lib_formprotection_Factory::get(
+ *     't3lib_formProtection_BackEnd'
+ * );
+ * </pre>
+ *
+ * Usage for the install tool form protection:
+ *
+ * <pre>
+ * $formProtection = t3lib_formprotection_Factory::get(
+ *     'tx_install_formprotection'
+ * );
+ * $formProtection->injectInstallTool($this);
+ * </pre>
+ *
+ * $Id$
+ *
+ * @package TYPO3
+ * @subpackage t3lib
+ *
+ * @author Oliver Klee <typo3-coding@oliverklee.de>
+ * @author Ernesto Baschny <ernst@cron-it.de>
+ */
+final class t3lib_formprotection_Factory {
+       /**
+        * created instances of form protections using the type as array key
+        *
+        * @var array<t3lib_formProtectionAbstract>
+        */
+       static protected $instances = array();
+
+       /**
+        * Private constructor to prevent instantiation.
+        */
+       private function __construct() {}
+
+       /**
+        * Gets a form protection instance for the requested class $className.
+        *
+        * If there already is an existing instance of the requested $className, the
+        * existing instance will be returned.
+        *
+        * @param string $className
+        *        the name of the class for which to return an instance, must be
+        *        "t3lib_formProtection_BackEnd" or "t3lib_formprotection_InstallToolFormProtection"
+        *
+        * @return t3lib_formprotection_Abstract the requested instance
+        */
+       static public function get($className) {
+               if (!isset(self::$instances[$className])) {
+                       if (!class_exists($className, TRUE)) {
+                               throw new InvalidArgumentException(
+                                       '$className must be the name of an existing class, but ' .
+                                               'actually was "' . $className . '".',
+                                       1285352962
+                               );
+                       }
+
+                       $instance = t3lib_div::makeInstance($className);
+                       if (!$instance instanceof t3lib_formprotection_Abstract) {
+                               throw new InvalidArgumentException(
+                                       '$className must be a subclass of ' .
+                                               't3lib_formprotection_Abstract, but actually was "' .
+                                               $className . '".',
+                                       1285353026
+                               );
+                       }
+                       self::$instances[$className] = $instance;
+               }
+               return self::$instances[$className];
+       }
+
+       /**
+        * Sets the instance that will be returned by get() for a specific class
+        * name.
+        *
+        * Note: This function is intended for testing purposes only.
+        *
+        * @param string $className
+        *        the name of the class for which to set an instance, must be
+        *        "t3lib_formProtection_BackEnd" or "t3lib_formprotection_InstallToolFormProtection"
+        * @param t3lib_formprotection_Abstract $instance
+        *        the instance to set
+        *
+        * @return void
+        */
+       static public function set($className, t3lib_formprotection_Abstract $instance) {
+               self::$instances[$className] = $instance;
+       }
+
+       /**
+        * Purges all existing instances.
+        *
+        * This function is particularly useful when cleaning up in unit testing.
+        *
+        * @return void
+        */
+       static public function purgeInstances() {
+               foreach (self::$instances as $key => $instance) {
+                       $instance->__destruct();
+                       unset(self::$instances[$key]);
+               }
+       }
+}
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/formprotection/class.t3lib_formprotection_factory.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/formprotection/class.t3lib_formprotection_factory.php']);
+}
+?>
\ No newline at end of file
diff --git a/t3lib/formprotection/class.t3lib_formprotection_installtoolformprotection.php b/t3lib/formprotection/class.t3lib_formprotection_installtoolformprotection.php
new file mode 100644 (file)
index 0000000..a5019a8
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2010 Oliver Klee <typo3-coding@oliverklee.de>
+* All rights reserved
+*
+* This script is part of the TYPO3 project. The TYPO3 project is
+* free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* The GNU General Public License can be found at
+* http://www.gnu.org/copyleft/gpl.html.
+*
+* This script is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+/**
+ * Class t3lib_formprotection_InstallToolFormProtection.
+ *
+ * This class provides protection against cross-site request forgery (XSRF/CSRF)
+ * in the install tool.
+ *
+ *
+ * How to use this in the install tool:
+ *
+ * For each form in the install tool (or link that changes some data), create a
+ * token and insert is as a hidden form element. The name of the form element
+ * does not matter; you only need it to get the form token for verifying it.
+ *
+ * <pre>
+ * $formToken = $this->formProtection->generateToken(
+ *    'installToolPassword', 'change'
+ * );
+ * // then puts the generated form token in a hidden field in the template
+ * </pre>
+ *
+ * The three parameters $formName, $action and $formInstanceName can be
+ * arbitrary strings, but they should make the form token as specific as
+ * possible. For different forms (e.g. the password change and editing a the
+ * configuration), those values should be different.
+ *
+ * At the end of the form, you need to persist the tokens. This makes sure that
+ * generated tokens get saved, and also that removed tokens stay removed:
+ *
+ * <pre>
+ * $this->formProtection()->persistTokens();
+ * </pre>
+ *
+ *
+ * When processing the data that has been submitted by the form, you can check
+ * that the form token is valid like this:
+ *
+ * <pre>
+ * if ($dataHasBeenSubmitted && $this->formProtection()->validateToken(
+ *     (string) $_POST['formToken'],
+ *     'installToolPassword',
+ *     'change'
+ * ) {
+ *     // processes the data
+ * } else {
+ *     // no need to do anything here as the install tool form protection will
+ *     // create an error message for an invalid token
+ * }
+ * </pre>
+ *
+ * Note that validateToken invalidates the token with the token ID. So calling
+ * validate with the same parameters two times in a row will always return FALSE
+ * for the second call.
+ *
+ * It is important that the tokens get validated <em>before</em> the tokens are
+ * persisted. This makes sure that the tokens that get invalidated by
+ * validateToken cannot be used again.
+ *
+ * $Id$
+ *
+ * @package TYPO3
+ * @subpackage t3lib
+ *
+ * @author Oliver Klee <typo3-coding@oliverklee.de>
+ */
+class t3lib_formprotection_InstallToolFormProtection extends t3lib_formProtection_Abstract {
+       /**
+        * the maximum number of tokens that can exist at the same time
+        *
+        * @var integer
+        */
+       protected $maximumNumberOfTokens = 100;
+
+       /**
+        * an instance of the install tool used for displaying messages
+        *
+        * @var tx_install
+        */
+       protected $installTool = NULL;
+
+       /**
+        * Frees as much memory as possible.
+        */
+       public function __destruct() {
+               $this->installTool = NULL;
+               parent::__destruct();
+       }
+
+       /**
+        * Injects the current instance of the install tool.
+        *
+        * This instance will be used for displaying messages.
+        *
+        * @param tx_install $installTool the current instance of the install tool
+        *
+        * @return void
+        */
+       public function injectInstallTool(tx_install $installTool) {
+               $this->installTool = $installTool;
+       }
+
+       /**
+        * Creates or displayes an error message telling the user that the submitted
+        * form token is invalid.
+        *
+        * @return void
+        */
+       protected function createValidationErrorMessage() {
+               $this->installTool->addErrorMessage(
+                       'Validating the security token of this form has failed. ' .
+                               'Please reload the form and submit it again.'
+               );
+       }
+
+       /**
+        * Retrieves all saved tokens.
+        *
+        * @return array<array>
+        *         the saved tokens, will be empty if no tokens have been saved
+        */
+       protected function retrieveTokens() {
+               if (isset($_SESSION['installToolFormTokens'])
+                       && is_array($_SESSION['installToolFormTokens'])
+               ) {
+                       $this->tokens = $_SESSION['installToolFormTokens'];
+               } else {
+                       $this->tokens = array();
+               }
+       }
+
+       /**
+        * Saves the tokens so that they can be used by a later incarnation of this
+        * class.
+        *
+        * @return void
+        */
+       public function persistTokens() {
+               $_SESSION['installToolFormTokens'] = $this->tokens;
+       }
+}
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/install/mod/class.tx_install_formprotection.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/install/mod/class.tx_install_formprotection.php']);
+}
+?>
\ No newline at end of file
diff --git a/tests/t3lib/formprotection/fixtures/class.t3lib_formprotection_testing.php b/tests/t3lib/formprotection/fixtures/class.t3lib_formprotection_testing.php
new file mode 100644 (file)
index 0000000..2a61104
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2010 Oliver Klee <typo3-coding@oliverklee.de>
+* All rights reserved
+*
+* This script is part of the TYPO3 project. The TYPO3 project is
+* free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* The GNU General Public License can be found at
+* http://www.gnu.org/copyleft/gpl.html.
+*
+* This script is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+/**
+ * Class t3lib_formProtection_Testing.
+ *
+ * This is a testing subclass of the abstract t3lib_formprotection_Abstract
+ * class.
+ *
+ * $Id$
+ *
+ * @package TYPO3
+ * @subpackage t3lib
+ *
+ * @author Oliver Klee <typo3-coding@oliverklee.de>
+ */
+class t3lib_formProtection_Testing extends t3lib_formprotection_Abstract {
+       /**
+        * the maximum number of tokens that can exist at the same time
+        *
+        * @var integer
+        */
+       protected $maximumNumberOfTokens = 100;
+
+       /**
+        * Sets the maximum number of tokens that can exist at the same time.
+        *
+        * @param integer $number maximum number of tokens, must be > 0
+        *
+        * @return void
+        */
+       public function setMaximumNumberOfTokens($number) {
+               $this->maximumNumberOfTokens = $number;
+       }
+
+       /**
+        * Creates or displayes an error message telling the user that the submitted
+        * form token is invalid.
+        *
+        * @return void
+        */
+       protected function createValidationErrorMessage() {}
+
+       /**
+        * Retrieves all saved tokens.
+        *
+        * @return array the saved tokens as a two-dimensional array, will be empty
+        *               if no tokens have been saved
+        */
+       protected function retrieveTokens() {}
+
+       /**
+        * Saves the tokens so that they can be used by a later incarnation of this
+        * class.
+        *
+        * @return void
+        */
+       public function persistTokens() {}
+
+       /**
+        * Drops the token with the ID $tokenId and persists all tokens.
+        *
+        * If there is no token with that ID, this function is a no-op.
+        *
+        * @param string $tokenId
+        *        the 32-character ID of an existing token, must not be empty
+        *
+        * @return void
+        */
+       public function dropToken($tokenId) {
+               parent::dropToken($tokenId);
+       }
+}
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/formprotection/class.t3lib_formprotection_testing.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/formprotection/class.t3lib_formprotection_testing.php']);
+}
+?>
\ No newline at end of file
diff --git a/tests/t3lib/formprotection/t3lib_formprotection_AbstractTest.php b/tests/t3lib/formprotection/t3lib_formprotection_AbstractTest.php
new file mode 100644 (file)
index 0000000..2e0a140
--- /dev/null
@@ -0,0 +1,475 @@
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2010 Oliver Klee (typo3-coding@oliverklee.de)
+* All rights reserved
+*
+* This script is part of the TYPO3 project. The TYPO3 project is
+* free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* The GNU General Public License can be found at
+* http://www.gnu.org/copyleft/gpl.html.
+*
+* This script is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+require_once('fixtures/class.t3lib_formprotection_testing.php');
+
+/**
+ * Testcase for the t3lib_formprotection_Abstract class.
+ *
+ * $Id$
+ *
+ * @package TYPO3
+ * @subpackage t3lib
+ *
+ * @author Oliver Klee <typo3-coding@oliverklee.de>
+ */
+class t3lib_formprotection_AbstractTest extends tx_phpunit_testcase {
+       /**
+        * @var t3lib_formProtection_Testing
+        */
+       private $fixture;
+
+       public function setUp() {
+               $this->fixture = new t3lib_formProtection_Testing();
+       }
+
+       public function tearDown() {
+               $this->fixture->__destruct();
+               unset($this->fixture);
+       }
+
+
+       /////////////////////////////////////////
+       // Tests concerning the basic functions
+       /////////////////////////////////////////
+
+       /**
+        * @test
+        */
+       public function constructionRetrievesTokens() {
+               $className = uniqid('t3lib_formProtection');
+               eval(
+                       'class ' . $className . ' extends t3lib_formProtection_Testing {' .
+                               'public $tokensHaveBeenRetrieved = FALSE; ' .
+                               'protected function retrieveTokens() {' .
+                               '$this->tokensHaveBeenRetrieved = TRUE;' .
+                               '}' .
+                       '}'
+               );
+
+               $fixture = new $className();
+
+               $this->assertTrue(
+                       $fixture->tokensHaveBeenRetrieved
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function cleanMakesTokenInvalid() {
+               $formName = 'foo';
+               $tokenId = $this->fixture->generateToken($formName);
+
+               $this->fixture->clean();
+
+               $this->assertFalse(
+                       $this->fixture->validateToken($tokenId, $formName)
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function cleanPersistsTokens() {
+               $fixture = $this->getMock(
+                       't3lib_formProtection_Testing', array('persistTokens')
+               );
+               $fixture->expects($this->once())->method('persistTokens');
+
+               $fixture->clean();
+       }
+
+
+       ///////////////////////////////////
+       // Tests concerning generateToken
+       ///////////////////////////////////
+
+       /**
+        * @test
+        */
+       public function generateTokenFormForEmptyFormNameThrowsException() {
+               $this->setExpectedException(
+                       'InvalidArgumentException', '$formName must not be empty.'
+               );
+
+               $this->fixture->generateToken('', 'edit', 'bar');
+       }
+
+       /**
+        * @test
+        */
+       public function generateTokenFormForEmptyActionNotThrowsException() {
+               $this->fixture->generateToken('foo', '', '42');
+       }
+
+       /**
+        * @test
+        */
+       public function generateTokenFormForEmptyFormInstanceNameNotThrowsException() {
+               $this->fixture->generateToken('foo', 'edit', '');
+       }
+
+       /**
+        * @test
+        */
+       public function generateTokenFormForOmittedActionAndFormInstanceNameNotThrowsException() {
+               $this->fixture->generateToken('foo');
+       }
+
+       /**
+        * @test
+        */
+       public function generateTokenReturns32CharacterHexToken() {
+               $this->assertRegexp(
+                       '/^[0-9a-f]{32}$/',
+                       $this->fixture->generateToken('foo')
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function generateTokenCalledTwoTimesWithSameParametersReturnsDifferentTokens() {
+               $this->assertNotEquals(
+                       $this->fixture->generateToken('foo', 'edit', 'bar'),
+                       $this->fixture->generateToken('foo', 'edit', 'bar')
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function generatingTooManyTokensInvalidatesOldestToken() {
+               $this->fixture->setMaximumNumberOfTokens(2);
+
+               $formName = 'foo';
+
+               $token1 = $this->fixture->generateToken($formName);
+               $token2 = $this->fixture->generateToken($formName);
+               $token3 = $this->fixture->generateToken($formName);
+
+               $this->assertFalse(
+                       $this->fixture->validateToken($token1, $formName)
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function generatingTooManyTokensNotInvalidatesNewestToken() {
+               $this->fixture->setMaximumNumberOfTokens(2);
+
+               $formName = 'foo';
+               $formInstanceName = 'bar';
+
+               $token1 = $this->fixture->generateToken($formName);
+               $token2 = $this->fixture->generateToken($formName);
+               $token3 = $this->fixture->generateToken($formName);
+
+               $this->assertTrue(
+                       $this->fixture->validateToken($token3, $formName)
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function generatingTooManyTokensNotInvalidatesTokenInTheMiddle() {
+               $this->fixture->setMaximumNumberOfTokens(2);
+
+               $formName = 'foo';
+               $formInstanceName = 'bar';
+
+               $token1 = $this->fixture->generateToken($formName);
+               $token2 = $this->fixture->generateToken($formName);
+               $token3 = $this->fixture->generateToken($formName);
+
+               $this->assertTrue(
+                       $this->fixture->validateToken($token2, $formName)
+               );
+       }
+
+
+       ///////////////////////////////////
+       // Tests concerning validateToken
+       ///////////////////////////////////
+
+       /**
+        * @test
+        */
+       public function validateTokenWithFourEmptyParametersNotThrowsException() {
+               $this->fixture->validateToken('', '', '', '');
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenWithTwoEmptyAndTwoMissingParametersNotThrowsException() {
+               $this->fixture->validateToken('', '');
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenWithDataFromGenerateTokenWithFormInstanceNameReturnsTrue() {
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = 'bar';
+
+               $this->assertTrue(
+                       $this->fixture->validateToken(
+                               $this->fixture->generateToken($formName, $action, $formInstanceName),
+                               $formName,
+                               $action,
+                               $formInstanceName
+                       )
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenWithDataFromGenerateTokenWithMissingActionAndFormInstanceNameReturnsTrue() {
+               $formName = 'foo';
+
+               $this->assertTrue(
+                       $this->fixture->validateToken(
+                               $this->fixture->generateToken($formName), $formName
+                       )
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenWithValidDataDropsToken() {
+               $formName = 'foo';
+
+               $fixture = $this->getMock(
+                       't3lib_formProtection_Testing', array('dropToken')
+               );
+
+               $tokenId = $fixture->generateToken($formName);
+               $fixture->expects($this->once())->method('dropToken')
+                       ->with($tokenId);
+
+               $fixture->validateToken($tokenId, $formName);
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenWithValidDataCalledTwoTimesReturnsFalseOnSecondCall() {
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = 'bar';
+
+               $tokenId = $this->fixture->generateToken($formName, $action, $formInstanceName);
+
+               $this->fixture->validateToken($tokenId, $formName, $action, $formInstanceName);
+
+               $this->assertFalse(
+                       $this->fixture->validateToken($tokenId, $formName, $action, $formInstanceName)
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenWithMismatchingTokenIdReturnsFalse() {
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = 'bar';
+
+               $this->fixture->generateToken($formName, $action, $formInstanceName);
+
+               $this->assertFalse(
+                       $this->fixture->validateToken(
+                               'Hello world!', $formName, $action, $formInstanceName
+                       )
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenWithMismatchingFormNameReturnsFalse() {
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = 'bar';
+
+               $tokenId = $this->fixture->generateToken($formName, $action, $formInstanceName);
+
+               $this->assertFalse(
+                       $this->fixture->validateToken(
+                               $tokenId, 'espresso', $action, $formInstanceName
+                       )
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenWithMismatchingActionReturnsFalse() {
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = 'bar';
+
+               $tokenId = $this->fixture->generateToken($formName, $action, $formInstanceName);
+
+               $this->assertFalse(
+                       $this->fixture->validateToken(
+                               $tokenId, $formName, 'delete', $formInstanceName
+                       )
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenWithMismatchingFormInstanceNameReturnsFalse() {
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = 'bar';
+
+               $tokenId = $this->fixture->generateToken($formName, $action, $formInstanceName);
+
+               $this->assertFalse(
+                       $this->fixture->validateToken(
+                               $tokenId, $formName, $action, 'beer'
+                       )
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenWithTwoTokensForSameFormNameAndActionAndFormInstanceNameReturnsTrueForBoth() {
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = 'bar';
+
+               $tokenId1 = $this->fixture->generateToken($formName, $action, $formInstanceName);
+               $tokenId2 = $this->fixture->generateToken($formName, $action, $formInstanceName);
+
+               $this->assertTrue(
+                       $this->fixture->validateToken(
+                               $tokenId1, $formName, $action, $formInstanceName
+                       )
+               );
+               $this->assertTrue(
+                       $this->fixture->validateToken(
+                               $tokenId2, $formName, $action, $formInstanceName
+                       )
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenWithTwoTokensForSameFormNameAndActionAndFormInstanceNameCalledInReverseOrderReturnsTrueForBoth() {
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = 'bar';
+
+               $tokenId1 = $this->fixture->generateToken($formName, $action, $formInstanceName);
+               $tokenId2 = $this->fixture->generateToken($formName, $action, $formInstanceName);
+
+               $this->assertTrue(
+                       $this->fixture->validateToken(
+                               $tokenId2, $formName, $action, $formInstanceName
+                       )
+               );
+               $this->assertTrue(
+                       $this->fixture->validateToken(
+                               $tokenId1, $formName, $action, $formInstanceName
+                       )
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenForValidTokenNotCallsCreateValidationErrorMessage() {
+               $fixture = $this->getMock(
+                       't3lib_formProtection_Testing', array('createValidationErrorMessage')
+               );
+               $fixture->expects($this->never())->method('createValidationErrorMessage');
+
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = 'bar';
+
+               $token = $fixture->generateToken($formName, $action, $formInstanceName);
+               $fixture->validateToken(
+                       $token, $formName, $action, $formInstanceName
+               );
+
+               $fixture->__destruct();
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenForInvalidTokenCallsCreateValidationErrorMessage() {
+               $fixture = $this->getMock(
+                       't3lib_formProtection_Testing', array('createValidationErrorMessage')
+               );
+               $fixture->expects($this->once())->method('createValidationErrorMessage');
+
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = 'bar';
+
+               $fixture->generateToken($formName, $action, $formInstanceName);
+               $fixture->validateToken(
+                       'an invalid token ...', $formName, $action, $formInstanceName
+               );
+
+               $fixture->__destruct();
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenForInvalidFormNameCallsCreateValidationErrorMessage() {
+               $fixture = $this->getMock(
+                       't3lib_formProtection_Testing', array('createValidationErrorMessage')
+               );
+               $fixture->expects($this->once())->method('createValidationErrorMessage');
+
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = 'bar';
+
+               $token = $fixture->generateToken($formName, $action, $formInstanceName);
+               $fixture->validateToken(
+                       $token, 'another form name', $action, $formInstanceName
+               );
+
+               $fixture->__destruct();
+       }
+}
+?>
\ No newline at end of file
diff --git a/tests/t3lib/formprotection/t3lib_formprotection_BackendFormProtectionTest.php b/tests/t3lib/formprotection/t3lib_formprotection_BackendFormProtectionTest.php
new file mode 100644 (file)
index 0000000..a631ff0
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2010 Oliver Klee (typo3-coding@oliverklee.de)
+* All rights reserved
+*
+* This script is part of the TYPO3 project. The TYPO3 project is
+* free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* The GNU General Public License can be found at
+* http://www.gnu.org/copyleft/gpl.html.
+*
+* This script is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+/**
+ * Testcase for the t3lib_formprotection_BackendFormProtection class.
+ *
+ * $Id$
+ *
+ * @package TYPO3
+ * @subpackage t3lib
+ *
+ * @author Oliver Klee <typo3-coding@oliverklee.de>
+ */
+class t3lib_formprotection_BackendFormProtectionTest extends tx_phpunit_testcase {
+       /**
+        * a backup of the current BE user
+        *
+        * @var t3lib_beUserAuth
+        */
+       private $backEndUserBackup = NULL;
+
+       /**
+        * @var t3lib_formprotection_BackendFormProtection
+        */
+       private $fixture;
+
+       public function setUp() {
+               $this->backEndUserBackup = $GLOBALS['BE_USER'];
+               $GLOBALS['BE_USER'] = $this->getMock(
+                       't3lib_beUserAuth',
+                       array('getSessionData', 'setAndSaveSessionData')
+               );
+
+               $className = $this->createAccessibleProxyClass();
+               $this->fixture = new $className;
+       }
+
+       public function tearDown() {
+               $this->fixture->__destruct();
+               unset($this->fixture);
+
+               $GLOBALS['BE_USER'] = $this->backEndUserBackup;
+
+               t3lib_FlashMessageQueue::getAllMessagesAndFlush();
+       }
+
+
+       //////////////////////
+       // Utility functions
+       //////////////////////
+
+       /**
+        * Creates a subclass t3lib_formprotection_BackendFormProtection with retrieveTokens made
+        * public.
+        *
+        * @return string the name of the created class, will not be empty
+        */
+       private function createAccessibleProxyClass() {
+               $className = 't3lib_formprotection_BackendFormProtectionAccessibleProxy';
+               if (!class_exists($className)) {
+                       eval(
+                               'class ' . $className . ' extends t3lib_formprotection_BackendFormProtection {' .
+                               '  public function createValidationErrorMessage() {' .
+                               '    parent::createValidationErrorMessage();' .
+                               '  }' .
+                               '  public function retrieveTokens() {' .
+                               '    return parent::retrieveTokens();' .
+                               '  }' .
+                               '}'
+                       );
+               }
+
+               return $className;
+       }
+
+
+       ////////////////////////////////////
+       // Tests for the utility functions
+       ////////////////////////////////////
+
+       /**
+        * @test
+        */
+       public function createAccessibleProxyCreatesBackendFormProtectionSubclass() {
+               $className = $this->createAccessibleProxyClass();
+
+               $this->assertTrue(
+                       (new $className()) instanceof t3lib_formprotection_BackendFormProtection
+               );
+       }
+
+
+       //////////////////////////////////////////////////////////
+       // Tests concerning the reading and saving of the tokens
+       //////////////////////////////////////////////////////////
+
+       /**
+        * @test
+        */
+       public function retrieveTokensReadsTokensFromSessionData() {
+               $GLOBALS['BE_USER']->expects($this->once())->method('getSessionData')
+                       ->with('formTokens')->will($this->returnValue(array()));
+
+               $this->fixture->retrieveTokens();
+       }
+
+       /**
+        * @test
+        */
+       public function tokensFromSessionDataAreAvailableForValidateToken() {
+               $tokenId = '51a655b55c54d54e5454c5f521f6552a';
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = '42';
+
+               $GLOBALS['BE_USER']->expects($this->once())->method('getSessionData')
+                       ->with('formTokens')->will($this->returnValue(array(
+                               $tokenId => array(
+                                       'formName' => $formName,
+                                       'action' => $action,
+                                       'formInstanceName' => $formInstanceName,
+                               ),
+                       )));
+
+               $this->fixture->retrieveTokens();
+
+               $this->assertTrue(
+                       $this->fixture->validateToken($tokenId, $formName, $action,  $formInstanceName)
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function persistTokensWritesTokensToSession() {
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = '42';
+
+               $tokenId = $this->fixture->generateToken(
+                       $formName, $action, $formInstanceName
+               );
+               $allTokens = array(
+                       $tokenId => array(
+                                       'formName' => $formName,
+                                       'action' => $action,
+                                       'formInstanceName' => $formInstanceName,
+                               ),
+               );
+
+               $GLOBALS['BE_USER']->expects($this->once())
+                       ->method('setAndSaveSessionData')->with('formTokens', $allTokens);
+
+               $this->fixture->persistTokens();
+       }
+
+
+       //////////////////////////////////////////////////
+       // Tests concerning createValidationErrorMessage
+       //////////////////////////////////////////////////
+
+       /**
+        * @test
+        */
+       public function createValidationErrorMessageAddsErrorFlashMessage() {
+               $this->fixture->createValidationErrorMessage();
+
+               $messages = t3lib_FlashMessageQueue::getAllMessagesAndFlush();
+               $this->assertContains(
+                       $GLOBALS['LANG']->sL(
+                               'LLL:EXT:lang/locallang_core.xml:error.formProtection.tokenInvalid'
+                       ),
+                       $messages[0]->render()
+               );
+       }
+}
+?>
\ No newline at end of file
diff --git a/tests/t3lib/formprotection/t3lib_formprotection_FactoryTest.php b/tests/t3lib/formprotection/t3lib_formprotection_FactoryTest.php
new file mode 100644 (file)
index 0000000..e80b65f
--- /dev/null
@@ -0,0 +1,172 @@
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2010 Oliver Klee (typo3-coding@oliverklee.de)
+* All rights reserved
+*
+* This script is part of the TYPO3 project. The TYPO3 project is
+* free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* The GNU General Public License can be found at
+* http://www.gnu.org/copyleft/gpl.html.
+*
+* This script is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+require_once('fixtures/class.t3lib_formprotection_testing.php');
+
+/**
+ * Testcase for the t3lib_formprotection_Factory class.
+ *
+ * $Id$
+ *
+ * @package TYPO3
+ * @subpackage t3lib
+ *
+ * @author Oliver Klee <typo3-coding@oliverklee.de>
+ * @author Ernesto Baschny <ernst@cron-it.de>
+ */
+class t3lib_formprotection_FactoryTest extends tx_phpunit_testcase {
+       public function setUp() {
+       }
+
+       public function tearDown() {
+               t3lib_formprotection_Factory::purgeInstances();
+       }
+
+
+       /////////////////////////
+       // Tests concerning get
+       /////////////////////////
+
+       /**
+        * @test
+        *
+        * @expectedException InvalidArgumentException
+        */
+       public function getForInexistentClassThrowsException() {
+               t3lib_formprotection_Factory::get('noSuchClass');
+       }
+
+       /**
+        * @test
+        *
+        * @expectedException InvalidArgumentException
+        */
+       public function getForClassThatIsNoFormProtectionSubclassThrowsException() {
+               t3lib_formprotection_Factory::get('t3lib_formprotection_FactoryTest');
+       }
+
+       /**
+        * @test
+        */
+       public function getForTypeBackEndWithExistingBackEndReturnsBackEndFormProtection() {
+               $this->assertTrue(
+                       t3lib_formprotection_Factory::get(
+                               't3lib_formprotection_BackendFormProtection'
+                       ) instanceof t3lib_formprotection_BackendFormProtection
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function getForTypeBackEndCalledTwoTimesReturnsTheSameInstance() {
+               $this->assertSame(
+                       t3lib_formprotection_Factory::get(
+                               't3lib_formprotection_BackendFormProtection'
+                       ),
+                       t3lib_formprotection_Factory::get(
+                               't3lib_formprotection_BackendFormProtection'
+                       )
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function getForTypeInstallToolReturnsInstallToolFormProtection() {
+               $this->assertTrue(
+                       t3lib_formprotection_Factory::get(
+                               't3lib_formprotection_InstallToolFormProtection'
+                       ) instanceof t3lib_formprotection_InstallToolFormProtection
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function getForTypeInstallToolCalledTwoTimesReturnsTheSameInstance() {
+               $this->assertSame(
+                       t3lib_formprotection_Factory::get(
+                               't3lib_formprotection_InstallToolFormProtection'
+                       ),
+                       t3lib_formprotection_Factory::get(
+                               't3lib_formprotection_InstallToolFormProtection'
+                       )
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function getForTypesInstallToolAndBackEndReturnsDifferentInstances() {
+               $this->assertNotSame(
+                       t3lib_formprotection_Factory::get(
+                               't3lib_formprotection_InstallToolFormProtection'
+                       ),
+                       t3lib_formprotection_Factory::get(
+                               't3lib_formprotection_BackendFormProtection'
+                       )
+               );
+       }
+
+
+       /////////////////////////
+       // Tests concerning set
+       /////////////////////////
+
+       /**
+        * @test
+        */
+       public function setSetsInstanceForType() {
+               $instance = new t3lib_formProtection_Testing();
+               t3lib_formprotection_Factory::set(
+                       't3lib_formprotection_BackendFormProtection', $instance
+               );
+
+               $this->assertSame(
+                       $instance,
+                       t3lib_formprotection_Factory::get(
+                               't3lib_formprotection_BackendFormProtection'
+                       )
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function setNotSetsInstanceForOtherType() {
+               $instance = new t3lib_formProtection_Testing();
+               t3lib_formprotection_Factory::set(
+                       't3lib_formprotection_BackendFormProtection', $instance
+               );
+
+               $this->assertNotSame(
+                       $instance,
+                       t3lib_formprotection_Factory::get(
+                               't3lib_formprotection_InstallToolFormProtection'
+                       )
+               );
+       }
+}
+?>
\ No newline at end of file
diff --git a/tests/t3lib/formprotection/t3lib_formprotection_InstallToolFormProtectionTest.php b/tests/t3lib/formprotection/t3lib_formprotection_InstallToolFormProtectionTest.php
new file mode 100644 (file)
index 0000000..e78abc2
--- /dev/null
@@ -0,0 +1,190 @@
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2010 Oliver Klee (typo3-coding@oliverklee.de)
+* All rights reserved
+*
+* This script is part of the TYPO3 project. The TYPO3 project is
+* free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* The GNU General Public License can be found at
+* http://www.gnu.org/copyleft/gpl.html.
+*
+* This script is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+require_once(t3lib_extMgm::extPath('install') . 'mod/class.tx_install.php');
+
+/**
+ * Testcase for the t3lib_formprotection_InstallToolFormProtection class.
+ *
+ * $Id$
+ *
+ * @package TYPO3
+ * @subpackage t3lib
+ *
+ * @author Oliver Klee <typo3-coding@oliverklee.de>
+ */
+class t3lib_formprotection_InstallToolFormProtectionTest extends tx_phpunit_testcase {
+       /**
+        * @var t3lib_formprotection_InstallToolFormProtection
+        */
+       private $fixture;
+
+       /**
+        * backup of $_SESSION
+        *
+        * @var array
+        */
+       private $sessionBackup;
+
+       public function setUp() {
+               $this->sessionBackup = $_SESSION;
+
+               $className = $this->createAccessibleProxyClass();
+               $this->fixture = new $className();
+       }
+
+       public function tearDown() {
+               $this->fixture->__destruct();
+               unset($this->fixture);
+
+               t3lib_FlashMessageQueue::getAllMessagesAndFlush();
+
+               $_SESSION = $this->sessionBackup;
+       }
+
+
+       //////////////////////
+       // Utility functions
+       //////////////////////
+
+       /**
+        * Creates a subclass t3lib_formprotection_InstallToolFormProtection with retrieveTokens made
+        * public.
+        *
+        * @return string the name of the created class, will not be empty
+        */
+       private function createAccessibleProxyClass() {
+               $className = 't3lib_formprotection_InstallToolFormProtectionAccessibleProxy';
+               if (!class_exists($className)) {
+                       eval(
+                               'class ' . $className . ' extends t3lib_formprotection_InstallToolFormProtection {' .
+                               '  public function createValidationErrorMessage() {' .
+                               '    parent::createValidationErrorMessage();' .
+                               '  }' .
+                               '  public function retrieveTokens() {' .
+                               '    return parent::retrieveTokens();' .
+                               '  }' .
+                               '}'
+                       );
+               }
+
+               return $className;
+       }
+
+
+       ////////////////////////////////////
+       // Tests for the utility functions
+       ////////////////////////////////////
+
+       /**
+        * @test
+        */
+       public function createAccessibleProxyCreatesInstallToolFormProtectionSubclass() {
+               $className = $this->createAccessibleProxyClass();
+
+               $this->assertTrue(
+                       (new $className()) instanceof t3lib_formprotection_InstallToolFormProtection
+               );
+       }
+
+
+       //////////////////////////////////////////////////////////
+       // Tests concerning the reading and saving of the tokens
+       //////////////////////////////////////////////////////////
+
+       /**
+        * @test
+        */
+       public function tokensFromSessionDataAreAvailableForValidateToken() {
+               $tokenId = '51a655b55c54d54e5454c5f521f6552a';
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = '42';
+
+               $_SESSION['installToolFormTokens'] = array(
+                       $tokenId => array(
+                               'formName' => $formName,
+                               'action' => $action,
+                               'formInstanceName' => $formInstanceName,
+                       ),
+               );
+
+               $this->fixture->retrieveTokens();
+
+               $this->assertTrue(
+                       $this->fixture->validateToken(
+                               $tokenId, $formName, $action,  $formInstanceName
+                       )
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function persistTokensWritesTokensToSession() {
+               $formName = 'foo';
+               $action = 'edit';
+               $formInstanceName = '42';
+
+               $tokenId = $this->fixture->generateToken(
+                       $formName, $action, $formInstanceName
+               );
+
+               $this->fixture->persistTokens();
+
+               $this->assertEquals(
+                       array(
+                               $tokenId => array(
+                                               'formName' => $formName,
+                                               'action' => $action,
+                                               'formInstanceName' => $formInstanceName,
+                                       ),
+                       ),
+                       $_SESSION['installToolFormTokens']
+               );
+       }
+
+
+       //////////////////////////////////////////////////
+       // Tests concerning createValidationErrorMessage
+       //////////////////////////////////////////////////
+
+       /**
+        * @test
+        */
+       public function createValidationErrorMessageAddsErrorMessage() {
+               $installTool = $this->getMock(
+                       'tx_install', array('addErrorMessage'), array(), '', FALSE
+               );
+               $installTool->expects($this->once())->method('addErrorMessage')
+                       ->with(
+                               'Validating the security token of this form has failed. ' .
+                                       'Please reload the form and submit it again.'
+                       );
+               $this->fixture->injectInstallTool($installTool);
+
+               $this->fixture->createValidationErrorMessage();
+       }
+}
+?>
\ No newline at end of file
index b43f30a..1ca7637 100755 (executable)
@@ -266,6 +266,7 @@ Would you like to save now in order to refresh the display?</label>
                        <label index="warning.memcache_not_usable">Memcache is configured, but connection to memcached failed with these configuration(s):</label>
                        <label index="warning.file_missing">File is missing!</label>
                        <label index="warning.file_missing_text">This content element references a file which seems to not exist:</label>
+                       <label index="error.formProtection.tokenInvalid">Validating the security token of this form has failed. Please reload the form and submit it again.</label>
                        <label index="toolbarItems.shortcuts">Shortcuts</label>
                        <label index="toolbarItems.shortcut">Shortcut</label>
                        <label index="toolbarItems.shortcutsGroup">Shortcut Group</label>