2016-07-07 23:55:23 +02:00
|
|
|
# Base package for Password modules
|
|
|
|
package Lemonldap::NG::Portal::Password::Base;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Mouse;
|
|
|
|
use Lemonldap::NG::Portal::Main::Constants qw(
|
|
|
|
PE_OK
|
|
|
|
PE_PASSWORD_OK
|
2020-11-04 16:48:43 +01:00
|
|
|
PE_BADOLDPASSWORD
|
2016-07-07 23:55:23 +02:00
|
|
|
PE_PASSWORD_MISMATCH
|
2019-09-03 14:30:22 +02:00
|
|
|
PE_PP_PASSWORD_TOO_SHORT
|
2020-04-02 12:14:05 +02:00
|
|
|
PE_PP_NOT_ALLOWED_CHARACTER
|
|
|
|
PE_PP_NOT_ALLOWED_CHARACTERS
|
2020-04-02 00:57:02 +02:00
|
|
|
PE_PP_MUST_SUPPLY_OLD_PASSWORD
|
2019-09-03 16:10:04 +02:00
|
|
|
PE_PP_INSUFFICIENT_PASSWORD_QUALITY
|
2016-07-07 23:55:23 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
extends 'Lemonldap::NG::Portal::Main::Plugin';
|
|
|
|
|
2021-04-14 13:15:26 +02:00
|
|
|
our $VERSION = '2.0.12';
|
2016-07-07 23:55:23 +02:00
|
|
|
|
2016-07-20 22:47:43 +02:00
|
|
|
# INITIALIZATION
|
|
|
|
|
2020-12-23 14:57:55 +01:00
|
|
|
has requireOldPwdRule => ( is => 'rw' );
|
|
|
|
|
2016-07-20 22:47:43 +02:00
|
|
|
sub init {
|
2020-12-23 14:57:55 +01:00
|
|
|
my ($self) = shift;
|
|
|
|
$self->requireOldPwdRule(
|
|
|
|
$self->p->buildRule(
|
|
|
|
$self->conf->{portalRequireOldPassword},
|
|
|
|
'portalRequireOldPassword'
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return 0 unless $self->requireOldPwdRule;
|
|
|
|
|
|
|
|
$self->p->{_passwordDB} = $self;
|
2016-07-20 22:47:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# INTERFACE
|
|
|
|
|
2020-04-02 00:57:02 +02:00
|
|
|
use constant forAuthUser => '_modifyPassword';
|
2016-07-07 23:55:23 +02:00
|
|
|
|
2016-07-20 22:47:43 +02:00
|
|
|
# RUNNING METHODS
|
|
|
|
|
2016-07-07 23:55:23 +02:00
|
|
|
sub _modifyPassword {
|
2019-02-11 15:40:27 +01:00
|
|
|
my ( $self, $req, $requireOldPwd ) = @_;
|
2016-07-07 23:55:23 +02:00
|
|
|
|
|
|
|
# Exit if no password change requested
|
|
|
|
return PE_OK
|
2018-07-05 22:56:16 +02:00
|
|
|
unless ( $req->data->{newpassword} = $req->param('newpassword') );
|
2016-07-07 23:55:23 +02:00
|
|
|
|
2018-11-19 18:50:35 +01:00
|
|
|
# Verify that old password is good
|
|
|
|
return PE_PASSWORD_MISMATCH
|
|
|
|
unless ( $req->data->{newpassword} eq $req->param('confirmpassword') );
|
|
|
|
|
2020-08-29 20:13:11 +02:00
|
|
|
my $oldPwdRule = $self->p->HANDLER->buildSub(
|
2020-05-24 00:04:33 +02:00
|
|
|
$self->p->HANDLER->substitute(
|
|
|
|
$self->conf->{portalRequireOldPassword}
|
|
|
|
)
|
|
|
|
);
|
2020-08-29 20:13:11 +02:00
|
|
|
unless ($oldPwdRule) {
|
|
|
|
my $error = $self->p->HANDLER->tsv->{jail}->error || '???';
|
|
|
|
}
|
|
|
|
|
|
|
|
my $pwdPolicyRule = $self->p->HANDLER->buildSub(
|
|
|
|
$self->p->HANDLER->substitute(
|
2020-08-29 22:50:29 +02:00
|
|
|
$self->conf->{passwordPolicyActivation}
|
2020-08-29 20:13:11 +02:00
|
|
|
)
|
|
|
|
);
|
|
|
|
unless ($pwdPolicyRule) {
|
2020-04-27 22:08:12 +02:00
|
|
|
my $error = $self->p->HANDLER->tsv->{jail}->error || '???';
|
|
|
|
}
|
|
|
|
|
2016-07-07 23:55:23 +02:00
|
|
|
# Check if portal require old password
|
2020-08-29 20:13:11 +02:00
|
|
|
if ( $oldPwdRule->( $req, $req->userData ) or $requireOldPwd ) {
|
2016-07-07 23:55:23 +02:00
|
|
|
|
|
|
|
# TODO: verify oldpassword
|
2018-07-05 22:56:16 +02:00
|
|
|
unless ( $req->data->{oldpassword} = $req->param('oldpassword') ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->warn("Portal require old password");
|
2016-07-07 23:55:23 +02:00
|
|
|
return PE_PP_MUST_SUPPLY_OLD_PASSWORD;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Verify old password
|
2016-07-11 23:02:32 +02:00
|
|
|
return PE_BADOLDPASSWORD
|
2018-07-05 22:56:16 +02:00
|
|
|
unless ( $self->confirm( $req, $req->data->{oldpassword} ) );
|
2019-09-03 14:30:22 +02:00
|
|
|
}
|
|
|
|
|
2020-08-29 19:08:47 +02:00
|
|
|
my $cpq =
|
2020-08-29 20:13:11 +02:00
|
|
|
$pwdPolicyRule->( $req, $req->userData )
|
2020-08-29 19:08:47 +02:00
|
|
|
? $self->checkPasswordQuality( $req->data->{newpassword} )
|
|
|
|
: PE_OK;
|
2019-09-05 12:02:51 +02:00
|
|
|
return $cpq unless ( $cpq == PE_OK );
|
|
|
|
|
2021-06-02 09:35:02 +02:00
|
|
|
my $hook_result = $self->p->processHook(
|
|
|
|
$req, 'passwordBeforeChange', $req->user,
|
|
|
|
$req->data->{newpassword},
|
|
|
|
$req->data->{oldpassword}
|
|
|
|
);
|
|
|
|
return $hook_result if ( $hook_result != PE_OK );
|
|
|
|
|
2019-09-05 12:02:51 +02:00
|
|
|
# Call password package
|
|
|
|
my $res = $self->modifyPassword( $req, $req->data->{newpassword} );
|
|
|
|
if ( $res == PE_PASSWORD_OK ) {
|
2021-06-02 09:35:02 +02:00
|
|
|
|
|
|
|
$self->p->processHook(
|
|
|
|
$req, 'passwordAfterChange', $req->user,
|
|
|
|
$req->data->{newpassword},
|
|
|
|
$req->data->{oldpassword}
|
|
|
|
);
|
|
|
|
|
2019-09-05 12:02:51 +02:00
|
|
|
$self->logger->debug( 'Update password in session for ' . $req->user );
|
2020-11-29 18:02:13 +01:00
|
|
|
my $userlog = $req->sessionInfo->{ $self->conf->{whatToTrace} };
|
|
|
|
my $iplog = $req->sessionInfo->{ipAddr};
|
|
|
|
$self->userLogger->notice("Password changed for $userlog ($iplog)")
|
|
|
|
if ( defined $userlog and $iplog );
|
2019-09-05 12:02:51 +02:00
|
|
|
my $infos;
|
|
|
|
|
|
|
|
# Store new password if asked
|
|
|
|
if ( $self->conf->{storePassword} ) {
|
|
|
|
$self->p->updateSession(
|
|
|
|
$req,
|
|
|
|
{
|
|
|
|
_passwordDB => $self->p->getModule( $req, 'password' ),
|
2021-01-04 21:16:03 +01:00
|
|
|
_password => $req->data->{newpassword}
|
2019-09-05 12:02:51 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->p->updateSession( $req,
|
|
|
|
{ _passwordDB => $self->p->getModule( $req, 'password' ) } );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Set a flag to ignore password change in Menu
|
|
|
|
$req->{ignorePasswordChange} = 1;
|
|
|
|
|
|
|
|
# Set a flag to allow sending a mail
|
|
|
|
$req->{passwordWasChanged} = 1;
|
|
|
|
|
|
|
|
# Continue process if password change is ok
|
|
|
|
return PE_PASSWORD_OK;
|
|
|
|
}
|
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub checkPasswordQuality {
|
|
|
|
my ( $self, $password ) = @_;
|
|
|
|
|
2019-09-03 14:30:22 +02:00
|
|
|
# Min size
|
|
|
|
if ( $self->conf->{passwordPolicyMinSize}
|
2019-09-05 12:02:51 +02:00
|
|
|
and length($password) < $self->conf->{passwordPolicyMinSize} )
|
2019-09-03 14:30:22 +02:00
|
|
|
{
|
|
|
|
$self->logger->error("Password too short");
|
|
|
|
return PE_PP_PASSWORD_TOO_SHORT;
|
2016-07-07 23:55:23 +02:00
|
|
|
}
|
|
|
|
|
2019-09-03 16:10:04 +02:00
|
|
|
# Min lower
|
|
|
|
if ( $self->conf->{passwordPolicyMinLower} ) {
|
|
|
|
my $lower = 0;
|
2019-09-05 12:02:51 +02:00
|
|
|
$lower++ while ( $password =~ m/\p{lowercase}/g );
|
2019-09-03 16:10:04 +02:00
|
|
|
if ( $lower < $self->conf->{passwordPolicyMinLower} ) {
|
|
|
|
$self->logger->error("Password has not enough lower characters");
|
|
|
|
return PE_PP_INSUFFICIENT_PASSWORD_QUALITY;
|
|
|
|
}
|
2019-09-03 18:45:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Min upper
|
|
|
|
if ( $self->conf->{passwordPolicyMinUpper} ) {
|
|
|
|
my $upper = 0;
|
2019-09-05 12:02:51 +02:00
|
|
|
$upper++ while ( $password =~ m/\p{uppercase}/g );
|
2019-09-03 18:45:35 +02:00
|
|
|
if ( $upper < $self->conf->{passwordPolicyMinUpper} ) {
|
|
|
|
$self->logger->error("Password has not enough upper characters");
|
|
|
|
return PE_PP_INSUFFICIENT_PASSWORD_QUALITY;
|
|
|
|
}
|
2019-09-03 19:08:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Min digit
|
|
|
|
if ( $self->conf->{passwordPolicyMinDigit} ) {
|
|
|
|
my $digit = 0;
|
2019-09-05 12:02:51 +02:00
|
|
|
$digit++ while ( $password =~ m/\d/g );
|
2019-09-03 19:08:19 +02:00
|
|
|
if ( $digit < $self->conf->{passwordPolicyMinDigit} ) {
|
|
|
|
$self->logger->error("Password has not enough digit characters");
|
|
|
|
return PE_PP_INSUFFICIENT_PASSWORD_QUALITY;
|
|
|
|
}
|
2019-09-03 16:10:04 +02:00
|
|
|
}
|
|
|
|
|
2020-08-29 19:08:47 +02:00
|
|
|
### Special characters policy
|
2020-04-02 12:14:05 +02:00
|
|
|
my $speChars = $self->conf->{passwordPolicySpecialChar};
|
|
|
|
$speChars =~ s/\s+//g;
|
|
|
|
|
2020-08-29 19:08:47 +02:00
|
|
|
## Min special characters
|
|
|
|
# Just number of special characters must be checked
|
|
|
|
if ( $self->conf->{passwordPolicyMinSpeChar} && $speChars eq '__ALL__' ) {
|
2021-04-21 22:14:47 +02:00
|
|
|
my $spe = $password =~ s/\W//g;
|
2020-08-29 19:08:47 +02:00
|
|
|
if ( $spe < $self->conf->{passwordPolicyMinSpeChar} ) {
|
|
|
|
$self->logger->error("Password has not enough special characters");
|
|
|
|
return PE_PP_INSUFFICIENT_PASSWORD_QUALITY;
|
|
|
|
}
|
|
|
|
return PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Number of special characters must be checked
|
2020-04-02 12:14:05 +02:00
|
|
|
if ( $self->conf->{passwordPolicyMinSpeChar} && $speChars ) {
|
|
|
|
my $test = $password;
|
2020-08-29 19:08:47 +02:00
|
|
|
my $spe = $test =~ s/[\Q$speChars\E]//g;
|
2020-04-02 00:57:02 +02:00
|
|
|
if ( $spe < $self->conf->{passwordPolicyMinSpeChar} ) {
|
|
|
|
$self->logger->error("Password has not enough special characters");
|
|
|
|
return PE_PP_INSUFFICIENT_PASSWORD_QUALITY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-29 19:08:47 +02:00
|
|
|
## Fobidden special characters
|
2021-04-14 13:15:26 +02:00
|
|
|
unless ( $speChars eq '__ALL__' ) {
|
|
|
|
$password =~ s/[\Q$speChars\E\w]//g;
|
|
|
|
if ($password) {
|
|
|
|
$self->logger->error( 'Password contains '
|
|
|
|
. length($password)
|
|
|
|
. " forbidden character(s): $password" );
|
|
|
|
return length($password) > 1
|
|
|
|
? PE_PP_NOT_ALLOWED_CHARACTERS
|
|
|
|
: PE_PP_NOT_ALLOWED_CHARACTER;
|
|
|
|
}
|
2020-04-02 12:14:05 +02:00
|
|
|
}
|
|
|
|
|
2019-09-05 12:02:51 +02:00
|
|
|
return PE_OK;
|
2016-07-07 23:55:23 +02:00
|
|
|
}
|
|
|
|
|
2021-06-02 09:35:02 +02:00
|
|
|
# This method should be called when resetting the password
|
|
|
|
# in order to call the password hook
|
|
|
|
sub setNewPassword {
|
|
|
|
my ( $self, $req, $pwd, $useMail ) = @_;
|
|
|
|
|
|
|
|
my $hook_result =
|
|
|
|
$self->p->processHook( $req, 'passwordBeforeChange', $req->user, $pwd );
|
|
|
|
return $hook_result if ( $hook_result != PE_OK );
|
|
|
|
|
|
|
|
# Delegate to subclass
|
|
|
|
my $mod_result = $self->modifyPassword( $req, $pwd, $useMail );
|
|
|
|
|
|
|
|
if ( $mod_result == PE_PASSWORD_OK ) {
|
|
|
|
$hook_result =
|
|
|
|
$self->p->processHook( $req, 'passwordAfterChange', $req->user,
|
|
|
|
$pwd );
|
|
|
|
if ( $hook_result != PE_OK ) {
|
|
|
|
return $hook_result;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return PE_PASSWORD_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return $mod_result;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-07-07 23:55:23 +02:00
|
|
|
1;
|