You are here

ldapsync.module in LDAP integration 6

ldapsync keeps LDAP and Drupal user lists synchronized.

File

ldapsync.module
View source
<?php

/**
 * @file
 * ldapsync keeps LDAP and Drupal user lists synchronized.
 */

//////////////////////////////////////////////////////////////////////////////
define('LDAPSYNC_CONFLICT_FOLLOW_LDAPAUTH', 3);
define('LDAPSYNC_TIME_INTERVAL', variable_get('ldapsync_time_interval', -1));
define('LDAPSYNC_FILTER', variable_get('ldapsync_filter', ''));
define('LDAPSYNC_LOGIN_CONFLICT', variable_get('ldapsync_login_conflict', LDAPSYNC_CONFLICT_FOLLOW_LDAPAUTH));

//////////////////////////////////////////////////////////////////////////////

// Core API hooks

/**
 * Implements hook_init().
 */
function ldapsync_init() {
  module_load_include('inc', 'ldapauth', 'includes/ldap.core');
  module_load_include('inc', 'ldapauth', 'includes/LDAPInterface');
}

/**
 * Implementation of hook_help().
 */
function ldapsync_help($path, $arg) {
  $output = '';
  switch ($path) {
    case "admin/help#ldapsync":
      $output = '<p>' . t("Searches LDAP to update Drupal user membership and information.") . '</p>';
      break;
  }
  return $output;
}

/**
 * Implementation of hook_menu().
 */
function ldapsync_menu() {
  return array(
    'admin/settings/ldap/ldapsync' => array(
      'title' => 'Synchronization',
      'description' => 'Configure LDAP sync settings.',
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'ldapsync_admin_settings',
      ),
      'access arguments' => array(
        'administer ldap modules',
      ),
      'file' => 'ldapsync.admin.inc',
    ),
  );
}

/**
 * Implements hook_cron().
 *
 * Checks ldapsync_time_interval and ldapsync_last_sync_time variables to determine whether to run ldapsync.
 */
function ldapsync_cron() {
  $time_interval = variable_get('ldapsync_time_interval', -1);

  // -1 means "only sync manually"
  $last_sync_time = variable_get('ldapsync_last_sync_time', 0);
  if (time() - $last_sync_time > $time_interval && $time_interval != -1) {
    _ldapsync_sync();
  }
}

/**
 * Main routine.
 */
function _ldapsync_sync() {
  global $_ldapsync_ldap;

  // If ldapgroups is enabled, include it for groups-role sync.
  if (module_exists('ldapgroups')) {
    module_load_include('inc', 'ldapgroups', 'ldapgroups');
  }

  // Find all users in specified OU (using base DN and bind information from ldapauth).
  // and take appropriate action on the Drupal side.
  $ldap_users = _ldapsync_search();
  $count_orphaned_users = 0;

  // Do we have any LDAP-authentified Drupal users who don't exist in LDAP?
  if ($ldap_users) {
    $result = db_query("SELECT uid, name, data FROM {users} WHERE status = %d", 1);
    while ($row = db_fetch_array($result)) {
      if (!isset($ldap_users[drupal_strtolower($row['name'])])) {
        $data = unserialize($row['data']);
        if ($data['ldap_authentified']) {

          // Block user if appropriate module setting is set.
          if (variable_get('ldapsync_missing_users_action', 'warn') == 'block') {

            // Block user.
            db_query("UPDATE {users} SET status=0 WHERE uid=%d", $row['uid']);

            // Log out blocked user.
            $account = user_load(array(
              'uid' => $row['uid'],
            ));
            $array = array();
            user_module_invoke('logout', $array, $account);

            // Log this.
            watchdog('ldapsync', 'Disabled LDAP-authentified user %name because the corresponding LDAP account does not exist or is disabled.', array(
              '%name' => $row['name'],
            ));
          }
          $count_orphaned_users++;
        }
      }
    }
  }

  // Send watchdog message with process summary.
  $params = array(
    '@ldap_users' => ldapsync_stats('ldap_users'),
    '@existing_users' => ldapsync_stats('existing_users'),
    '@new_users' => ldapsync_stats('new_users'),
    '@orphaned_users' => $count_orphaned_users,
  );
  $converted = ldapsync_stats('converted');
  $ldap_disabled = ldapsync_stats('ldap_users_disabled');
  $notices = ldapsync_stats('notices');
  $denied_by_module = ldapsync_stats('denied_by_module');
  $summary = t('Completed LDAP sync. LDAP users found: @ldap_users. Existing users updated: @existing_users. New users created: @new_users. LDAP-authentified users that do not have an enabled LDAP account: @orphaned_users.', $params);
  if ($converted) {
    $summary .= ' ' . t('Existing users converted to LDAP: @converted.', array(
      '@converted' => $converted,
    ));
  }
  if ($ldap_disabled) {
    $summary .= ' ' . t('Disabled LDAP users: @disabled.', array(
      '@disabled' => $ldap_disabled,
    ));
  }
  if ($notices) {
    $summary .= ' ' . t('Watchdog notices/warnings written: @notices.', array(
      '@notices' => $notices,
    ));
  }
  if ($denied_by_module) {
    $summary .= ' ' . t('Access denied by other modules: @denied.', array(
      '@denied' => $denied_by_module,
    ));
  }
  watchdog('ldapsync', $summary, NULL);

  // Update last sync time variable, so that we don't sync again until the specified time period passes again.
  variable_set('ldapsync_last_sync_time', time());

  // Useful if calling manually from settings page.
  return $summary;
}

