From ad0c56cb84bdafed667ca73cb3b4021c9babd036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Oudot?= Date: Mon, 5 Oct 2015 14:07:24 +0000 Subject: [PATCH] Manage password expiration for AD authentication backend (#822) --- .../lib/Lemonldap/NG/Portal/AuthAD.pm | 16 +++- .../lib/Lemonldap/NG/Portal/_LDAP.pm | 83 ++++++++++++++++++- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/AuthAD.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/AuthAD.pm index 47b637338..d96b64d05 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/AuthAD.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/AuthAD.pm @@ -7,7 +7,7 @@ package Lemonldap::NG::Portal::AuthAD; use strict; -our $VERSION = '1.4.0'; +our $VERSION = '1.4.6'; use Lemonldap::NG::Portal::Simple; use base qw(Lemonldap::NG::Portal::AuthLDAP); @@ -22,6 +22,8 @@ sub authInit { $self->{ldapExportedVars}->{_AD_pwdLastSet} = 'pwdLastSet'; $self->{ldapExportedVars}->{_AD_userAccountControl} = 'userAccountControl'; + $self->{ldapExportedVars}->{_AD_msDS_UACC} = + 'msDS-User-Account-Control-Computed'; return $self->SUPER::authInit(); } @@ -39,10 +41,20 @@ sub authenticate { # Check specific AD attributes my $pls = $self->{entry}->get_value('pwdLastSet'); + my $computed = + $self->{entry}->get_value('msDS-User-Account-Control-Computed'); + my $mask = 0xf00000; # mask to get the 8 at 6th position + my $expired_flag = + 0x800000; # 8 at 6th position for flag UF_PASSWORD_EXPIRED to be set + if ( ( $computed & $mask ) == $expired_flag ) { + $self->lmLog( "[AD] Password has expired", 'warn' ); + $res = PE_PP_PASSWORD_EXPIRED; + } # Password must be changed if pwdLastSet 0 if ( $pls == 0 ) { - $self->lmLog( "[AD] User must change its password", 'debug' ); + $self->lmLog( "[AD] Password reset. User must change his password", + 'warn' ); $res = PE_PP_CHANGE_AFTER_RESET; } diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/_LDAP.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/_LDAP.pm index 479d10119..5ed54f420 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/_LDAP.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/_LDAP.pm @@ -146,6 +146,59 @@ sub loadPP { # @return Lemonldap::NG portal error code sub userBind { my $self = shift; + + # Checking password expired warning + if ( $self->{portal}->{authentication} eq 'AD' ) { + + # Getting password max age (delay) + my $ADPwdMaxAge = $self->{portal}->{ADPwdMaxAge} || 0; + $ADPwdMaxAge *= 10000000; # padding with '0' to obtain 0.1 micro-seconds + + # Getting password expiration warning time (delay) + my $ADPwdExpireWarning = $self->{portal}->{ADPwdExpireWarning} || 0; + $ADPwdExpireWarning *= + 10000000; # padding with '0' to obtain 0.1 micro-seconds + + if ( $ADPwdExpireWarning > $ADPwdMaxAge ) { + $ADPwdExpireWarning = $ADPwdMaxAge; + $self->{portal}->lmLog( +"Error: ADPwdExpireWarning > ADPwdMaxAge, this should not happen", + 'warn' + ); + } + + # Compute current timestamp in AD format (date) + my $time = time; # unix timestamp (seconds since Jan 01 1970) + my $a_time = + $time + 11644473600; # adding difference (in s) from Jan 01 1601 + my $timestamp = + $a_time . '0000000'; # padding with '0' to obatin 0.1 micro-seconds + + # Compute password expiration time (date) + my $_pwdExpire = + $self->{portal}->{sessionInfo}->{_AD_pwdLastSet} || $timestamp; + $_pwdExpire += $ADPwdMaxAge; + +# computing when the warning message is displayed on portal (date - delay = date) + my $_pwdWarning = $_pwdExpire - $ADPwdExpireWarning; + + if ( $timestamp > $_pwdWarning && $timestamp < $_pwdExpire ) { + + # calculating remaining time before password expiration + my $remainingTime = $_pwdExpire - $timestamp; + $self->{portal}->info( + "

" + . sprintf( + $self->{portal}->msg(PM_PP_EXP_WARNING), + $self->{portal}->convertSec( + substr( $remainingTime, 0, length($remainingTime) - 7 ) + ) + ) + . "

" + ); + } + } + if ( $self->{portal}->{ldapPpolicyControl} ) { # Create Control object @@ -318,9 +371,33 @@ sub userModifyPassword { # Check old password with a bind $mesg = $self->bind( $dn, password => $oldpassword ); - if ( $mesg->code != 0 ) { - $self->{portal}->lmLog( "Bad old password", 'debug' ); - return PE_BADOLDPASSWORD; + + # For AD password expiration to work: + # ppolicy must be desactivated, + # and "change as user" must be desactivated + if ($ad) { + if ( $mesg->error =~ /LdapErr: .* data ([^,]+),.*/ ) { + +# extended data message code: +# 532: password expired (but provided password is correct) +# 773: must change password at next connection (but provided password is correct) +# 52e: password is incorrect + unless ( ( $1 eq '532' ) || ( $1 eq '773' ) ) { + $self->{portal} + ->lmLog( "Bad old password", 'warn' ); + return PE_BADOLDPASSWORD; + } + } + + # if error message has not been catched, then it IS a success + } + else + { # this is not AD, a 0 error code means good old password + if ( $mesg->code != 0 ) { + $self->{portal} + ->lmLog( "Bad old password", 'warn' ); + return PE_BADOLDPASSWORD; + } } # Rebind as Manager only if user is not granted to change its password