Preventing form_token from rendering in Drupal "GET" forms - drupal

Drupal inserts a form_token as a hidden field when it renders forms. The form_token is then checked on form submission to prevent cross-site request forgery attacks. The form data that is submitted is guaranteed to have come from the original form rendered by Drupal.
However, forms using the "GET" method shouldn't need this token. All it does is lengthen and uglify the resulting URL.
Is there any way of suppressing it?

Yes, there is a way, but use it consciously (see warning below):
If you create the form yourself, adding
$form['#token'] = FALSE;
to the form definition array should prevent a token from being generated in the first place.
If you are dealing with an existing form, you can bypass the token validation process by unsetting the '#token' element on hook_form_alter:
// Example for removal of token validation from login (NOTE: BAD IDEA!)
function yourmodule_form_alter(&$form, &$form_state, $form_id) {
if ($form_id == 'user_login_block') {
unset($form['#token']);
}
}
Warning: Given your question, I think there is a slight misconception concerning the difference (better, the lack of a difference) between GET and POST requests.
... on forms using the "GET" method
shouldn't need this token. All it does
is lengthen and uglify the resulting
URL.
This is wrong! GET and POST are just two different, but mostly equivalent methods of transmitting data from the client to the server. Since POST is better suited to transfer large amounts of data (or difficult formatted data), it is the established standard for submitting forms, but it is in no way safer/unsafer or more/less secure than GET requests. Both type of requests can be tampered with by malicious users in the same ways, hence both types should use the same protection mechanisms.
With a GET request, the token does exactly the same as with a POST request - it proves to the server that the submitted data comes from the same Browser on the same machine as the request he build the form for! So you should only remove it if you are sure that the request can not be misused via XSRF.

This worked for me. I had to unset all the form api elements and set the #token property to false. Notice the after_build function for unsetting the other properties.
function mymodule_form(&$form_state){
$form['name'] = array(
'#type' => 'textfield',
'#title' => 'name',
'#value' => 'name',
);
$form['#method'] = 'get';
$form['#action'] = url('someurl');
$form['submit'] = array('#type' => 'submit', '#value' => 'go');
$form['#token'] = false;
$form['#after_build'] = array('mymodule_unset_default_form_elements');
return $form;
}
function mymodule_unset_default_form_elements($form){
unset($form['#build_id'], $form['form_build_id'], $form['form_id']);
return $form;
}

The site I work on uses the Drupal 6 form API for custom search forms, so by removing the token and build id we were able to cache the results in memcache. Now we've moved to Acquia hosting, it's cached using Varnish.
To remove the form_token and form_build_id from your form and submit it as a GET request, use the following method:
<?php
function module_example_form($form_state, $form_id = NULL) {
// Form root settings.
$form = array();
// Set the submission callback for this form.
$form['#submit'][] = __FUNCTION__ . '_submit';
// Set the request method for this form to GET instead of the default
// of POST.
$form['#method'] = 'get';
// Remove unique form token so request can be cached. This is accompanied by
// code in hook_form_alter to ignore the token and remove the build_id.
$form['#token'] = FALSE;
// Submit button.
$form['go'] = array(
'#type' => 'submit',
'#value' => t('Go!'),
);
return $form;
}
/**
* Implements hook_form_alter().
*/
function module_form_alter(&$form, $form_state, $form_id) {
// Changes to the 'module_example_form' form.
if ($form_id == 'module_example_form') {
// Unset the hidden token field and form_build_id field.
unset($form['#token'], $form['form_build_id'], $form['#build_id']);
}
}
?>

I find that simply throwing away the CSRF token is not an option. We solved it using hook_theme_registry_alter() to overwrite the Drupal core theme_hidden() function so that the hidden form element 'form_token' is rendered as an <esi /> tag. The tag will cause Varnish to make a call to a PHP file which we allow to pass through the cache. This file will calculate the proper form token for the current user and will then output the HTML code for the hidden field. You can calculate this token without a Drupal bootstrap, but you will need a single DB query to fetch the *drupal_private_key* for your site, which is stored in the variable table.

Related

How to Test a Form with Multiple Submit

