Problem/Motivation

I've found there is a problem with one-time login link generated during registration or password reset request, that leads to user/%/edit form. If there is a file field and user uploads an file by clicking "Upload" button form is submitted through AJAX and one-time login expires. So as soon as user fills new password and submits full form an errors occurs "Your current password is missing or incorrect; it's required to change the Password.".

Steps to reproduce:

  1. Set "Who can register accounts" to "Visitors, but administrator approval is required" on Account Settings page
  2. Check "Require e-mail verification when a visitor creates an account." on Account Settings page
  3. Add a file or image field to account entity
  4. Create a new user and activate it or request a password reset
  5. Make sure you're logged out, then use one-time login link received in email
  6. Select an file/image and click "Upload"
  7. Fill new password and submit form

Proposed resolution

It's only a workaround but unchecking "Require e-mail verification when a visitor creates an account." on Account Settings page solves the problem for registration process. It won't help with Reset Password request.

Remaining tasks

I think it needs reviewing cause this completly breaks form after uploading a file. I'll try to fix this as soon as I have time.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug
Unfrozen changes Unfrozen because it is a bug that exists in Drupal 7 as well

Comments

trawekp-1’s picture

Status: Active » Needs review
StatusFileSize
new2.21 KB

I think I've found a solution. The real problem lies in handling Account Form rebuild. The form needs a 'pass-reset-token' GET variable to skip Current Password validation so every AJAX rebuild breaks this.

I have figured out that to fix it we need to store pass-reset-token as hidden value inside the form and then retrieve it when $_GET['pass-reset-token'] is not set.

I know that all the submitted values should be fetched from $form_state['values'] but it is necessary to get stored token from $form_state['input'] because AJAX rebuilds are usually called by buttons with #limit_validation_errors.

Anyway I am not sure if this is safe, what I know is that token is public so storing it as hidden value is not a problem.

Here's the patch:

Johnny vd Laar’s picture

Status: Needs review » Reviewed & tested by the community

This fix seems to work. Very odd that nobody found this problem before as it seems like a common use case.

Johnny vd Laar’s picture

If you don't want to patch your drupal core then you can do this in a form alter:

/**
 * Implements hook_form_alter() for user_profile_form.
 */
function custommodule_form_user_profile_form_alter(&$form, $form_state, $form_id) {
  global $user;
  $account = $form['#user'];
  // Fix the ajax call (http://drupal.org/node/1858486).
  // Code below is from user.module user_account_form().
  //
  // When Form is rebuild using AJAX, GET variables won't be set
  // so check if form had been built using pass reset token
  $pass_reset_token = isset($_GET['pass-reset-token']) ? $_GET['pass-reset-token'] : (isset($form_state['input']['pass_reset_token']) ? $form_state['input']['pass_reset_token'] : FALSE);
  // To skip the current password field, the user must have logged in via a
  // one-time link and have the token in the URL (or stored by form).
  $pass_reset = isset($_SESSION['pass_reset_' . $account->uid]) && $pass_reset_token && ($pass_reset_token == $_SESSION['pass_reset_' . $account->uid]);

  $protected_values = array();
  $current_pass_description = '';
  // The user may only change their own password without their current
  // password if they logged in via a one-time login link.
  if (!$pass_reset) {
    $protected_values['mail'] = $form['account']['mail']['#title'];
    $protected_values['pass'] = t('Password');
    $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
    $current_pass_description = t('Enter your current password to change the %mail or %pass. !request_new.', array('%mail' => $protected_values['mail'], '%pass' => $protected_values['pass'], '!request_new' => $request_new));
  }
  else {
    // Store pass reset token in form as hidden value
    $form['pass_reset_token'] = array(
      '#type' => 'hidden',
      '#value' => $pass_reset_token,
    );
  }

  // The user must enter their current password to change to a new one.
  if ($user->uid == $account->uid) {
    $form['account']['current_pass_required_values']['#value'] = $protected_values;
    $form['account']['current_pass']['#access'] = !empty($protected_values);
    $form['account']['current_pass']['#description'] = $current_pass_description;
  }
}
David_Rothstein’s picture

