# HG changeset patch
# User Rich Siomporas
# Date 1311366060 14400
# Node ID 86128f1e9b426596ce84f0d50920612aa9c6a7c5
# Parent  e0a50853231be85a50f0c49a248e983e8beb9739
Got things mostly ported...now it hangs, but its close

diff -r e0a50853231b -r 86128f1e9b42 ldap_authentication/LdapAuthenticationConf.class.php
--- a/ldap_authentication/LdapAuthenticationConf.class.php	Fri Jul 22 09:42:11 2011 -0400
+++ b/ldap_authentication/LdapAuthenticationConf.class.php	Fri Jul 22 16:21:00 2011 -0400
@@ -21,6 +21,9 @@
   public $acctCreation = LDAP_AUTHENTICATION_ACCT_CREATION_DEFAULT;
   public $emailOption = LDAP_AUTHENTICATION_EMAIL_FIELD_DEFAULT;
   public $emailUpdate = LDAP_AUTHENTICATION_EMAIL_UPDATE_ON_LDAP_CHANGE_DEFAULT;
+  public $seamlessLogin = FALSE;
+  public $ldapImplementation = FALSE;
+  public $cookieExpire = LDAP_AUTHENTICATION_COOKIE_EXPIRE;
   public $apiPrefs = array();
   public $createLDAPAccounts; // should an drupal account be created when an ldap user authenticates
   public $createLDAPAccountsAdminApproval; // create them, but as blocked accounts
@@ -51,6 +54,9 @@
     'excludeIfTextInDn',
     'allowTestPhp',
     'excludeIfNoAuthorizations',
+    'seamlessLogin',
+    'ldapImplementation',
+    'cookieExpire',
   );
 
   /** are any ldap servers that are enabled associated with ldap authentication **/
diff -r e0a50853231b -r 86128f1e9b42 ldap_authentication/LdapAuthenticationConfAdmin.class.php
--- a/ldap_authentication/LdapAuthenticationConfAdmin.class.php	Fri Jul 22 09:42:11 2011 -0400
+++ b/ldap_authentication/LdapAuthenticationConfAdmin.class.php	Fri Jul 22 16:21:00 2011 -0400
@@ -90,11 +90,41 @@
     LDAP_AUTHENTICATION_EMAIL_UPDATE_ON_LDAP_CHANGE_DISABLE => t('Don\'t update stored email if LDAP email differs at login.'),
     );
 
+    /**
+   * 5. Single Sign-On / Seamless Sign-On
+   */
+
+  $values['ldapImplementationOptions'] = array(
+    'mod_auth_sspi' => t('mod_auth_sspi'),
+    //'mod_auth_ntlm_winbind' => t('mod_auth_ntlm_winbind'),
+    //'mod_ntlm' => t('mod_ntlm')
+    );
+
+    $values['cookieExpirePeriod'] = array(0 => t('Immediately')) +
+        drupal_map_assoc(array(3600, 86400, 604800, 2592000, 31536000, 315360000), 'format_interval')
+        + array(-1 => t('Never'));
+
+
+    $values['seamlessLogInDescription'] = t('This requires that you '.
+                  'have operational NTLM authentication turned on for at least '.
+                   'the path user/login/sso, or for the whole domain.');
+    $values['cookieExpireDescription'] = t('If using the seamless login, a '.
+                  'cookie is necessary to prevent automatic login after a user '.
+                  'manually logs out. Select the lifetime of the cookie.');
+    $values['ldapImplementationDescription'] = t('Select the type of '.
+                  'authentication mechanism you are using.');
+
+
+
     foreach ($values as $property => $default_value) {
       $this->$property = $default_value;
     }
   }
 
+
+
+
+
   /**
    * 1.  logon options
    */
@@ -137,6 +167,18 @@
   public $emailUpdateDefault = LDAP_AUTHENTICATION_EMAIL_UPDATE_ON_LDAP_CHANGE_ENABLE_NOTIFY;
   public $emailUpdateOptions;
 