I have a form with 2 submit
// src/Form/FooType.php
$builder
->add('mainsubmit', SubmitType::class, [])
->add('extrasubmit', SubmitType::class, [])
In my controller, I do some different treatment depending of the submit pressed
// src/Controller/FooController.php
if ($form->isSubmitted() && $form->isValid()) {
if ($form->get('extrasubmit')->isClicked()) {
// do some extra stuff
}
When I click on the extra button, I can see it the symfony Profiler in the request POST parameters "extrasubmit" => "".
Everything works fine.
I'm doing functional tests with the crawler.
Without trying to submit with the extra submit, it works fine, so we can assume my test doesn't have a typo.
How can I simulate the click on the extra submit ?
First Try:
$form = $crawler->filter('form')->form();
// [...]
$form['my_form_name[extrasubmit]'] = true;
$httpClient->submit($form);
// => InvalidArgumentException: Unreachable field "extrasubmit"
Second Try:
$form->get('my_form_name[extrasubmit]')->setValue("");
// => InvalidArgumentException: Unreachable field "extrasubmit"
Like often when have trouble with the DomCrawler, it is much better to fo throught $form->getPhpValues() which returns an array of values.
Then you just have to mimic the values given in the request
$values = $form->getPhpValues();
$values['my_form_name']['extrasubmit'] = '';
$httpClient->request(
$form->getMethod(),
$form->getUri(),
$values
);
According to code you can set the button via the setNode($domnode) method on the form. Sadly there is no function to find the nodes on the form object itself (since the node doesn't have to be a descendant of the form, that might not even help much).
So I guess something similar to
$form->setNode($crawler->filter('#extrasubmitbutton-id')[0]);
would set the submit button...

Render controller and get form errors from child

I have a template where I render a widget which contains a form:
{{ render(controller('ApplicationDemoBundle:Demo:newWidget', {'demo' : entity })) }}
The newWidgetAction calls a createAction:
public function createAction(Request $request)
{
$entity = new Demo();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('demo_show', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView(),
)
// Something like this would be awesome with passing the form containing errors with
// return $this->redirect($this->getRequest()->headers->get('referer'));
}
Imagine the submitted form (user acts in show theme) produces an error. This would make a return to the newWidget template which does not display the full layout.
My question is now: What is the right way to pass the errors from the child controller (newWidget) to the main template (show)?, without modifying the showActions function parameter to pass the formerrors over there.
There is a similar thread to this question: Symfony2 render and form
In this case where sessions used but I'm more than curious if this is the way to go.
The problem is that each fragment (a sub-controller) uses a virtual request. This is to protect the original request from modification by possibly unexpected forwards, and a fragment is essentially a forward taking place during a rendering stage.
It is possible to access the top level request using:
$this->container->get('request'); then handing the request with the form in the fragment, but this may get very confusing very quickly if you are using multiple forms per page.
My strategy is to follow a convention that limits the number of validated form on a page to just one. Any other forms don't require validation, or it is otherwise impossible for a form to be submitted incorrectly (hacked forms would throw server side exceptions, but the user should only see those if they are being naughty).
Try to structure your template inheritance to accommodate navigation to forms, while always showing most of the same layouts and data. You can do this by expanding the use of fragments which gives the bonus of separating your display logic.

Drupal login error message displayed without using form_set_error()

I created a module that adds a field to the user login block form to display errors. I want to display the error in the new field instead of using form_set_error(). I am able to see the warning field in the login block. But when I submit with an error it does not display the error.
Code is as follows. I do not understand how to refresh the value of form once it gets an error.
function usermoved_form_user_login_block_alter(&$form, &$form_state) {
$form['warning'] = array(
'#value' => t('oops'),
'#weight' => 11
);
$form['#submit'][] = 'usermoved_form_submit_code';
}
function usermoved_form_submit_code($form, &$form_state) {
global $user;
if (!$user->uid) {
$form['warning']['value']= "changed to someting";
}
}
Your code is wrong: Instead of using $form['warning']['value'], it should use $form['warning']['#value']. Even doing so, the message (which should be passed to t()) is not shown to the users because $form is not passed by reference, and it is not the value returned from the form submission handler, which is not expected to return any value.
Form submission handlers are then never called, when a form validation handler raises an error. If you are checking the user ID to see if the user has not been logged-in, then you cannot do it in a form submission handler, as the user not being logged-in means there have been errors during the validation phase, and the form submission handlers are not invoked.
What you can use is a form validation handler.
function usermoved_form_user_login_block_alter(&$form, &$form_state) {
$message = (empty($_SESSION['usermoved_form_user_login_block_alter']) ? t('Initial message') : $_SESSION['usermoved_form_user_login_block_alter']);
$form['warning'] = array(
'#value' => $message,
'#weight' => 11
);
$form['#validate'][] = 'usermoved_form_validate_code';
}
function usermoved_form_validate_code($form, &$form_state) {
if (empty($form_state['uid'])) {
$_SESSION['usermoved_form_user_login_block_alter'] = t('The error message');
}
else {
unset($_SESSION['usermoved_form_user_login_block_alter']);
}
}
$form_state['uid'] is a value set from user_login_authenticate_validate(), the second validation handler invoked by Drupal. When it is not set, it means there have been some errors before Drupal tried to authenticate the user, or the user didn't authenticate. The first case can happen when the user is blocked (user_login_name_validate() verifies that), or the user tried too much times to authenticate (see user_login_authenticate_validate() which uses flood_is_allowed() to verify the user didn't try too much times to authenticate); the latter case can happen when the user didn't enter the right password. (See user_login_final_validate().)
As side notes:
I used t('Initial message') as default message, but that could be replaced with '', if you don't have any message to show to the users before they log in.
The user login block is not shown to already logged-in users. $user->uid (where $user is the global variable) is always 0, when the login block is shown, and when logging-in failed; checking the value of $user->uid while showing the login block doesn't make sense.

Bypass Node Delete Confirm Form in Drupal

Looking for the best way to allow users to delete nodes on a site without the need to use a confirm form. I have tried using a form_alter to direct people to a custom submit function, without success.
Anyone ever tried this?
Assuming drupal 7, the node/%/delete menu entry is wired directly to the node_delete_confirm form. You can modify it with with a hook_menu_alter, and change the function from drupal_get_form to a page callback of your own design that will just delete the node.
Example:
In your module file you'd need:
function mymodule_menu_alter(&$items) {
$items['node/%node/delete']['page callback'] = 'my_node_delete_function';
$items['node/%node/delete']['page arguments'] = array(1);
$items['node/%node/delete']['module'] = 'mymodule';
$items['node/%node/delete']['file'] = 'mymodule.pages.inc';
}
And in your mymodule.pages.inc file you'd need:
function my_node_delete_function($node) {
// Taken from node modules node_delete_confirm submit handler
node_delete($node->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)));
// Do a drupal goto here to preserver the 'destination' parameter
drupal_goto();
}