Version: 7.x-dev » 8.x-dev
Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs backport to D7

This is reproducible in Drupal 8 also.

Maybe it would be better to store the token in $form_state directly the first time it's found, rather than passing it back and forth to the browser?

David_Rothstein’s picture

Status: Needs work » Needs review
StatusFileSize
new1.66 KB

Something like the attached, maybe... Seems to work based on some quick testing.

trawekp-1’s picture

Seems fine to me

rob230’s picture

It is surprising that this bug with Drupal 7 core has not been reported more. I suppose not many people use AJAX on the user profile / registration forms, but it is completely site breaking for one of my sites. Nobody can register at all.

I fixed this simply by using the nocurrent_pass module but really I think this should be fixed in 7 as well as 8.

David_Rothstein’s picture

Yeah, I'm a bit surprised too.

Once the above patch is reviewed and committed to Drupal 8, it can be backported to Drupal 7 and fixed there also.

eldargp’s picture

Version: 8.x-dev » 7.20
Priority: Normal » Critical

Hi there, I am experiencing this issue with my registration form since I am using an extra image field with upload Ajax button. Are any of this two patches working with drupal core 7.20? tried #1 but failed when submitting form SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry
thanks

marcingy’s picture

Version: 7.20 » 8.x-dev
Priority: Critical » Normal

Issues are fixed in d8 first

super_romeo’s picture

and what about D7 commit?

marcingy’s picture

It gets committed once it is fixed in D8.

mrharolda’s picture

StatusFileSize
new2.18 KB

Here's a re-rolled patch for D7 that fixes some code and comment style.

jhedstrom’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll
idebr’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
StatusFileSize
new1.59 KB

Status: Needs review » Needs work

The last submitted patch, 15: ajax_password_reset-1858486-15.patch, failed testing.

idebr’s picture

Status: Needs work » Needs review
StatusFileSize
new1.67 KB
new1.71 KB
  • Updated the assignment to $form_state to use the FormStateInterface.
  • Replaced the $pass_reset variable with the latest version in HEAD
jhedstrom’s picture

Issue summary: View changes

Would it be possible to add a test here?

The fix itself looks solid to me, although I haven't confirmed the fix manually.

I've updated the issue summary to include a beta phase evaluation.

jhedstrom’s picture

Issue tags: +Needs tests
idebr’s picture

Issue tags: -Needs tests
StatusFileSize
new1.49 KB
new3.01 KB
new3.04 KB

I added a test and updated the code slightly.

The last submitted patch, 20: 1858486-20.fail_.patch, failed testing.

jhedstrom’s picture

Status: Needs review » Reviewed & tested by the community

Thanks for the test! I think this is good to go.

alexpott’s picture

Version: 8.0.x-dev » 7.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

Committed 29f441d and pushed to 8.0.x. Thanks!

Thanks for adding the beta evaluation to the issue summary.

  • alexpott committed 29f441d on 8.0.x
    Issue #1858486 by idebr, David_Rothstein, trawekp, MrHaroldA: Ajax call...
idebr’s picture

Status: Patch (to be ported) » Needs review
StatusFileSize
new2.38 KB
new3.94 KB

Ported the patch to D7.

The last submitted patch, 25: 1858486-25.fail_.patch, failed testing.

leahtard’s picture

I ran into this same issue and thankfully found this patch. While this patch got rid of the ugly error for me, it didn't completely resolve my issue. On my user profile form, I have a multi value text field that accepts unlimited values. This initially presents itself as one text field with an "Add Another Item" button. Now, with this patch in place, when I click the "Add Another Item" no second text field appears -- I get a 500 Internal Server Error in my console log.

If additional information is needed from my end, please request :)

Cheers, Leah

