Growing Venture Solutions - GVS - captcha http://growingventuresolutions.com/taxonomy/term/68/0 en Submitting Protected Forms Programatically with Safe User Impersonation http://growingventuresolutions.com/blog/submitting-protected-forms-programatically-safe-user-impersonation <p>When a form protected by spam prevention measures such as captcha or Mollom is submitted with drupal_execute, validation can fail unless the spam protection is properly suppressed.</p> <p>This blog post describes the background and solution to a bug that previously existed in the Signup Integration for Ubercart module (uc_signup), and explains the techniques used to fix the bug. It is written with developers and aspiring developers in mind, though other people interested in how Drupal works might also find it interesting.</p> <p><a href="http://api.drupal.org/api/function/drupal_execute/6">drupal_execute</a> is a function often used in data imports that allows a developer to take a collection of form values and submit them programatically.<br /> A main reason to use drupal_execute over another technique such as <a href="http://api.drupal.org/api/function/user_save/6">user_save()</a> is that with drupal_execute, Drupal calls the validation and submission functions for the form.</p> <h3>The Context: How uc_signup Uses drupal_execute</h3> <p>In the <a href="http://drupal.org/project/uc_signup">Signup integration for Ubercart</a> module, we sometimes create a new user account and populate the user's profile with data that was submitted on a form separate from the core user profile form. In earlier versions of uc_signup, we created the new account with user_save, however this <a href="http://drupal.org/node/547242">allowed crafty users to leave required fields blank by skipping the form and proceeding to checkout</a>, so we switched to drupal_execute which ensures that the form's validation gets executed.</p> <h3>The Problem</h3> <p>The drupal_execute function is relatively easy to use -- just pass in the form_id and form values you'd like to submit to the form.</p> <p>However, we soon got a <a href="http://drupal.org/node/585760">bug report</a> with one user reporting a validation error at the time that the new user account is saved, with the <a href="http://drupal.org/project/captcha">captcha module</a> enabled for the user registration form.</p> <p><a href="http://en.wikipedia.org/wiki/Captcha">Captchas</a> often take the shape of "those squiggly letters" used to determine whether you are a human being or a computer program that submits spam to websites.</p> <p>Uc_signup can collect profile information for multiple users on a single "attendee contact information" form that is integrated into the Ubercart checkout process.</p> <p><img src="http://img.skitch.com/20090929-pjhwt3sb57yyna8cnnnu3j7rs.png" title="uc_signup attendee contact info form" /><br /> Uc_signup's Attendee Contact Information" form.</p> <p>By design, this form has a unique form_id, so it isn't protected by captchas that protect the user registration form. The required fields on the attendee information form correspond to those on the user registration form -- except that on the attendee information form, there is no captcha. When uc_signup takes user-entered data from this form and submits it into the user registration form, validation on the user registration form fails because this form has a captcha, which was never presented to the user and is therefore not filled in.</p> <h3>Finding a Solution</h3> <p>So, we needed a way to bypass the captcha protection.<br /> Should we unset the form validation function from the captcha element? Should we remove the captcha element from the form altogether?</p> <p>The simplest and least error-prone solution turns out to be to prevent the captcha from ever being added to the user registration form for this specific situation.</p> <p>Both internal spam prevention services (such as captcha and spam.module) and external ones (such as <a href="http://mollom.com/">Mollom</a> and <a href="http://akismet.com/">Akismet</a>) use Drupal's user permissions system to allow certain users to bypass the protection that they add to forms.</p> <p>drupal_execute submits forms with the permissions of whichever user is logged in (or an anonymous user). In most cases, people who are using the attendee information form will not have permission to bypass spam protection site-wide. The solution to our captcha validation problem is to have our module submit the form as if it were a privileged user. That way, the captcha will never be added to the form.</p> <h3>User Impersonation: Not just for Halloween.</h3> <p><img src="http://growingventuresolutions.com/gvsfiles/user_impersonation_small.jpg" /></p> <p>User impersonation is a technique that developers can use to allow a particular section of code to be run as a particular user. In Drupal, the user with UID 1 has all permissions available on a site. In uc_signup, we impersonate User 1 before performing the drupal_execute so that captcha waves protection for the form.</p> <h3>Practicing Safe Impersonation</h3> <p>It's important to follow best practices when performing user impersonation. Not doing so can allow users on your site to gain control over the account being used for the impersonation, resulting in a serious security vulnerability.</p> <p>After calling <code> global $user;</code>, setting the $user variable equal to a different user account will cause the currently logged in user to switch to that account. That is why it is a best practice to use another variable, such as $account, when working with user accounts <strong>without</strong> the intention of impersonating users.</p> <p>You must set <a href="http://drupal.org/node/218104">session_save_session(FALSE)</a>; before switching to the impersonated user. The reason for this is that if your action fails or interrupts the request with a <a href="http://api.drupal.org/api/function/drupal_goto/6">drupal_goto</a> and session saving is enabled, the user will then be logged in as user 1, with all privileges on your site. By setting this value to FALSE and then back to TRUE, we make sure that the impersonation only exists when we're performing the required action, and does not persist if the action fails.</p> <p><a href="http://www.flickr.com/photos/faeryboots/2874160660"><img src="http://growingventuresolutions.com/gvsfiles/impersonation_wrong.png" /></a></p> <p>The code snippet below is a more heavily commented version of the code used in uc_signup. This code snippet impersonates User 1, submits the user registration form with the previously entered values, and then switches the session back to the user who was previously logged in. Note that we have three variables that refer to user accounts:</p> <p><code>global $user</code> - The account of the currently logged in user.<br /> <code>$temp_user</code> - The variable used to store the current user's account while we temporarily switch the session to the privileged user.<br /> <code>$account</code> - The account created as a result of submitting the user registration form.</p> <p>This code actually appears inside of a <code>foreach</code> loop and some conditional logic, so that it if the attendee contact information form collects information for multiple new users, the user registration form is submitted once for each new account.</p> <p>To get the full context of this snippet, you may wish to <a href="http://cvs.drupal.org/viewvc.py/drupal/contributions/modules/uc_signup/uc_signup.module?view=markup">view the full contents of uc_signup.module</a>.</p> <p><div class="codeblock"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /></span><span style="color: #FF8000">//Load the global $user object that contains the account of the currently&nbsp;&nbsp; logged in user.<br /></span><span style="color: #007700">global </span><span style="color: #0000BB">$user</span><span style="color: #007700">;<br /></span><span style="color: #FF8000">//Preserve this account in the $temp_user variable so that we can switch back to it after impersonating the privileged user.<br /></span><span style="color: #0000BB">$temp_user </span><span style="color: #007700">= </span><span style="color: #0000BB">$user</span><span style="color: #007700">;<br /></span><span style="color: #FF8000">//We must set this to FALSE in case the operation on the following lines fails.<br /></span><span style="color: #0000BB">session_save_session</span><span style="color: #007700">(</span><span style="color: #0000BB">FALSE</span><span style="color: #007700">);<br /></span><span style="color: #FF8000">//Switch the currently logged in user to user 1.<br /></span><span style="color: #0000BB">$user </span><span style="color: #007700">= </span><span style="color: #0000BB">user_load</span><span style="color: #007700">(</span><span style="color: #0000BB">1</span><span style="color: #007700">);<br /></span><span style="color: #FF8000">//Submit the user registration form.<br /></span><span style="color: #0000BB">drupal_execute</span><span style="color: #007700">(</span><span style="color: #DD0000">'user_register'</span><span style="color: #007700">, </span><span style="color: #0000BB">$form_state</span><span style="color: #007700">);<br /></span><span style="color: #FF8000">//Switch back to the account we saved in the $temp_user variable.<br /></span><span style="color: #0000BB">$user </span><span style="color: #007700">= </span><span style="color: #0000BB">$temp_user</span><span style="color: #007700">;<br /></span><span style="color: #FF8000">//Restore session saving.<br /></span><span style="color: #0000BB">session_save_session</span><span style="color: #007700">(</span><span style="color: #0000BB">TRUE</span><span style="color: #007700">);<br /></span><span style="color: #FF8000">//Populate the $account variable with the account created as a result of submitting the user_register form. We later go on to sign up this user to one or more events that are being purchased.<br /></span><span style="color: #0000BB">$account </span><span style="color: #007700">= </span><span style="color: #0000BB">$form_state</span><span style="color: #007700">[</span><span style="color: #DD0000">'user'</span><span style="color: #007700">];<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div></p> <div class="field field-type-nodereference field-field-related-project"> <div class="field-label">Related Project:&nbsp;</div> <div class="field-items"> <div class="field-item odd"> <a href="/portfolio/aussie-australian-united-states-services-education">AUSSIE (Australian United States Services in Education)</a> </div> </div> </div> http://growingventuresolutions.com/blog/submitting-protected-forms-programatically-safe-user-impersonation#comments Planet Drupal captcha mollom security uc_signup user impersonation Wed, 30 Sep 2009 17:45:02 +0000 Ezra 683 at http://growingventuresolutions.com