Refactoring FormHelper security token generation, fixing code formatting

git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@7639 3807eeeb-6ff5-0310-8944-8be069107fe0
This commit is contained in:
nate 2008-09-21 04:06:37 +00:00
parent 42911f5bc3
commit 80c1950530
3 changed files with 208 additions and 102 deletions

View file

@ -610,8 +610,9 @@ class Helper extends Overloadable {
* @param array $options * @param array $options
* @param string $key * @param string $key
* @return array * @return array
* @access protected
*/ */
function __initInputField($field, $options = array()) { function _initInputField($field, $options = array()) {
if ($field !== null) { if ($field !== null) {
$this->setEntity($field); $this->setEntity($field);
} }

View file

@ -149,9 +149,15 @@ class FormHelper extends AppHelper {
} }
$this->fieldset = array('fields' => $fields, 'key' => $object->primaryKey, 'validates' => $validates); $this->fieldset = array('fields' => $fields, 'key' => $object->primaryKey, 'validates' => $validates);
} }
$data = $this->fieldset;
if (isset($this->data[$model]) && isset($this->data[$model][$data['key']]) && !empty($this->data[$model][$data['key']])) { $data = $this->fieldset;
$recordExists = (
isset($this->data[$model]) &&
isset($this->data[$model][$data['key']]) &&
!empty($this->data[$model][$data['key']])
);
if ($recordExists) {
$created = true; $created = true;
$id = $this->data[$model][$data['key']]; $id = $this->data[$model][$data['key']];
} }
@ -199,7 +205,9 @@ class FormHelper extends AppHelper {
case 'post': case 'post':
case 'put': case 'put':
case 'delete': case 'delete':
$append .= $this->hidden('_method', array('name' => '_method', 'value' => strtoupper($options['type']), 'id' => null)); $append .= $this->hidden('_method', array(
'name' => '_method', 'value' => strtoupper($options['type']), 'id' => null
));
default: default:
$htmlAttributes['method'] = 'post'; $htmlAttributes['method'] = 'post';
break; break;
@ -303,7 +311,12 @@ class FormHelper extends AppHelper {
} }
} }
ksort($fields, SORT_STRING); ksort($fields, SORT_STRING);
$append .= $this->hidden('_Token.fields', array('value' => urlencode(Security::hash(serialize($fields) . Configure::read('Security.salt'))), 'id' => 'TokenFields' . mt_rand())); $fields = Security::hash(serialize($fields) . Configure::read('Security.salt'));
$append .= $this->hidden('_Token.fields', array(
'value' => urlencode($fields),
'id' => 'TokenFields' . mt_rand()
));
$append .= '</fieldset>'; $append .= '</fieldset>';
return $append; return $append;
} }
@ -316,7 +329,10 @@ class FormHelper extends AppHelper {
* @param mixed $options Options * @param mixed $options Options
* @access private * @access private
*/ */
function __secure($model = null, $options = null) { function __secure($model = null, $value = null) {
if (!isset($this->params['_Token']) || empty($this->params['_Token'])) {
return;
}
if (!$model) { if (!$model) {
$model = $this->model(); $model = $this->model();
} }
@ -324,24 +340,27 @@ class FormHelper extends AppHelper {
$field = $view->field; $field = $view->field;
$fieldSuffix = $view->fieldSuffix; $fieldSuffix = $view->fieldSuffix;
if (isset($this->params['_Token']) && !empty($this->params['_Token'])) {
if (!empty($this->params['_Token']['disabledFields'])) { if (!empty($this->params['_Token']['disabledFields'])) {
foreach ($this->params['_Token']['disabledFields'] as $value) { foreach ($this->params['_Token']['disabledFields'] as $f) {
$parts = explode('.', $value); if (!strpos($f, '.') && ($f === $field || $f === $fieldSuffix)) {
if (count($parts) == 1) { return;
if ($parts[0] === $field || $parts[0] === $fieldSuffix) { } elseif ($value === join('.', $view->entity())) {
return; return;
} }
} elseif (count($parts) == 2) { $parts = explode('.', $f);
if ($parts[0] === $model && ($parts[1] === $field || $parts[1] === $fieldSuffix)) {
if (count($parts) == 2) {
$fieldMatch = (
$parts[0] === $model &&
($parts[1] === $field || $parts[1] === $fieldSuffix)
);
if ($fieldMatch) {
return; return;
} }
} }
} }
} }
$this->__fields($model, $field, $fieldSuffix, $options); $this->__fields($model, $field, $fieldSuffix, $value);
return;
}
} }
/** /**
* Generates a field list used to secure forms * Generates a field list used to secure forms
@ -352,17 +371,24 @@ class FormHelper extends AppHelper {
* @param mixed $options * @param mixed $options
* @access private * @access private
*/ */
function __fields($model, $field, $fieldSuffix, $options) { function __fields($model, $field, $fieldSuffix, $value) {
if (!is_null($options)) { if (!is_null($value)) {
if (is_numeric($field)) { if (is_numeric($field)) {
$this->fields[$model][$field][$fieldSuffix] = $options; $this->fields[$model][$field][$fieldSuffix] = $value;
} else { } else {
$this->fields[$model][$field] = $options; $this->fields[$model][$field] = $value;
} }
return; return;
} }
$invalid = (
(!isset($this->fields[$model]) || in_array($field, $this->fields[$model], true)) &&
isset($this->fields[$model])
);
if ($invalid) {
return;
}
if ((isset($this->fields[$model]) && !in_array($field, $this->fields[$model], true)) || !isset($this->fields[$model])) {
if (is_numeric($field)) { if (is_numeric($field)) {
if (!isset($this->fields[$model][$field])) { if (!isset($this->fields[$model][$field])) {
$this->fields[$model][$field][] = $fieldSuffix; $this->fields[$model][$field][] = $fieldSuffix;
@ -379,8 +405,6 @@ class FormHelper extends AppHelper {
$this->fields[$model][] = $field; $this->fields[$model][] = $field;
} }
} }
return;
}
/** /**
* Returns true if there is an error for the given field, otherwise false * Returns true if there is an error for the given field, otherwise false
* *
@ -568,7 +592,8 @@ class FormHelper extends AppHelper {
function input($fieldName, $options = array()) { function input($fieldName, $options = array()) {
$view =& ClassRegistry::getObject('view'); $view =& ClassRegistry::getObject('view');
$this->setEntity($fieldName); $this->setEntity($fieldName);
$options = array_merge(array('before' => null, 'between' => null, 'after' => null), $options); $defaults = array('before' => null, 'between' => null, 'after' => null);
$options = array_merge($defaults, $options);
if (!isset($options['type'])) { if (!isset($options['type'])) {
$options['type'] = 'text'; $options['type'] = 'text';
@ -617,8 +642,9 @@ class FormHelper extends AppHelper {
} }
} }
} }
$types = array('text', 'checkbox', 'radio', 'select');
if (!isset($options['options']) && in_array($options['type'], array('text', 'checkbox', 'radio', 'select'))) { if (!isset($options['options']) && in_array($options['type'], $types)) {
$view =& ClassRegistry::getObject('view'); $view =& ClassRegistry::getObject('view');
$varName = Inflector::variable(Inflector::pluralize(preg_replace('/_id$/', '', $this->field()))); $varName = Inflector::variable(Inflector::pluralize(preg_replace('/_id$/', '', $this->field())));
$varOptions = $view->getVar($varName); $varOptions = $view->getVar($varName);
@ -767,20 +793,30 @@ class FormHelper extends AppHelper {
$options = array_merge(array('options' => array()), $options); $options = array_merge(array('options' => array()), $options);
$list = $options['options']; $list = $options['options'];
unset($options['options']); unset($options['options']);
$out = $before . $out . $between . $this->select($fieldName, $list, $selected, $options, $empty); $out = $before . $out . $between . $this->select(
$fieldName, $list, $selected, $options, $empty
);
break; break;
case 'time': case 'time':
$out = $before . $out . $between . $this->dateTime($fieldName, null, $timeFormat, $selected, $options, $empty); $out = $before . $out . $between . $this->dateTime(
$fieldName, null, $timeFormat, $selected, $options, $empty
);
break; break;
case 'date': case 'date':
$out = $before . $out . $between . $this->dateTime($fieldName, $dateFormat, null, $selected, $options, $empty); $out = $before . $out . $between . $this->dateTime(
$fieldName, $dateFormat, null, $selected, $options, $empty
);
break; break;
case 'datetime': case 'datetime':
$out = $before . $out . $between . $this->dateTime($fieldName, $dateFormat, $timeFormat, $selected, $options, $empty); $out = $before . $out . $between . $this->dateTime(
$fieldName, $dateFormat, $timeFormat, $selected, $options, $empty
);
break; break;
case 'textarea': case 'textarea':
default: default:
$out = $before . $out . $between . $this->textarea($fieldName, array_merge(array('cols' => '30', 'rows' => '6'), $options)); $out = $before . $out . $between . $this->textarea($fieldName, array_merge(
array('cols' => '30', 'rows' => '6'), $options
));
break; break;
} }
@ -808,39 +844,29 @@ class FormHelper extends AppHelper {
* @param array $options Array of HTML attributes. * @param array $options Array of HTML attributes.
* 'value' - the value of the checkbox * 'value' - the value of the checkbox
* 'checked' - boolean indicate that this checkbox is checked. * 'checked' - boolean indicate that this checkbox is checked.
* * @todo Right now, automatically setting the 'checked' value is dependent on whether or not the
* checkbox is bound to a model. This should probably be re-evaluated in future versions.
* @return string An HTML text input element * @return string An HTML text input element
*/ */
function checkbox($fieldName, $options = array()) { function checkbox($fieldName, $options = array()) {
$value = 1; $options = $this->_initInputField($fieldName, $options);
if (isset($options['value'])) { $value = current($this->value());
$value = $options['value'];
unset($options['value']);
}
$options = $this->__initInputField($fieldName, $options); if (!isset($options['value']) || empty($options['value'])) {
$this->__secure();
$model = $this->model();
if (ClassRegistry::isKeySet($model)) {
$object =& ClassRegistry::getObject($model);
}
$output = null;
if (isset($object) && isset($options['value']) && ($options['value'] == 0 || $options['value'] == 1)) {
$db =& ConnectionManager::getDataSource($object->useDbConfig);
$value = $db->boolean($options['value'], false);
$options['value'] = 1; $options['value'] = 1;
} } elseif (!empty($value) && $value === $options['value']) {
$output = $this->hidden($fieldName, array('name' => $options['name'], 'value' => '0', 'id' => $options['id'] . '_'), true);
if (isset($options['value']) && $value == $options['value']) {
$options['checked'] = 'checked'; $options['checked'] = 'checked';
} elseif (!empty($value)) {
$options['value'] = $value;
} }
$output .= sprintf($this->Html->tags['checkbox'], $options['name'], $this->_parseAttributes($options, array('name'), null, ' ')); $output = $this->hidden($fieldName, array(
'name' => $options['name'], 'value' => '0', 'id' => $options['id'] . '_'
));
$output .= sprintf(
$this->Html->tags['checkbox'],
$options['name'],
$this->_parseAttributes($options, array('name'), null, ' ')
);
return $this->output($output); return $this->output($output);
} }
/** /**
@ -857,8 +883,7 @@ class FormHelper extends AppHelper {
* @return string * @return string
*/ */
function radio($fieldName, $options = array(), $attributes = array()) { function radio($fieldName, $options = array(), $attributes = array()) {
$attributes = $this->__initInputField($fieldName, $attributes); $attributes = $this->_initInputField($fieldName, $attributes);
$this->__secure();
$legend = false; $legend = false;
if (isset($attributes['legend'])) { if (isset($attributes['legend'])) {
@ -893,23 +918,36 @@ class FormHelper extends AppHelper {
if (isset($value) && $optValue == $value) { if (isset($value) && $optValue == $value) {
$optionsHere['checked'] = 'checked'; $optionsHere['checked'] = 'checked';
} }
$parsedOptions = $this->_parseAttributes(array_merge($attributes, $optionsHere), array('name', 'type', 'id'), '', ' '); $parsedOptions = $this->_parseAttributes(
$tagName = Inflector::camelize($this->model() . '_' . $this->field() . '_'.Inflector::underscore($optValue)); array_merge($attributes, $optionsHere),
array('name', 'type', 'id'), '', ' '
);
$tagName = Inflector::camelize(
$this->model() . '_' . $this->field() . '_' . Inflector::underscore($optValue)
);
if ($label) { if ($label) {
$optTitle = sprintf($this->Html->tags['label'], $tagName, null, $optTitle); $optTitle = sprintf($this->Html->tags['label'], $tagName, null, $optTitle);
} }
$out[] = sprintf($this->Html->tags['radio'], $attributes['name'], $tagName, $parsedOptions, $optTitle); $out[] = sprintf(
$this->Html->tags['radio'], $attributes['name'],
$tagName, $parsedOptions, $optTitle
);
} }
$hidden = null; $hidden = null;
if (!isset($value) || $value === '') { if (!isset($value) || $value === '') {
$hidden = $this->hidden($fieldName, array('value' => '', 'id' => $attributes['id'] . '_'), true); $hidden = $this->hidden($fieldName, array(
'id' => $attributes['id'] . '_', 'value' => ''
));
} }
$out = $hidden . join($inbetween, $out); $out = $hidden . join($inbetween, $out);
if ($legend) { if ($legend) {
$out = sprintf($this->Html->tags['fieldset'], '', sprintf($this->Html->tags['legend'], $legend) . $out); $out = sprintf(
$this->Html->tags['fieldset'], '',
sprintf($this->Html->tags['legend'], $legend) . $out
);
} }
return $this->output($out); return $this->output($out);
} }
@ -921,9 +959,14 @@ class FormHelper extends AppHelper {
* @return string An HTML text input element * @return string An HTML text input element
*/ */
function text($fieldName, $options = array()) { function text($fieldName, $options = array()) {
$options = $this->__initInputField($fieldName, array_merge(array('type' => 'text'), $options)); $options = $this->_initInputField($fieldName, array_merge(
$this->__secure(); array('type' => 'text'), $options
return $this->output(sprintf($this->Html->tags['input'], $options['name'], $this->_parseAttributes($options, array('name'), null, ' '))); ));
return $this->output(sprintf(
$this->Html->tags['input'],
$options['name'],
$this->_parseAttributes($options, array('name'), null, ' ')
));
} }
/** /**
* Creates a password input widget. * Creates a password input widget.
@ -933,9 +976,12 @@ class FormHelper extends AppHelper {
* @return string * @return string
*/ */
function password($fieldName, $options = array()) { function password($fieldName, $options = array()) {
$options = $this->__initInputField($fieldName, $options); $options = $this->_initInputField($fieldName, $options);
$this->__secure(); return $this->output(sprintf(
return $this->output(sprintf($this->Html->tags['password'], $options['name'], $this->_parseAttributes($options, array('name'), null, ' '))); $this->Html->tags['password'],
$options['name'],
$this->_parseAttributes($options, array('name'), null, ' ')
));
} }
/** /**
* Creates a textarea widget. * Creates a textarea widget.
@ -945,8 +991,7 @@ class FormHelper extends AppHelper {
* @return string An HTML text input element * @return string An HTML text input element
*/ */
function textarea($fieldName, $options = array()) { function textarea($fieldName, $options = array()) {
$options = $this->__initInputField($fieldName, $options); $options = $this->_initInputField($fieldName, $options);
$this->__secure();
$value = null; $value = null;
if (array_key_exists('value', $options)) { if (array_key_exists('value', $options)) {
@ -956,7 +1001,12 @@ class FormHelper extends AppHelper {
} }
unset($options['value']); unset($options['value']);
} }
return $this->output(sprintf($this->Html->tags['textarea'], $options['name'], $this->_parseAttributes($options, array('type', 'name'), null, ' '), $value)); return $this->output(sprintf(
$this->Html->tags['textarea'],
$options['name'],
$this->_parseAttributes($options, array('type', 'name'), null, ' '),
$value
));
} }
/** /**
* Creates a hidden input field. * Creates a hidden input field.
@ -967,7 +1017,9 @@ class FormHelper extends AppHelper {
* @access public * @access public
*/ */
function hidden($fieldName, $options = array()) { function hidden($fieldName, $options = array()) {
$options = $this->__initInputField($fieldName, $options); $options = $this->_initInputField($fieldName, array_merge(
$options, array('secure' => false)
));
$model = $this->model(); $model = $this->model();
$value = ''; $value = '';
$key = '_' . $model; $key = '_' . $model;
@ -987,7 +1039,11 @@ class FormHelper extends AppHelper {
$this->__secure($model); $this->__secure($model);
} }
} }
return $this->output(sprintf($this->Html->tags['hidden'], $options['name'], $this->_parseAttributes($options, array('name', 'class'), '', ' '))); return $this->output(sprintf(
$this->Html->tags['hidden'],
$options['name'],
$this->_parseAttributes($options, array('name', 'class'), '', ' ')
));
} }
/** /**
* Creates file input widget. * Creates file input widget.
@ -998,9 +1054,12 @@ class FormHelper extends AppHelper {
* @access public * @access public
*/ */
function file($fieldName, $options = array()) { function file($fieldName, $options = array()) {
$options = $this->__initInputField($fieldName, $options); $options = $this->_initInputField($fieldName, $options);
$this->__secure(); return $this->output(sprintf(
return $this->output(sprintf($this->Html->tags['file'], $options['name'], $this->_parseAttributes($options, array('name'), '', ' '))); $this->Html->tags['file'],
$options['name'],
$this->_parseAttributes($options, array('name'), '', ' ')
));
} }
/** /**
* Creates a button tag. * Creates a button tag.
@ -1019,9 +1078,13 @@ class FormHelper extends AppHelper {
} }
$name = $options['name']; $name = $options['name'];
unset($options['name']); unset($options['name']);
$options = $this->__initInputField($name, $options); $options = $this->_initInputField($name, $options);
} }
return $this->output(sprintf($this->Html->tags['button'], $options['type'], $this->_parseAttributes($options, array('type'), '', ' '))); return $this->output(sprintf(
$this->Html->tags['button'],
$options['type'],
$this->_parseAttributes($options, array('type'), '', ' ')
));
} }
/** /**
* Creates a submit button element. * Creates a submit button element.
@ -1037,8 +1100,8 @@ class FormHelper extends AppHelper {
if (!$caption) { if (!$caption) {
$caption = __('Submit', true); $caption = __('Submit', true);
} }
$secured = null; $secured = null;
if (isset($this->params['_Token']) && !empty($this->params['_Token'])) { if (isset($this->params['_Token']) && !empty($this->params['_Token'])) {
$secured = $this->secure($this->fields); $secured = $this->secure($this->fields);
$this->fields = array(); $this->fields = array();
@ -1060,21 +1123,32 @@ class FormHelper extends AppHelper {
} elseif (is_array($div)) { } elseif (is_array($div)) {
$divOptions = array_merge(array('class' => 'submit', 'tag' => 'div'), $div); $divOptions = array_merge(array('class' => 'submit', 'tag' => 'div'), $div);
} }
$out = $secured; $out = $secured;
if (strpos($caption, '://') !== false) { if (strpos($caption, '://') !== false) {
$out .= $this->output(sprintf($this->Html->tags['submitimage'], $caption, $this->_parseAttributes($options, null, '', ' '))); $out .= $this->output(sprintf(
} elseif (preg_match('/\.(jpg|jpe|jpeg|gif|png)$/', $caption)) { $this->Html->tags['submitimage'],
$caption,
$this->_parseAttributes($options, null, '', ' ')
));
} elseif (preg_match('/\.(jpg|jpe|jpeg|gif|png|ico)$/', $caption)) {
if ($caption{0} !== '/') { if ($caption{0} !== '/') {
$url = $this->webroot(IMAGES_URL . $caption); $url = $this->webroot(IMAGES_URL . $caption);
} else { } else {
$caption = trim($caption, '/'); $caption = trim($caption, '/');
$url = $this->webroot($caption); $url = $this->webroot($caption);
} }
$out .= $this->output(sprintf($this->Html->tags['submitimage'], $url, $this->_parseAttributes($options, null, '', ' '))); $out .= $this->output(sprintf(
$this->Html->tags['submitimage'],
$url,
$this->_parseAttributes($options, null, '', ' ')
));
} else { } else {
$options['value'] = $caption; $options['value'] = $caption;
$out .= $this->output(sprintf($this->Html->tags['submit'], $this->_parseAttributes($options, null, '', ' '))); $out .= $this->output(sprintf(
$this->Html->tags['submit'],
$this->_parseAttributes($options, null, '', ' ')
));
} }
if (isset($divOptions)) { if (isset($divOptions)) {
@ -1111,7 +1185,9 @@ class FormHelper extends AppHelper {
$escapeOptions = $attributes['escape']; $escapeOptions = $attributes['escape'];
unset($attributes['escape']); unset($attributes['escape']);
} }
$attributes = $this->__initInputField($fieldName, $attributes); $attributes = $this->_initInputField($fieldName, array_merge(
$attributes, array('secure' => false)
));
if (is_string($options) && isset($this->__options[$options])) { if (is_string($options) && isset($this->__options[$options])) {
$options = $this->__generateOptions($options); $options = $this->__generateOptions($options);
@ -1731,5 +1807,28 @@ class FormHelper extends AppHelper {
$this->__options[$name] = $data; $this->__options[$name] = $data;
return $this->__options[$name]; return $this->__options[$name];
} }
/**
* Sets field defaults and adds field to form security input hash
*
* @param string $field
* @param array $options
* @return array
* @access protected
*/
function _initInputField($field, $options = array()) {
$secure = true;
if (isset($options['secure'])) {
$secure = $options['secure'];
unset($options['secure']);
}
$result = parent::_initInputField($field, $options);
if ($secure) {
$this->__secure();
}
return $result;
}
} }
?> ?>

View file

@ -663,7 +663,8 @@ class FormHelperTest extends CakeTestCase {
'_Model' => array( '_Model' => array(
0 => array('hidden' => 'value', 'valid' => '0'), 0 => array('hidden' => 'value', 'valid' => '0'),
1 => array('hidden' => 'value', 'valid' => '0')), 1 => array('hidden' => 'value', 'valid' => '0')),
'__Token' => array('key' => $key)); '__Token' => array('key' => $key)
);
$this->Form->params['_Token']['key'] = $key; $this->Form->params['_Token']['key'] = $key;
$result = $this->Form->secure($fields); $result = $this->Form->secure($fields);
@ -762,14 +763,18 @@ class FormHelperTest extends CakeTestCase {
'_Addresses' => array( '_Addresses' => array(
0 => array('id' => '123456'), 0 => array('id' => '123456'),
1 => array('id' => '654321')), 1 => array('id' => '654321')),
'__Token' => array('key' => $key)); '__Token' => array('key' => $key)
);
$fields = $this->__sortFields($fields); $fields = $this->__sortFields($fields);
$result = $this->Form->secure($this->Form->fields); $result = $this->Form->secure($this->Form->fields);
$expected = urlencode(Security::hash(serialize($fields) . Configure::read('Security.salt'))); $expected = urlencode(Security::hash(serialize($fields) . Configure::read('Security.salt')));
$expected = array( $expected = array(
'fieldset' => array('style' => 'display:none;'), 'fieldset' => array('style' => 'display:none;'),
'input' => array('type' => 'hidden', 'name' => 'data[__Token][fields]', 'value' => $expected, 'id' => 'preg:/TokenFields\d+/'), 'input' => array(
'type' => 'hidden', 'name' => 'data[__Token][fields]',
'value' => $expected, 'id' => 'preg:/TokenFields\d+/'
),
'/fieldset' '/fieldset'
); );
$this->assertTags($result, $expected); $this->assertTags($result, $expected);
@ -797,7 +802,8 @@ class FormHelperTest extends CakeTestCase {
$fields = array( $fields = array(
'Addresses' => array('title', 'last_name', 'city', 'phone'), 'Addresses' => array('title', 'last_name', 'city', 'phone'),
'_Addresses' => array('id' => '123456'), '_Addresses' => array('id' => '123456'),
'__Token' => array('key' => $key)); '__Token' => array('key' => $key)
);
$fields = $this->__sortFields($fields); $fields = $this->__sortFields($fields);
$result = $this->Form->secure($this->Form->fields); $result = $this->Form->secure($this->Form->fields);