It is not necessary to control if the user has access to a form in its submission handler.
Drupal protects its forms from form forgery, and verifies the values contained in the submitted form. The first control is done in drupal_validate_form(), where the form token (which is automatically assigned to every form built through the form API) is verified; if the value of $form_state['values']['form_token']
is different from the value of drupal_hmac_base64($form['#token'], session_id() . drupal_get_private_key() . drupal_get_hash_salt())
, the form is rejected.
The submitted values are also validated in _form_validate(), where the length of the entered value is checked against the maximum length reported for the form field, and the value entered for a "checkboxes," or a "tableselect" form field is checked against its acceptable values. In both the cases, the value used to verify the submitted values is not taken from the submitted values.
To be more sure, you should not pass sensible data, such as the ID of the node being edited as hidden field; it is preferable to set it inside the $form
variable, using an array index that starts with "#" and that is unique for that form (such as "#nid"), or use a "value" form field; in that way, the value will not be passed to the HTML form rendered in the browser, and a forged form would not be able to alter that value to effectuate an operation on an entity for which the user doesn't have access. For example, this is the code used for a confirmation form that allows to delete a node:
function node_delete_confirm($form, &$form_state, $node) {
$form['#node'] = $node;
// Always provide entity id in the same form key as in the entity edit form.
$form['nid'] = array(
'#type' => 'value',
'#value' => $node->nid,
);
return confirm_form($form,
t('Are you sure you want to delete %title?', array('%title' => $node->title)),
'node/' . $node->nid,
t('This action cannot be undone.'),
t('Delete'),
t('Cancel')
);
}
Both $form['#node']
and $form['nid']
are not contained in the form passed to the browser.
The submission handler for that form builder is the following one:
function node_delete_confirm_submit($form, &$form_state) {
if ($form_state['values']['confirm']) {
$node = node_load($form_state['values']['nid']);
node_delete($form_state['values']['nid']);
watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title));
drupal_set_message(t('@type %title has been deleted.', array('@type' => node_type_get_name($node), '%title' => $node->title)));
}
$form_state['redirect'] = '<front>';
}
The value of $form
passed to the form handlers is not passed to the browser, but it is retrieved in the Drupal cache from drupal_build_form(), which calls form_get_cache() when the form has been submitted. The documentation of drupal_build_form()
contains the following sentence:
The form may also be retrieved from the cache if the form was built in a previous page-load. The form is then passed on for processing, validation and submission if there is proper input.
If you look at Drupal code, you will notice that none of the core modules check if the user has the permission of accessing the form in the form submission; the only code you will find is the code that checks if an error message must be output, in the case that error message should be visible to a group of users that is smaller than the group of users that has access to the form.
As side note, Drupal.org has a security team that investigates on security issues reported for Drupal core code, and any third-party modules hosted on Drupal.org; for third-party modules, they don't investigate on reports for non official releases of the modules (e.g. alpha, beta, and development snapshots). If you find a security issue in Drupal, you should report it in the project queue for Drupal core.