Hooking user registration in Drupal

I have a site where some users will be registered by our staff, and won't have emails associated with them. I would like to keep the email field a required field, so I devised a random email generator.
function generateRandomEmail() {
$email = 'noemail'. rand(0,1000000) . '#noemail.com';
return $email;
}
So, I attached that to the user register form alter, and it worked nicely, effectively generating an email for these users.
However, in the process, all the other fields associated with the main account section (password, username, notify, etc.) disappeared. My question, is there a quick way to populate the rest of the fields that I don't want to alter? I've used drupal_render($form); in a tpl.php, but it didn't work in the form alter.
Here is where I'm altering the form:
function accountselect_user($op, &$edit, &$account, $category) {
if ($op == 'register') {
$fields['account']['mail'] = array(
'#type' => 'textfield',
'#default_value' => generateRandomEmail(),
);
You are currently using hook_user for your manipulation, but that is the wrong place. On $op 'registration', you can return additional fields you want to inject to the registration process, but not alter existing fields. Use hook_form_alter() or hook_form_FORM_ID_alter() for that, e.g.:
function yourModule_form_user_register_alter(&$form, &$form_state) {
$form['account']['mail']['#default_value'] = generateRandomEmail();
}
You probably want to add a check that the request is in fact coming from the staff, since the above code would prepopulate the email field for the normal registration form also!
Also, please do not generate 'random' mail addresses using existing third party domains (like 'nomail.com'). Use the reserved 'example.com', or better yet, one that you own yourself!

Resources