[!!!][+FEATURE] Extbase (Security): Added a HMAC generator and checker to prevent...
authorSebastian Kurfürst <sebastian@typo3.org>
Mon, 12 Oct 2009 06:27:19 +0000 (06:27 +0000)
committerSebastian Kurfürst <sebastian@typo3.org>
Mon, 12 Oct 2009 06:27:19 +0000 (06:27 +0000)
17 files changed:
typo3/sysext/extbase/Classes/Dispatcher.php
typo3/sysext/extbase/Classes/MVC/Controller/ActionController.php
typo3/sysext/extbase/Classes/MVC/Controller/Argument.php
typo3/sysext/extbase/Classes/MVC/Web/Request.php
typo3/sysext/extbase/Classes/MVC/Web/Routing/UriBuilder.php
typo3/sysext/extbase/Classes/Reflection/ObjectAccess.php
typo3/sysext/extbase/Classes/Security/Channel/RequestHashService.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Security/Cryptography/HashService.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Security/Exception.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Security/Exception/InvalidArgumentForHashGeneration.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Security/Exception/InvalidArgumentForRequestHashGeneration.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Security/Exception/SyntacticallyWrongRequestHash.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/MVC/Controller/ActionController_testcase.php
typo3/sysext/extbase/Tests/MVC/Controller/Argument_testcase.php
typo3/sysext/extbase/Tests/MVC/Web/Routing/UriBuilder_testcase.php
typo3/sysext/extbase/Tests/Security/Channel/RequestHashService_testcase.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Security/Cryptography/HashService_testcase.php [new file with mode: 0644]