/**
 * Find all LDAP users from servers and OUs specified in ldapauth settings and
 * create or update existing users as needed.
 *
 * @return An array keyed by lower cased Drupal account name of all users found.
 */
function _ldapsync_search() {
  global $_ldapsync_ldap;
  $users = array();

  // Cycle through LDAP configurations.
  $result = db_query("SELECT sid FROM {ldapauth} WHERE status = %d ORDER BY sid", 1);
  while ($row = db_fetch_object($result)) {

    // Initialize LDAP.
    if (!_ldapsync_init($row->sid)) {
      watchdog('ldapsync', 'ldapsync init failed for ldap server %sid.', array(
        '%sid' => $row->sid,
      ));
      continue;
    }

    // Set up for LDAP search.
    $name_attr = $_ldapsync_ldap
      ->getOption('user_attr') ? $_ldapsync_ldap
      ->getOption('user_attr') : LDAPAUTH_DEFAULT_USER_ATTR;
    $user_attr = drupal_strtolower($name_attr);

    // used to find in results.
    $filters = array();
    if (LDAPSYNC_FILTER) {
      $filters = explode("\r\n", LDAPSYNC_FILTER);
    }
    else {
      $filters[] = "({$name_attr}=*)";
    }
    $attrs = ldapauth_attributes_needed(LDAPAUTH_SYNC_CONTEXT_AUTHENTICATE_DRUPAL_USER, $_ldapsync_ldap
      ->getOption('sid'));

    // If there is no bindn and bindpw - the connect will be an anonymous connect.
    $_ldapsync_ldap
      ->connect($_ldapsync_ldap
      ->getOption('binddn'), $_ldapsync_ldap
      ->getOption('bindpw'));

    // Search each basedn defined for this server
    foreach (explode("\r\n", $_ldapsync_ldap
      ->getOption('basedn')) as $base_dn) {
      if (empty($base_dn)) {
        continue;
      }

      // Re-initialize database object each time.
      $ldapresult = array();
      $filter_found_users = array();

      // Search this server and basedn using all defined filters.
      foreach ($filters as $filter) {
        $filter = trim($filter);
        if (empty($filter)) {
          continue;
        }
        if (variable_get('ldapsync_filter_append_default', 0)) {
          $filter = "(&{$filter}({$name_attr}=*))";
        }
        if (!($ldapresult = $_ldapsync_ldap
          ->search($base_dn, $filter, $attrs))) {
          continue;
        }

        // Cycle through results to build array of user information.
        foreach ($ldapresult as $entry) {
          $name = $entry[$user_attr][0];

          // Don't include if no name attribute.
          if (empty($name)) {
            continue;
          }

          // Don't process the same entry found by different filters twice.
          $lcname = drupal_strtolower($name);
          if (isset($filter_found_users[$lcname])) {
            continue;

            // Already found
          }
          $filter_found_users[$lcname] = $name;
          ldapsync_stats('ldap_users', 1);

          // Don't include if LDAP account is disabled.
          $status = $entry['useraccountcontrol'][0];
          if (($status & 2) != 0) {

            // This only works for Active Directory -- search includes disabled accounts in other directories.
            ldapsync_stats('ldap_users_disabled', 1);
            continue;
          }

          // Process this entry to create/update drupal user (if any).
          $account = _ldapsync_process_entry($_ldapsync_ldap, $name, $entry);
          if (!$account) {
            continue;
          }
          $users[drupal_strtolower($account->name)] = array(
            'uid' => $account->uid,
          );
        }
      }
    }
  }
  return $users;
}