+
+   /**
+   * 5. Single Sign-On / Seamless Sign-On
+   */
+
+
+  public $ldapImplementationOptions;
+  public $cookieExpirePeriod;
+  public $seamlessLogInDescription;
+  public $cookieExpireDescription;
+  public $ldapImplementationDescription;
+
   public $errorMsg = NULL;
   public $hasError = FALSE;
   public $errorName = NULL;
@@ -330,6 +372,45 @@
       '#options' => $this->emailUpdateOptions,
       );
 
+
+    /**
+     *
+     * For the single sign-on settings, Oo-styled array key names will be
+     * omitted, and instead classic procedural-style naming will be used. This
+     * is primarily because these settings need to be accessible during
+     * hook_boot before the application is fully bootstrapped, so these settings
+     * must be stored and be accessible via variable_get(), not from
+     * LdapAuthenticationConfAdmin()
+     *
+     */
+    $form['sso'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Single Sign-On'),
+      '#collapsible' => TRUE,
+      '#collapsed' => FALSE,
+    );
+
+    $form['sso']['seamlessLogin'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Turn on automated single sign-on'),
+    '#description' => t($this->seamlessLogInDescription),
+    '#default_value' => $this->seamlessLogin,
+    );
+    $form['sso']['cookieExpire'] = array(
+      '#type' => 'select',
+      '#title' => t('Cookie Lifetime'),
+      '#description' => t($this->cookieExpireDescription),
+      '#default_value' => $this->cookieExpire,
+      '#options' => $this->cookieExpirePeriod,
+    );
+    $form['sso']['ldapImplementation'] = array(
+      '#type' => 'select',
+      '#title' => t('Authentication Mechanism'),
+      '#description' => t($this->ldapImplementationDescription),
+      '#default_value' => $this->ldapImplementation,
+      '#options' => $this->ldapImplementationOptions,
+    );
+
     $form['submit'] = array(
       '#type' => 'submit',
       '#value' => 'Save',
@@ -372,7 +453,9 @@
     $this->excludeIfNoAuthorizations = ($values['excludeIfNoAuthorizations']) ? (int)$values['excludeIfNoAuthorizations'] : NULL;
     $this->emailOption  = ($values['emailOption']) ? (int)$values['emailOption'] : NULL;
     $this->emailUpdate  = ($values['emailUpdate']) ? (int)$values['emailUpdate'] : NULL;
-
+    $this->seamlessLogin = ($values['seamlessLogin']) ? (int)$values['seamlessLogin'] : NULL;
+    $this->cookieExpire = ($values['cookieExpire']) ? (int)$values['cookieExpire'] : NULL;
+    $this->ldapImplementation = ($values['ldapImplementation']) ? (string)$values['ldapImplementation'] : NULL;
   }
 
   public function drupalFormSubmit($values) {
diff -r e0a50853231b -r 86128f1e9b42 ldap_authentication/ldap_authentication.inc
--- a/ldap_authentication/ldap_authentication.inc	Fri Jul 22 09:42:11 2011 -0400
+++ b/ldap_authentication/ldap_authentication.inc	Fri Jul 22 16:21:00 2011 -0400
@@ -117,8 +117,19 @@
   $detailed_watchdog_log = variable_get('ldap_help_watchdog_detail', 0);
   $name = $form_state['values']['name'];
   $pass = $form_state['values']['pass'];
+  
+  /*
+   * If a fake form state was passed into this function from 
+   * _ldap_authentication_user_login_sso(), there will be a value outside of the
+   * form_state[values] array to let us know that we are not authenticating with
+   * a password, but instead just looking up a username/dn in LDAP since the web
+   * server already authenticated the user.
+   */
+  $sso_login = $form_state['sso_login'];
   $watchdog_tokens = array('%username' => $name);
-
+  if ($detailed_watchdog_log) {
+    watchdog('ldap_authentication', '%username : Beginning authentification....', $watchdog_tokens, WATCHDOG_DEBUG);
+  }
   if (!$auth_conf = ldap_authentication_get_valid_conf()) {
     watchdog('ldap_authentication', 'Failed to get valid ldap authentication configuration.', array(), WATCHDOG_ERROR);
     form_set_error('name', 'Server Error: Failed to get valid ldap authentication configuration.' . $error);
@@ -182,6 +193,7 @@
       watchdog('ldap_authentication', '%username : Existing Drupal User Account not found.  Continuing on to attempt ldap authentication', $watchdog_tokens, WATCHDOG_DEBUG);
     }
   }
