Problem/Motivation

Whenever a user with a picture (e.g. $user->picture != 0) logs in, their picture is removed. Specifically, the image file remains intact, but the file ID ($user->picture and/or $user->picture->fid, depending on the context) is set to 0.

Note that this is using the built-in user picture feature and not a 3rd-party module.

Steps to reproduce using 7.x-2.x-dev and 7.x-2.x-beta5:

  1. Log into a site with ldap_user enabled. (on my test rig, role and field provisioning to Drupal accounts is enabled, but the picture field is not setup for provisioning)
  2. Ensure that user pictures are enabled via admin/config/people/accounts.
  3. Upload a user picture.
  4. Verify that /user displays the uploaded picture.
  5. Log out.
  6. (Optional) Verify that the user's picture is still displayed at /user/uid.
  7. Log back in as the same user.
  8. The user's picture is no longer set.

Proposed resolution

It appears that the call to user_save() in the ldap_user_user_login(&$edit, $account) hook is to blame. Specifically, the value of $account->picture is merely the file ID rather than the full picture object that is normally returned by user_load(). This causes user_save() to set the user's picture to 0 (see Issue #935592: User picture is deleted after calls to user_save()).

The best solution I've found so far is to call user_load() prior to the save. I looked through the ldap_user_user_login(&$edit, $account) function and did not see any changes being made to $account prior to the call to user_save(); it also seems unlikely that provisioning to LDAP would modify the Drupal user in the future, so it appears to not cause issues with potential future changes.

Thus,

function ldap_user_user_login(&$edit, $account) {
  // Code for provisioning from a Drupal user/account to LDAP.

  // Provision from LDAP to a Drupal user.
  if (/* configured to provision to Drupal users */) {
    // Code to populate $user_edit with the information from LDAP.

    $account = user_save($account, $user_edit, 'ldap_user');
  }
}

becomes

function ldap_user_user_login(&$edit, $account) {
  // Code for provisioning from a Drupal user/account to LDAP.

  // Provision from LDAP to a Drupal user.
  if (/* configured to provision to Drupal users */) {
    // Load the user before saving to fully populate the $account->picture field.
    $account = user_load($account->uid);

    // Code to populate $user_edit with the information from LDAP.

    $account = user_save($account, $user_edit, 'ldap_user');
  }
}

Remaining tasks

(reviews needed, tests to be written or run, documentation to be written, etc.)
I have done a brief scan through the rest of the code base for other places that may be calling user_save() on user objects without fully-loaded picture objects and didn't spot any obvious ones. There are, however, several functions that are passed a user object as a parameter that do not load the user object prior to saving it, so the safety of those methods depends on the calling function. Here's a rough example of the functions that are potentially unsafe for the user's picture:

function myFunction($account, &$user_edit) {
  // Code that adds to and/or changes $user_edit.

  user_save($account, $user_edit);
}

The functions I've identified as at-risk are as follows:

  • _ldap_authorizations_user_authorizations_set
  • revokeSingleAuthorization
  • grantsAndRevokes
  • provisionDrupalAccount
  • ldapAssociateDrupalAccount
  • _ldap_user_orphans

I may have missed some, and these functions may not actually cause any problems; I just am not (yet) familiar enough with the flow of execution within these modules and consequently cannot easily verify if they will break the user's picture.

Tests should be added to check for user objects with gettype($account->picture) == 'string' being passed to user_save().

Issue #935592: User picture is deleted after calls to user_save()

Comments

madprgmr’s picture

Here's a patch that implements my suggested changes.

johnbarclay’s picture

Title: User pictures disappearing on login » LDAP User: User pictures disappearing on login

Thanks. Appreciae the details. I read through this and issue #935592: User picture is deleted after calls to user_save(). Its unclear if #935592: User picture is deleted after calls to user_save() is a documentation bug or an issue with the picture field.

If its a picture issue, I prefer only reloading the picture field to avoid conflicts with other modules.

$account2 = user_load($account->uid);  // see #1973352 and #935592
$account->picture = $account2->picture;
$account = user_save($account, $user_edit, 'ldap_user');

I don't see any other contrib or core modules that do a user_save in hook_user_login() so the correct path is unclear to me. Does the above change work for you?

hansfn’s picture

It appears that the call to user_save() in the ldap_user_user_login(&$edit, $account) hook is to blame. Specifically, the value of $account->picture is merely the file ID rather than the full picture object that is normally returned by user_load().

ldap_user_user_login implements hook_user_login. According to the documentation $account should be the fully loaded (global) $user object. If this isn't the case for you, there is a bug in the core of your version of Drupal. What version of Drupal are you using? Or some other module that implements hook_user_login is messing with $user->picture.

You could try to track it down by inserting some dd statements (or whatever you use to debug Drupal):

  1. Check the global $user variable in the function user_login_finalize in the file modules/user/user.module before user_module_invoke('login', $edit, $user) is called.
  2. Check the $account variable in the function user_module_invoke (in the same file) after each call to $function($edit, $account, $category).

This way should be possible to determine where $account->picture is modified.

johnbarclay’s picture

#1 and #2 are both viable approaches to me. #1 I believe will produce more consistent behavior over the long run. #2 is more of a quick fix that is less invasive. My preference is #1 but testing needs to happen with either.

johnbarclay’s picture

I committed #2 until #1 is tested more.

notmike’s picture

We tried the dev version from April 24 (7.x-2.0-beta5+5-dev); however, we are still losing the user images when users log in. Has anyone else had success with the dev version?

Ten days ago, we had moved from 1.0-beta12 to 2.0-beta5 on a production system, but only discovered the user image problem a few days ago.

humansky’s picture

My attached patch helps those who also rely on LDAP for authorization as well.

johnbarclay’s picture

Status: Active » Needs review

#7 looks correct to me. I'll commit it at some point. Can people test it?

madprgmr’s picture

#7 seems to work, but I do not know if correcting $user->picture through the edit array is better than directly changing the account object *shrug*.

I went ahead and rolled #2 and a variant of #7 (based off #2) into a patch. I also wrapped the fix in an if statement to prevent this fix from potentially kludging other changes to the user's picture (although other sources of change should be via the $edit array). The if statement also prevents an unnecessary call to user_load() in the event that the user/account object passed in is already fully loaded.

madprgmr’s picture

In response to #3:

Core is 7.22.

  1. the global $user already has $user->picture reduced to the (string) file ID rather than the full object.
  2. user_module_invoke() already has the $account without the full picture object,

After some brief poking around, I'm betting that the CAS module (1.2) is the source of the hook_user_login user variable being messed up. There's a release candidate for the next version, so I'm putting off in-depth searching until I have time to migrate our test server to it.

If so, then there's a good chance that fixing that would address a portion of this issue. However, as at least the authorizations portion has the user_save exposed via public functions, checks for (at least) these functions should remain in place (i.e. the _ldap_authorizations_user_authorizations_set function, since it is called from public functions).

notmike’s picture

In response to #8:

I did not have success when testing the previous patches, but I did have success when we tested #7.

johnbarclay’s picture

#9 is committed to 7.x-2.x-dev.

haydeniv’s picture

If we are looking for an automated test for this we can probably borrow from this patch Add test to ensure that user picture is not deleted after calls to user_save()

johnbarclay’s picture

Status: Needs review » Fixed

Status: Fixed » Closed (fixed)

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

katannshaw’s picture

@johnbarclay, I know that it's been awhile since you asked for others to test this, but patch #9 solved this problem for me with LDAP 7.x-2.0-beta5 in test and production. Thanks humansky and madprgmr.