Converting 4.7.x modules to 4.7.4
Drupal 4.7.4 saw the addition of a new default form field; form_token, to protect against cross site request forgeries. The token ensures that forms submitted to the site are actually requested first.
There are a few potential issues surrounding the form_token.
Relying on specific, known form fields
Modules that rely on only known form fields to be present, have to account for the new form_token field when saving data or providing specific theme functions.
The following example theme function will result in a form that will always fail validation as the new form_token field is not included in the form that is presented to the user.
<?php
// This form will fail validation
function your_form() {
$form['field'] = array(
'#type' => 'textfield',
'#title' => t('Example'),
'#default_value' => 'text',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return drupal_get_form('your_form_id', $form);
}
function theme_your_form_id($form) {
// Output fields in a specific order / with markup:
$output = form_render($form['field'])
$output .= form_render($form['form_id']);
$output .= form_render($form['submit']);
return $output;
}
?>The solution is easy; be sure to output the form_token field by adapting your custom theme function.
<?php
function theme_your_form_id($form) {
// Output fields in a specific order / with markup:
$output = form_render($form['field'])
$output .= form_render($form['form_id']);
$output .= form_render($form['submit']);
// form_token necessary to pass validation
$output .= form_render($form['form_token']);
return $output;
}
// Or better
function theme_your_form_id($form) {
// Output fields in a specific order / with markup:
$output = form_render($form['field'])
$output .= form_render($form['submit']);
// Render the remainder of the form, including hidden fields.
$output .= form_render($form);
return $output;
}
?>Accessing $_POST and taking actions at the form definition stage
Modules that do not use the forms api's definition => validation => submit pipeline will not be protected against cross site request forgeries.
As rewriting the form code may be daunting, the following code demonstrates an easier alternative; call drupal_valid_token() yourself:
<?php
// Not protected against cross site request forgeries!
function user_admin_access_add($mask = NULL, $type = NULL) {
if ($edit = $_POST['edit']) {
if (!$edit['mask']) {
form_set_error('mask', t('You must enter a mask.'));
}
else {
$aid = db_next_id('{access}_aid');
db_query("DO SOMETHING here");
drupal_set_message(t('The access rule has been added.'));
drupal_goto('admin/access/rules');
}
}
else {
$edit['mask'] = $mask;
$edit['type'] = $type;
}
$form = _user_admin_access_form($edit);
$form['submit'] = array('#type' => 'submit', '#value' => t('Add rule'));
return drupal_get_form('access_rule', $form);
}
?>The form_token needs to be validated before taking action. To do so call drupal_valid_token with the arguments; the token ($_POST['edit']['form_token']), $form_id, TRUE.
The function drupal_valid_token returns true when the token is valid. When the third argument is true (as in the example) it returns true for anonymous users. While perhaps not particularly relevant for this example, that is necessary because anonymous users may receive a cached token that will always fail.
<?php
function user_admin_access_add($mask = NULL, $type = NULL) {
if ($edit = $_POST['edit']) {
if (!$edit['mask']) {
form_set_error('mask', t('You must enter a mask.'));
}
else if (drupal_valid_token($_POST['edit']['form_token'], 'access_rule', TRUE)) {
$aid = db_next_id('{access}_aid');
db_query("DO SOMETHING here");
drupal_set_message(t('The access rule has been added.'));
drupal_goto('admin/access/rules');
}
else {
form_set_error('form_token',
t('Validation error, please try again. If this error persists, please contact the site administrator.'));
}
}
else {
$edit['mask'] = $mask;
$edit['type'] = $type;
}
$form = _user_admin_access_form($edit);
$form['submit'] = array('#type' => 'submit', '#value' => t('Add rule'));
return drupal_get_form('access_rule', $form);
}
?>If you want your module to remain compatible with Drupal 4.7.0 - 4.7.3 check whether drupal_valid_token exists before calling the function.
For example:
<?php
if (function_exists('drupal_valid_token')) {
// Check the token here
}
?>What if I don't want a token?
If you do not want a form token, set #token to false.
<?php
function your_form() {
$form['#token'] = FALSE;
//
// other fields
//
return drupal_get_form('your_form_id', $form);
}
?>