2

Every so often, my users get logged out, and are unable to log back in without doing the "forgot your password" thing (which works fine). I've put some watchdog statements into core to try to see what's going on, and, while I'm hardly expert in the ins and outs of the Drupal login process, I can tell that, in _password_crypt(), down in password.inc, things make it all the way through to the comparison of the hashed entered and stored passwords, where it fails. This is perhaps because, in checking the database, the users' values for PASS have changed relative to what I saw there when the users were able to log in successfully.

Some other details:

  • Yes, I'm up to date on patches (Drupal 7.15, and up-to-date versions of contrib)

  • System and browser caches have been cleared multiple times.

  • The site is under apache-level password control, and I've confirmed from the log files that nobody from the outside world is sneaking in. So there's no funny business there.

  • There don't seem to be any exact time aspects to this, but I can typically run, stay logged-in for a day or so before getting logged out.

  • I'm running the Twitter Bootstrap theme, and I am also using the Less module for CSS generation. Both seem to work fine.

  • I've experimented with setting $base_url and $cookie_domain in the settings file; neither seem to have much effect on the problem, either set or unset.

  • Because of the watchdog statements I added (see above), I've been able to see that _password_crypt() is running at times when I wouldn't have expected it to be running (like, not in response to a user's logging in). Checking user->pass in the database, I found that at least one of the users' entries was no longer what it was after the user set the password after getting logged in through the login link. When this happened, that user was still logged into a browser, and he was still able to function as that logged-in user. However, once he logged out, he couldn't log back in without requesting a new password. More system tracing is underway.

This is seriously killing me; I've worked with Drupal a lot, and have never seen anything like this. Can anybody offer some thoughts on this?

  • What are those custom modules doing? Which hooks are they using? Try using the site with those modules disabled and see if you get the same issue. – Aram Boyajyan Aug 22 '12 at 9:12
  • Clearly, there is code that is saving a new password in the database. _password_crypt() is used to verify the password entered during login is the correct one; the function generates the password hash, which is what Drupal stores in the database. – kiamlaluno Aug 22 '12 at 11:49
1

OK, I think I've got this figured out:

As part of the siteI need to be able to programmatically set user field values -- what used to be called "profile values" in Drupal 6. I looked around for some ways of doing that, and found a possible solution at http://drupal.org/node/1047600#comment-4094988, which recommends something like this:

$existing = user_load($uid);
$edit = (array) $existing; 
$edit['field_somefield']['und'][0]['value'] = 'blah';
user_save($existing, $edit);

The instrumentation I did showed user_save was the guy calling _password_crypt(). My belief is that, somewhere along the way (either the method itself or the way I was using it), my call to user_save() was trashing/regenerating user->pass, leaving me in my unfortunate state.

So: I'm now doing the field-saving stuff in a way that doesn't involve user_save (some day I'll figure out this new entities stuff, but, in the meantime, I'm just bashing values into the database) and, so far, I'm staying logged in. Hopefully this will solve the problem; thanks to all for your attention.

  • You could have achieved the same by explicity unsettings the password from the edit-array unset($edit['pass']). But indeed, this code uses the hashed password as input for a new password, double hashing it. – Neograph734 Dec 16 '14 at 15:50
1

TL;DR

The solution is to unset the pass array element. Added the necessary extra line to your code:

$existing = user_load($uid);
$edit = (array) $existing; 
unset($edit['pass']); // added line
$edit['field_somefield']['und'][0]['value'] = 'blah';
user_save($existing, $edit);

Explanation / Justification

The Drupal(7) API for user_save() (function user_save (Drupal 7.x)) starts off with the following code :

if (isset($edit['pass']) && strlen(trim($edit['pass'])) > 0) {
  // Allow alternate password hashing schemes.
  require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
  $edit['pass'] = user_hash_password(trim($edit['pass']));
  // Abort if the hashing failed and returned FALSE.
  if (!$edit['pass']) {
    return FALSE;
  }
}
else {
  // Avoid overwriting an existing password with a blank password.
  unset($edit['pass']);
}

This part show clearly what the necessary solution is : if (isset($edit['pass']) (the rest of the code is interesting to see at least once, fyi :).

Neograph734 had the correct answer in his comment, but because of the conflicting answers posted I felt this needed a decent explanation and justification.

Extra info

The reason why the answer by aaronbauman is not correct is that the original object property serves mainly as comparison to see what fields have been updated. If original is not set by the user in his custom code, the user_save() function will set this value :

// Load the stored entity, if any.
if (!empty($account->uid) && !isset($account->original)) {
  $account->original = entity_load_unchanged('user', $account->uid);
}

If you want to change the password, the new value will be hashed at the start of the user_save() function with the code shown above. The new hash will be different from the hash contained in the original, triggering the following code later in the same function :

  // If the password changed, delete all open sessions and recreate
  // the current one.
  if ($account->pass != $account->original->pass) {
    drupal_session_destroy_uid($account->uid);
    if ($account->uid == $GLOBALS['user']->uid) {
      drupal_session_regenerate();
    }
  }
0

Is this happening when viewing a particular page? If so, check any function using $user for storing any data other than the global $user.

0

See lines 546 - 551 of user.module:

// If the password changed, delete all open sessions and recreate
// the current one.
if ($account->pass != $account->original->pass) {
  drupal_session_destroy_uid($account->uid);
  if ($account->uid == $GLOBALS['user']->uid) {
    drupal_session_regenerate();
  }
}

So, setting $account->original before calling user_save() should fix this for you:

$existing = user_load($uid);
// LINE ADDED:
$existing->original = $existing;
$edit = (array) $existing; 
$edit['field_somefield']['und'][0]['value'] = 'blah';
user_save($existing, $edit);

For me this was happening when i called user_save during cron.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.