index ee9d2cc..d84ba1b 100644 (file)
@@ -100,6 +100,10 @@ class Tx_Extbase_Dispatcher {
                $request = $requestBuilder->build();
                $response = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_Response');
 
+               // Request hash service
+               $requestHashService = t3lib_div::makeInstance('Tx_Extbase_Security_Channel_RequestHashService'); // singleton
+               $requestHashService->verifyRequest($request);
+
                $persistenceManager = self::getPersistenceManager();
 
                $dispatchLoopCount = 0;
@@ -109,8 +113,6 @@ class Tx_Extbase_Dispatcher {
                        try {
                                $controller->processRequest($request, $response);
                        } catch (Tx_Extbase_MVC_Exception_StopAction $ignoredException) {
-                       } catch (Tx_Extbase_MVC_Exception_InvalidArgumentValue $exception) {
-                               return '';
                        }
                }
 
@@ -258,7 +260,7 @@ class Tx_Extbase_Dispatcher {
        public static function getConfigurationManager() {
                return self::$configurationManager;
        }
-       
+
        /**
         * This function returns the settings of Extbase
         *
index 3796dca..a62dcbf 100644 (file)
@@ -133,6 +133,7 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
                }
 
                $this->mapRequestArgumentsToControllerArguments();
+               $this->checkRequestHash();
                $this->view = $this->resolveView();
                if ($this->view !== NULL) $this->initializeView($this->view);
                $this->callActionMethod();
@@ -275,7 +276,7 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
                if (method_exists($view, 'injectSettings')) {
                        $view->injectSettings($this->settings);
                }
-               $view->initializeView(); // In FLOW3, solved through Object Lifecycle methods, we need to call it explicitely           
+               $view->initializeView(); // In FLOW3, solved through Object Lifecycle methods, we need to call it explicitely
                $view->assign('settings', $this->settings); // same with settings injection.
                return $view;
        }
@@ -349,7 +350,7 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
                if ($errorFlashMessage !== FALSE) {
                        $this->flashMessages->add($errorFlashMessage);
                }
-               
+
                if ($this->request->hasArgument('__referrer')) {
                        $referrer = $this->request->getArgument('__referrer');
                        $this->forward($referrer['actionName'], $referrer['controllerName'], $referrer['extensionName'], $this->request->getArguments());
@@ -364,7 +365,7 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
                }
                return $message;
        }
-       
+
        /**
         * A template method for displaying custom error flash messages, or to
         * display no flash message at all on errors. Override this to customize
@@ -378,6 +379,34 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
        }
 
        /**
+        * Checks the request hash (HMAC), if arguments have been touched by the property mapper.
+        *
+        * In case the @dontverifyrequesthash-Annotation has been set, this suppresses the exception.
+        *
+        * @return void
+        * @throws Tx_Extbase_MVC_Exception_InvalidOrNoRequestHash In case request hash checking failed
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        */
+       protected function checkRequestHash() {
+               if (!($this->request instanceof Tx_Extbase_MVC_Web_Request)) return; // We only want to check it for now for web requests.
+               if ($this->request->isHmacVerified()) return; // all good
+
+               $verificationNeeded = FALSE;
+               foreach ($this->arguments as $argument) {
+                       if ($argument->getOrigin() == Tx_Extbase_MVC_Controller_Argument::ORIGIN_NEWLY_CREATED
+                        || $argument->getOrigin() == Tx_Extbase_MVC_Controller_Argument::ORIGIN_PERSISTENCE_AND_MODIFIED) {
+                               $verificationNeeded = TRUE;
+                       }
+               }
+               if ($verificationNeeded) {
+                       $methodTagsValues = $this->reflectionService->getMethodTagsValues(get_class($this), $this->actionMethodName);
+                       if (!isset($methodTagsValues['dontverifyrequesthash'])) {
+                               throw new Tx_Extbase_MVC_Exception_InvalidOrNoRequestHash('Request hash (HMAC) checking failed. The parameter __hmac was invalid or not set, and objects were modified.', 1255082824);
+                       }
+               }
+       }
+
+       /**
         * Clear cache of current page on error. Needed because we want a re-evaluation of the data.
         * Better would be just do delete the cache for the error action, but that is not possible right now.
         *
index 8da877e..fcc59e9 100644 (file)
@@ -104,6 +104,19 @@ class Tx_Extbase_MVC_Controller_Argument {
         */
        protected $uid = NULL;
 
+       const ORIGIN_CLIENT = 0;
+       const ORIGIN_PERSISTENCE = 1;
+       const ORIGIN_PERSISTENCE_AND_MODIFIED = 2;
+       const ORIGIN_NEWLY_CREATED = 3;
+
+       /**
+        * The origin of the argument value. This is only meaningful after argument mapping.
+        *
+        * One of the ORIGIN_* constants above
+        * @var integer
+        */
+       protected $origin = 0;
+
        /**
         * Constructs this controller argument
         *
@@ -282,6 +295,16 @@ class Tx_Extbase_MVC_Controller_Argument {
        }
 
        /**
+        * Get the origin of the argument value. This is only meaningful after argument mapping.
+        *
+        * @return integer one of the ORIGIN_* constants
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        */
+       public function getOrigin() {
+               return $this->origin;
+       }
+
+       /**
         * Sets the value of this argument.
         *
         * @param mixed $value: The value of this argument
@@ -313,21 +336,28 @@ class Tx_Extbase_MVC_Controller_Argument {
                }
                $transformedValue = NULL;
                if ($this->dataTypeClassSchema !== NULL) {
-                       // It is an Entity or ValueObject.
+                       // The target object is an Entity or ValueObject.
                        if (is_numeric($value)) {
+                               $this->origin = self::ORIGIN_PERSISTENCE;
                                $transformedValue = $this->findObjectByUid($value);
                        } elseif (is_array($value)) {
+                               $this->origin = self::ORIGIN_PERSISTENCE_AND_MODIFIED;
                                $transformedValue = $this->propertyMapper->map(array_keys($value), $value, $this->dataType);
                        }
                } else {
                        if (!is_array($value)) {
                                throw new Tx_Extbase_MVC_Exception_InvalidArgumentValue('The value was a simple type, so we could not map it to an object. Maybe the @entity or @valueobject annotations are missing?', 1251730701);
                        }
+                       $this->origin = self::ORIGIN_NEWLY_CREATED;
                        $transformedValue = $this->propertyMapper->map(array_keys($value), $value, $this->dataType);
                }
 
                if (!($transformedValue instanceof $this->dataType)) {
-                       throw new Tx_Extbase_MVC_Exception_InvalidArgumentValue('The value must be of type "' . $this->dataType . '", but was of type "' . get_class($transformedValue) . '".', 1251730701);
+                       if (is_object($transformedValue)) {
+                               throw new Tx_Extbase_MVC_Exception_InvalidArgumentValue('The value must be of type "' . $this->dataType . '", but was of type "' . get_class($transformedValue) . '".', 1251730701);
+                       } else {
+                               throw new Tx_Extbase_MVC_Exception_InvalidArgumentValue('The value must be of type "' . $this->dataType . '", but was of type "' . gettype($transformedValue) . '".', 1251730702);
+                       }
                }
                return $transformedValue;
        }
index ef5503a..46ed89c 100755 (executable)
@@ -57,6 +57,11 @@ class Tx_Extbase_MVC_Web_Request extends Tx_Extbase_MVC_Request {
        protected $baseURI;
 
        /**
+        * @var boolean TRUE if the HMAC of this request could be verified, FALSE otherwise.
+        */
+       protected $hmacVerified = FALSE;
+
+       /**
         * Sets the request method
         *
         * @param string $method Name of the request method
@@ -121,5 +126,26 @@ class Tx_Extbase_MVC_Web_Request extends Tx_Extbase_MVC_Request {
                        return $this->baseURI;
                }
        }
+
+       /**
+        * Could the request be verified via a HMAC?
+        *
+        * @param boolean $hmacVerified TRUE if request could be verified, FALSE otherwise.
+        * @return void
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        */
+       public function setHmacVerified($hmacVerified) {
+               $this->hmacVerified = (boolean)$hmacVerified;
+       }
+
+       /**
+        * Could the request be verified via a HMAC?
+        *
+        * @return boolean TRUE if request could be verified, FALSE otherwise.
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        */
+       public function isHmacVerified() {
+               return $this->hmacVerified;
+       }
 }
 ?>
\ No newline at end of file
index f10d1d4..211ee7f 100644 (file)
@@ -39,6 +39,12 @@ class Tx_Extbase_MVC_Web_Routing_UriBuilder {
        protected $arguments = array();
 
        /**
+        * Arguments which have been used for building the last URI
+        * @var array
+        */
+       protected $lastArguments = array();
+
+       /**
         * @var string
         */
        protected $section = '';
@@ -346,6 +352,17 @@ class Tx_Extbase_MVC_Web_Routing_UriBuilder {
        }
 
        /**
+        * Returns the arguments being used for the last URI being built.
+        * This is only set after build() / uriFor() has been called.
+        *
+        * @return array The last arguments
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        */
+       public function getLastArguments() {
+               return $this->lastArguments;
+       }
+
+       /**
         * Resets all UriBuilder options to their default value
         *
         * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
@@ -444,12 +461,13 @@ class Tx_Extbase_MVC_Web_Routing_UriBuilder {
                }
                $arguments = t3lib_div::array_merge_recursive_overrule($arguments, $this->arguments);
                $arguments = $this->convertDomainObjectsToIdentityArrays($arguments);
+               $this->lastArguments = $arguments;
                $uri = 'mod.php?' . http_build_query($arguments, NULL, '&');
                if ($this->section !== '') {
                        $uri .= '#' . $this->section;
                }
                if ($this->createAbsoluteUri === TRUE) {
-                       $uri = $this->request->getBaseURI() . TYPO3_mainDir . $uri;
+                       $uri = $this->request->getBaseURI() . $uri;
                }
                return $uri;
        }
@@ -487,6 +505,7 @@ class Tx_Extbase_MVC_Web_Routing_UriBuilder {
 
                if (count($this->arguments) > 0) {
                        $arguments = $this->convertDomainObjectsToIdentityArrays($this->arguments);
+                       $this->lastArguments = $arguments;
                        $typolinkConfiguration['additionalParams'] = '&' . http_build_query($arguments, NULL, '&');
                }
 
@@ -497,6 +516,7 @@ class Tx_Extbase_MVC_Web_Routing_UriBuilder {
                                        'exclude' => implode(',', $this->argumentsToBeExcludedFromQueryString)
                                );
                        }
+                       // TODO: Support for __hmac and addQueryString!
                }
 
                if ($this->noCache === TRUE) {
index 9549cfa..b56a3ac 100644 (file)
@@ -68,6 +68,24 @@ class Tx_Extbase_Reflection_ObjectAccess {
        }
 
        /**
+        * Gets a property path from a given object.
+        * If propertyPath is "bla.blubb", then we first call getProperty($object, 'bla'),
+        * and on the resulting object we call getProperty(..., 'blubb')
+        *
+        * @param object $object
+        * @param string $propertyPath
+        * @return object Value of the property
+        */
+       static public function getPropertyPath($object, $propertyPath) {
+               $propertyPathSegments = explode('.', $propertyPath);
+               foreach ($propertyPathSegments as $pathSegment) {
+                       $object = self::getProperty($object, $pathSegment);
+                       if ($object === NULL) return NULL;
+               }
+               return $object;
+       }
+
+       /**
         * Set a property for a given object.
         * Tries to set the property the following ways:
         * - if public setter method exists, call it.
diff --git a/typo3/sysext/extbase/Classes/Security/Channel/RequestHashService.php b/typo3/sysext/extbase/Classes/Security/Channel/RequestHashService.php
new file mode 100644 (file)
index 0000000..34331eb
--- /dev/null
@@ -0,0 +1,184 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Sebastian Kurfürst <sebastian@typo3.org>
+*  All rights reserved
+*
+*  This class is a backport of the corresponding class of FLOW3.
+*  All credits go to the v5 team.
+*
+*  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!
+***************************************************************/
+
+/**
+ * This is a Service which can generate a request hash and check whether the currently given arguments
+ * fit to the request hash.
+ *
+ * It is used when forms are generated and submitted:
+ * After a form has been generated, the method "generateRequestHash" is called with the names of all form fields.
+ * It cleans up the array of form fields and creates another representation of it, which is then serialized and hashed.
+ *
+ * Both serialized form field list and the added hash form the request hash, which will be sent over the wire (as an argument __hmac).
+ *
+ * On the validation side, the validation happens in two steps:
+ * 1) Check if the request hash is consistent (the hash value fits to the serialized string)
+ * 2) Check that _all_ GET/POST parameters submitted occur inside the form field list of the request hash.
+ *
+ * Note: It is crucially important that a private key is computed into the hash value! This is done inside the HashService.
+ *
+ * @version $Id: HTTPSInterceptor.php 2813 2009-07-16 14:02:34Z k-fish $
+ * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
+ */
+class Tx_Extbase_Security_Channel_RequestHashService implements t3lib_singleton {
+
+       /**
+        * @var Tx_Extbase_Security_Cryptography_HashService
+        */
+       protected $hashService;
+
+       /**
+        * Constructor
+        */
+       public function __construct() {
+               $this->hashService = t3lib_div::makeInstance('Tx_Extbase_Security_Cryptography_HashService'); // Singleton
+       }
+
+       /**
+        * Generate a request hash for a list of form fields
+        *
+        * @param array $formFieldNames Array of form fields
+        * @return string request hash
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        * @todo might need to become public API lateron, as we need to call it from Fluid
+        */
+       public function generateRequestHash($formFieldNames, $fieldNamePrefix = '') {
+               $formFieldArray = array();
+               foreach ($formFieldNames as $formField) {
+                       $formFieldParts = explode('[', $formField);
+                       $currentPosition =& $formFieldArray;
+                       for ($i=0; $i < count($formFieldParts); $i++) {
+                               $formFieldPart = $formFieldParts[$i];
+                               if (substr($formFieldPart, -1) == ']') $formFieldPart = substr($formFieldPart, 0, -1); // Strip off closing ] if needed
+
+                               if (!is_array($currentPosition)) {
+                                       throw new Tx_Extbase_Security_Exception_InvalidArgumentForRequestHashGeneration('The form field name "' . $formField . '" collides with a previous form field name which declared the field as string. (String overridden by Array)', 1255072196);
+                               }
+
+                               if ($i == count($formFieldParts) - 1) {
+                                       if (isset($currentPosition[$formFieldPart]) && is_array($currentPosition[$formFieldPart])) {
+                                               throw new Tx_Extbase_Security_Exception_InvalidArgumentForRequestHashGeneration('The form field name "' . $formField . '" collides with a previous form field name which declared the field as array. (Array overridden by String)', 1255072587);
+                                       }
+                                       // Last iteration - add a string
+                                       if ($formFieldPart === '') {
+                                               $currentPosition[] = 1;
+                                       } else {
+                                               $currentPosition[$formFieldPart] = 1;
+                                       }
+                               } else {
+                                       if ($formFieldPart === '') {
+                                               throw new Tx_Extbase_Security_Exception_InvalidArgumentForRequestHashGeneration('The form field name "' . $formField . '" is invalid. Reason: "[]" used not as last argument.', 1255072832);
+                                       }
+                                       if (!isset($currentPosition[$formFieldPart])) {
+                                               $currentPosition[$formFieldPart] = array();
+                                       }
+                                       $currentPosition =& $currentPosition[$formFieldPart];
+                               }
+                       }
+               }
+               if ($fieldNamePrefix !== '') {
+
+                       $formFieldArray = (isset($formFieldArray[$fieldNamePrefix]) ? $formFieldArray[$fieldNamePrefix] : array() );
+               }
+               return $this->serializeAndHashFormFieldArray($formFieldArray);
+       }
+
+       /**
+        * Serialize and hash the form field array
+        *
+        * @param array $formFieldArray form field array to be serialized and hashed
+        * @return string Hash
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        */
+       protected function serializeAndHashFormFieldArray($formFieldArray) {
+               $serializedFormFieldArray = serialize($formFieldArray);
+               return $serializedFormFieldArray . $this->hashService->generateHash($serializedFormFieldArray);
+       }
+
+       /**
+        * Verify the request. Checks if there is an __hmac argument, and if yes, tries to validate and verify it.
+        *
+        * In the end, $request->setHmacVerified is set depending on the value.
+        * @param \F3\FLOW3\MVC\Web\Request $request The request to verify
+        * @return void
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        */
+       public function verifyRequest(Tx_Extbase_MVC_Web_Request $request) {
+               if (!$request->hasArgument('__hmac')) {
+                       $request->setHmacVerified(FALSE);
+                       return;
+               }
+               $hmac = $request->getArgument('__hmac');
+               if (strlen($hmac) < 40) {
+                       throw new Tx_Extbase_Security_Exception_SyntacticallyWrongRequestHash('Request hash too short. This is a probably manipulation attempt!', 1255089361);
+               }
+               $serializedFieldNames = substr($hmac, 0, -40); // TODO: Constant for hash length needs to be introduced
+               $hash = substr($hmac, -40);
+               if ($this->hashService->validateHash($serializedFieldNames, $hash)) {
+                       $requestArguments = $request->getArguments();
+                       // Unset framework arguments
+                       unset($requestArguments['__referrer']);
+                       unset($requestArguments['__hmac']);
+                       if ($this->checkFieldNameInclusion($requestArguments, unserialize($serializedFieldNames))) {
+                               $request->setHmacVerified(TRUE);
+                       } else {
+                               $request->setHmacVerified(FALSE);
+                       }
+               } else {
+                       $request->setHmacVerified(FALSE);
+               }
+
+       }
+
+       /**
+        * Check if every element in $requestArguments is in $allowedFields as well.
+        *
+        * @param array $requestArguments
+        * @param array $allowedFiels
+        * @return boolean TRUE if ALL fields inside requestArguments are in $allowedFields, FALSE otherwise.
+        */
+       protected function checkFieldNameInclusion(array $requestArguments, array $allowedFields) {
+               foreach ($requestArguments as $argumentName => $argumentValue) {
+                       if (!isset($allowedFields[$argumentName])) {
+                               return FALSE;
+                       }
+                       if (is_array($requestArguments[$argumentName]) && is_array($allowedFields[$argumentName])) {
+                               if (!$this->checkFieldNameInclusion($requestArguments[$argumentName], $allowedFields[$argumentName])) {
+                                       return FALSE;
+                               }
+                       } elseif (!is_array($requestArguments[$argumentName]) && !is_array($allowedFields[$argumentName])) {
+                               // do nothing, as this is allowed
+                       } else {
+                               // different types - error
+                               return FALSE;
+                       }
+               }
+               return TRUE;
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/extbase/Classes/Security/Cryptography/HashService.php b/typo3/sysext/extbase/Classes/Security/Cryptography/HashService.php
new file mode 100644 (file)
index 0000000..cb8df82
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Sebastian Kurfürst <sebastian@typo3.org>
+*  All rights reserved
+*
+*  This class is a backport of the corresponding class of FLOW3.
+*  All credits go to the v5 team.
+*
+*  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!
+***************************************************************/
+
+/**
+ * A hash service which should be used to generate and validate hashes.
+ *
+ * It will use some salt / encryption key in the future.
+ *
+ * @version $Id: OpenSSLRSAKey.php 2813 2009-07-16 14:02:34Z k-fish $
+ * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser Public License, version 3 or later
+ */
+class Tx_Extbase_Security_Cryptography_HashService implements t3lib_singleton {
+       /**
+        * Generate a hash for a given string
+        *
+        * @param string $string The string for which a hash should be generated
+        * @return string The hash of the string
+        * @throws F3\FLOW3\Security\Exception\InvalidArgumentForHashGeneration if something else than a string was given as parameter
+        * @todo Mark as API once it is more stable
+        * @todo encryption key has to come from somewhere else
+        */
+       public function generateHash($string) {
+               if (!is_string($string)) throw new Tx_Extbase_Security_Exception_InvalidArgumentForHashGeneration('A hash can only be generated for a string, but "' . gettype($string) . '" was given.', 1255069587);
+               $encryptionKey = '7nN5#n8guP/oA9Bq95x=e/x}.hL[:7yv1BJcWrB0AYQ5WJ!KGd'; // TODO
+               return sha1($string . $encryptionKey);
+       }
+
+       /**
+        * Test if a string $string has the hash given by $hash.
+        *
+        * @param string $string The string which should be validated
+        * @param string $hash The hash of the string
+        * @return boolean TRUE if string and hash fit together, FALSE otherwise.
+        * @todo Mark as API once it is more stable
+        */
+       public function validateHash($string, $hash) {
+               return ($this->generateHash($string) === $hash);
+       }
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/extbase/Classes/Security/Exception.php b/typo3/sysext/extbase/Classes/Security/Exception.php
new file mode 100644 (file)
index 0000000..be5ac7f
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Sebastian Kurfürst <sebastian@typo3.org>
+*  All rights reserved
+*
+*  This class is a backport of the corresponding class of FLOW3.
+*  All credits go to the v5 team.
+*
+*  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!
+***************************************************************/
+
+/**
+ * A hash service which should be used to generate and validate hashes.
+ *
+ * It will use some salt / encryption key in the future.
+ *
+ * @version $Id: OpenSSLRSAKey.php 2813 2009-07-16 14:02:34Z k-fish $
+ * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser Public License, version 3 or later
+ */
+class Tx_Extbase_Security_Exception extends Tx_Extbase_Exception {
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/extbase/Classes/Security/Exception/InvalidArgumentForHashGeneration.php b/typo3/sysext/extbase/Classes/Security/Exception/InvalidArgumentForHashGeneration.php
new file mode 100644 (file)
index 0000000..27fa076
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Sebastian Kurfürst <sebastian@typo3.org>
+*  All rights reserved
+*
+*  This class is a backport of the corresponding class of FLOW3.
+*  All credits go to the v5 team.
+*
+*  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!
+***************************************************************/
+
+/**
+ * An "InvalidArgumentForHashGeneration" exception
+ *
+ * @package Extbase
+ * @subpackage Security\Exception
+ * @version $Id: InfiniteLoop.php 1052 2009-08-05 21:51:32Z sebastian $
+ */
+class Tx_Extbase_Security_Exception_InvalidArgumentForHashGeneration extends Tx_Extbase_Security_Exception {
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/extbase/Classes/Security/Exception/InvalidArgumentForRequestHashGeneration.php b/typo3/sysext/extbase/Classes/Security/Exception/InvalidArgumentForRequestHashGeneration.php
new file mode 100644 (file)
index 0000000..9af16bb
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Sebastian Kurfürst <sebastian@typo3.org>
+*  All rights reserved
+*
+*  This class is a backport of the corresponding class of FLOW3.
+*  All credits go to the v5 team.
+*
+*  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!
+***************************************************************/
+
+/**
+ * An "InvalidArgumentForRequestHashGeneration" exception
+ *
+ * @package Extbase
+ * @subpackage Security\Exception
+ * @version $Id: InfiniteLoop.php 1052 2009-08-05 21:51:32Z sebastian $
+ */
+class Tx_Extbase_Security_Exception_InvalidArgumentForRequestHashGeneration extends Tx_Extbase_Security_Exception {
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/extbase/Classes/Security/Exception/SyntacticallyWrongRequestHash.php b/typo3/sysext/extbase/Classes/Security/Exception/SyntacticallyWrongRequestHash.php
new file mode 100644 (file)
index 0000000..7d0c811
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Sebastian Kurfürst <sebastian@typo3.org>
+*  All rights reserved
+*
+*  This class is a backport of the corresponding class of FLOW3.
+*  All credits go to the v5 team.
+*
+*  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!
+***************************************************************/
+
+/**
+ * An "SyntacticallyWrongRequestHash" exception
+ *
+ * @package Extbase
+ * @subpackage Security\Exception
+ * @version $Id: InfiniteLoop.php 1052 2009-08-05 21:51:32Z sebastian $
+ */
+class Tx_Extbase_Security_Exception_SyntacticallyWrongRequestHash extends Tx_Extbase_Security_Exception {
+}
+?>
\ No newline at end of file
index ff2b9ec..370b20e 100644 (file)
@@ -40,7 +40,7 @@ class Tx_Extbase_MVC_Controller_ActionController_testcase extends Tx_Extbase_Bas
                $mockView = $this->getMock('Tx_Extbase_MVC_View_ViewInterface');
 
                $mockController = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_MVC_Controller_ActionController'), array(
-                       'initializeFooAction', 'initializeAction', 'resolveActionMethodName', 'initializeActionMethodArguments', 'initializeActionMethodValidators', 'mapRequestArgumentsToControllerArguments', 'resolveView', 'initializeView', 'callActionMethod'),
+                       'initializeFooAction', 'initializeAction', 'resolveActionMethodName', 'initializeActionMethodArguments', 'initializeActionMethodValidators', 'mapRequestArgumentsToControllerArguments', 'resolveView', 'initializeView', 'callActionMethod', 'checkRequestHash'),
                        array(), '', FALSE);
                $mockController->_set('objectFactory', $mockObjectFactory);
                $mockController->expects($this->at(0))->method('resolveActionMethodName')->will($this->returnValue('fooAction'));
@@ -49,9 +49,10 @@ class Tx_Extbase_MVC_Controller_ActionController_testcase extends Tx_Extbase_Bas
                $mockController->expects($this->at(3))->method('initializeAction');
                $mockController->expects($this->at(4))->method('initializeFooAction');
                $mockController->expects($this->at(5))->method('mapRequestArgumentsToControllerArguments');
-               $mockController->expects($this->at(6))->method('resolveView')->will($this->returnValue($mockView));
-               $mockController->expects($this->at(7))->method('initializeView');
-               $mockController->expects($this->at(8))->method('callActionMethod');
+                $mockController->expects($this->at(6))->method('checkRequestHash');
+               $mockController->expects($this->at(7))->method('resolveView')->will($this->returnValue($mockView));
+               $mockController->expects($this->at(8))->method('initializeView');
+               $mockController->expects($this->at(9))->method('callActionMethod');
 
                $mockController->processRequest($mockRequest, $mockResponse);
                $this->assertSame($mockRequest, $mockController->_get('request'));
@@ -489,5 +490,80 @@ class Tx_Extbase_MVC_Controller_ActionController_testcase extends Tx_Extbase_Bas
                $this->markTestIncomplete('To be implemented');
        }
 
+        /**
+        * Data Provider for checkRequestHashDoesNotThrowExceptionInNormalOperations
+        */
+       public function checkRequestHashInNormalOperation() {
+               return array(
+                       // HMAC is verified
+                       array(TRUE),
+                       // HMAC not verified, but objects are directly fetched from persistence layer
+                       array(FALSE, FALSE, Tx_Extbase_MVC_Controller_Argument::ORIGIN_PERSISTENCE, Tx_Extbase_MVC_Controller_Argument::ORIGIN_PERSISTENCE),
+                       // HMAC not verified, objects new and modified, but dontverifyrequesthash-annotation set
+                       array(FALSE, TRUE, Tx_Extbase_MVC_Controller_Argument::ORIGIN_PERSISTENCE, Tx_Extbase_MVC_Controller_Argument::ORIGIN_PERSISTENCE_AND_MODIFIED, array('dontverifyrequesthash' => ''))
+               );
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        * @dataProvider checkRequestHashInNormalOperation
+        */
+       public function checkRequestHashDoesNotThrowExceptionInNormalOperations($hmacVerified, $reflectionServiceNeedsInitialization = FALSE, $argument1Origin = 3, $argument2Origin = 3, $methodTagsValues = array()) {
+               $mockRequest = $this->getMock('Tx_Extbase_MVC_Web_Request', array('isHmacVerified'), array(), '', FALSE);
+               $mockRequest->expects($this->once())->method('isHmacVerified')->will($this->returnValue($hmacVerified));
+
+               $argument1 = $this->getMock('Tx_Extbase_MVC_Controller_Argument', array('getOrigin'), array(), '', FALSE);
+               $argument1->expects($this->any())->method('getOrigin')->will($this->returnValue($argument1Origin));
+               $argument2 = $this->getMock('Tx_Extbase_MVC_Controller_Argument', array('getOrigin'), array(), '', FALSE);
+               $argument2->expects($this->any())->method('getOrigin')->will($this->returnValue($argument2Origin));
+
+               $mockController = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_MVC_Controller_ActionController'), array('dummy'), array(), '', FALSE);
+
+               $mockReflectionService = $this->getMock('Tx_Extbase_Reflection_Service', array('getMethodTagsValues'), array(), '', FALSE);
+               if ($reflectionServiceNeedsInitialization) {
+                       // Somehow this is needed, else I get "Mocked method does not exist."
+                       $mockReflectionService->expects($this->any())->method('getMethodTagsValues')->with(get_class($mockController), 'fooAction')->will($this->returnValue($methodTagsValues));
+               }
+               $mockController->_set('arguments', array($argument1, $argument2));
+               $mockController->_set('request', $mockRequest);
+               $mockController->_set('actionMethodName', 'fooAction');
+               $mockController->injectReflectionService($mockReflectionService);
+
+               $mockController->_call('checkRequestHash');
+       }
+
+       /**
+        * @test
+        * @expectedException Tx_Extbase_MVC_Exception_InvalidOrNoRequestHash
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        */
+       public function checkRequestHashThrowsExceptionIfNeeded() {
+               $hmacVerified = FALSE;
+               $argument1Origin = Tx_Extbase_MVC_Controller_Argument::ORIGIN_PERSISTENCE_AND_MODIFIED;
+               $argument2Origin = Tx_Extbase_MVC_Controller_Argument::ORIGIN_PERSISTENCE;
+               $methodTagsValues = array();
+
+               $mockRequest = $this->getMock('Tx_Extbase_MVC_Web_Request', array('isHmacVerified'), array(), '', FALSE);
+               $mockRequest->expects($this->once())->method('isHmacVerified')->will($this->returnValue($hmacVerified));
+
+               $argument1 = $this->getMock('Tx_Extbase_MVC_Controller_Argument', array('getOrigin'), array(), '', FALSE);
+               $argument1->expects($this->any())->method('getOrigin')->will($this->returnValue($argument1Origin));
+               $argument2 = $this->getMock('Tx_Extbase_MVC_Controller_Argument', array('getOrigin'), array(), '', FALSE);
+               $argument2->expects($this->any())->method('getOrigin')->will($this->returnValue($argument2Origin));
+
+               $mockController = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_MVC_Controller_ActionController'), array('dummy'), array(), '', FALSE);
+
+               $mockReflectionService = $this->getMock('Tx_Extbase_Reflection_Service', array('getMethodTagsValues'), array(), '', FALSE);
+               $mockReflectionService->expects($this->any())->method('getMethodTagsValues')->with(get_class($mockController), 'fooAction')->will($this->returnValue($methodTagsValues));
+
+               $mockController->_set('arguments', array($argument1, $argument2));
+               $mockController->_set('request', $mockRequest);
+               $mockController->_set('actionMethodName', 'fooAction');
+               $mockController->injectReflectionService($mockReflectionService);
+
+               $mockController->_call('checkRequestHash');
+       }
+
 }
 ?>
index af5e4e9..5ad2d64 100644 (file)
@@ -5,7 +5,7 @@
 *  (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
 *  All rights reserved
 *
-*  This class is a backport of the corresponding class of FLOW3. 
+*  This class is a backport of the corresponding class of FLOW3.
 *  All credits go to the v5 team.
 *
 *  This script is part of the TYPO3 project. The TYPO3 project is
@@ -26,7 +26,7 @@
 ***************************************************************/
 
 class Tx_Extbase_MVC_Controller_Argument_testcase extends Tx_Extbase_BaseTestCase {
-               
+
        /**
         * @test
         * @expectedException InvalidArgumentException
@@ -34,7 +34,7 @@ class Tx_Extbase_MVC_Controller_Argument_testcase extends Tx_Extbase_BaseTestCas
        public function constructingArgumentWithoutNameThrowsException() {
                new Tx_Extbase_MVC_Controller_Argument(NULL, 'Text');
        }
-       
+
        /**
         * @test
         * @expectedException InvalidArgumentException
@@ -42,7 +42,7 @@ class Tx_Extbase_MVC_Controller_Argument_testcase extends Tx_Extbase_BaseTestCas
        public function constructingArgumentWithInvalidNameThrowsException() {
                new Tx_Extbase_MVC_Controller_Argument(new ArrayObject(), 'Text');
        }
-       
+
        /**
         * @test
         */
@@ -50,7 +50,7 @@ class Tx_Extbase_MVC_Controller_Argument_testcase extends Tx_Extbase_BaseTestCas
                $argument = new Tx_Extbase_MVC_Controller_Argument('dummy', 'Number');
                $this->assertEquals('Number', $argument->getDataType(), 'The specified data type has not been set correctly.');
        }
-       
+
        /**
         * @test
         */
@@ -59,7 +59,7 @@ class Tx_Extbase_MVC_Controller_Argument_testcase extends Tx_Extbase_BaseTestCas
                $returnedArgument = $argument->setShortName('x');
                $this->assertSame($argument, $returnedArgument, 'The returned argument is not the original argument.');
        }
-       
+
        /**
         * @test
         */
@@ -68,13 +68,13 @@ class Tx_Extbase_MVC_Controller_Argument_testcase extends Tx_Extbase_BaseTestCas
                $returnedArgument = $argument->setValue('x');
                $this->assertSame($argument, $returnedArgument, 'The returned argument is not the original argument.');
        }
-       
+
        /**
         * @test
         */
        public function setValueTriesToConvertAnUIDIntoTheRealObjectIfTheDataTypeClassSchemaIsSet() {
                $object = new StdClass();
-               
+
                $argument = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_MVC_Controller_Argument'), array('findObjectByUid'), array(), '', FALSE);
                $argument->expects($this->once())->method('findObjectByUid')->with('42')->will($this->returnValue($object));
                $argument->_set('dataTypeClassSchema', 'stdClass');
@@ -83,20 +83,21 @@ class Tx_Extbase_MVC_Controller_Argument_testcase extends Tx_Extbase_BaseTestCas
                $argument->setValue('42');
 
                $this->assertSame($object, $argument->_get('value'));
+               $this->assertSame(Tx_Extbase_MVC_Controller_Argument::ORIGIN_PERSISTENCE, $argument->getOrigin());
        }
 
-               
+
        /**
         * @test
         */
        public function toStringReturnsTheStringVersionOfTheArgumentsValue() {
                $argument = new Tx_Extbase_MVC_Controller_Argument('dummy', 'Text');
                $argument->setValue(123);
-       
+
                $this->assertSame((string)$argument, '123', 'The returned argument is not a string.');
                $this->assertNotSame((string)$argument, 123, 'The returned argument is identical to the set value.');
        }
-       
+
        /**
         * @test
         */
@@ -104,17 +105,17 @@ class Tx_Extbase_MVC_Controller_Argument_testcase extends Tx_Extbase_BaseTestCas
                $argument = new Tx_Extbase_MVC_Controller_Argument('SomeArgument');
                $this->assertSame('Text', $argument->getDataType());
        }
-       
+
        /**
         * @test
         */
        public function setNewValidatorConjunctionCreatesANewValidatorConjunctionObject() {
                $argument = new Tx_Extbase_MVC_Controller_Argument('dummy', 'Text');
                $argument->setNewValidatorConjunction(array());
-       
+
                $this->assertType('Tx_Extbase_Validation_Validator_ConjunctionValidator', $argument->getValidator(), 'The returned validator is not a conjunction as expected.');
        }
-       
+
        /**
         * @test
         */
@@ -129,28 +130,28 @@ class Tx_Extbase_MVC_Controller_Argument_testcase extends Tx_Extbase_BaseTestCas
                        public function setOptions(array $validationOptions) {}
                        public function getErrors() {}
                }');
-               
+
                $validator1 = new Validator1;
                $validator2 = new Validator2;
-       
+
                $mockValidatorConjunction = $this->getMock('Tx_Extbase_Validation_Validator_ConjunctionValidator');
                $mockValidatorConjunction->expects($this->at(0))->method('addValidator')->with($validator1);
                $mockValidatorConjunction->expects($this->at(1))->method('addValidator')->with($validator2);
-               
+
                $argument = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_MVC_Controller_Argument'), array('dummy'), array(), '', FALSE);
                $argument->_set('validator', $mockValidatorConjunction);
                $argument->setNewValidatorConjunction(array('Validator1', 'Validator2'));
        }
