LDAP is hard to get working with your Drupal site. Luckily there is a module that helps with that (as there usually is). Unfortunately the module currently only provides the ability to log in through LDAP. You can not check to see if the user exists in LDAP, and if not, use the Drupal database. Many sites may want this functionality and it is quite easy to add.

Please note that all of the code below are HACKS to the ldap_integration module. If you do not feel comfortable hacking modules, do not attempt to write any of this! All of this code was placed in ldapauth.module and inside of the ldapauth_authenticate() function. Also, much of the code below has been slightly modified to remove any information that may cause security implications.

The first thing we want to do is ping LDAP for a user. If the user does not exist in active directory, the module will check to see if they are active in the Drupal user database. If the user is active, it will assure that the user does not have any of the roles that LDAP assigns, specifically the employee and the various departmental roles. To start, scroll down the ldapauth_authenticate() function and find the following conditional statement.

  if (!($dn = _ldapauth_auth($name, $pass))) {
    // If LDAP authentication fails, use Drupal authentication!
  }

This condition runs if LDAP sends back a negative request on the user, meaning it could not find the user in its system. The code placed inside of this conditional statement is quite simple. Grab the information the user submitted through the login form and test it against the Drupal database. The code below will just load the users information if LDAP authentication fails. Comments are place throughout to help understand what is going on.

    $result = db_query("SELECT name, data FROM {users_table} WHERE name='%s'", $name);
    if ($row = db_fetch_array($result)) {
      $data = unserialize($row['data']);
      if (isset($data['ldap_authentified']) && $data['ldap_authentified'] != 0) {
        drupal_set_message('The LDAP server could not find your user. The LDAP server may be down or the password you entered is incorrect. If the problem persists, please contact the webmaster.', 'error');
      }
      else {
        // core user_login_name_validate
        if (isset($form_values['name'])) {
          if (user_is_blocked($form_values['name'])) {
            // blocked in user administration
            form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => $form_values['name'])));
          }
          else if (drupal_is_denied('user', $form_values['name'])) {
            // denied by access controls
            form_set_error('name', t('The name %name is a reserved username.', array('%name' => $form_values['name'])));
          }
        }
 
        // core user_authenticate
        $account = user_load(array('name' => $form_values['name'], 'status' => 1));
        if ($account && drupal_is_denied('mail', $account->mail)) {
          form_set_error('name', t('The name %name is registered using a reserved e-mail address and therefore could not be logged in.', array('%name' => $account->name)));
        }
 
        // Name and pass keys are required.
        // The user is about to be logged in, so make sure no error was previously
        // encountered in the validation process.
        if (!form_get_errors() && !empty($form_values['name']) && !empty($form_values['pass']) && $account) {
          $user = $account;
          user_authenticate_finalize($form_values);
          // If a user was logged in this way and not through LDAP, they should only have basic roles. 
          // Roles are changed in case employees are dropped on the LDAP side, we don't want them to retain the
          // roles on the Drupal side.
          if((in_array('City Employee', $user->roles) || in_array('County Employee', $user->roles)) && !in_array('Super User', $user->roles)) {
            $user->roles = array(
              '101' => 'authenticated user',  // 'role_id' => 'role name'
              '102' => 'Citizen',
            );
            $edit = array('roles' => $user->roles);
            $user = user_save($user, $edit);
            watchdog('user', 'User %user was not found through LDAP and their employee roles were revoked.', array('%user' => $form_values['name']));          
          }
          return $user;
        }
        else {
          watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_values['name']));
        }
 
        // core user_login_final_validate
        if (!$user->uid) {
          form_set_error('name', t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password'))));
        }
        return;
      }
    }    
    return;

