Merge branch '2178-new' into 'v2.0'

Make require old password option configurable by a rule

See merge request lemonldap-ng/lemonldap-ng!143
This commit is contained in:
Christophe Maudoux 2020-04-28 18:42:51 +02:00
commit b3b354853b
10 changed files with 58 additions and 19 deletions

View File

@ -24,7 +24,7 @@ use constant MANAGERSECTION => "manager";
use constant SESSIONSEXPLORERSECTION => "sessionsExplorer";
use constant APPLYSECTION => "apply";
our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|macro)s|o(?:idc(?:S(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar|Macro)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option|Macro)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|c(?:as(?:A(?:ppMetaData(?:(?:ExportedVar|Option|Macro)s|Node)|ttributes)|S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions))|(?:ustom(?:Plugins|Add)Param|ombModule)s)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/;
our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|kipRenewConfirmation|fRemovedUseNotif|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:Allow(?:PasswordGrant|Offline)|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|RequireOldPassword|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|o(?:ntextSwitchingStopWithLogout|mpactConf|rsEnabled)|heck(?:State|User|XSS)|da)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|re(?:st(?:(?:Session|Config)Server|ExportSecretKeys)|freshSessions)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|g(?:roupsBeforeMacros|lobalLogoutTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs))$/;
our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|kipRenewConfirmation|fRemovedUseNotif|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:Allow(?:PasswordGrant|Offline)|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|o(?:ntextSwitchingStopWithLogout|mpactConf|rsEnabled)|heck(?:State|User|XSS)|da)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|re(?:st(?:(?:Session|Config)Server|ExportSecretKeys)|freshSessions)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|g(?:roupsBeforeMacros|lobalLogoutTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs))$/;
our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' );

View File

