Skip to content

Commit

Permalink
MDL-28585 LDAP Auth doesn't handle password expiration
Browse files Browse the repository at this point in the history
All credit goes to Mark Ward for proposing the initial patch.

Signed-off-by: Iñaki Arenaza <[email protected]>
  • Loading branch information
iarenaza committed Dec 13, 2012
1 parent 4bd6f71 commit cd37c1d
Showing 1 changed file with 52 additions and 5 deletions.
57 changes: 52 additions & 5 deletions auth/ldap/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@
define('AUTH_NTLM_DEFAULT_FORMAT', '%domain%\\%username%');
}

// Allows us to retrieve a diagnostic message in case of LDAP operation error
if (!defined('LDAP_OPT_DIAGNOSTIC_MESSAGE')) {
define('LDAP_OPT_DIAGNOSTIC_MESSAGE', 0x0032);
}

require_once($CFG->libdir.'/authlib.php');
require_once($CFG->libdir.'/ldaplib.php');

Expand Down Expand Up @@ -192,11 +197,28 @@ function user_login($username, $password) {

// Try to bind with current username and password
$ldap_login = @ldap_bind($ldapconnection, $ldap_user_dn, $extpassword);
$this->ldap_close();
if ($ldap_login) {
return true;

// If login fails and we are using MS Active Directory, retrieve the diagnostic
// message to see if this is due to an expired password, or that the user is forced to
// change the password on first login. If it is, only proceed if we can change
// password from Moodle (otherwise we'll get stuck later in the login process).
if (!$ldap_login && ($this->config->user_type == 'ad')
&& $this->can_change_password()
&& (!empty($this->config->expiration) and ($this->config->expiration == 1))) {

// We need to get the diagnostic message right after the call to ldap_bind(),
// before any other LDAP operation.
ldap_get_option($ldapconnection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $diagmsg);

if ($this->ldap_ad_pwdexpired_from_diagmsg($diagmsg)) {
// If login failed because user must change the password now or the
// password has expired, let the user in. We'll catch this later in the
// login process when we explicitly check for expired passwords.
$ldap_login = true;
}
}
return false;
$this->ldap_close();
return $ldap_login;
}

/**
Expand Down Expand Up @@ -593,7 +615,7 @@ function password_expire($username) {
$info = ldap_get_entries_moodle($ldapconnection, $sr);
if (!empty ($info)) {
$info = array_change_key_case($info[0], CASE_LOWER);
if (!empty($info[$this->config->expireattr][0])) {
if (isset($info[$this->config->expireattr][0])) {
$expiretime = $this->ldap_expirationtime2unix($info[$this->config->expireattr][0], $ldapconnection, $user_dn);
if ($expiretime != 0) {
$now = time();
Expand Down Expand Up @@ -2123,4 +2145,29 @@ protected function get_ntlm_remote_user($remoteuser) {
return '';
}

/**
* Check if the diagnostic message for the LDAP login error tells us that the
* login is denied because the user password has expired or the password needs
* to be changed on first login (using interactive SMB/Windows logins, not
* LDAP logins).
*
* @param string the diagnostic message for the LDAP login error
* @return bool true if the password has expired or the password must be changed on first login
*/
protected function ldap_ad_pwdexpired_from_diagmsg($diagmsg) {
// The format of the diagnostic message is (actual examples from W2003 and W2008):
// "80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 52e, vece" (W2003)
// "80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 773, vece" (W2003)
// "80090308: LdapErr: DSID-0C0903AA, comment: AcceptSecurityContext error, data 52e, v1771" (W2008)
// "80090308: LdapErr: DSID-0C0903AA, comment: AcceptSecurityContext error, data 773, v1771" (W2008)
// We are interested in the 'data nnn' part.
// if nnn == 773 then user must change password on first login
// if nnn == 532 then user password has expired
$diagmsg = explode(',', $diagmsg);
if (preg_match('/data (773|532)/i', trim($diagmsg[2]))) {
return true;
}
return false;
}

} // End of the class

0 comments on commit cd37c1d

Please sign in to comment.