Should an employee no longer work at the office, this system will automatically remove the Drupal employee and departmental roles from the website, assuring that the user cannot continue to update website content. In the future, this user id will exclusively be logged in as either a visitor or as an employee through the traditional Drupal authentication system. But what if LDAP authentication doesn't fail? If LDAP auth passes, we still need to make sure they have a Drupal profile. The code below runs if they are in LDAP, but not Drupal

  if (!($dn = _ldapauth_auth($name, $pass))) {
    // The code above...
  }
  if (!$account) {
    // Register this new user.
    if ($ldap_user = _ldapauth_user_lookup($name)) {
      $ldap_roles = unserialize($_ldapauth_ldap->roles);
      // If mail attribute is missing, set the name as mail.
      $init = $mail = key_exists(($_ldapauth_ldap->getOption('mail_attr') ? $_ldapauth_ldap->getOption('mail_attr') : LDAPAUTH_DEFAULT_MAIL_ATTR), $ldap_user) ? $ldap_user[$_ldapauth_ldap->getOption('mail_attr')][0] : $name . '@example.com';
 
      // Check if the e-mail is not denied.
      if (drupal_is_denied('mail', $mail)) {
        form_set_error('name', t('The name %name is registered using a reserved e-mail address and therefore could not be logged in.', array('%name' => $name)));
        return;
      }
 
      // Generate a random drupal password. LDAP password will be used anyways. If the Drupal password 
      // is guessed somehow it will not matter because it never looks at it.
      $pass_new = (LDAPAUTH_LOGIN_PROCESS == LDAPAUTH_AUTH_EXCLUSIVED || !LDAPAUTH_SYNC_PASSWORDS) ? user_password(20) : $pass;
 
      // Get the rid of the imported role, if any.
      $new_dn = preg_replace('/^[CN=](.*?),/', '', $ldap_user['dn']);
      $role_id = db_result(db_query("SELECT rid FROM {roles_table} WHERE name = '%s' LIMIT 1", $ldap_roles[$new_dn]));
 
      $userinfo = array(
        'name' => $name, 
        'pass' => $pass_new, 
        'mail' => $mail, 
        'init' => $init, 
        'status' => 1, 
        'authname_ldapauth' => $name, 
        'ldap_authentified' => TRUE, 
        'ldap_dn' => $ldap_user['dn'], 
        'ldap_config' => $_ldapauth_ldap->getOption('sid'),
      );
      $userinfo['roles'] = array(
        DRUPAL_AUTHENTICATED_RID => 'authenticated user',
        $role_id => $ldap_roles[$new_dn],
        '102' => 'County Employee',
      );
      $user = user_save(null, $userinfo, array('roles' => $new_roles));
      watchdog('ldapauth', 'New external user %name created from the LDAP server %server.', 
      array('%name' => $name, '%server' => $_ldapauth_ldap->getOption('name')), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $user->uid .'/edit'));
    }
  }

Now if an LDAP user is authenticated through LDAP and already has a Drupal profile, all we have to do is make sure that the user roles are up to date. For example, if John Somebody leaves the accountant office and joins the IT office, he should no longer have accountant permissions and should be granted IT permissions.

  else {
    // Login existing user.
    $data = array(
      'ldap_dn' => $dn,
      'ldap_config' => $_ldapauth_ldap->getOption('sid'),
    );
    $ldap_roles = unserialize($_ldapauth_ldap->roles);
 
    // Gets the correct dn by slicing off the CN portion. Only need OU information
    $new_dn = preg_replace('/^[CN=](.*?),/', '', $dn);
 
    if(!in_array($ldap_roles[$new_dn], $account->roles)) {
      // Get the rid of the imported role, if any.
      $role_id = db_result(db_query("SELECT rid FROM {roles_table} WHERE name = '%s' LIMIT 1", $ldap_roles[$new_dn]));
      if(!empty($role_id)) {  
        $account->roles = array(
          DRUPAL_AUTHENTICATED_RID => 'authenticated user',
          $role_id => $ldap_roles[$new_dn],
          '102' => 'County Employee',
        );
      }
      else {
        $account->roles = array(
          DRUPAL_AUTHENTICATED_RID => 'authenticated user',
          '102' => 'County Employee',
        );   
      }
      $edit = array('roles' => $account->roles);
      $account = user_save($account, $edit);
    }
    if (!isset($account->ldap_authentified)) {
      // LDAP and local user conflict.
      if (LDAPAUTH_LOGIN_CONFLICT == LDAPAUTH_CONFLICT_LOG) {
        watchdog('ldapauth', 'LDAP user with DN %dn has a naming conflict with a local drupal user %name', array('%dn' => $dn, '%name' => $account->name), WATCHDOG_ERROR);
        drupal_set_message(t('Another user already exists in the system with the same login name. You should contact the system administrator in order to solve this conflict.'), 'error');
        return;
      }
      else {
        $data['ldap_authentified'] = TRUE;
        $data['authname_ldapauth'] = $name;
      }
    }

Now if LDAP fails, it will use Drupal's database to log the user in. It will act intelligently with roles, making sure they work in sync with the LDAP server. Any users that do not belong to LDAP but are Drupal users will get basic roles.