@ -2610,7 +2610,7 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
},
'portalRequireOldPassword' => {
'default' => 1,
'type' => 'bool'
'type' => 'boolOrExpr'
},
'portalSkin' => {
'default' => 'bootstrap',

View File

@ -1425,8 +1425,8 @@ sub attributes {
},
portalRequireOldPassword => {
default => 1,
type => 'bool',
documentation => 'Old password is required to change the password',
type => 'boolOrExpr',
documentation => 'Rule to require old password to change the password',
},
hideOldPassword => {
default => 0,

File diff suppressed because one or more lines are too long

View File

@ -266,20 +266,21 @@ sub userBind {
return PE_BADCREDENTIALS;
}
## @method int userModifyPassword(string dn, string newpassword, string oldpassword, boolean ad)
## @method int userModifyPassword(string dn, string newpassword, string oldpassword, boolean ad, boolean requireOldPassword)
# Change user's password.
# @param $dn DN
# @param $newpassword New password
# @param $oldpassword Current password
# @param $ad Active Directory mode
# @param $requireOldPassword Old password is needed to update
# @return Lemonldap::NG::Portal constant
sub userModifyPassword {
my ( $self, $dn, $newpassword, $oldpassword, $ad ) = @_;
my $ppolicyControl = $self->{conf}->{ldapPpolicyControl};
my $setPassword = $self->{conf}->{ldapSetPassword};
my $asUser = $self->{conf}->{ldapChangePasswordAsUser};
my $requireOldPassword = $self->{conf}->{portalRequireOldPassword};
my $passwordAttribute = "userPassword";
my ( $self, $dn, $newpassword, $oldpassword, $ad, $requireOldPassword ) =
@_;
my $ppolicyControl = $self->{conf}->{ldapPpolicyControl};
my $setPassword = $self->{conf}->{ldapSetPassword};
my $asUser = $self->{conf}->{ldapChangePasswordAsUser};
my $passwordAttribute = "userPassword";
my $err;
my $mesg;

View File

@ -10,7 +10,8 @@ use Mouse;
use JSON;
use URI;
has skinRules => ( is => 'rw' );
has skinRules => ( is => 'rw' );
has requireOldPwd => ( is => 'rw', default => sub { 1 } );
sub displayInit {
my ($self) = @_;
@ -29,6 +30,13 @@ sub displayInit {
}
}
}
my $rule = HANDLER->buildSub(
HANDLER->substitute( $self->conf->{portalRequireOldPassword} ) );
unless ($rule) {
my $error = HANDLER->tsv->{jail}->error || '???';
$self->logger->error( "Bad requireOldPwd rule: " . $error );
}
$self->requireOldPwd($rule);
}
# Call portal process and set template parameters
@ -143,7 +151,7 @@ sub display {
CHOICE_VALUE => $req->data->{_authChoice},
FORM_METHOD => $method,
(
(not $req->{urldc}) ? ( SEND_PARAMS => 1 )
( not $req->{urldc} ) ? ( SEND_PARAMS => 1 )
: ()
),
(
@ -222,7 +230,7 @@ sub display {
LOGOUT_URL => $self->conf->{portal} . "?logout=1",
APPSLIST_ORDER => $req->{sessionInfo}->{'_appsListOrder'},
PING => $self->conf->{portalPingInterval},
REQUIRE_OLDPASSWORD => $self->conf->{portalRequireOldPassword},
REQUIRE_OLDPASSWORD => $self->requireOldPwd->($req, $req->userData),
HIDE_OLDPASSWORD => 0,
DISPLAY_PPOLICY => $self->conf->{portalDisplayPasswordPolicy},
PPOLICY_MINSIZE => $self->conf->{passwordPolicyMinSize},

View File

@ -33,6 +33,19 @@ sub modifyPassword {
$self->logger->error('"dn" is not set, aborting password modification');
return PE_ERROR;
}
my $rule = $self->p->HANDLER->buildSub(
$self->p->HANDLER->substitute(
$self->conf->{portalRequireOldPassword}
)
);
unless ($rule) {
my $error = $self->p->HANDLER->tsv->{jail}->error || '???';
}
my $requireOldPassword = (
$req->userData
? $rule->( $req, $req->userData )
: $rule->( $req, $req->sessionInfo )
);
# Ensure connection is valid
$self->bind;
@ -41,7 +54,7 @@ sub modifyPassword {
# Call the modify password method
my $code =
$self->ldap->userModifyPassword( $dn, $pwd, $req->data->{oldpassword},
1 );
1, $requireOldPassword );
unless ( $code == PE_PASSWORD_OK ) {
return $code;

View File

@ -42,8 +42,14 @@ sub _modifyPassword {
return PE_PASSWORD_MISMATCH
unless ( $req->data->{newpassword} eq $req->param('confirmpassword') );
my $rule =
$self->p->HANDLER->buildSub( $self->p->HANDLER->substitute( $self->conf->{portalRequireOldPassword} ) );
unless ($rule) {
my $error = $self->p->HANDLER->tsv->{jail}->error || '???';
}
# Check if portal require old password
if ( $self->conf->{portalRequireOldPassword} or $requireOldPwd ) {
if ( $rule->($req, $req->userData) or $requireOldPwd ) {
# TODO: verify oldpassword
unless ( $req->data->{oldpassword} = $req->param('oldpassword') ) {

View File

@ -29,12 +29,23 @@ sub confirm {
sub modifyPassword {
my ( $self, $req, $pwd ) = @_;
my $dn;
my $requireOldPassword;
my $rule = $self->p->HANDLER->buildSub(
$self->p->HANDLER->substitute(
$self->conf->{portalRequireOldPassword}
)
);
unless ($rule) {
my $error = $self->p->HANDLER->tsv->{jail}->error || '???';
}
if ( $req->userData->{_dn} ) {
$dn = $req->userData->{_dn};
$requireOldPassword = $rule->( $req, $req->userData );
$self->logger->debug("Get DN from request data: $dn");
}
else {
$dn = $req->sessionInfo->{_dn};
$requireOldPassword = $rule->( $req, $req->sessionInfo );
$self->logger->debug("Get DN from session data: $dn");
}
unless ($dn) {
@ -48,7 +59,7 @@ sub modifyPassword {
# Call the modify password method
my $code =
$self->ldap->userModifyPassword( $dn, $pwd, $req->data->{oldpassword} );
$self->ldap->userModifyPassword( $dn, $pwd, $req->data->{oldpassword}, 0 , $requireOldPassword );
unless ( $code == PE_PASSWORD_OK ) {
return $code;

View File

@ -19,7 +19,7 @@ SKIP: {
portal => 'http://auth.example.com/',
userDB => 'Same',
passwordDB => 'LDAP',
portalRequireOldPassword => 1,
portalRequireOldPassword => '$uid eq "dwho"',
ldapServer => 'ldap://127.0.0.1:19389/',
ldapBase => 'ou=users,dc=example,dc=com',
managerDn => 'cn=admin,dc=example,dc=com',