Raised DBAL version from 1.1.5 to 1.1.6
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Security / Channel / RequestHashService.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Sebastian Kurfürst <sebastian@typo3.org>
6 * All rights reserved
7 *
8 * This class is a backport of the corresponding class of FLOW3.
9 * All credits go to the v5 team.
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * This is a Service which can generate a request hash and check whether the currently given arguments
30 * fit to the request hash.
31 *
32 * It is used when forms are generated and submitted:
33 * After a form has been generated, the method "generateRequestHash" is called with the names of all form fields.
34 * It cleans up the array of form fields and creates another representation of it, which is then serialized and hashed.
35 *
36 * Both serialized form field list and the added hash form the request hash, which will be sent over the wire (as an argument __hmac).
37 *
38 * On the validation side, the validation happens in two steps:
39 * 1) Check if the request hash is consistent (the hash value fits to the serialized string)
40 * 2) Check that _all_ GET/POST parameters submitted occur inside the form field list of the request hash.
41 *
42 * Note: It is crucially important that a private key is computed into the hash value! This is done inside the HashService.
43 *
44 * @version $Id: RequestHashService.php 1729 2009-11-25 21:37:20Z stucki $
45 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
46 */
47 class Tx_Extbase_Security_Channel_RequestHashService implements t3lib_singleton {
48
49 /**
50 * @var Tx_Extbase_Security_Cryptography_HashService
51 */
52 protected $hashService;
53
54 /**
55 * Constructor
56 */
57 public function __construct() {
58 $this->hashService = t3lib_div::makeInstance('Tx_Extbase_Security_Cryptography_HashService'); // Singleton
59 }
60
61 /**
62 * Generate a request hash for a list of form fields
63 *
64 * @param array $formFieldNames Array of form fields
65 * @return string request hash
66 * @author Sebastian Kurfürst <sebastian@typo3.org>
67 * @todo might need to become public API lateron, as we need to call it from Fluid
68 */
69 public function generateRequestHash($formFieldNames, $fieldNamePrefix = '') {
70 $formFieldArray = array();
71 foreach ($formFieldNames as $formField) {
72 $formFieldParts = explode('[', $formField);
73 $currentPosition =& $formFieldArray;
74 for ($i=0; $i < count($formFieldParts); $i++) {
75 $formFieldPart = $formFieldParts[$i];
76 if (substr($formFieldPart, -1) == ']') $formFieldPart = substr($formFieldPart, 0, -1); // Strip off closing ] if needed
77
78 if (!is_array($currentPosition)) {
79 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);
80 }
81
82 if ($i == count($formFieldParts) - 1) {
83 if (isset($currentPosition[$formFieldPart]) && is_array($currentPosition[$formFieldPart])) {
84 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);
85 }
86 // Last iteration - add a string
87 if ($formFieldPart === '') {
88 $currentPosition[] = 1;
89 } else {
90 $currentPosition[$formFieldPart] = 1;
91 }
92 } else {
93 if ($formFieldPart === '') {
94 throw new Tx_Extbase_Security_Exception_InvalidArgumentForRequestHashGeneration('The form field name "' . $formField . '" is invalid. Reason: "[]" used not as last argument.', 1255072832);
95 }
96 if (!isset($currentPosition[$formFieldPart])) {
97 $currentPosition[$formFieldPart] = array();
98 }
99 $currentPosition =& $currentPosition[$formFieldPart];
100 }
101 }
102 }
103 if ($fieldNamePrefix !== '') {
104
105 $formFieldArray = (isset($formFieldArray[$fieldNamePrefix]) ? $formFieldArray[$fieldNamePrefix] : array() );
106 }
107 return $this->serializeAndHashFormFieldArray($formFieldArray);
108 }
109
110 /**
111 * Serialize and hash the form field array
112 *
113 * @param array $formFieldArray form field array to be serialized and hashed
114 * @return string Hash
115 * @author Sebastian Kurfürst <sebastian@typo3.org>
116 */
117 protected function serializeAndHashFormFieldArray($formFieldArray) {
118 $serializedFormFieldArray = serialize($formFieldArray);
119 return $serializedFormFieldArray . $this->hashService->generateHash($serializedFormFieldArray);
120 }
121
122 /**
123 * Verify the request. Checks if there is an __hmac argument, and if yes, tries to validate and verify it.
124 *
125 * In the end, $request->setHmacVerified is set depending on the value.
126 * @param \F3\FLOW3\MVC\Web\Request $request The request to verify
127 * @return void
128 * @author Sebastian Kurfürst <sebastian@typo3.org>
129 */
130 public function verifyRequest(Tx_Extbase_MVC_Web_Request $request) {
131 if (!$request->hasArgument('__hmac')) {
132 $request->setHmacVerified(FALSE);
133 return;
134 }
135 $hmac = $request->getArgument('__hmac');
136 if (strlen($hmac) < 40) {
137 throw new Tx_Extbase_Security_Exception_SyntacticallyWrongRequestHash('Request hash too short. This is a probably manipulation attempt!', 1255089361);
138 }
139 $serializedFieldNames = substr($hmac, 0, -40); // TODO: Constant for hash length needs to be introduced
140 $hash = substr($hmac, -40);
141 if ($this->hashService->validateHash($serializedFieldNames, $hash)) {
142 $requestArguments = $request->getArguments();
143 // Unset framework arguments
144 unset($requestArguments['__referrer']);
145 unset($requestArguments['__hmac']);
146 if ($this->checkFieldNameInclusion($requestArguments, unserialize($serializedFieldNames))) {
147 $request->setHmacVerified(TRUE);
148 } else {
149 $request->setHmacVerified(FALSE);
150 }
151 } else {
152 $request->setHmacVerified(FALSE);
153 }
154
155 }
156
157 /**
158 * Check if every element in $requestArguments is in $allowedFields as well.
159 *
160 * @param array $requestArguments
161 * @param array $allowedFiels
162 * @return boolean TRUE if ALL fields inside requestArguments are in $allowedFields, FALSE otherwise.
163 */
164 protected function checkFieldNameInclusion(array $requestArguments, array $allowedFields) {
165 foreach ($requestArguments as $argumentName => $argumentValue) {
166 if (!isset($allowedFields[$argumentName])) {
167 return FALSE;
168 }
169 if (is_array($requestArguments[$argumentName]) && is_array($allowedFields[$argumentName])) {
170 if (!$this->checkFieldNameInclusion($requestArguments[$argumentName], $allowedFields[$argumentName])) {
171 return FALSE;
172 }
173 } elseif (!is_array($requestArguments[$argumentName]) && !is_array($allowedFields[$argumentName])) {
174 // do nothing, as this is allowed
175 } else {
176 // different types - error
177 return FALSE;
178 }
179 }
180 return TRUE;
181 }
182 }
183
184 ?>