+  //Deep, deep, deep breath...
   foreach ($auth_conf->servers as $sid => $ldap_server) {
     $watchdog_tokens['%sid'] = $sid;
     $watchdog_tokens['%bind_method'] = $ldap_server->bind_method;
@@ -213,9 +225,42 @@
         $search = array('%basedn', '%username');
         $replace = array($basedn, $name);
         $userdn = str_replace($search, $replace, $ldap_server->user_dn_expression);
-        $result = $ldap_server->bind($userdn, $pass);
-        if ($result == LDAP_SUCCESS) {
-          break;
+        
+        /*
+         * If we have $sso_login passed in as true from the fake form state in
+         * passed from _ldap_authentication_user_login_sso(), we will be relying
+         * on the webserver for actually authenticating the user, either by NTLM
+         * or user/password if configured as a fallback. Since the webserver has
+         * already authenticated the user, and the web server only contains the
+         * user's LDAP user name, instead of binding on the username/pass, we 
+         * simply look up the user's account in LDAP, and make sure it matches
+         * what is contained in the global $_SERVER array populated by the web
+         * server authentication. Basically anywhere that queries for a password
+         */
+
+        if($sso_login){
+          $result = $ldap_server->user_lookup($userdn);
+          if ($detailed_watchdog_log) {
+            $watchdog_tokens['%result'] = var_export($result, true);
+            watchdog('ldap_authentication', '%username : attempting single sign-on
+              login in bind_method of LDAP_SERVERS_BIND_METHOD_USER. Result of
+              user_lookup: <pre>%result</pre>', $watchdog_tokens, WATCHDOG_DEBUG);
+          }
+          if($result){
+            if ($detailed_watchdog_log) {
+              watchdog('ldap_authentication', '%username : Result in bind_method
+                of LDAP_SERVERS_BIND_METHOD_USER returns LDAP_SUCCESS',
+                $watchdog_tokens, WATCHDOG_DEBUG);
+            }
+            $result = LDAP_SUCCESS;
+            break;
+          }
+        }
+        else{
+          $result = $ldap_server->bind($userdn, $pass);
+          if ($result == LDAP_SUCCESS) {
+            break;
+          }
         }
       }
     }
@@ -228,10 +273,26 @@
         $result = LDAP_FAIL;
         foreach ($ldap_server->basedn as $basedn) {
           $userdn = $ldap_server->user_lookup($name);
-          $result = $ldap_server->bind($userdn, $pass);
-          if ($result == LDAP_SUCCESS) {
-            break;
+          if($sso_login){
+            if($userdn){
+              if ($detailed_watchdog_log) {
+                $watchdog_tokens['%sso_userdn'] = var_export($result, true);
+                watchdog('ldap_authentication', '%username : attempting single sign-on
+                  login in bind_method of LDAP_SERVERS_BIND_METHOD_ANON_USER. Result of
+                  user_lookup: <pre>%sso_userdn</pre>, and a success',
+                  $watchdog_tokens, WATCHDOG_DEBUG);
+              }
+              $result = LDAP_SUCCESS;
+              break;
+            }
           }
+          else {
+            $result = $ldap_server->bind($userdn, $pass);
+            if ($result == LDAP_SUCCESS) {
+              break;
+            }
+          }
+
         }
       }
     }
@@ -276,19 +337,33 @@
     /**
     * test password
     */
