TYPO3 API  SVNRelease
RequestHashService.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 2009 Sebastian Kurfürst <sebastian@typo3.org>
00006 *  All rights reserved
00007 *
00008 *  This class is a backport of the corresponding class of FLOW3.
00009 *  All credits go to the v5 team.
00010 *
00011 *  This script is part of the TYPO3 project. The TYPO3 project is
00012 *  free software; you can redistribute it and/or modify
00013 *  it under the terms of the GNU General Public License as published by
00014 *  the Free Software Foundation; either version 2 of the License, or
00015 *  (at your option) any later version.
00016 *
00017 *  The GNU General Public License can be found at
00018 *  http://www.gnu.org/copyleft/gpl.html.
00019 *
00020 *  This script is distributed in the hope that it will be useful,
00021 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023 *  GNU General Public License for more details.
00024 *
00025 *  This copyright notice MUST APPEAR in all copies of the script!
00026 ***************************************************************/
00027 
00028 /**
00029  * This is a Service which can generate a request hash and check whether the currently given arguments
00030  * fit to the request hash.
00031  *
00032  * It is used when forms are generated and submitted:
00033  * After a form has been generated, the method "generateRequestHash" is called with the names of all form fields.
00034  * It cleans up the array of form fields and creates another representation of it, which is then serialized and hashed.
00035  *
00036  * Both serialized form field list and the added hash form the request hash, which will be sent over the wire (as an argument __hmac).
00037  *
00038  * On the validation side, the validation happens in two steps:
00039  * 1) Check if the request hash is consistent (the hash value fits to the serialized string)
00040  * 2) Check that _all_ GET/POST parameters submitted occur inside the form field list of the request hash.
00041  *
00042  * Note: It is crucially important that a private key is computed into the hash value! This is done inside the HashService.
00043  *
00044  * @version $Id: RequestHashService.php 1729 2009-11-25 21:37:20Z stucki $
00045  * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
00046  */
00047 class Tx_Extbase_Security_Channel_RequestHashService implements t3lib_singleton {
00048 
00049     /**
00050      * @var Tx_Extbase_Security_Cryptography_HashService
00051      */
00052     protected $hashService;
00053 
00054     /**
00055      * @param Tx_Extbase_Security_Cryptography_HashService $hashService 
00056      * @return void
00057      */
00058     public function injectHashService(Tx_Extbase_Security_Cryptography_HashService $hashService) {
00059         $this->hashService = $hashService;
00060     }
00061 
00062     /**
00063      * Generate a request hash for a list of form fields
00064      *
00065      * @param array $formFieldNames Array of form fields
00066      * @return string request hash
00067      * @author Sebastian Kurfürst <sebastian@typo3.org>
00068      * @todo might need to become public API lateron, as we need to call it from Fluid
00069      */
00070     public function generateRequestHash($formFieldNames, $fieldNamePrefix = '') {
00071         $formFieldArray = array();
00072         foreach ($formFieldNames as $formField) {
00073             $formFieldParts = explode('[', $formField);
00074             $currentPosition =& $formFieldArray;
00075             for ($i=0; $i < count($formFieldParts); $i++) {
00076                 $formFieldPart = $formFieldParts[$i];
00077                 if (substr($formFieldPart, -1) == ']') $formFieldPart = substr($formFieldPart, 0, -1); // Strip off closing ] if needed
00078 
00079                 if (!is_array($currentPosition)) {
00080                     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);
00081                 }
00082 
00083                 if ($i == count($formFieldParts) - 1) {
00084                     if (isset($currentPosition[$formFieldPart]) && is_array($currentPosition[$formFieldPart])) {
00085                         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);
00086                     }
00087                     // Last iteration - add a string
00088                     if ($formFieldPart === '') {
00089                         $currentPosition[] = 1;
00090                     } else {
00091                         $currentPosition[$formFieldPart] = 1;
00092                     }
00093                 } else {
00094                     if ($formFieldPart === '') {
00095                         throw new Tx_Extbase_Security_Exception_InvalidArgumentForRequestHashGeneration('The form field name "' . $formField . '" is invalid. Reason: "[]" used not as last argument.', 1255072832);
00096                     }
00097                     if (!isset($currentPosition[$formFieldPart])) {
00098                         $currentPosition[$formFieldPart] = array();
00099                     }
00100                     $currentPosition =& $currentPosition[$formFieldPart];
00101                 }
00102             }
00103         }
00104         if ($fieldNamePrefix !== '') {
00105 
00106             $formFieldArray = (isset($formFieldArray[$fieldNamePrefix]) ? $formFieldArray[$fieldNamePrefix] : array() );
00107         }
00108         return $this->serializeAndHashFormFieldArray($formFieldArray);
00109     }
00110 
00111     /**
00112      * Serialize and hash the form field array
00113      *
00114      * @param array $formFieldArray form field array to be serialized and hashed
00115      * @return string Hash
00116      * @author Sebastian Kurfürst <sebastian@typo3.org>
00117      */
00118     protected function serializeAndHashFormFieldArray($formFieldArray) {
00119         $serializedFormFieldArray = serialize($formFieldArray);
00120         return $serializedFormFieldArray . $this->hashService->generateHash($serializedFormFieldArray);
00121     }
00122 
00123     /**
00124      * Verify the request. Checks if there is an __hmac argument, and if yes, tries to validate and verify it.
00125      *
00126      * In the end, $request->setHmacVerified is set depending on the value.
00127      * @param \F3\FLOW3\MVC\Web\Request $request The request to verify
00128      * @return void
00129      * @author Sebastian Kurfürst <sebastian@typo3.org>
00130      */
00131     public function verifyRequest(Tx_Extbase_MVC_Web_Request $request) {
00132         if (!$request->hasArgument('__hmac')) {
00133             $request->setHmacVerified(FALSE);
00134             return;
00135         }
00136         $hmac = $request->getArgument('__hmac');
00137         if (strlen($hmac) < 40) {
00138             throw new Tx_Extbase_Security_Exception_SyntacticallyWrongRequestHash('Request hash too short. This is a probably manipulation attempt!', 1255089361);
00139         }
00140         $serializedFieldNames = substr($hmac, 0, -40); // TODO: Constant for hash length needs to be introduced
00141         $hash = substr($hmac, -40);
00142         if ($this->hashService->validateHash($serializedFieldNames, $hash)) {
00143             $requestArguments = $request->getArguments();
00144             // Unset framework arguments
00145             unset($requestArguments['__referrer']);
00146             unset($requestArguments['__hmac']);
00147             if ($this->checkFieldNameInclusion($requestArguments, unserialize($serializedFieldNames))) {
00148                 $request->setHmacVerified(TRUE);
00149             } else {
00150                 $request->setHmacVerified(FALSE);
00151             }
00152         } else {
00153             $request->setHmacVerified(FALSE);
00154         }
00155 
00156     }
00157 
00158     /**
00159      * Check if every element in $requestArguments is in $allowedFields as well.
00160      *
00161      * @param array $requestArguments
00162      * @param array $allowedFiels
00163      * @return boolean TRUE if ALL fields inside requestArguments are in $allowedFields, FALSE otherwise.
00164      */
00165     protected function checkFieldNameInclusion(array $requestArguments, array $allowedFields) {
00166         foreach ($requestArguments as $argumentName => $argumentValue) {
00167             if (!isset($allowedFields[$argumentName])) {
00168                 return FALSE;
00169             }
00170             if (is_array($requestArguments[$argumentName]) && is_array($allowedFields[$argumentName])) {
00171                 if (!$this->checkFieldNameInclusion($requestArguments[$argumentName], $allowedFields[$argumentName])) {
00172                     return FALSE;
00173                 }
00174             } elseif (!is_array($requestArguments[$argumentName]) && !is_array($allowedFields[$argumentName])) {
00175                 // do nothing, as this is allowed
00176             } elseif (!is_array($requestArguments[$argumentName]) && $requestArguments[$argumentName] === '' && is_array($allowedFields[$argumentName])) {
00177                 // do nothing, as this is allowed.
00178                 // This case is needed for making an array of checkboxes work, in case they are fully unchecked.
00179                 // Example: if the following checkbox names are defined:
00180                 //     foo[a]
00181                 //     foo[b]
00182                 // then, Fluid automatically renders a hidden field "foo" with the value '' (empty string) in front of it,
00183                 // to determine the case if the user un-checks all checkboxes.
00184                 // in this case, the property mapping already does the right thing, but without this condition here,
00185                 // the request hash checking would fail because of the strong type checks.
00186             } else {
00187                 // different types - error
00188                 return FALSE;
00189             }
00190         }
00191         return TRUE;
00192     }
00193 }
00194 
00195 ?>