-       
+
        /**
         * @test
         */
        public function settingDefaultValueReallySetsDefaultValue() {
                $argument = new Tx_Extbase_MVC_Controller_Argument('dummy', 'Text');
                $argument->setDefaultValue(42);
-       
+
                $this->assertEquals(42, $argument->getValue(), 'The default value was not stored in the Argument.');
        }
-       
+
 }
 ?>
\ No newline at end of file
index a32142d..c2d463a 100644 (file)
@@ -262,7 +262,7 @@ class Tx_Extbase_MVC_Web_Routing_UriBuilder_testcase extends Tx_Extbase_BaseTest
        public function buildBackendUriCreatesAbsoluteUrisIfSpecified() {
                t3lib_div::_GETset(array('M' => 'moduleKey'));
 
-               $this->request->expects($this->any())->method('getBaseURI')->will($this->returnValue('http://baseuri/'));
+               $this->request->expects($this->any())->method('getBaseURI')->will($this->returnValue('http://baseuri/' . TYPO3_mainDir));
                $this->uriBuilder->setCreateAbsoluteUri(TRUE);
 
                $expectedResult = 'http://baseuri/' . TYPO3_mainDir . 'mod.php?M=moduleKey';
diff --git a/typo3/sysext/extbase/Tests/Security/Channel/RequestHashService_testcase.php b/typo3/sysext/extbase/Tests/Security/Channel/RequestHashService_testcase.php
new file mode 100644 (file)
index 0000000..e8ab36e
--- /dev/null
@@ -0,0 +1,405 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Sebastian Kurfürst <sebastian@typo3.org>
+*  All rights reserved
+*
+*  This class is a backport of the corresponding class of FLOW3.
+*  All credits go to the v5 team.
+*
+*  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 Request Hash Service
+ *
+ * @version $Id: RSAWalletServicePHPTest.php 2813 2009-07-16 14:02:34Z k-fish $
+ * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser Public License, version 3 or later
+ */
+class Tx_Extbase_Security_Channel_RequestHashService_testcase extends Tx_Extbase_BaseTestCase {
+
+       public function dataProviderForGenerateRequestHash() {
+               return array(
+                       // Simple cases
+                       array(
+                               array(),
+                               array(),
+                       ),
+                       array(
+                               array('field1'),
+                               array('field1' => 1),
+                       ),
+                       array(
+                               array('field1', 'field2'),
+                               array(
+                                       'field1' => 1,
+                                       'field2' => 1
+                               ),
+                       ),
+                       // recursion
+                       array(
+                               array('field1', 'field[subfield1]', 'field[subfield2]'),
+                               array(
+                                       'field1' => 1,
+                                       'field' => array(
+                                               'subfield1' => 1,
+                                               'subfield2' => 1
+                                       )
+                               ),
+                       ),
+                       // recursion with duplicated field name
+                       array(
+                               array('field1', 'field[subfield1]', 'field[subfield2]', 'field1'),
+                               array(
+                                       'field1' => 1,
+                                       'field' => array(
+                                               'subfield1' => 1,
+                                               'subfield2' => 1
+                                       )
+                               ),
+                       ),
+                       // Recursion with un-named fields at the end (...[]). There, they should be made explicit by increasing the counter
+                       array(
+                               array('field1', 'field[subfield1][]', 'field[subfield1][]', 'field[subfield2]'),
+                               array(
+                                       'field1' => 1,
+                                       'field' => array(
+                                               'subfield1' => array(
+                                                       0 => 1,
+                                                       1 => 1
+                                               ),
+                                               'subfield2' => 1
+                                       )
+                               ),
+                       ),
+               );
+       }
+
+       // Data provider for error cases which should throw an exception
+       public function dataProviderForGenerateRequestHashWithUnallowedValues() {
+               return array(
+                       // Overriding form fields (string overridden by array)
+                       array(
+                               array('field1', 'field2', 'field2[bla]', 'field2[blubb]'),
+                       ),
+                       array(
+                               array('field1', 'field2[bla]', 'field2[bla][blubb][blubb]'),
+                       ),
+                       // Overriding form fields (array overridden by string)
+                       array(
+                               array('field1', 'field2[bla]', 'field2[blubb]', 'field2'),
+                       ),
+                       array(
+                               array('field1', 'field2[bla][blubb][blubb]', 'field2[bla]'),
+                       ),
+                       // Empty [] not as last argument
+                       array(
+                               array('field1', 'field2[][bla]'),
+                       )
+
+               );
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        * @dataProvider dataProviderForGenerateRequestHash
+        */
+       public function generateRequestHashGeneratesTheCorrectHashesInNormalOperation($input, $expected) {
+               $requestHashService = $this->getMock('Tx_Extbase_Security_Channel_RequestHashService', array('serializeAndHashFormFieldArray'));
+               $requestHashService->expects($this->once())->method('serializeAndHashFormFieldArray')->with($expected);
+               $requestHashService->generateRequestHash($input);
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        * @dataProvider dataProviderForGenerateRequestHashWithUnallowedValues
+        * @expectedException Tx_Extbase_Security_Exception_InvalidArgumentForRequestHashGeneration
+        */
+       public function generateRequestHashThrowsExceptionInWrongCases($input) {
+               $requestHashService = $this->getMock('Tx_Extbase_Security_Channel_RequestHashService', array('serializeAndHashFormFieldArray'));
+               $requestHashService->generateRequestHash($input);
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        */
+       public function serializeAndHashFormFieldArrayWorks() {
+               $formFieldArray = array(
+                       'bla' => array(
+                               'blubb' => 1,
+                               'hu' => 1
+                       )
+               );
+               $mockHash = '12345';
+
+               $hashService = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_Security_Cryptography_HashService'), array('generateHash'));
+               $hashService->expects($this->once())->method('generateHash')->with(serialize($formFieldArray))->will($this->returnValue($mockHash));
+
+               $requestHashService = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_Security_Channel_RequestHashService'), array('dummy'));
+               $requestHashService->_set('hashService', $hashService);
+
+               $expected = serialize($formFieldArray) . $mockHash;
+               $actual = $requestHashService->_call('serializeAndHashFormFieldArray', $formFieldArray);
+               $this->assertEquals($expected, $actual);
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst
+        */
+       public function verifyRequestHashSetsHmacVerifiedToFalseIfRequestDoesNotHaveAnHmacArgument() {
+               $request = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_MVC_Web_Request'), array('hasArgument', 'setHmacVerified'));
+               $request->expects($this->once())->method('hasArgument')->with('__hmac')->will($this->returnValue(FALSE));
+               $request->expects($this->once())->method('setHmacVerified')->with(FALSE);
+               $requestHashService = new Tx_Extbase_Security_Channel_RequestHashService;
+               $requestHashService->verifyRequest($request);
+       }
+
+       /**
+        * @test
+        * @expectedException Tx_Extbase_Security_Exception_SyntacticallyWrongRequestHash
+        * @author Sebastian Kurfürst
+        */
+       public function verifyRequestHashThrowsExceptionIfHmacIsShortherThan40Characters() {
+               $request = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_MVC_Web_Request'), array('hasArgument', 'getArgument', 'setHmacVerified'));
+               $request->expects($this->once())->method('hasArgument')->with('__hmac')->will($this->returnValue(TRUE));
+               $request->expects($this->once())->method('getArgument')->with('__hmac')->will($this->returnValue('abc'));
+               $requestHashService = new Tx_Extbase_Security_Channel_RequestHashService;
+               $requestHashService->verifyRequest($request);
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst
+        */
+       public function verifyRequestHashValidatesTheHashAndSetsHmacVerifiedToFalseIfHashCouldNotBeVerified() {
+               $request = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_MVC_Web_Request'), array('hasArgument', 'getArgument', 'setHmacVerified'));
+               $request->expects($this->once())->method('hasArgument')->with('__hmac')->will($this->returnValue(TRUE));
+               $request->expects($this->once())->method('getArgument')->with('__hmac')->will($this->returnValue('11111' . '0000000000000000000000000000000000000000'));
+               $request->expects($this->once())->method('setHmacVerified')->with(FALSE);
+
+               $hashService = $this->getMock('Tx_Extbase_Security_Cryptography_HashService', array('validateHash'));
+               $hashService->expects($this->once())->method('validateHash')->with('11111', '0000000000000000000000000000000000000000')->will($this->returnValue(FALSE));
+
+               $requestHashService = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_Security_Channel_RequestHashService'), array('dummy'));
+               $requestHashService->_set('hashService', $hashService);
+               $requestHashService->verifyRequest($request);
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst
+        */
+       public function verifyRequestHashValidatesTheHashAndSetsHmacVerifiedToTrueIfArgumentsAreIncludedInTheAllowedArgumentList() {
+               $data = serialize(array('a' => 1));
+               $request = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_MVC_Web_Request'), array('hasArgument', 'getArgument', 'getArguments', 'setHmacVerified'));
+               $request->expects($this->once())->method('hasArgument')->with('__hmac')->will($this->returnValue(TRUE));
+               $request->expects($this->once())->method('getArgument')->with('__hmac')->will($this->returnValue($data . '0000000000000000000000000000000000000000'));
+               $request->expects($this->once())->method('getArguments')->will($this->returnValue(array(
+                       '__hmac' => 'ABC',
+                       '__referrer' => '...',
+                       'a' => 'bla'
+               )));
+               $request->expects($this->once())->method('setHmacVerified')->with(TRUE);
+
+               $hashService = $this->getMock('Tx_Extbase_Security_Cryptography_HashService', array('validateHash'));
+               $hashService->expects($this->once())->method('validateHash')->with($data, '0000000000000000000000000000000000000000')->will($this->returnValue(TRUE));
+
+               $requestHashService = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_Security_Channel_RequestHashService'), array('checkFieldNameInclusion'));
+               $requestHashService->expects($this->once())->method('checkFieldNameInclusion')->with(array('a' => 'bla'), array('a' => 1))->will($this->returnValue(TRUE));
+               $requestHashService->_set('hashService', $hashService);
+               $requestHashService->verifyRequest($request);
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst
+        */
+       public function verifyRequestHashValidatesTheHashAndSetsHmacVerifiedToFalseIfNotAllArgumentsAreIncludedInTheAllowedArgumentList() {
+               $data = serialize(array('a' => 1));
+               $request = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_MVC_Web_Request'), array('hasArgument', 'getArgument', 'getArguments', 'setHmacVerified'));
+               $request->expects($this->once())->method('hasArgument')->with('__hmac')->will($this->returnValue(TRUE));
+               $request->expects($this->once())->method('getArgument')->with('__hmac')->will($this->returnValue($data . '0000000000000000000000000000000000000000'));
+               $request->expects($this->once())->method('getArguments')->will($this->returnValue(array(
+                       '__hmac' => 'ABC',
+                       '__referrer' => '...',
+                       'a' => 'bla',
+                       'b' => 'blubb'
+               )));
+               $request->expects($this->once())->method('setHmacVerified')->with(FALSE);
+
+               $hashService = $this->getMock('Tx_Extbase_Security_Cryptography_HashService', array('validateHash'));
+               $hashService->expects($this->once())->method('validateHash')->with($data, '0000000000000000000000000000000000000000')->will($this->returnValue(TRUE));
+
+               $requestHashService = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_Security_Channel_RequestHashService'), array('checkFieldNameInclusion'));
+               $requestHashService->expects($this->once())->method('checkFieldNameInclusion')->with(array('a' => 'bla', 'b' => 'blubb'), array('a' => 1))->will($this->returnValue(FALSE));
+               $requestHashService->_set('hashService', $hashService);
+               $requestHashService->verifyRequest($request);
+       }
+
+       /**
+        * Data Provider for checkFieldNameInclusionWorks
+        */
+       public function dataProviderForCheckFieldNameInclusion() {
+               return array(
+                       // Simple fields with requestfields = responsefields
+                       array(
+                               // Request
+                               array(
+                                       'a' => 'X',
+                                       'b' => 'X',
+                                       'c' => 'X'
+                               ),
+                               // Allowed
+                               array(
+                                       'a' => 1,
+                                       'b' => 1,
+                                       'c' => 1
+                               ),
+                               // Expected result
+                               TRUE
+                       ),
+                       // Simple fields with requestfields < responsefields
+                       array(
+                               // Request
+                               array(
+                                       'a' => 'X',
+                                       'c' => 'X'
+                               ),
+                               // Allowed
+                               array(
+                                       'a' => 1,
+                                       'b' => 1,
+                                       'c' => 1
+                               ),
+                               // Expected result
+                               TRUE
+                       ),
+                       // Simple fields with requestfields > responsefields
+                       array(
+                               // Request
+                               array(
+                                       'a' => 'X',
+                                       'b' => 'X',
+                                       'c' => 'X'
+                               ),
+                               // Allowed
+                               array(
+                                       'a' => 1,
+                                       'b' => 1
+                               ),
+                               // Expected result
+                               FALSE
+                       ),
+                       // Hierarchical fields with requestfields < responsefields
+                       array(
+                               // Request
+                               array(
+                                       'a' => array(
+                                               'b' => 'X'
+                                       ),
+                                       'c' => 'X'
+                               ),
+                               // Allowed
+                               array(
+                                       'a' => array(
+                                               'b' => 1,
+                                               'abc' => 1
+                                       ),
+                                       'c' => 1
+                               ),
+                               // Expected result
+                               TRUE
+                       ),
+                       // Hierarchical fields with requestfields > responsefields
+                       array(
+                               // Request
+                               array(
+                                       'a' => array(
+                                               'b' => 'X',
+                                               'abc' => 'X'
+                                       ),
+                                       'c' => 'X'
+                               ),
+                               // Allowed
+                               array(
+                                       'a' => array(
+                                               'b' => 1
+                                       ),
+                                       'c' => 1
+                               ),
+                               // Expected result
+                               FALSE
+                       ),
+                       // hierarchical fields with requestfields != responsefields (different types) - 1
+                       array(
+                               // Request
+                               array(
+                                       'a' => array(
+                                               'b' => 'X',
+                                               'c' => 'X'
+                                       ),
+                                       'b' => 'X',
+                                       'c' => 'X'
+                               ),
+                               // Allowed
+                               array(
+                                       'a' => 1,
+                                       'b' => 1,
+                                       'c' => 1
+                               ),
+                               // Expected result
+                               FALSE
+                       ),
+                       // hierarchical fields with requestfields != responsefields (different types) - 2
+                       array(
+                               // Request
+                               array(
+                                       'a' => 'X',
+                                       'b' => 'X',
+                                       'c' => 'X'
+                               ),
+                               // Allowed
+                               array(
+                                       'a' => array(
+                                               'x' => 1,
+                                               'y' => 1
+                                       ),
+                                       'b' => 1,
+                                       'c' => 1
+                               ),
+                               // Expected result
+                               FALSE
+                       ),
+               );
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        * @dataProvider dataProviderForCheckFieldNameInclusion
+        */
+       public function checkFieldNameInclusionWorks($requestArguments, $allowedFields, $expectedResult) {
+               $requestHashService = $this->getMock($this->buildAccessibleProxy('Tx_Extbase_Security_Channel_RequestHashService'), array('dummy'));
+               $this->assertEquals($expectedResult, $requestHashService->_call('checkFieldNameInclusion', $requestArguments, $allowedFields));
+       }
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/extbase/Tests/Security/Cryptography/HashService_testcase.php b/typo3/sysext/extbase/Tests/Security/Cryptography/HashService_testcase.php
new file mode 100644 (file)
index 0000000..410acfc
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Sebastian Kurfürst <sebastian@typo3.org>
+*  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 Hash Service
+ *
+ * @version $Id: RSAWalletServicePHPTest.php 2813 2009-07-16 14:02:34Z k-fish $
+ * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser Public License, version 3 or later
+ */
+class Tx_Extbase_Security_Cryptography_HashService_testcase extends Tx_Extbase_BaseTestCase {
+
+       protected $hashService;
+
+       public function setUp() {
+               $this->hashService = new Tx_Extbase_Security_Cryptography_HashService();
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst
+        */
+       public function generateHashReturnsHashStringIfStringIsGiven() {
+               $hash = $this->hashService->generateHash('asdf');
+               $this->assertTrue(is_string($hash));
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst
+        */
+       public function generateHashReturnsHashStringWhichContainsSomeSalt() {
+               $hash = $this->hashService->generateHash('asdf');
+               $this->assertNotEquals(sha1('asdf'), $hash);
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst
+        */
+       public function generateHashReturnsDifferentHashStringsForDifferentInputStrings() {
+               $hash1 = $this->hashService->generateHash('asdf');
+               $hash2 = $this->hashService->generateHash('blubb');
+               $this->assertNotEquals($hash1, $hash2);
+       }
+
+       /**
+        * @test
+        * @expectedException Tx_Extbase_Security_Exception_InvalidArgumentForHashGeneration
+        * @author Sebastian Kurfürst
+        */
+       public function generateHashThrowsExceptionIfNoStringGiven() {
+               $hash = $this->hashService->generateHash(NULL);
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        */
+       public function generatedHashCanBeValidatedAgain() {
+               $string = 'asdf';
+               $hash = $this->hashService->generateHash($string);
+               $this->assertTrue($this->hashService->validateHash($string, $hash));
+       }
+
+       /**
+        * @test
+        * @author Sebastian Kurfürst <sebastian@typo3.org>
+        */
+       public function generatedHashWillNotBeValidatedIfHashHasBeenChanged() {
+               $string = 'asdf';
+               $hash = 'myhash';
+               $this->assertFalse($this->hashService->validateHash($string, $hash));
+       }
+}
+?>
\ No newline at end of file