Index: t3lib/class.t3lib_beuserauth.php =================================================================== --- t3lib/class.t3lib_beuserauth.php (revision 8834) +++ t3lib/class.t3lib_beuserauth.php (working copy) @@ -69,7 +69,9 @@ * t3lib_userauthgroup contains most of the functions used for checking permissions, authenticating users, setting up the user etc. This class is most interesting in terms of an API for user from outside. * This class contains the configuration of the database fields used plus some functions for the authentication process of backend users. * - * @author Kasper Skårhøj + * @author Kasper Skårhøj + * @author Oliver Klee + * * @package TYPO3 * @subpackage t3lib */ @@ -393,13 +395,19 @@ return $isUserAllowedToLogin; } -} - - + /** + * Logs out the current user and clears the form protection tokens. + */ + public function logoff() { + t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_BACK_END + )->clean(); + parent::logoff(); + } +} if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_beuserauth.php']) { include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_beuserauth.php']); } - ?> \ No newline at end of file Index: t3lib/core_autoload.php =================================================================== --- t3lib/core_autoload.php (revision 8834) +++ t3lib/core_autoload.php (working copy) @@ -116,6 +116,10 @@ '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_backend' => PATH_t3lib . 'formprotection/class.t3lib_formprotection_backend.php', + 't3lib_formprotection_installtool' => PATH_t3lib . 'formprotection/class.t3lib_formprotection_installtool.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', Index: t3lib/formprotection/class.t3lib_formprotection_backend.php =================================================================== --- t3lib/formprotection/class.t3lib_formprotection_backend.php (revision 0) +++ t3lib/formprotection/class.t3lib_formprotection_backend.php (revision 0) @@ -0,0 +1,165 @@ + +* 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_Backend. + * + * 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. + * + *
+ * $formToken = t3lib_formProtection_Factory::get(
+ *     t3lib_formProtection_Factory::TYPE_BACK_END
+ * )->generateToken(
+ *     'BE user setup', 'edit'
+ * );
+ * $this->content .= '';
+ * 
+ * + * 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: + * + *
+ * $formToken = t3lib_formProtection_Factory::get(
+ *     t3lib_formProtection_Factory::TYPE_BACK_END
+ * )->getFormProtection()->generateToken(
+ *    'tt_content', 'edit', $uid
+ * );
+ * 
+ * + * 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: + * + *
+ * t3lib_formProtection_Factory::get(
+ *     t3lib_formProtection_Factory::TYPE_BACK_END
+ * )->persistTokens();
+ * 
+ * + * 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: + * + *
+ * 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
+ * }
+ * 
+ * + * 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 before 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 + */ +class t3lib_formProtection_Backend extends t3lib_formProtection_Abstract { + /** + * the maximum number of tokens that can exist at the same time + * + * @var integer + */ + protected $maximumNumberOfTokens = 20000; + + /** + * 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 + * 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_backend.php']) { + include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/formprotection/class.t3lib_formprotection_backend.php']); +} +?> \ No newline at end of file Property changes on: t3lib/formprotection/class.t3lib_formprotection_backend.php ___________________________________________________________________ Added: svn:mime-type + text/plain Index: t3lib/formprotection/class.t3lib_formprotection_factory.php =================================================================== --- t3lib/formprotection/class.t3lib_formprotection_factory.php (revision 0) +++ t3lib/formprotection/class.t3lib_formprotection_factory.php (revision 0) @@ -0,0 +1,181 @@ + +* 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: + * + *
+ * $formProtection = t3lib_formProtection_Factory::get(
+ *     t3lib_formProtection_Factory::TYPE_BACK_END
+ * );
+ * 
+ * + * Usage for the install tool form protection: + * + *
+ * $formProtection = t3lib_formProtection_Factory::get(
+ *     t3lib_formProtection_Factory::TYPE_INSTALL_TOOL
+ * );
+ * $formProtection->injectInstallTool($this);
+ * 
+ * + * $Id$ + * + * @package TYPO3 + * @subpackage t3lib + * + * @author Oliver Klee + */ +final class t3lib_formProtection_Factory { + /** + * constant used for creation back-end form protection instances + * + * @var integer + */ + const TYPE_BACK_END = 0; + + /** + * constant used for creation install tool form protection instances + * + * @var integer + */ + const TYPE_INSTALL_TOOL = 1; + + /** + * created instances of form protections using the type as array key. + * + * @var array + */ + static protected $instances = array(); + + /** + * Private constructor to prevent instantiation. + */ + private function __construct() {} + + /** + * Gets a form protection instance for the requested $type. + * + * If there already is an existing instance of the requested $type, the + * existing instance will be returned. + * + * @param integer $type + * the type of the form protection instance to get, must be either + * TYPE_BACK_END or TYPE_INSTALL_TOOL + * + * @return t3lib_formProtection_Abstract the requested instance + */ + static public function get($type) { + switch ($type) { + case self::TYPE_BACK_END: + if (!isset(self::$instances[self::TYPE_BACK_END])) { + self::createBackEndFormProtectionInstance(); + } + break; + case self::TYPE_INSTALL_TOOL: + if (!isset(self::$instances[self::TYPE_INSTALL_TOOL])) { + self::createInstallToolFormProtectionInstance(); + } + break; + default: + throw new InvalidArgumentException( + '$type must be either TYPE_BACK_END or TYPE_INSTALL_TOOL,' . + ' but actually was ' . $type . '.', + 1285064208 + ); + break; + } + + return self::$instances[$type]; + } + + /** + * Sets the instance that will be returned by get() for a specific type. + * + * Note: This function is intended for testing purposes only. + * + * @param integer $type + * the type for which to set the instance, must be either + * TYPE_BACK_END or TYPE_INSTALL_TOOL + * @param t3lib_formProtection_Abstract $instance + * the instance to set + * + * @return void + */ + static public function set($type, t3lib_formProtection_Abstract $instance) { + self::$instances[$type] = $instance; + } + + /** + * Creates a back-end form protection instance in self::$instances. + * + * @return void + */ + static protected function createBackEndFormProtectionInstance() { + 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 + ); + } + + self::$instances[self::TYPE_BACK_END] + = new t3lib_formProtection_BackEnd(); + } + + /** + * Creates an install tool form protection instance in self::$instances. + * + * @return void + */ + static protected function createInstallToolFormProtectionInstance() { + self::$instances[self::TYPE_INSTALL_TOOL] + = new t3lib_formProtection_InstallTool(); + } + + /** + * Purges all existing instances. + * + * This function is particularly useful when cleaning up in unit testing. + */ + 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 Property changes on: t3lib/formprotection/class.t3lib_formprotection_factory.php ___________________________________________________________________ Added: svn:mime-type + text/plain Index: t3lib/formprotection/class.t3lib_formprotection_abstract.php =================================================================== --- t3lib/formprotection/class.t3lib_formprotection_abstract.php (revision 0) +++ t3lib/formprotection/class.t3lib_formprotection_abstract.php (revision 0) @@ -0,0 +1,247 @@ + +* 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_Backend. + * + * $Id$ + * + * @package TYPO3 + * @subpackage t3lib + * + * @author Oliver Klee + */ +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 + */ + 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 + * 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 Property changes on: t3lib/formprotection/class.t3lib_formprotection_abstract.php ___________________________________________________________________ Added: svn:mime-type + text/plain Index: t3lib/formprotection/class.t3lib_formprotection_installtool.php =================================================================== --- t3lib/formprotection/class.t3lib_formprotection_installtool.php (revision 0) +++ t3lib/formprotection/class.t3lib_formprotection_installtool.php (revision 0) @@ -0,0 +1,168 @@ + +* 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_InstallTool. + * + * 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. + * + *
+ * $formToken = $this->formProtection->generateToken(
+ *    'installToolPassword', 'change'
+ * );
+ * // then puts the generated form token in a hidden field in the template
+ * 
+ * + * 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: + * + *
+ * $this->formProtection()->persistTokens();
+ * 
+ * + * + * When processing the data that has been submitted by the form, you can check + * that the form token is valid like this: + * + *
+ * 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
+ * }
+ * 
+ * + * 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 before 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 + */ +class t3lib_formProtection_InstallTool 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 + * 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']['t3lib/formprotection/class.t3lib_formprotection_installtool.php']) { + include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/formprotection/class.t3lib_formprotection_installtool.php']); +} +?> \ No newline at end of file Property changes on: t3lib/formprotection/class.t3lib_formprotection_installtool.php ___________________________________________________________________ Added: svn:mime-type + text/plain Index: typo3/sysext/install/Resources/Private/Templates/AlterPasswordForm.html =================================================================== --- typo3/sysext/install/Resources/Private/Templates/AlterPasswordForm.html (revision 8834) +++ typo3/sysext/install/Resources/Private/Templates/AlterPasswordForm.html (working copy) @@ -23,6 +23,7 @@
  • +
  • Index: typo3/sysext/install/mod/class.tx_install.php =================================================================== --- typo3/sysext/install/mod/class.tx_install.php (revision 8834) +++ typo3/sysext/install/mod/class.tx_install.php (working copy) @@ -159,6 +159,8 @@ /** * Install Tool module * + * $Id$ + * * @author Kasper Skårhøj * @author Ingmar Schlecht * @package TYPO3 @@ -218,6 +220,13 @@ */ protected $session = NULL; + /** + * the form protection instance used for creating and verifying form tokens + * + * @var t3lib_formProtection_InstallTool + */ + protected $formProtection = NULL; + var $menuitems = array( 'config' => 'Basic Configuration', 'database' => 'Database Analyser', @@ -349,6 +358,11 @@ if($this->redirect_url) { t3lib_utility_Http::redirect($this->redirect_url); } + + $this->formProtection = t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_INSTALL_TOOL + ); + $this->formProtection->injectInstallTool($this); } else { $this->loginForm(); } @@ -503,6 +517,7 @@ TRUE, TRUE ); + // Send content to the page wrapper function $this->output($this->outputWrapper($content)); } @@ -729,6 +744,7 @@ if (is_file($enableInstallToolFile) && trim(file_get_contents($enableInstallToolFile)) !== 'KEEP_FILE') { unlink(PATH_typo3conf . 'ENABLE_INSTALL_TOOL'); } + $this->formProtection->clean(); $this->session->destroySession(); t3lib_utility_Http::redirect($this->scriptSelf); break; @@ -882,6 +898,8 @@ break; } } + + $this->formProtection->persistTokens(); } /** @@ -2072,13 +2090,24 @@ $doit=1; if ($k=='BE' && $vk=='installToolPassword') { if ($value) { - if (isset($_POST['installToolPassword_check']) && (!t3lib_div::_GP('installToolPassword_check') || strcmp(t3lib_div::_GP('installToolPassword_check'),$value))) { - $doit=0; - $this->errorMessages[] = ' - The two passwords did not - match! The password was not - changed. - '; + if (isset($_POST['installToolPassword_check'])) { + if (!$this->formProtection->validateToken( + (string) $_POST['formToken'], + 'installToolPassword', + 'change' + )) { + $doit = FALSE; + break; + } + + if (!t3lib_div::_GP('installToolPassword_check') + || strcmp(t3lib_div::_GP('installToolPassword_check'), $value) + ) { + $doit = FALSE; + $this->errorMessages[] + = 'The two passwords did not ' . + 'match! The password was not changed.'; + } } if (t3lib_div::_GP('installToolPassword_md5')) $value =md5($value); } else $doit=0; @@ -7921,7 +7950,10 @@ 'action' => $this->scriptSelf.'?TYPO3_INSTALL[type]=extConfig', 'enterPassword' => 'Enter new password:', 'enterAgain' => 'Enter again:', - 'submit' => 'Set new password' + 'submit' => 'Set new password', + 'formToken' => $this->formProtection->generateToken( + 'installToolPassword', 'change' + ), ); // Fill the markers $content = t3lib_parsehtml::substituteMarkerArray( @@ -8276,6 +8308,20 @@ $bytes = t3lib_div::generateRandomBytes($keyLength); return substr(bin2hex($bytes), -96); } + + /** + * Adds an error message that should be displayed. + * + * @param string $messageText + * the text of the message to display, must not be empty + */ + public function addErrorMessage($messageText) { + if ($messageText == '') { + throw new InvalidArgumentException('$messageText must not be empty.'); + } + + $this->errorMessages[] = $messageText; + } } if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/install/mod/class.tx_install.php']) { Index: typo3/sysext/setup/mod/index.php =================================================================== --- typo3/sysext/setup/mod/index.php (revision 8834) +++ typo3/sysext/setup/mod/index.php (working copy) @@ -127,11 +127,10 @@ /** * If settings are submitted to _POST[DATA], store them - * NOTICE: This method is called before the template.php is included. See buttom of document - * - * @return void + * NOTICE: This method is called before the template.php is included. See + * bottom of document. */ - function storeIncomingData() { + public function storeIncomingData() { /* @var $BE_USER t3lib_beUserAuth */ global $BE_USER; @@ -142,8 +141,14 @@ $storeRec = array(); $fieldList = $this->getFieldsFromShowItem(); - if (is_array($d)) { - + $formProtection = t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_BACK_END + ); + if (is_array($d) && $formProtection->validateToken( + (string) t3lib_div::_POST('formToken'), + 'BE user setup', 'edit' + ) + ) { // UC hashed before applying changes $save_before = md5(serialize($BE_USER->uc)); @@ -414,19 +419,22 @@ $this->content .= $this->doc->spacer(20) . $this->doc->getDynTabMenu($menuItems, 'user-setup', false, false, 100, 1, false, 1, $this->dividers2tabs); + $formProtection = t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_BACK_END + ); + $formToken = $formProtection->generateToken('BE user setup', 'edit'); // Submit and reset buttons $this->content .= $this->doc->spacer(20); $this->content .= $this->doc->section('', t3lib_BEfunc::cshItem('_MOD_user_setup', 'reset', $BACK_PATH) . ' + ' ); - - // Notice $this->content .= $this->doc->spacer(30); $flashMessage = t3lib_div::makeInstance( @@ -935,4 +943,6 @@ $SOBE->main(); $SOBE->printContent(); +t3lib_formProtection_Factory::get(t3lib_formProtection_Factory::TYPE_BACK_END) + ->persistTokens(); ?> \ No newline at end of file Index: typo3/sysext/lang/locallang_core.xml =================================================================== --- typo3/sysext/lang/locallang_core.xml (revision 8834) +++ typo3/sysext/lang/locallang_core.xml (working copy) @@ -265,6 +265,7 @@ + Index: tests/t3lib/t3lib_beuserauthTest.php =================================================================== --- tests/t3lib/t3lib_beuserauthTest.php (revision 0) +++ tests/t3lib/t3lib_beuserauthTest.php (revision 0) @@ -0,0 +1,69 @@ + + */ +class t3lib_beUserAuthTest extends tx_phpunit_testcase { + /** + * @var t3lib_beUserAuth + */ + private $fixture = NULL; + + public function setUp() { + $this->fixture = new t3lib_beUserAuth(); + } + + public function tearDown() { + unset($this->fixture); + + t3lib_formProtection_Factory::purgeInstances(); + } + + + ///////////////////////////////////////// + // Tests concerning the form protection + ///////////////////////////////////////// + + /** + * @test + */ + public function logoffCleansFormProtection() { + $formProtection = $this->getMock( + 't3lib_formProtection_Backend', array('clean') + ); + $formProtection->expects($this->atLeastOnce())->method('clean'); + t3lib_formProtection_Factory::set( + t3lib_formProtection_Factory::TYPE_BACK_END, $formProtection + ); + + $this->fixture->logoff(); + } +} +?> \ No newline at end of file Property changes on: tests/t3lib/t3lib_beuserauthTest.php ___________________________________________________________________ Added: svn:mime-type + text/plain Index: tests/t3lib/formprotection/t3lib_formProtection_FactoryTest.php =================================================================== --- tests/t3lib/formprotection/t3lib_formProtection_FactoryTest.php (revision 0) +++ tests/t3lib/formprotection/t3lib_formProtection_FactoryTest.php (revision 0) @@ -0,0 +1,186 @@ + + */ +class t3lib_formProtection_FactoryTest extends tx_phpunit_testcase { + /** + * backup of $GLOBALS['BE_USER'] + * + * @var t3lib_beUserAuth + */ + protected $backEndUserBackup = NULL; + + public function setUp() { + $this->backEndUserBackup = $GLOBALS['BE_USER']; + $GLOBALS['BE_USER'] = $this->getMock('t3lib_beUserAuth'); + } + + public function tearDown() { + t3lib_formProtection_Factory::purgeInstances(); + + $GLOBALS['BE_USER'] = $this->backEndUserBackup; + } + + + ///////////////////////// + // Tests concerning get + ///////////////////////// + + /** + * @test + * + * @expectedException InvalidArgumentException + */ + public function getForInvalidTypeThrowsException() { + t3lib_formProtection_Factory::get(42); + } + + /** + * @test + */ + public function getForTypeBackEndWithExistingBackEndReturnsBackEndFormProtection() { + $this->assertTrue( + t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_BACK_END + ) instanceof t3lib_formProtection_Backend + ); + } + + /** + * @test + * + * @expectedException t3lib_error_Exception + */ + public function getForTypeBackEndWithoutExistingBackEndThrowsException() { + unset($GLOBALS['BE_USER']); + + t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_BACK_END + ); + } + + /** + * @test + */ + public function getForTypeBackEndCalledTwoTimesReturnsTheSameInstance() { + $this->assertSame( + t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_BACK_END + ), + t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_BACK_END + ) + ); + } + + /** + * @test + */ + public function getForTypeInstallToolReturnsInstallToolFormProtection() { + $this->assertTrue( + t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_INSTALL_TOOL + ) instanceof t3lib_formProtection_InstallTool + ); + } + + /** + * @test + */ + public function getForTypeInstallToolCalledTwoTimesReturnsTheSameInstance() { + $this->assertSame( + t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_INSTALL_TOOL + ), + t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_INSTALL_TOOL + ) + ); + } + + /** + * @test + */ + public function getForTypesInstallToolAndBackEndReturnsDifferentInstances() { + $this->assertNotSame( + t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_INSTALL_TOOL + ), + t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_BACK_END + ) + ); + } + + + ///////////////////////// + // Tests concerning set + ///////////////////////// + + /** + * @test + */ + public function setSetsInstanceForType() { + $instance = new t3lib_formProtection_Testing(); + t3lib_formProtection_Factory::set( + t3lib_formProtection_Factory::TYPE_BACK_END, $instance + ); + + $this->assertSame( + $instance, + t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_BACK_END + ) + ); + } + + /** + * @test + */ + public function setNotSetsInstanceForOtherType() { + $instance = new t3lib_formProtection_Testing(); + t3lib_formProtection_Factory::set( + t3lib_formProtection_Factory::TYPE_BACK_END, $instance + ); + + $this->assertNotSame( + $instance, + t3lib_formProtection_Factory::get( + t3lib_formProtection_Factory::TYPE_INSTALL_TOOL + ) + ); + } +} +?> \ No newline at end of file Property changes on: tests/t3lib/formprotection/t3lib_formProtection_FactoryTest.php ___________________________________________________________________ Added: svn:mime-type + text/plain Index: tests/t3lib/formprotection/t3lib_formProtection_AbstractTest.php =================================================================== --- tests/t3lib/formprotection/t3lib_formProtection_AbstractTest.php (revision 0) +++ tests/t3lib/formprotection/t3lib_formProtection_AbstractTest.php (revision 0) @@ -0,0 +1,475 @@ + + */ +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 Property changes on: tests/t3lib/formprotection/t3lib_formProtection_AbstractTest.php ___________________________________________________________________ Added: svn:mime-type + text/plain Index: tests/t3lib/formprotection/t3lib_formProtection_InstallToolTest.php =================================================================== --- tests/t3lib/formprotection/t3lib_formProtection_InstallToolTest.php (revision 0) +++ tests/t3lib/formprotection/t3lib_formProtection_InstallToolTest.php (revision 0) @@ -0,0 +1,190 @@ + + */ +class t3lib_formProtection_InstallToolTest extends tx_phpunit_testcase { + /** + * @var t3lib_formProtection_InstallTool + */ + 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_InstallTool with retrieveTokens made + * public. + * + * @return string the name of the created class, will not be empty + */ + private function createAccessibleProxyClass() { + $className = 't3lib_formProtection_InstallToolAccessibleProxy'; + if (!class_exists($className)) { + eval( + 'class ' . $className . ' extends t3lib_formProtection_InstallTool {' . + ' 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_InstallTool + ); + } + + + ////////////////////////////////////////////////////////// + // 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 Property changes on: tests/t3lib/formprotection/t3lib_formProtection_InstallToolTest.php ___________________________________________________________________ Added: svn:mime-type + text/plain Index: tests/t3lib/formprotection/fixtures/class.t3lib_formprotection_testing.php =================================================================== --- tests/t3lib/formprotection/fixtures/class.t3lib_formprotection_testing.php (revision 0) +++ tests/t3lib/formprotection/fixtures/class.t3lib_formprotection_testing.php (revision 0) @@ -0,0 +1,99 @@ + +* 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 + */ +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 Property changes on: tests/t3lib/formprotection/fixtures/class.t3lib_formprotection_testing.php ___________________________________________________________________ Added: svn:mime-type + text/plain Index: tests/t3lib/formprotection/t3lib_formProtection_BackendTest.php =================================================================== --- tests/t3lib/formprotection/t3lib_formProtection_BackendTest.php (revision 0) +++ tests/t3lib/formprotection/t3lib_formProtection_BackendTest.php (revision 0) @@ -0,0 +1,198 @@ + + */ +class t3lib_formProtection_BackendTest extends tx_phpunit_testcase { + /** + * a backup of the current BE user + * + * @var t3lib_beUserAuth + */ + private $backEndUserBackup = NULL; + + /** + * @var t3lib_formProtection_Backend + */ + 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_Backend with retrieveTokens made + * public. + * + * @return string the name of the created class, will not be empty + */ + private function createAccessibleProxyClass() { + $className = 't3lib_formProtection_BackendAccessibleProxy'; + if (!class_exists($className)) { + eval( + 'class ' . $className . ' extends t3lib_formProtection_Backend {' . + ' 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_Backend + ); + } + + + ////////////////////////////////////////////////////////// + // 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 Property changes on: tests/t3lib/formprotection/t3lib_formProtection_BackendTest.php ___________________________________________________________________ Added: svn:mime-type + text/plain