/**
 * Take an ldap object entry and determine if there is an existing account or
 * a new account needs to be created.
 *
 * @param LDAPInterface $ldap An initialized LDAP server interface object
 * @param String $name The user name attribute value
 * @param Array $ldap_entry LDAP attributes for user.
 * @return The account object or FALSE if problem
 */
function _ldapsync_process_entry($ldap, $name, $ldap_entry) {

  // check whether user is in an OU mapped in module settings (need to create admin/settings/ldapsync page)
  $dn = $ldap_entry['dn'];
  if ($ldap
    ->getOption('puid_attr')) {
    $puid = ldapauth_extract_puid($server, $name, $ldap_entry);
  }

  // See if there is a matching Drupal user account
  $error = '';
  $account = ldapauth_drupal_user_lookup($ldap, $name, $dn, $error, $puid);
  if ($account === NULL) {
    ldapsync_stats('notices', 1);
    $msg = t('drupal_user_lookup() returned: ') . $error;
    watchdog('ldapsync', $msg, NULL, WATCHDOG_ERROR);
    return FALSE;
  }

  // Handle map by e-mail option (Issue #1209556)
  // If no account or PUID not used and account found does not have matching e-mail
  $user_test_method = variable_get('ldapsync_load_user_by', 'name');
  if ($user_test_method == 'email' && (!$account || !$ldap
    ->getOption('puid_attr') && drupal_strtolower($account->mail) != drupal_strtolower($ldap_entry['mail']))) {
    $account = user_load(array(
      'mail' => $ldap_entry['mail'],
    ));
  }

  // Allow other modules to determine if this ldap user can access server.
  if (ldapauth_user_denied($ldap, $name, $dn, $account)) {
    ldapsync_stats('denied_by_module', 1);
    return;
  }

  // No account found - try to create one
  if (!$account) {
    if (variable_get('ldapsync_existing_only', 0)) {
      return FALSE;
    }
    $error = '';
    $account = ldapauth_drupal_user_create($ldap, $name, $ldap_entry, $error);
    if ($account === FALSE) {
      ldapsync_stats('notices', 1);
      return FALSE;
    }
    ldapsync_stats('new_users', 1);

    // Increment counter
  }
  else {

    // Check authentication method.
    if (!$account->ldap_authentified) {
      $conflict_resolution = LDAPSYNC_LOGIN_CONFLICT;
      if ($conflict_resolution == LDAPSYNC_CONFLICT_FOLLOW_LDAPAUTH) {
        $conflict_resolution = LDAPAUTH_LOGIN_CONFLICT;
      }
      if ($conflict_resolution == LDAPAUTH_CONFLICT_LOG) {
        ldapsync_stats('notices', 1);
        watchdog('ldapsync', 'Could not create ldap-authentified account for user %name because a local user by that %test_value already exists.', array(
          '%name' => $name,
          '%test_value' => $user_test_method,
        ));
        return FALSE;
      }
      else {
        $converted = TRUE;
        ldapsync_stats('converted', 1);
      }
    }

    // Make sure all the information is up to date.
    $drupal_name = ldapauth_drupal_user_name($name, $ldap, $dn);
    $data = array(
      'ldap_dn' => $dn,
      'ldap_config' => $ldap
        ->getOption('sid'),
      'ldap_authentified' => TRUE,
      'authname_ldapauth' => $drupal_name,
      'ldap_name' => $name,
    );

    // Follow ldapauth password sync rules.
    if (LDAPAUTH_LOGIN_PROCESS == LDAPAUTH_AUTH_MIXED && LDAPAUTH_SYNC_PASSWORDS) {
      $data['pass'] = $pass;
    }
    $puid = $account->ldap_puid;

    // save setting from drupal_user_lookupsave.
    $account = user_save($account, $data);

    // Make sure the ldapauth_users info is current (User object may have been moved).
    $user_info = ldapauth_userinfo_load_by_uid($account->uid);
    if (empty($user_info)) {

      // Don't have entry, so make one.
      $user_info = new stdClass();
      $user_info->uid = $account->uid;
    }
    $user_info->sid = $account->ldap_config;
    $user_info->machine_name = $ldap
      ->getOption('machine_name');
    $user_info->dn = $dn;
    $user_info->puid = $puid ? $puid : $account->{$name};
    ldapauth_userinfo_save($user_info);
    if (!$converted) {
      ldapsync_stats('existing_users', 1);
    }
  }

  // Update user's groups if ldapgroups is enabled.
  if (module_exists('ldapgroups')) {
    ldapgroups_user_login($account);
  }

  // Update user's data if ldapdata is enabled.
  if (module_exists('ldapdata')) {
    _ldapdata_user_load($account, TRUE, $ldap_users);
  }

  // Enable any blocked user who is enabled in LDAP.
  if (!$account->status) {
    ldapsync_stats('notices', 1);
    db_query("UPDATE {users} SET status = %d where uid = %d", 1, $account->uid);
    watchdog('ldapsync', 'Enabled LDAP-authentified user %name because the corresponding LDAP account is enabled.', array(
      '%name' => $name,
    ));
  }

  // Reset user specific caches to prevent memory problems
  ldapauth_user_lookup_by_dn(NULL, NULL, NULL, TRUE);
  ldapauth_drupal_user_name(NULL, NULL, NULL, TRUE);
  if (module_exists('ldapgroups')) {
    ldapgroups_groups_load(NULL, NULL, NULL, TRUE);
  }
  return $account;
}

