You are here

simple_ldap_user.module in Simple LDAP 7.2

Same filename and directory in other branches
  1. 7 simple_ldap_user/simple_ldap_user.module

Main simple_ldap_user module file.

File

simple_ldap_user/simple_ldap_user.module
View source
<?php

/**
 * @file
 * Main simple_ldap_user module file.
 */

/**
 * Implements hook_permission()
 */
function simple_ldap_permission() {
  return array(
    'view own ldap data' => array(
      'title' => t('View own LDAP data'),
      'description' => t('Display LDAP information on the user\'s own profile page.'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function simple_ldap_user_menu() {
  $items = array();
  $items['admin/config/people/simple_ldap/user'] = array(
    'title' => 'Users',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simple_ldap_user_admin',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'simple_ldap_user.admin.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => 1,
  );
  $items['admin/config/people/simple_ldap/profile'] = array(
    'title' => 'Profile',
    'description' => 'Map user profile fields with LDAP user object',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simple_ldap_user_profile_map_form',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'simple_ldap_user.admin.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => 10,
  );
  $items['admin/people/simple_ldap_user_import'] = array(
    'title' => 'Import from LDAP',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simple_ldap_user_import',
    ),
    'access arguments' => array(
      'administer users',
    ),
    'file' => 'simple_ldap_user.admin.inc',
    'type' => MENU_LOCAL_ACTION,
  );
  return $items;
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Overrides the built-in user module's list of users, setting accounts to
 * "blocked" if there is no matching LDAP account.
 */
function simple_ldap_user_form_user_admin_account_alter(&$form, &$form_state, $form_id) {

  // Update the user array.
  foreach ($form['accounts']['#options'] as $uid => $user) {

    // Don't mess with user/1.
    if ($uid == 1) {
      continue;
    }

    // Verify active users. Blocked users may be provisioned to LDAP when they
    // are set to active, so they are left alone here.
    if ($user['status'] == 'active') {

      // Load the user objects.
      $drupal_user = user_load($uid);
      $ldap_user = SimpleLdapUser::singleton($drupal_user->name);

      // Check whether the user exists in LDAP.
      if (!$ldap_user->exists) {
        $form['accounts']['#options'][$uid]['status'] = 'blocked';
      }

      // Check whether the user is disabled (Active Directory only).
      // http://support.microsoft.com/kb/305144
      if ($ldap_user->server->type == 'Active Directory') {
        if (isset($ldap_user->useraccountcontrol[0]) && (int) $ldap_user->useraccountcontrol[0] & 2) {
          $form['accounts']['#options'][$uid]['status'] = 'blocked';
        }
      }
    }
  }
}

/**
 * Implements hook_entity_info_alter().
 *
 * Specifies that SimpleLdapuserController should be used to load users instead
 * of the default controller.
 */
function simple_ldap_user_entity_info_alter(&$entity_info) {
  if (isset($entity_info['user'])) {

    // Use the SimpleLdapUserController class to manage users.
    $entity_info['user']['controller class'] = 'SimpleLdapUserController';
  }
}

/**
 * Implements hook_form_alter().
 */
function simple_ldap_user_form_alter(&$form, &$form_state, $form_id) {
  switch ($form_id) {
    case 'user_login_block':

      // Remove the register and password reminder links.
      $server = SimpleLdapServer::singleton();
      if ($server->readonly) {
        unset($form['links']);
      }
    case 'user_login':
    case 'user_pass':

      // Insert simple_ldap_user's username validation.
      array_unshift($form['#validate'], 'simple_ldap_user_login_name_validate');
      break;
    case 'user_profile_form':

      // Disable mapped fields if LDAP Server is read-only.
      $server = SimpleLdapServer::singleton();
      $attribute_map = simple_ldap_user_variable_get('simple_ldap_user_attribute_map');
      $user_fields = simple_ldap_user_user_fields();

      // Name, Mail, and Password are special attributes.
      $form['account']['name']['#disabled'] = $server->readonly;
      $form['account']['mail']['#disabled'] = $server->readonly;
      $form['account']['pass']['#disabled'] = $server->readonly;

      // Active Directory has some additional restrictions.
      if ($server->type == 'Active Directory') {
        $form['account']['name']['#disabled'] = TRUE;
        $form['account']['pass']['#disabled'] = stripos($server->host, 'ldaps://') === FALSE;
      }

      // Other mapped fields.
      foreach ($attribute_map as $ldap_attr_name => $drupal_fields) {
        foreach ($drupal_fields as $index => $drupal_field_name) {

          // Skip #delimeter and other special entries
          if (!is_int($index)) {
            continue;
          }
          switch ($user_fields[$drupal_field_name]['module']) {
            case 'field':

              // Use Field API.
              $form[$drupal_field_name]['#disabled'] = $server->readonly;
              break;
            case 'user':

              // Use the user object directly.
              $form['account'][$drupal_field_name]['#disabled'] = $server->readonly;
              break;
            default:
              watchdog('Simple LDAP', 'Can\'t disable field from unknown module %module. (not supported yet)', array(
                '%module' => $field['module'],
              ), WATCHDOG_WARNING);
          }
        }
      }
      array_unshift($form['#validate'], 'simple_ldap_user_profile_form_validate');
      break;
    case 'user_register_form':
      array_unshift($form['#validate'], 'simple_ldap_user_register_form_validate');
      break;
    default:
  }
}

/**
 * Implements hook_menu_alter().
 *
 * Disables the user register and password reminder pages if the LDAP server is
 * read-only.
 */
function simple_ldap_user_menu_alter(&$items) {
  $server = SimpleLdapServer::singleton();
  if ($server->readonly) {
    $items['user/register']['access callback'] = FALSE;
    $items['user/password']['access callback'] = FALSE;
  }
}

/**
 * Implements hook_user_login().
 *
 * Fires when a user logs in.
 *
 * @param array $edit
 *   The form values submitted by the user to log in,
 *   including raw username and password.
 */
function simple_ldap_user_user_login(&$edit, $account) {
  if (property_exists($account, 'uid') && $account->uid == 1) {
    return;
  }
  $sync = simple_ldap_user_variable_get('simple_ldap_user_sync');
  if ($sync == 'hook_user_login') {
    switch (simple_ldap_user_variable_get('simple_ldap_user_source')) {
      case 'ldap':
        simple_ldap_user_sync_user_to_drupal($account);
        break;
      case 'drupal':
        simple_ldap_user_sync_user_to_ldap($account);
        break;
    }
  }
}

/**
 * Implements hook_user_presave().
 *
 * Fires before an account is created or changed.
 *
 * @param array $edit
 *   The form values submitted by the user.
 */
function simple_ldap_user_user_presave(&$edit, $account, $category) {

  // Skip for UID 1 .
  if (property_exists($account, 'uid') && $account->uid == 1) {
    return;
  }

  // Do not overwrite the user status in the database.
  if (isset($account->simple_ldap_user_drupal_status)) {

    // If status is in the edit array, we need to be sure we're not
    // unintentionally saving the LDAP value to the database. To check this, we
    // see if $edit['status'] matches $account->status. If it does, set
    // $edit['status'] to the simple_ldap_user_drupal_status value.
    if (isset($edit['status']) && $edit['status'] == $account->status) {
      $edit['status'] = $account->simple_ldap_user_drupal_status;
    }

    // Now set the $account->status back to the value in the database, just to
    // be safe. This ensures that if $edit['status'] is empty, we don't mess up
    // what's in the database with what is on the user object.
    $account->status = $account->simple_ldap_user_drupal_status;
  }

  // To make sure we've covered all our bases, we also set $account->original's
  // status back to what is in the database as well. This avoids Drupal sending
  // account activation emails in user_save(), which it will do if it detects
  // that the status has changed from $account->original to $account.
  if (isset($account->original) && isset($account->original->simple_ldap_user_drupal_status)) {
    $account->original->status = $account->original->simple_ldap_user_drupal_status;
  }
  if ($account->is_new && isset($edit['name'])) {
    $ldap_user = SimpleLdapUser::singleton($edit['name']);
    if ($ldap_user->exists) {

      // Force an initial sync from LDAP to drupal.
      $attribute_mail = simple_ldap_user_variable_get('simple_ldap_user_attribute_mail');

      // Get the user's email address.
      $edit['mail'] = $ldap_user->{$attribute_mail}[0];

      // Get the remaining mapped attributes.
      // Process the LDAP user to generate the edit array that gets passed to user_save();
      simple_ldap_user_generate_edit_ldap_to_drupal($edit, $ldap_user, $account);
    }
  }

  //If account updated and username changed, RDN default to drupal name and ldap

  //entry already exists from elsewhere, reset new changed username to its previous value.
  if (isset($account->original)) {

    //New name edited or programmatically set ?
    $new_username = '';
    if (!empty($edit['name']) && strcasecmp($account->original->name, $edit['name']) != 0) {
      $new_username = $edit['name'];
    }
    elseif (strcasecmp($account->original->name, $account->name) != 0) {
      $new_username = $account->name;
    }
    if ($new_username) {
      $ldap_user = SimpleLdapUser::singleton($new_username);
      $attribute_rdn = simple_ldap_user_variable_get('simple_ldap_user_attribute_rdn');
      if (empty($attribute_rdn) && $ldap_user->exists) {
        $account->name = $account->original->name;
        $edit['name'] = $account->original->name;
        drupal_set_message(t('The new username %name could not be changed because a conflicting LDAP entry already exists. Previous names have been kept and no LDAP modification has been done.', array(
          '%name' => $new_username,
        )), 'error');
      }
    }
  }
}

/**
 * Implements hook_user_insert().
 *
 * Fires after a new account is created.
 *
 * @param array $edit
 *   The form values submitted by the user.
 */
function simple_ldap_user_user_insert(&$edit, $account, $category) {
  if ($account->uid == 1) {
    return;
  }
  $ldap_user = SimpleLdapUser::singleton($account->name);
  if (!$ldap_user->exists) {
    module_invoke_all('sync_user_to_ldap', $account);
    simple_ldap_user_update_authmap($account, $ldap_user);
  }
}
function simple_ldap_user_update_authmap($account, $ldap_user = NULL) {

  // Write out the PUID or the DN to the authmap
  if (!$ldap_user) {
    $ldap_user = SimpleLdapUser::singleton($account->name);
  }
  $puid_attr = strtolower(simple_ldap_user_variable_get('simple_ldap_user_unique_attribute'));
  $puid_attr = $puid_attr ? $puid_attr : 'dn';
  user_set_authmaps($account, array(
    'authmap_simple_ldap' => $ldap_user->{$puid_attr}[0],
  ));
}

/**
 * Implements hook_user_update().
 *
 * Fires when a user account is edited.
 *
 * @param array $edit
 *   The form values submitted by the user.
 */
function simple_ldap_user_user_update(&$edit, $account, $category) {

  // Don't do anything for uid 1.
  if ($account->uid == 1) {
    return;
  }

  // Don't do anything if the hook was called via hook_sync_user_to_drupal().
  if (empty($account->hook_sync_user_to_drupal)) {
    $ldap_user = SimpleLdapUser::singleton($account->name);
    $enabled = isset($edit['status']) ? $edit['status'] : NULL;

    // In hook_user_presave, we may have messed with the $edit['status'] and
    // $account->status values, setting them to the database values, not what
    // LDAP had set status to. Set those back to the LDAP values now.
    if (isset($account->simple_ldap_user_ldap_status)) {
      $enabled = $account->status = $account->simple_ldap_user_ldap_status;
    }
    if ($enabled || $ldap_user->exists) {
      module_invoke_all('sync_user_to_ldap', $account);
    }
  }
  else {
    unset($account->hook_sync_user_to_drupal);
  }
}

/**
 * Implements hook_user_delete().
 *
 * Fires when a user account is deleted, before account is
 * deleted.
 *
 * @throw SimpleLdapException
 */
function simple_ldap_user_user_delete($account) {
  if ($account->uid == 1) {
    return;
  }

  // Call hook_simple_ldap_user_delete so other modules can create customized delete behavior.
  $ldap_user = SimpleLdapUser::singleton($account->name);
  $user_altered = array_filter(module_invoke_all('simple_ldap_user_delete', $account, $ldap_user));
  if (!empty($user_altered)) {
    try {
      $ldap_user
        ->save();
    } catch (SimpleLdapException $e) {
      watchdog('Simple LDAP', 'Failed to save changes to @dn from hook_simple_ldap_user_delete().', array(
        '@dn' => $ldap_user->dn,
        WATCHDOG_ERROR,
      ));
    }
  }
  if (!simple_ldap_user_variable_get('simple_ldap_user_delete_from_ldap')) {
    return;
  }
  try {
    $ldap_user
      ->delete();
  } catch (SimpleLdapException $e) {
    drupal_set_message(t('Failed to delete %name from LDAP.  Error @code: @message.', array(
      '%name' => $account->name,
      '@code' => $e
        ->getCode(),
      '@message' => $e
        ->getMessage(),
    )), 'error', FALSE);
  }
}

/**
 * Implements hook_user_load().
 *
 * Fires when user information is being loaded from the database.
 * User information is cached, so this does not fire every time
 * a user object is handled.
 */
function simple_ldap_user_user_load($users) {
  $sync = simple_ldap_user_variable_get('simple_ldap_user_sync');
  $puid_attr = simple_ldap_user_variable_get('simple_ldap_user_unique_attribute');
  if ($sync == 'hook_user_load') {
    foreach ($users as $account) {
      if ($account->uid == 1) {
        continue;
      }
      switch (simple_ldap_user_variable_get('simple_ldap_user_source')) {
        case 'ldap':
          simple_ldap_user_sync_user_to_drupal($account);
          break;
        case 'drupal':
          simple_ldap_user_sync_user_to_ldap($account);
          break;
      }
    }
  }
}

/**
 * Return a list of user fields with attributes describing how to handle them.
 */
function simple_ldap_user_user_fields($reset = FALSE) {
  static $user_fields = array();

  // Static cache
  if (!empty($user_fields) && !$reset) {
    return $user_fields;
  }
  $user_fields = module_invoke_all('simple_ldap_user_fields');
  drupal_alter('simple_ldap_user_fields', $user_fields);
  return $user_fields;
}

/**
 * return the base set of fields.
 */
function simple_ldap_user_simple_ldap_user_fields() {
  $user_fields = array(
    'name' => array(
      'label' => t('Login'),
      'description' => t('The username of the user'),
      'required' => TRUE,
      'module' => 'user',
      'field_name' => 'name',
    ),
    'pass' => array(
      'label' => t('Password'),
      'description' => t('The user\'s password of the user'),
      'required' => TRUE,
      'module' => 'user',
      'field_name' => 'pass',
    ),
    'mail' => array(
      'label' => t('Email'),
      'description' => t('The user\'s email address'),
      'required' => TRUE,
      'module' => 'user',
      'field_name' => 'mail',
    ),
    'status' => array(
      'label' => t('Status'),
      'description' => t('Blocked or Active'),
      'required' => FALSE,
      'module' => 'user',
      'field_name' => 'status',
    ),
    'created' => array(
      'label' => t('Created Timestamp'),
      'description' => t('User registration time'),
      'required' => FALSE,
      'module' => 'user',
      'field_name' => 'created',
    ),
    'login' => array(
      'label' => t('Last Login Timestamp'),
      'description' => t('User most recent login timestamp'),
      'required' => FALSE,
      'module' => 'user',
      'field_name' => 'login',
    ),
    'access' => array(
      'label' => t('Last Access Timestamp'),
      'description' => t('Most recent visit timestamp'),
      'required' => FALSE,
      'module' => 'user',
      'field_name' => 'access',
    ),
  );
  if (variable_get('user_pictures', FALSE)) {
    $user_fields['picture'] = array(
      'label' => t('Picture'),
      'required' => FALSE,
      'description' => t('User headshot or avatar'),
      'field_name' => 'picture',
      'module' => 'user',
    );
  }
  if (variable_get('user_signatures', FALSE)) {
    $user_fields['signature'] = array(
      'label' => t('Signature'),
      'required' => FALSE,
      'description' => t('Signature block'),
      'field_name' => 'signature',
      'module' => 'user',
    );
  }

  // Pull entity fields for user records
  $user_entity_fields = field_info_instances('user');
  foreach ($user_entity_fields as $obj_key => $object) {
    foreach ($object as $key => $field) {
      $user_fields[$key] = $field;
      $user_fields[$key]['module'] = 'field';
    }
  }
  return $user_fields;
}

/**
 * Check the name and email both belong to the same LDAP account, or no
 * account at all.
 */
function simple_ldap_user_register_form_validate($form, &$form_state) {
  $ldap_user_by_name = SimpleLdapUser::singleton($form_state['values']['name']);
  $ldap_user_by_mail = SimpleLdapUser::singleton($form_state['values']['mail']);
  $name_dn = $ldap_user_by_name->dn;
  $mail_dn = $ldap_user_by_mail->dn;
  if ($name_dn !== $mail_dn) {
    if (empty($mail_dn)) {
      form_set_error('name', t('A user with that username is already registered, but not with that email address.'));
    }
    else {
      if (empty($name_dn)) {
        form_set_error('mail', t('A user with that email address is already registered, but not with that user name.'));
      }
      else {
        form_set_error('name', t('That username is already in use with a different email address.'));
      }
    }
  }
}
function simple_ldap_user_profile_form_validate($form, &$form_state) {
  $ldap_user_by_name = SimpleLdapUser::singleton($form_state['values']['name']);
  $ldap_user_by_mail = SimpleLdapUser::singleton($form_state['values']['mail']);
  $account = $form_state['user'];
  $ldap_user = SimpleLdapUser::singleton($account->name);

  // Pull all three DNs
  $name_dn = $ldap_user_by_name->dn;
  $mail_dn = $ldap_user_by_mail->dn;
  $user_dn = $ldap_user->dn;

  // Make sure he doesn't collide with an existing LDAP user
  if (!$ldap_user->exists) {
    if ($ldap_user_by_name->exists) {
      form_set_error('name', t('Another user has that username.'));
    }
    if ($ldap_user_by_mail->exists) {
      form_set_error('mail', t('Another user has that email address.'));
    }
  }
  else {
    if ($user_dn !== $name_dn && $ldap_user_by_name->exists) {
      form_set_error('name', t('Another user has that username.'));
    }
    if ($user_dn !== $mail_dn && $ldap_user_by_mail->exists) {
      form_set_error('mail', t('Another user has that email address.'));
    }
  }
}

/**
 * Implements hook_user_view()
 *
 * Adds an LDAP block to the user profile page if the viewing user
 * has sufficient permissions.
 */
function simple_ldap_user_view($account, $view_mode, $langcode) {
  global $user;
  if (user_access('administer users') || $user->uid == $account->uid && user_access('view own ldap data')) {
    $query = db_query("SELECT authname FROM {authmap} WHERE module = 'simple_ldap' AND uid = :uid", array(
      ':uid' => $account->uid,
    ));
    while ($row = $query
      ->fetchAssoc()) {
      $dn_list[] = $row['authname'];
    }
    if (!empty($dn_list)) {
      $account->content['ldap'] = array(
        '#type' => 'user_profile_category',
        '#title' => t("LDAP"),
        '#weight' => 30,
      );
      $account->content['ldap']['simple_ldap'] = array(
        '#type' => 'user_profile_item',
        '#title' => t('DNs'),
        '#markup' => theme('item_list', array(
          'items' => $dn_list,
        )),
        '#attributes' => array(
          'class' => 'simple_ldap',
        ),
      );
    }
  }
}

/**
 * Validate the username on a login or password reset form.
 */
function simple_ldap_user_login_name_validate($form, &$form_state) {

  // Get the username from the form data.
  $name = trim($form_state['values']['name']);
  if (simple_ldap_user_load_or_create_by_name($name) === NULL) {
    form_set_error('name', t('An account ID conflict has been detected.  Please contact your site administrator.'));
  }
}

/**
 * Create a valid LDAP user on this site if they don't already exist.
 *
 * @param string $name
 *   The username or email address to load.
 *
 * @return mixed
 *   The Drupal user object, FALSE if the process failed, NULL if there was a conflict.
 */
function simple_ldap_user_load_or_create_by_name($name) {

  // Load the LDAP user with the given username.
  $ldap_user = SimpleLdapUser::singleton($name);
  $attribute_name = strtolower(simple_ldap_user_variable_get('simple_ldap_user_attribute_name'));
  $attribute_mail = strtolower(simple_ldap_user_variable_get('simple_ldap_user_attribute_mail'));
  $puid_attr = strtolower(simple_ldap_user_variable_get('simple_ldap_user_unique_attribute'));

  // If the user doesn't exist in LDAP, there is nothing for us to do.
  if (!$ldap_user->exists) {
    return FALSE;
  }

  // Pull the username
  $user_name = $ldap_user->{$attribute_name}[0];
  if ($puid_attr) {
    $puid = $ldap_user->{$puid_attr}[0];
    $puid_status = simple_ldap_user_update_username_for_puid($puid, $user_name);

    // Abort if there is a conflict.
    if ($puid_status === FALSE) {
      return NULL;
    }
    $name = $user_name;
  }

  // Attempt to load the drupal user.
  $drupal_user = user_load_by_name($name);
  if (!$drupal_user) {
    $drupal_user = user_load_by_mail($name);
  }

  // If the user doesn't already exist in Drupal, create them.
  if (!$drupal_user) {
    $edit = array(
      'name' => $ldap_user->{$attribute_name}[0],
      'mail' => $ldap_user->{$attribute_mail}[0],
      'status' => 1,
    );
    $drupal_user = user_save(NULL, $edit);
    $authmap_attr = $puid_attr ? $puid_attr : 'dn';
    user_set_authmaps($drupal_user, array(
      'authmap_simple_ldap' => $ldap_user->{$authmap_attr}[0],
    ));
  }
  return $drupal_user;
}

/**
 * Check the authmaps for the PUID.  Update the {users} table if the PUID is present but the username is different.
 *
 * @param $puid The unique attribute for the user.
 *
 * @param $user_name The name of the user in LDAP
 *
 * @return FALSE if a name conflict exists
 *    NULL if no user has the PUID or no PUID Attr has been set
 *    TRUE if the user exists.
 */
function simple_ldap_user_update_username_for_puid($puid, $user_name) {
  $puid_attr = strtolower(simple_ldap_user_variable_get('simple_ldap_user_unique_attribute'));
  if (!$puid_attr) {
    return NULL;
  }

  // Look for the user in the authmaps.
  $drupal_user_map = db_query("SELECT u.name, u.uid FROM {users} u INNER JOIN {authmap} a ON u.uid = a.uid WHERE a.module='simple_ldap' AND a.authname=:authname", array(
    ':authname' => $puid,
  ))
    ->fetchAssoc();

  // No entry, return NULL
  if (empty($drupal_user_map)) {
    return NULL;
  }

  // If there is an authmap entry, make sure the username matches or is empty.
  if ($drupal_user_map['name'] != $user_name) {

    // Name has changed, make sure the new name isn't already in use.
    $conflicting_user = db_query("SELECT u.uid FROM {users} u WHERE u.name=:name", array(
      ':name' => $user_name,
    ))
      ->fetchAssoc();
    if ($conflicting_user) {
      watchdog('Simple LDAP', 'User %username (UID @uid) used to have login %oldname was renamed, but another user already has that name.', array(
        '%username' => $user_name,
        '@uid' => $conflicting_user['uid'],
        '%oldname' => $drupal_user_map['name'],
      ), WATCHDOG_ERROR);
      return FALSE;
    }

    // Update the username record directly so user_load_by_name() will find it.
    watchdog('Simple LDAP', 'User @uid was externally renamed from %oldname to %newname.', array(
      '@uid' => $drupal_user_map['uid'],
      '%oldname' => $drupal_user_map['name'],
      '%newname' => $user_name,
    ), WATCHDOG_NOTICE);
    db_query("UPDATE {users} SET name=:newname WHERE uid=:uid", array(
      ':newname' => $user_name,
      ':uid' => $drupal_user_map['uid'],
    ));
  }
  return TRUE;
}

/**
 * Implements HOOK_simple_ldap_data_handlers()
 */
function simple_ldap_user_simple_ldap_data_handlers() {
  $handlers_file = drupal_get_path('module', 'simple_ldap_user') . '/simple_ldap_user.ldap_handlers.inc';
  $handlers = array(
    'text' => array(
      'import_callback' => 'simple_ldap_user_translate_basic_ldap_to_drupal',
      'export_callback' => 'simple_ldap_user_translate_basic_drupal_to_ldap',
      'file' => $handlers_file,
    ),
    'file' => array(
      'import_callback' => 'simple_ldap_user_translate_file_ldap_to_drupal',
      'export_callback' => 'simple_ldap_user_translate_file_drupal_to_ldap',
      'file' => $handlers_file,
    ),
    'taxonomy_term_reference' => array(
      'import_callback' => 'simple_ldap_user_translate_term_ldap_to_drupal',
      'export_callback' => 'simple_ldap_user_translate_term_drupal_to_ldap',
      'file' => $handlers_file,
    ),
    'url' => array(
      'import_callback' => 'simple_ldap_user_translate_url_ldap_to_drupal',
      'export_callback' => 'simple_ldap_user_translate_url_drupal_to_ldap',
      'file' => $handlers_file,
    ),
    'datetime' => array(
      'import_callback' => 'simple_ldap_user_translate_datetime_ldap_to_drupal',
      'export_callback' => 'simple_ldap_user_translate_datetime_drupal_to_ldap',
      'file' => $handlers_file,
    ),
    '#default' => array(
      'import_callback' => 'simple_ldap_user_default_import_handler',
      'export_callback' => 'simple_ldap_user_default_export_handler',
      'file' => $handlers_file,
    ),
  );

  // Some field types have similar structure
  $handlers['number_integer'] = $handlers['text'];
  $handlers['text_long'] = $handlers['text'];
  $handlers['list_text'] = $handlers['text'];
  $handlers['image'] = $handlers['file'];
  return $handlers;
}

/**
 * Translate Drupal fields into a format suitable for LDAP.
 *
 * Invokes HOOK_simple_ldap_field_alter() to handle any fields that are
 * not simple strings copied out to LDAP.
 *
 */
function simple_ldap_user_translate_drupal_attr_to_ldap(&$value, $account, $attr) {
  $handlers = simple_ldap_user_get_data_handlers();
  $field_info = field_info_field($attr);
  if (!empty($field_info['type'])) {

    // Get the value using Field API.
    $items = field_get_items('user', $account, $attr);
    $handler = array_key_exists($field_info['type'], $handlers) ? $handlers[$field_info['type']] : $handlers['#default'];
    if (!empty($handler['file'])) {
      require_once $handler['file'];
    }
    $handler['export_callback']($value, $field_info, $items);
  }
}

/**
 * Call HOOK_simple_ldap_data_handlers(), cache the results.
 *
 * @param boolean $reset
 *   Reset the cache and recollect the list of handlers.
 */
function simple_ldap_user_get_data_handlers($reset = FALSE) {
  static $handlers = array();
  if (empty($handlers) || $reset) {
    $handlers = module_invoke_all('simple_ldap_data_handlers');
    drupal_alter('simple_ldap_data_handlers', $handlers);
  }
  return $handlers;
}

/**
 * Translate a single LDAP Attribute to the mapped Drupal user entity field.
 */
function simple_ldap_user_translate_ldap_attr_to_drupal(&$edit, $account, $ldap_attr_data, $drupal_field) {
  $items = field_get_items('user', $account, $drupal_field);
  $info = field_info_field($drupal_field);
  $language = field_language('user', $account, $drupal_field);
  $handlers = simple_ldap_user_get_data_handlers();
  $field_type = $info['type'];
  $handler = array_key_exists($field_type, $handlers) ? $handlers[$field_type] : $handlers['#default'];
  if (!empty($handler['file'])) {
    require_once $handler['file'];
  }
  $handler['import_callback']($edit, $info, $items, $ldap_attr_data, $language);
}

/**
 * Convert a field defined by the user module to a format suitable for LDAP
 *
 * @param array $ldap_edit
 *    An array of values to be handed to LDAP.  Similar in function to user_save()'s $edit
 *    parameter, but different in syntax.
 *
 * @param object $account
 *    The Drupal user object being written to LDAP
 *
 * @param string $drupal_field_name
 *    The name of the field to be converted.
 */
function simple_ldap_user_base_field_to_ldap(&$ldap_edit, $account, $drupal_field_name) {
  $ldap_user = SimpleLdapUser::singleton($account->name);
  switch ($drupal_field_name) {
    case 'access':
    case 'login':
    case 'created':
      if (!empty($account->{$drupal_field_name})) {
        $tz = date_default_timezone_get();
        date_default_timezone_set('UTC');
        $ldap_edit[] = date('YmdHis', $account->{$drupal_field_name}) . 'Z';
        date_default_timezone_set($tz);
      }
      break;
    case 'picture':
      if (is_object($account->picture)) {
        $file = $account->picture;
      }
      else {
        $file = file_load(empty($account->picture) ? 0 : $account->picture);
      }
      if (!empty($file)) {
        $ldap_edit[] = file_get_contents($file->uri);
      }
      break;
    case 'status':
      $status = property_exists($account, 'simple_ldap_user_drupal_status') ? $account->simple_ldap_user_drupal_status : $account->status;
      $ldap_edit[] = $status ? "1" : "0";
      break;
    default:
      $ldap_edit[] = $account->{$drupal_field_name};
  }
}
function simple_ldap_user_base_field_to_drupal(&$edit, $drupal_user, $ldap_values, $drupal_field_name) {
  @($ldap_value = $ldap_values[0]);
  switch ($drupal_field_name) {
    case 'access':
    case 'login':
    case 'created':

      // Never overwrite timestamps with NULL from a missing attribute
      if ($ldap_value === NULL) {
        if (!empty($drupal_user->{$drupal_field_name})) {
          $edit['#ignored'][] = $drupal_field_name;
        }
        break;
      }

      // translate timestamp
      $tz = date_default_timezone_get();
      $timestamp = strtotime($ldap_value);

      // Bad data?  Report, but don't copy.
      if ($timestamp === FALSE) {
        watchdog('SimpleLDAP', 'Invalid data trying to map LDAP to @drupal_field for user UID @user.', array(
          '@drupal_field' => $drupal_field_name,
          '@user' => $drupal_user->uid,
        ), WATCHDOG_WARNING);
        break;
      }
      if (!property_exists($drupal_user, $drupal_field_name)) {
        $edit[$drupal_field_name] = $timestamp;
      }
      else {
        if ($drupal_user->{$drupal_field_name} > $timestamp) {
          $edit['#ignored'][] = $drupal_field_name;
        }
        $edit[$drupal_field_name] = max($timestamp, $drupal_user->{$drupal_field_name});
      }
      date_default_timezone_set($tz);
      break;
    case 'picture':
      @($drupal_image = $drupal_user->{$drupal_field_name});

      // Skip if both are empty
      if (empty($drupal_image) && empty($ldap_value)) {
        break;
      }

      // Skip if both are identical
      if (!empty($drupal_image) && $drupal_image->filesize == strlen($ldap_value) && md5($ldap_value) == md5(file_get_contents($drupal_image->uri))) {
        break;
      }

      // Otherwise, create a FID
      $name_field = simple_ldap_user_variable_get('simple_ldap_user_attribute_name');
      $filename = file_default_scheme() . '://' . variable_get('user_picture_path', 'pictures') . '/' . preg_replace('/\\W+/', '_', $drupal_user->name) . '.jpg';
      $file_obj = file_save_data($ldap_value, $filename, FILE_EXISTS_RENAME);
      $edit[$drupal_field_name] = $file_obj;
      break;
    case 'status':
      $status = empty($ldap_value) || $ldap_value == 'FALSE' ? 0 : 1;
      if (!property_exists($drupal_user, 'status') || $drupal_user->status != $status) {
        $edit['status'] = $status;
      }
      break;

    // All plain string fields fall to here.
    default:
      if (isset($ldap_value) && (!property_exists($drupal_user, $drupal_field_name) || $drupal_user->{$drupal_field_name} != $ldap_value)) {
        $edit[$drupal_field_name] = $ldap_value;
      }
  }
}

/**
 * Synchronizes Drupal user properties to LDAP.
 */
function simple_ldap_user_sync_user_to_ldap($drupal_user) {

  // Don't try to sync anonymous or user 1.
  if ($drupal_user->uid == 0 || $drupal_user->uid == 1) {
    return;
  }

  // Don't try to sync if the server is read-only.
  $server = SimpleLdapServer::singleton();
  if ($server->readonly) {
    return;
  }

  // simple_ldap_user configuration.
  $user_fields = simple_ldap_user_user_fields();
  $attribute_map = simple_ldap_user_variable_get('simple_ldap_user_attribute_map');

  // Load the LDAP user.  If $drupal_user->orignal is set, this may be a move. Use the old name.
  $search_name = !empty($drupal_user->original) ? $drupal_user->original->name : $drupal_user->name;
  $ldap_user = SimpleLdapUser::singleton($search_name);

  // Synchronize the fields in the attribute map.
  foreach ($attribute_map as $ldap_attr_name => $drupal_fields) {

    // Initialize the Drupal value array.
    $drupal_values = array();

    // Parse the drupal attribute name.
    foreach ($drupal_fields as $drupal_field_name) {

      // Get the Drupal value(s) based on the field type.
      if (!array_key_exists($drupal_field_name, $user_fields)) {
        continue;
      }
      switch ($user_fields[$drupal_field_name]['module']) {
        case 'field':
          simple_ldap_user_translate_drupal_attr_to_ldap($drupal_values, $drupal_user, $drupal_field_name);
          break;
        case 'user':

          // Get the value directly from the user object.
          simple_ldap_user_base_field_to_ldap($drupal_values, $drupal_user, $drupal_field_name);
          break;
        default:
          watchdog('Simple LDAP', 'Field from unknown module %module. (not supported yet)', array(
            '%module' => $field['module'],
          ), WATCHDOG_WARNING);
      }
    }

    // Finally, add the values to the LDAP user.
    if (!empty($drupal_fields['#delimiter'])) {
      $drupal_values = implode($drupal_fields['#delimiter'], $drupal_values);
    }
    $ldap_user->{$ldap_attr_name} = $drupal_values;
  }

  // Set the DN.
  $attribute_rdn = simple_ldap_user_variable_get('simple_ldap_user_attribute_rdn');
  if (empty($attribute_rdn)) {
    $attribute_rdn = simple_ldap_user_variable_get('simple_ldap_user_attribute_name');
  }
  if ($ldap_user->{$attribute_rdn}['count'] > 0) {
    if ($ldap_user->dn) {

      // Reconstruct an existing DN.
      $parts = SimpleLdap::ldap_explode_dn($ldap_user->dn);
      $basedn = '';
      for ($i = 1; $i < $parts['count']; $i++) {
        $basedn .= ',' . $parts[$i];
      }
    }
    else {

      // Default to using the configured basedn.
      $basedn = ',' . simple_ldap_user_variable_get('simple_ldap_user_basedn');
    }
    $ldap_user->dn = $attribute_rdn . '=' . $ldap_user->{$attribute_rdn}[0] . $basedn;
  }

  // Allow altering the LDAP user object before saving.
  drupal_alter('simple_ldap_user_to_ldap', $drupal_user, $ldap_user);

  // Save any changes.
  try {
    $ldap_user
      ->save();
  } catch (SimpleLdapException $e) {
    drupal_set_message(t('Failed to save the user to LDAP: %error', array(
      '%error' => $e
        ->getMessage(),
    )), 'error');
  }
}

/**
 * Synchronizes LDAP attributes to Drupal user properties.
 */
function simple_ldap_user_sync_user_to_drupal($drupal_user) {

  // Skip UID 1
  if (property_exists($drupal_user, 'uid') && $drupal_user->uid == 1) {
    return;
  }

  // Load the LDAP user, force a cache reset.
  $ldap_user = SimpleLdapUser::singleton($drupal_user->name, TRUE);

  // Nothing to sync.
  if (!$ldap_user->exists) {
    return;
  }

  // Initialize array of attribute changes.
  $edit = array();

  // Process the LDAP user to generate the edit array that gets passed to user_save();
  simple_ldap_user_generate_edit_ldap_to_drupal($edit, $ldap_user, $drupal_user);

  // Save any changes.
  if (!empty($edit)) {
    if (!isset($drupal_user->original)) {

      // This avoids an infinite load/save loop.
      $drupal_user->original = clone $drupal_user;
    }
    $drupal_user->hook_sync_user_to_drupal = TRUE;
    $drupal_user = user_save($drupal_user, $edit);

    // If any conversion added a #ignored tag, that means
    // the Drupal values of some fields were not replaced
    // by values from the LDAP record and need to be written
    // back to the LDAP server.  Used by the timestamps to
    // ensure the latest values hold in both.
    if (!empty($edit['#ignored'])) {
      simple_ldap_user_sync_user_to_ldap($drupal_user);
    }
  }

  // Synchronized user.
  return $drupal_user;
}
function simple_ldap_user_generate_edit_ldap_to_drupal(&$edit, $ldap_user, $account) {
  $attribute_map = simple_ldap_user_variable_get('simple_ldap_user_attribute_map');
  $user_fields = simple_ldap_user_user_fields();

  // Synchronize the fields in the attribute map.
  foreach ($attribute_map as $ldap_attr => $drupal_fields) {

    // Skip drupal-to-ldap many-to-one mappings.
    $one_to_many = FALSE;
    $values = array();
    if (count($drupal_fields) > 2) {
      if (array_key_exists('#delimiter', $drupal_fields)) {
        if (array_key_exists(0, $ldap_user->{$ldap_attr})) {
          $values = explode($drupal_fields['#delimiter'], $ldap_user->{$ldap_attr}[0]);
        }
        $values['count'] = count($values);
        $one_to_many = TRUE;
      }
    }
    else {
      $values = $ldap_user->{$ldap_attr};
    }
    foreach ($drupal_fields as $key => $drupal_field_name) {

      // Skip special items like #delimeter
      if (!is_int($key)) {
        continue;
      }

      // Skip the password field
      if ($drupal_field_name == 'pass') {
        continue;
      }

      // Skip if not in user field list
      if (!array_key_exists($drupal_field_name, $user_fields)) {
        continue;
      }

      // Map one at a time if mapping across multiple fields
      // TODO: $values may not have enough entries.
      if ($one_to_many) {
        $map_values = array_key_exists($key, $values) ? array(
          $values[$key],
          'count' => 1,
        ) : array(
          'count' => 0,
        );
      }
      else {
        $map_values = $values;
      }
      $field = $user_fields[$drupal_field_name];
      switch ($field['module']) {

        // Update the value in drupal using Field API.
        case 'field':

          // Get the Drupal field values and metadata.
          simple_ldap_user_translate_ldap_attr_to_drupal($edit, $account, $map_values, $drupal_field_name);
          break;

        // Update the value directly on the user object.
        case 'user':
          simple_ldap_user_base_field_to_drupal($edit, $account, $map_values, $drupal_field_name);
          break;
        default:
          watchdog('Simple LDAP', 'Field from unknown module %module. (not supported yet)', array(
            '%module' => $field['module'],
          ), WATCHDOG_WARNING);
      }
    }
  }

  // Allow altering the Drupal user object before saving.
  drupal_alter('simple_ldap_user_to_drupal', $edit, $account, $ldap_user);
}

/**
 * Implements hook_user_operations().
 */
function simple_ldap_user_user_operations() {
  $operations = array();
  $server = SimpleLdapServer::singleton();
  if (!$server->readonly) {
    $operations['simple_ldap_user_export'] = array(
      'label' => t('Export selected users to LDAP'),
      'callback' => 'simple_ldap_user_export',
    );
  }
  return $operations;
}

/**
 * Handles bulk user export from admin/people.
 */
function simple_ldap_user_export($users) {

  // Generate the batch operation array.
  $operations = array();
  foreach ($users as $uid) {

    // Don't sync user1.
    if ($uid == 1) {
      continue;
    }
    $operations[] = array(
      'simple_ldap_user_export_user',
      array(
        $uid,
      ),
    );
  }
  $batch = array(
    'operations' => $operations,
  );
  batch_set($batch);
}

/**
 * Batch process function for mass user export.
 * &$context comes from batch_set().
 */
function simple_ldap_user_export_user($uid, &$context) {

  // Sync user to LDAP.
  $account = user_load($uid);
  simple_ldap_user_sync_user_to_ldap($account);
  simple_ldap_user_update_authmap($account);
  unset($account);
}
function simple_ldap_user_parent_objectclasses($classes) {
  if (!is_array($classes)) {
    $classes = array(
      $classes,
    );
  }

  // Pull the available LDAP schemas from the server
  $server = SimpleLdapServer::singleton();
  $schema = $server->schema;
  foreach ($classes as $class) {
    if (empty($class)) {
      continue;
    }
    $result[] = $class;
    $result = array_merge($result, $schema
      ->superclass($class, TRUE));
  }
  return array_unique(array_map('strtolower', $result));
}

/**
 * Pull the selected LDAP object classes for users
 *
 */
function simple_ldap_user_profile_classes($include_auxiliary = TRUE) {

  // Pull the LDAP objectClasses selected for users, plus any parent classes.
  $objectclass = simple_ldap_user_variable_get('simple_ldap_user_objectclass');
  $classes = array(
    $objectclass => $objectclass,
  );
  if ($include_auxiliary) {
    $classes += simple_ldap_user_variable_get('simple_ldap_user_auxiliaryclasses');
  }
  $classes = simple_ldap_user_parent_objectclasses($classes);
  return $classes;
}

/**
 * Given an array of objectClasses, return an array with the attributes in a
 * hierarchy, grouped by objectClass, and suitable for FAPI select elements.
 *
 * @param array
 *  An array of classes.
 *
 * @return array
 *  An array of attributes to be fed to FAPI select elements.
 */
function simple_ldap_user_class_attrs_as_options($classes) {

  // Get an LDAP server object.
  $server = SimpleLdapServer::singleton();

  // Verify LDAP server connectivity, return NULL (error) if unable to bind
  if (!$server
    ->bind()) {
    return NULL;
  }

  // Pull the available LDAP schemas from the server
  $schema = $server->schema;

  // Build a list of available LDAP attributes, grouped by Object Class
  $options[''] = ' - None - ';
  foreach ($classes as $class) {
    if ($class == 'top') {
      continue;
    }

    // Add a * to must attributes, to indiciate it must be set.
    $ldap_must_attrs = $schema
      ->must($class);
    foreach ($ldap_must_attrs as $attribute) {
      $options[$class][strtolower($attribute)] = $attribute . '*';
    }

    // Add the may attributes later.
    foreach ($schema
      ->may($class) as $attribute) {
      $options[$class][strtolower($attribute)] = $attribute;
    }
  }
  return $options;
}

/**
 * Returns the value for the specified variable.
 *
 * This function takes into account the configured LDAP server type, and
 * attempts to determine a reasonable default value to try to use in the event
 * that the module has not yet been configured.
 */
function simple_ldap_user_variable_get($name, $default = NULL, $force_default = FALSE) {

  // Cache certain variables
  static $cache = array();

  // Allow variable name shorthand by prepending 'simple_ldap_user_' to $name if
  // it is not already there.
  if (strpos($name, 'simple_ldap_user_') !== 0) {
    $name = 'simple_ldap_user_' . $name;
  }

  // Returned cached value if present
  if (array_key_exists($name, $cache)) {
    return $cache[$name];
  }

  // Get an LDAP server object.
  $server = SimpleLdapServer::singleton();

  // Handle special variables.
  switch ($name) {
    case 'simple_ldap_user_source':

      // If the LDAP server is set to read-only, force LDAP->Drupal sync.
      if ($server->readonly) {
        return 'ldap';
      }
      break;
    case 'simple_ldap_user_attribute_map':

      // Load the attribute map from settings.php.
      $attribute_map = variable_get($name, array());

      // LDAP likes lowercase.
      array_change_key_case($attribute_map, CASE_LOWER);
      foreach ($attribute_map as $key => $value) {

        // Make sure the Drupal attribute is an array.
        if (!is_array($attribute_map[$key])) {
          $attribute_map[$key] = array(
            $value,
          );
        }
      }
      $cache[$name] = $attribute_map;
      return $attribute_map;
  }

  // Define defaults that differ based on LDAP server type.
  switch ($server->type) {
    case 'Active Directory':
      $defaults = array(
        'simple_ldap_user_objectclass' => 'user',
        'simple_ldap_user_attribute_name' => 'samaccountname',
        'simple_ldap_user_attribute_mail' => 'mail',
        'simple_ldap_user_attribute_pass' => 'unicodepwd',
        'simple_ldap_user_password_hash' => 'unicode',
        'simple_ldap_user_attribute_rdn' => 'cn',
      );
      break;
    default:
      $defaults = array(
        'simple_ldap_user_objectclass' => 'inetorgperson',
        'simple_ldap_user_attribute_name' => 'cn',
        'simple_ldap_user_attribute_mail' => 'mail',
        'simple_ldap_user_attribute_pass' => 'userpassword',
        'simple_ldap_user_password_hash' => 'salted sha',
      );
  }

  // Define defaults that do not depend on LDAP server type.
  $defaults['simple_ldap_user_auxiliaryclasses'] = array();
  $defaults['simple_ldap_user_basedn'] = $server->basedn;
  $defaults['simple_ldap_user_scope'] = 'sub';
  $defaults['simple_ldap_user_source'] = 'ldap';
  $defaults['simple_ldap_user_sync'] = 'hook_user_load';
  $defaults['simple_ldap_user_delete_from_ldap'] = '1';
  $defaults['simple_ldap_user_auth_fallback'] = array();
  $defaults['simple_ldap_user_auth_fallback_writeback'] = array();
  $defaults['simple_ldap_user_extra_attrs'] = array();
  $defaults['simple_ldap_user_unique_attribute'] = '';

  // Determine the default value for the given variable.
  $default = isset($defaults[$name]) ? $defaults[$name] : $default;
  if ($force_default) {
    return $default;
  }
  return variable_get($name, $default);
}

Functions

Namesort descending Description
simple_ldap_permission Implements hook_permission()
simple_ldap_user_base_field_to_drupal
simple_ldap_user_base_field_to_ldap Convert a field defined by the user module to a format suitable for LDAP
simple_ldap_user_class_attrs_as_options Given an array of objectClasses, return an array with the attributes in a hierarchy, grouped by objectClass, and suitable for FAPI select elements.
simple_ldap_user_entity_info_alter Implements hook_entity_info_alter().
simple_ldap_user_export Handles bulk user export from admin/people.
simple_ldap_user_export_user Batch process function for mass user export. &$context comes from batch_set().
simple_ldap_user_form_alter Implements hook_form_alter().
simple_ldap_user_form_user_admin_account_alter Implements hook_form_FORM_ID_alter().
simple_ldap_user_generate_edit_ldap_to_drupal
simple_ldap_user_get_data_handlers Call HOOK_simple_ldap_data_handlers(), cache the results.
simple_ldap_user_load_or_create_by_name Create a valid LDAP user on this site if they don't already exist.
simple_ldap_user_login_name_validate Validate the username on a login or password reset form.
simple_ldap_user_menu Implements hook_menu().
simple_ldap_user_menu_alter Implements hook_menu_alter().
simple_ldap_user_parent_objectclasses
simple_ldap_user_profile_classes Pull the selected LDAP object classes for users
simple_ldap_user_profile_form_validate
simple_ldap_user_register_form_validate Check the name and email both belong to the same LDAP account, or no account at all.
simple_ldap_user_simple_ldap_data_handlers Implements HOOK_simple_ldap_data_handlers()
simple_ldap_user_simple_ldap_user_fields return the base set of fields.
simple_ldap_user_sync_user_to_drupal Synchronizes LDAP attributes to Drupal user properties.
simple_ldap_user_sync_user_to_ldap Synchronizes Drupal user properties to LDAP.
simple_ldap_user_translate_drupal_attr_to_ldap Translate Drupal fields into a format suitable for LDAP.
simple_ldap_user_translate_ldap_attr_to_drupal Translate a single LDAP Attribute to the mapped Drupal user entity field.
simple_ldap_user_update_authmap
simple_ldap_user_update_username_for_puid Check the authmaps for the PUID. Update the {users} table if the PUID is present but the username is different.
simple_ldap_user_user_delete Implements hook_user_delete().
simple_ldap_user_user_fields Return a list of user fields with attributes describing how to handle them.
simple_ldap_user_user_insert Implements hook_user_insert().
simple_ldap_user_user_load Implements hook_user_load().
simple_ldap_user_user_login Implements hook_user_login().
simple_ldap_user_user_operations Implements hook_user_operations().
simple_ldap_user_user_presave Implements hook_user_presave().
simple_ldap_user_user_update Implements hook_user_update().
simple_ldap_user_variable_get Returns the value for the specified variable.
simple_ldap_user_view Implements hook_user_view()