l0ke’s picture

Thank's for backport to D7.
Didn't test it with multi value text field but worked fine for "Address Field".

+1

  • alexpott committed 29f441d on 8.1.x
    Issue #1858486 by idebr, David_Rothstein, trawekp, MrHaroldA: Ajax call...

  • alexpott committed 29f441d on 8.3.x
    Issue #1858486 by idebr, David_Rothstein, trawekp, MrHaroldA: Ajax call...

  • alexpott committed 29f441d on 8.3.x
    Issue #1858486 by idebr, David_Rothstein, trawekp, MrHaroldA: Ajax call...
greatmatter’s picture

I'm excited to see this actually get released.

alesr’s picture

Status: Needs review » Reviewed & tested by the community

I reviewed the patch in #25.
It basically does one simple thing.
It stores the check

isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]);

in $form_state['user_pass_reset'] instead of using $pass_reset variable and by doing that it prevents the fail after form submission which is caused by any AJAX related form submit like user photo upload etc.

RTBC and would be nice to get it in the next D7 release too.

stefan.r’s picture

Issue tags: -Needs backport to D7
stefan.r’s picture

Assigned: Unassigned » David_Rothstein
Issue tags: +Needs issue summary update, +Drupal bugfix target

At first sight this looks good -- the D7 and D8 code are identical and the tests look OK as well.

David_Rothstein’s picture

Assigned: David_Rothstein » Unassigned

The tests look good to me also. The original patch was mine, so ideally someone else would commit it.

However, has anyone looked into #27? We need to make sure there isn't a regression there.

One other small thing (could be fixed on commit):

+    // Make sure the ajax request from uploading a file does not invalidate the
+    // reset token.

Should be "Ajax".

fabianx’s picture

Status: Reviewed & tested by the community » Needs review

Thanks for all the work on this, but given #27 I think this needs more review (and time).

Also while I understand that it can be a nuisance and is for sure a bug, it is rightly only normal priority as it is not that likely that a user first uploads a new file before proceeding to reset their password.

Obviously for password confirmation, this is way more likely.

Therefore setting back to needs review and not getting this in for 7.51 (but hopefully for 7.52).

--

Actionable item:

Try to re-create #27 on vanilla Drupal.

mxr576’s picture

Status: Needs review » Reviewed & tested by the community

We run into this issue therefore having address field (Company select) on profile forms.

I've tried to reproduce #27 based on the infos we had, but everything worked fine.

weri’s picture

We had this problem with D7 as we attached a form (field_attach_form) with an address field to the user form. The patch #25 solved the problem and we noticed no other problems with this patch.

  • alexpott committed 29f441d on 8.4.x
    Issue #1858486 by idebr, David_Rothstein, trawekp, MrHaroldA: Ajax call...

  • alexpott committed 29f441d on 8.4.x
    Issue #1858486 by idebr, David_Rothstein, trawekp, MrHaroldA: Ajax call...
stefan.r’s picture

Assigned: Unassigned » fabianx
joseph.olstad’s picture

D7 solution is identical, ready.

Fabianx can this get into this month's release?

David_Rothstein’s picture

Seems like with the additional testing that happened above, this is probably ready to go in.

#27 really doesn't sound related, and actually reading that again I'm not sure if it's even implying that this patch caused the problem; maybe it was a preexisting problem on that site that this patch just didn't happen to solve?

joseph.olstad’s picture

I'd say this is ready to go, all reports after #27 are saying this is good.

and the fact that it's already in D8 verbatim.

here's the patch again from 25, just for running the tests.

stefan.r’s picture

Assigned: fabianx » Unassigned
Issue tags: +Pending Drupal 7 commit

Looks good.

  • stefan.r committed 3047f78 on 7.x
    Issue #1858486 by idebr, David_Rothstein, trawekp, joseph.olstad,...
stefan.r’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: -Pending Drupal 7 commit

Committed and pushed to 7.x, thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.