-    $result = $ldap_server->bind($ldap_user['dn'], $pass);
-    if ($result != LDAP_SUCCESS) {
-      if ($detailed_watchdog_log) {
-        $watchdog_tokens['%err_text'] = $ldap_server->errorMsg('ldap');
-        watchdog('ldap_authentication', '%username : Testing user credentials on server %sid where bind_method = %bind_method.  Error: %err_text', $watchdog_tokens, WATCHDOG_DEBUG);
-        $watchdog_tokens['%err_text'] = NULL;
+    if(!$sso_login){
+      $result = $ldap_server->bind($ldap_user['dn'], $pass);
+      if ($result != LDAP_SUCCESS) {
+        if ($detailed_watchdog_log) {
+          $watchdog_tokens['%err_text'] = $ldap_server->errorMsg('ldap');
+          watchdog('ldap_authentication', '%username : Testing user credentials on server %sid where bind_method = %bind_method.  Error: %err_text', $watchdog_tokens, WATCHDOG_DEBUG);
+          $watchdog_tokens['%err_text'] = NULL;
+        }
+        $authentication_result = LDAP_AUTHENTICATION_RESULT_FAIL_CREDENTIALS;
+        continue; // next server, please
       }
-      $authentication_result = LDAP_AUTHENTICATION_RESULT_FAIL_CREDENTIALS;
-      continue; // next server, please
+      else {
+        $authentication_result = LDAP_AUTHENTICATION_RESULT_SUCCESS;
+        if ($detailed_watchdog_log) {
+          watchdog('ldap_authentication', '%username : $authentication_result is
+            LDAP_AUTHENTICATION_RESULT_SUCCESS',
+            $watchdog_tokens, WATCHDOG_DEBUG);
+        }
+        break; //success
+      }
     }
-    else {
-      $authentication_result = LDAP_AUTHENTICATION_RESULT_SUCCESS;
-      break; //success
+    else{
+      if($ldap_server->user_lookup($ldap_user['dn'])){
+        $authentication_result = LDAP_AUTHENTICATION_RESULT_SUCCESS;
+      }
+      else
+        $authentication_result = LDAP_AUTHENTICATION_RESULT_FAIL_CREDENTIALS;
     }
   }  // end loop through servers
   $watchdog_tokens['%result'] = $result;
@@ -462,3 +537,87 @@
   }
 
 }