//////////////////////////////////////////////////////////////////////////////

// Auxiliary functions

/**
 * Initiates the LDAPInterfase class.
 *
 * @param $sid
 *   An ID of the LDAP server configuration.
 *
 * @return
 */
function _ldapsync_init($sid) {
  global $_ldapsync_ldap;
  $server = ldapauth_server_load($sid);
  if (!empty($server)) {
    $_ldapsync_ldap = new LDAPInterface();
    $_ldapsync_ldap
      ->setOption('sid', $server->sid);
    $_ldapsync_ldap
      ->setOption('name', $server->name);
    $_ldapsync_ldap
      ->setOption('machine_name', $server->machine_name);
    $_ldapsync_ldap
      ->setOption('server', $server->server);
    $_ldapsync_ldap
      ->setOption('port', $server->port);
    $_ldapsync_ldap
      ->setOption('tls', $server->tls);
    $_ldapsync_ldap
      ->setOption('enc_type', $server->enc_type);
    $_ldapsync_ldap
      ->setOption('basedn', $server->basedn);
    $_ldapsync_ldap
      ->setOption('user_attr', $server->user_attr);
    $_ldapsync_ldap
      ->setOption('mail_attr', $server->mail_attr);
    $_ldapsync_ldap
      ->setOption('puid_attr', $server->puid_attr);
    $_ldapsync_ldap
      ->setOption('binary_puid', $server->binary_puid);
    $_ldapsync_ldap
      ->setOption('binddn', $server->binddn);
    $_ldapsync_ldap
      ->setOption('bindpw', $server->bindpw);
    return $_ldapsync_ldap;
  }
}

/**
 * Statistics counter function to track summary info.
 *
 * @param string $type The statistics category, e.g. new_users, notices, etc.
 * @param int $n number of users to add to category, defaults to 0.
 * @return int current count of users in category;
 */
function ldapsync_stats($type, $n = 0) {
  static $stats = array();
  if (!isset($stats[$type])) {
    $stats[$type] = 0;
  }
  $stats[$type] += $n;
  return $stats[$type];
}

Functions

Namesort descending Description
ldapsync_cron Implements hook_cron().
ldapsync_help Implementation of hook_help().
ldapsync_init Implements hook_init().
ldapsync_menu Implementation of hook_menu().
ldapsync_stats Statistics counter function to track summary info.
_ldapsync_init Initiates the LDAPInterfase class.
_ldapsync_process_entry Take an ldap object entry and determine if there is an existing account or a new account needs to be created.
_ldapsync_search Find all LDAP users from servers and OUs specified in ldapauth settings and create or update existing users as needed.
_ldapsync_sync Main routine.

Constants