+
+
+
+
+
+/**
+ * A proxy function for the actual authentication routine. This is in place
+ * so various implementations of grabbing NTLM credentials can be used and
+ * selected from an administration page. This is the real gatekeeper since
+ * this assumes that any NTLM authentication from the underlying web server
+ * is good enough, and only checks that there are values in place for the
+ * user name, and anything else that is set for a particular implementation. In
+ * the case that there is no credentials set by the underlying web server, the
+ * user is redirected to the normal user login form.
+ *
+ * @return false
+ */
+function _ldap_authentication_user_login_sso() {
+  $auth_conf = ldap_authentication_get_valid_conf();
+  $implementation = $auth_conf->ldapImplementation;
+  switch($implementation) {
+    case 'mod_auth_sspi' :
+      if(isset($_SERVER['REMOTE_USER']))
+        $remote_user = $_SERVER['REMOTE_USER'];
+      elseif(isset($_SERVER['REDIRECT_REMOTE_USER']))
+        $remote_user = $_SERVER['REDIRECT_REMOTE_USER'];
+      else
+        $remote_user = false;
+      break;
+  }
+
+  if($remote_user){
+    watchdog('ldap_authentication', '%username : $_SERVER[\'REMOTE_USER\'] found',
+            array('%username' => $remote_user), WATCHDOG_DEBUG);
+    $fake_form_state = array(
+      'values' => array(
+        'name' => check_plain($remote_user),
+        'pass' => user_password(20),
+      ),
+      'sso_login' => true,
+    );
+    $user = _ldap_authentication_user_login_authenticate_validate($fake_form_state);
+    if($user && $user->uid > 0){
+      if($auth_conf->seamlessLogin == 1){
+        setcookie("seamless_login", 'auto login', time() + $auth_conf->cookieExpire, base_path(), "");
+        $_SESSION['seamless_login'] = 'auto login';
+        setcookie("seamless_login_attempted", '');
+        unset($_SESSION['seamless_login_attempted']);
+
+    }
+      drupal_set_message(theme('ldap_authentication_login_message',
+                                array('message' => t('You have been successfully authenticated'))));
+      drupal_goto('home');
+    }
+    else{
+      if($auth_conf->seamlessLogin == 1){
+        setcookie("seamless_login", 'do not auto login', time() + $auth_conf->cookieExpire, base_path(), "");
+        $_SESSION['seamless_login'] = 'do not auto login';
+    }
+      drupal_set_message(theme('ldap_authentication_message_not_found',
+                                 array('message' => t('Sorry, your LDAP credentials were not found, '.
+                                 'or the LDAP is not available. You may log in '.
+                                  'with other credentials on the !user_login_form.',
+                                   array('!user_login_form' => l(t('user login form'), 'user/login'))))
+                              ), 'error');
+      drupal_goto('user/login');
+    }
+  }
+  else{
+    watchdog('ldap_authentication', '$_SERVER[\'REMOTE_USER\'] not found',
+            array(), WATCHDOG_DEBUG);
+    if($auth_conf->seamlessLogin == 1){
+      setcookie("seamless_login", 'do not auto login', time() + $auth_conf->cookieExpire, base_path(), "");
+      $_SESSION['seamless_login'] = 'do not auto login';
+    }
+    drupal_set_message(theme('ldap_authentication_message_not_authenticated',
+                               array('message' =>
+                                   t('You were not authenticated by the server.
+                                     You may log in with your credentials below.')
+                                 )
+                              ), 'error');
+    drupal_goto('user/login');
+  }
+}
\ No newline at end of file
diff -r e0a50853231b -r 86128f1e9b42 ldap_authentication/ldap_authentication.module
--- a/ldap_authentication/ldap_authentication.module	Fri Jul 22 09:42:11 2011 -0400
+++ b/ldap_authentication/ldap_authentication.module	Fri Jul 22 16:21:00 2011 -0400
@@ -47,6 +47,7 @@
 define('LDAP_AUTHENTICATION_HELP_LINK_TEXT_DEFAULT', 'Logon Help');
 
 define('LDAP_AUTHENTICATION_DISABLED_FOR_BAD_CONF_MSG' , 'The site logon is currently not working due to a configuration error.  Please see logs for additional details.');
+define('LDAP_AUTHENTICATION_COOKIE_EXPIRE', -1);
 
 /**
  * Implements hook_menu().
@@ -64,7 +65,12 @@
     'weight' => 2,
     'file' => 'ldap_authentication.admin.inc',
   );
-
+  $items['user/login/sso'] = array(
+    'title' => t('Log In'),
+    'page callback' => 'ldap_authentication_user_login_sso',
+    'access callback' => '_ldap_authentication_user_access',
+    'type' => MENU_NORMAL_ITEM,
+  );
   return $items;
 }
 
@@ -106,6 +112,21 @@
       'render element' => 'element',
       'file' => 'ldap_authentication.theme.inc'
     ),
+    'ldap_authentication_login_message' => array(
+      'render element' => 'element',
+      'variables' => array('message' => NULL),
+      'file' => 'ldap_authentication.theme.inc'
+    ),
+    'ldap_authentication_message_not_found' => array(
+      'render element' => 'element',
+      'variables' => array('message' => NULL),
+      'file' => 'ldap_authentication.theme.inc'
+    ),
+    'ldap_authentication_message_not_authenticated' => array(
+      'render element' => 'element',
+      'variables' => array('message' => NULL),
+      'file' => 'ldap_authentication.theme.inc'
+    ),
   );
 }
 
@@ -176,6 +197,19 @@
 
 
 /**
+ * A user access callback for using the single sign-on URL, denying access to
+ * authenticated users, and granting access to anonymous users and menu
+ * administrators viewing the menu item.
+ *
+ */
+function _ldap_authentication_user_access() {
+  if (!$GLOBALS['user']->uid || !empty($GLOBALS['menu_admin']))
+    return true;
+  else
+    return false;
+}
+
+/**
  * get LdapAuthenticationConf object
  *
  * @return object LdapAuthenticationConf object if configured, otherwise FALSE
@@ -328,10 +362,25 @@
  *
  */
 
-function ldap_authentication_user_login(&$edit, $account) {
-
+function ldap_authentication_user_login_sso() {
+  require_once('ldap_authentication.inc');
+  _ldap_authentication_user_login_sso();
 }
 
+/**
+ * Implements hook_user_logout().
+ *
+ * The user just logged out.
+ *
+ */
+
+function ldap_authentication_user_logout($account) {
+  $auth_conf = ldap_authentication_get_valid_conf();
+  if($auth_conf->seamlessLogin == 1){
+    setcookie("seamless_login", 'do not auto login', time() + (int)$auth_conf->cookieExpire, base_path(), "");
+    $_SESSION['seamless_login'] = $_COOKIE['seamless_login'];
+  }
+}
 
 /**
  * Implements hook_user_presave().
@@ -341,6 +390,39 @@
 function ldap_authentication_user_presave(&$edit, $account, $category = NULL) {
 
 }
+/**
+ * Implements hook_boot().
+ *  Perform setup tasks. This entry point is used because hook_user_load no
+ *  longer runs on anonymous users, and hook_boot is guaranteed to run,
+ *  regardless of cache
+ */
+function ldap_authentication_boot() {
+  if($GLOBALS['user']->uid == 0){
+    //Full bootstrap needed to load common.inc for redirects
+    drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+    $auth_conf = ldap_authentication_get_valid_conf();
+    if(!(isset($_COOKIE['seamless_login'])) || $_COOKIE['seamless_login'] == 'auto login'){
+      if((arg(0) == 'user' && !(is_numeric(arg(1)))) || arg(0) == 'logout' ){
+        return;
+      }
+      else{
+        if(isset($_COOKIE['seamless_login_attempted']))
+          $login_attempted = $_COOKIE['seamless_login_attempted'];
+        else
+          $login_attempted = false;
+        if($auth_conf->seamlessLogin == 1 && ($login_attempted != 'true')){
+          setcookie("seamless_login_attempted", 'true', time() + (int)$auth_conf->cookieExpire, base_path(), "");
+          $_SESSION['seamless_login_attempted'] = $login_attempted;
+          drupal_goto('user/login/sso', array('query' => array('destination' => rawurlencode($_GET['q']))));
+        }
+        else{
+          return;
+        }
+      }
+    }
+  }
+}
+

diff -r e0a50853231b -r 86128f1e9b42 ldap_authentication/ldap_authentication.theme.inc
--- a/ldap_authentication/ldap_authentication.theme.inc	Fri Jul 22 09:42:11 2011 -0400
+++ b/ldap_authentication/ldap_authentication.theme.inc	Fri Jul 22 16:21:00 2011 -0400
@@ -86,3 +86,49 @@
   }
   return $msg;
 }
+
+
+
+
+/**
+ * The following three functions are theme callbacks for various messages
+ * from NTLM/seamless login integration.
+ *
+ * Provides a theme callback for successful login messages. The reason for 
+ * using theme callbacks instead of a simple t() function is to provide the 
+ * ability to have more complex message handling performed; an example would
+ * be to use the Real Name module to say "Welcome, User Name" upon successful
+ * login.
+ * @param $message
+ *   A text string containing a translatable success message
+ *
+ * @ingroup themeable
+ */
+function theme_ldap_authentication_login_message($variables){
+  extract($variables);
+  return $message;
+}
+
+/**
+ * Provides a theme callback for user not found messages.
+ * @param $message
+ *   A text string containing a translatable "user not found" message
+ *
+ * @ingroup themeable
+ */
+function theme_ldap_authentication_message_not_found($variables){
+  extract($variables);
+  return $message;
+}
+
+/**
+ * Provides a theme callback for authentication failure messages.
+ * @param $message
+ *   A text string containing a translatable "authentication failure" message
+ *
+ * @ingroup themeable
+ */
+function theme_ldap_authentication_message_not_authenticated($variables){
+  extract($variables);
+  return $message;
+}
\ No newline at end of file

