Merge branch '2660' into 'v2.0'

Restore stop() method in Combination and LDAP backends

See merge request lemonldap-ng/lemonldap-ng!250
This commit is contained in:
Yadd 2022-02-03 10:49:18 +00:00
commit 3a2ae337df
6 changed files with 361 additions and 21 deletions

View File

@ -5,7 +5,7 @@ use Mouse;
use Safe;
use constant PE_OK => 0;
our $VERSION = '2.0.6';
our $VERSION = '2.0.14';
# Handle "if then else" (used during init)
# return a sub that can be called with ($req) to get a [array] of combination
@ -92,10 +92,12 @@ sub parseAnd {
$str{$r}++;
}
else {
return ( $r, $name ) unless ( $r == PE_OK );
return ( wantarray ? ( $r, $name ) : $r )
unless ( $r == PE_OK );
}
}
return ( ( %str ? join( ',', keys %str ) : PE_OK ), $expr );
my $res = %str ? join( ',', keys %str ) : PE_OK;
return wantarray ? ( $res, $expr ) : $res;
};
}
return \@res;
@ -135,7 +137,8 @@ sub parseMod {
my ($m) = @mods;
return sub {
my $sub = shift;
return ( $m->$sub(@_), $expr );
my $res = $m->$sub(@_);
return wantarray ? ( $res, $expr ) : $res;
};
}
return sub {
@ -149,10 +152,12 @@ sub parseMod {
$str{$res}++;
}
else {
return ( $res, $list[$i] ) unless ( $res == PE_OK );
return ( wantarray ? ( $res, $list[$i] ) : $res )
unless ( $res == PE_OK );
}
}
return ( ( %str ? join( ',', keys %str ) : PE_OK ), $expr );
my $res = %str ? join( ',', keys %str ) : PE_OK;
return wantarray ? ( $res, $expr ) : $res;
};
}

View File

@ -5,10 +5,14 @@ package Lemonldap::NG::Portal::Auth::AD;
use strict;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants
qw(PE_OK PE_PP_PASSWORD_EXPIRED PE_PP_CHANGE_AFTER_RESET PE_BADCREDENTIALS);
use Lemonldap::NG::Portal::Main::Constants qw(
PE_OK
PE_PP_PASSWORD_EXPIRED
PE_PP_CHANGE_AFTER_RESET
PE_BADCREDENTIALS
);
our $VERSION = '2.0.6';
our $VERSION = '2.0.14';
extends 'Lemonldap::NG::Portal::Auth::LDAP';
@ -158,4 +162,16 @@ sub authenticate {
return $res;
}
# Define which error codes will stop Combination process
# @param res error code
# @return result 1 if stop is needed
sub stop {
my ( $self, $res ) = @_;
return 1
if ( $res == PE_PP_PASSWORD_EXPIRED
or $res == PE_PP_CHANGE_AFTER_RESET );
return 0;
}
1;

View File

@ -3,12 +3,18 @@ package Lemonldap::NG::Portal::Auth::Combination;
use strict;
use Mouse;
use Lemonldap::NG::Common::Combination::Parser;
use Lemonldap::NG::Portal::Main::Constants qw(PE_OK PE_ERROR PE_FIRSTACCESS);
use Lemonldap::NG::Portal::Main::Constants qw(
PE_CONFIRM
PE_ERROR
PE_FIRSTACCESS
PE_FORMEMPTY
PE_PASSWORD_OK
PE_OK
);
use Scalar::Util 'weaken';
our $VERSION = '2.0.12';
our $VERSION = '2.0.14';
# TODO: See Lib::Wrapper
extends 'Lemonldap::NG::Portal::Main::Auth';
with 'Lemonldap::NG::Portal::Lib::OverConf';
@ -126,7 +132,7 @@ sub getDisplayType {
$req->data->{dataKeep}->{combinationTry},
$req->data->{combinationStack}
);
my ( $res, $name ) = $stack->[$nb]->[0]->( 'getDisplayType', @_ );
my $res = $stack->[$nb]->[0]->( 'getDisplayType', @_ );
return $res;
}
@ -231,13 +237,14 @@ sub try {
return PE_ERROR;
}
my $stop = 0;
if ( $nb < @$stack - 1 ) {
# TODO: change logLevel for userLog()
( $res, $name ) = $stack->[$nb]->[$type]->( $subname, $req, @args );
# On error, restart authentication with next scheme
if ( $res > PE_OK ) {
unless ( $stop = $self->stop( $stack->[$nb]->[$type], $res ) ) {
$self->logger->info(qq'Scheme "$name" returned $res, trying next');
$req->data->{dataKeep}->{combinationTry}++;
$req->steps( [ @{ $req->data->{combinationSteps} } ] );
@ -251,11 +258,17 @@ sub try {
$req->sessionInfo->{ [ '_auth', '_userDB' ]->[$type] } = $name;
$req->sessionInfo->{_combinationTry} =
$req->data->{dataKeep}->{combinationTry};
if ( $res > 0 and $res != PE_FIRSTACCESS ) {
$self->userLogger->warn( 'All schemes failed'
. ( $req->user ? ' for user ' . $req->user : '' ) . ' ('
. $req->address
. ')' );
if ( $res > 0 ) {
if ($stop) {
$self->userLogger->info(
"Combination stopped by plugin $name (code $res)");
}
elsif ( $res != PE_FIRSTACCESS ) {
$self->userLogger->warn( 'All schemes failed'
. ( $req->user ? ' for user ' . $req->user : '' ) . ' ('
. $req->address
. ')' );
}
}
return $res;
}
@ -269,6 +282,32 @@ sub name {
|| 'Combination';
}
sub stop {
my ( $self, $mod, $res ) = @_;
return 1
if (
$res <= 0 # PE_OK
or $res == PE_CONFIRM
or $res == PE_PASSWORD_OK
# TODO: adding this may generate behavior change
#or $res == PE_FIRSTACCESS
#or $res == PE_FORMEMPTY
);
my ( $ret, $name );
$ret = $mod->( 'can', 'stop' );
if ($ret) {
eval { ( $ret, $name ) = $mod->( 'stop', $res ) };
if ($@) {
$self->logger->error(
"Optional ${name}::stop() method failed: " . $@ );
return 0;
}
}
return $ret;
}
package Lemonldap::NG::Portal::Lib::Combination::UserLogger;
# This logger rewrite "warn" to "notice"

View File

@ -7,11 +7,12 @@ use Lemonldap::NG::Portal::Main::Constants qw(
PE_DONE
PE_ERROR
PE_LDAPCONNECTFAILED
PE_PP_ACCOUNT_LOCKED
PE_PP_PASSWORD_EXPIRED
PE_PP_CHANGE_AFTER_RESET
);
our $VERSION = '2.0.10';
our $VERSION = '2.0.14';
# Inheritance: UserDB::LDAP provides all needed ldap functions
extends qw(
@ -99,4 +100,17 @@ sub authLogout {
return PE_OK;
}
# Define which error codes will stop Combination process
# @param res error code
# @return result 1 if stop is needed
sub stop {
my ( $self, $res ) = @_;
return 1
if ( $res == PE_PP_PASSWORD_EXPIRED
or $res == PE_PP_ACCOUNT_LOCKED
or $res == PE_PP_CHANGE_AFTER_RESET );
return 0;
}
1;

View File

@ -3,7 +3,7 @@ package Lemonldap::NG::Portal::Main::Auth;
use strict;
use Mouse;
our $VERSION = '2.0.0';
our $VERSION = '2.0.14';
extends 'Lemonldap::NG::Portal::Main::Plugin';
@ -11,4 +11,6 @@ extends 'Lemonldap::NG::Portal::Main::Plugin';
has authnLevel => ( is => 'rw' );
sub stop {0}
1;

View File

@ -0,0 +1,264 @@
use Test::More;
use strict;
use IO::String;
require 't/test-lib.pm';
use lib 't/lib';
my $res;
my $maintests = 42;
SKIP: {
skip( 'LLNGTESTLDAP is not set', $maintests ) unless ( $ENV{LLNGTESTLDAP} );
require 't/test-ldap.pm';
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
useSafeJail => 1,
portal => 'http://auth.example.com/',
authentication => 'Combination',
userDB => 'Same',
passwordDB => 'LDAP',
combModules => {
'LDAP' => { 'for' => 0, 'type' => 'LDAP' },
'Demo' => { 'for' => 0, 'type' => 'Demo' }
},
combination => '[LDAP, LDAP] or [Demo, Demo]',
portalRequireOldPassword => 1,
ldapServer => 'ldap://127.0.0.1:19389/',
ldapBase => 'ou=users,dc=example,dc=com',
managerDn => 'cn=lemonldapng,ou=dsa,dc=example,dc=com',
managerPassword => 'lemonldapng',
ldapAllowResetExpiredPassword => 1,
ldapPpolicyControl => 1,
passwordPolicyMinSize => 4,
passwordPolicyMinLower => 1,
passwordPolicyMinUpper => 1,
passwordPolicyMinDigit => 1,
passwordPolicyMinSpeChar => 1,
passwordPolicySpecialChar => '__ALL__',
portalDisplayPasswordPolicy => 1,
whatToTrace => 'uid',
macros => {
_whatToTrace => '' # Test 2377
},
}
}
);
use Lemonldap::NG::Portal::Main::Constants qw(
PE_PASSWORD_OK
PE_PP_ACCOUNT_LOCKED
PE_PP_PASSWORD_EXPIRED
PE_PP_CHANGE_AFTER_RESET
PE_PP_PASSWORD_TOO_SHORT PE_PP_GRACE
);
my ( $user, $code, $postString, $match );
# 1 - TEST PE_PP_CHANGE_AFTER_RESET AND PE_PP_PASSWORD_EXPIRED
# ------------------------------------------------------------
foreach my $tpl (
[ 'reset', PE_PP_CHANGE_AFTER_RESET ],
[ 'expire', PE_PP_PASSWORD_EXPIRED ]
)
{
$user = $tpl->[0];
$code = $tpl->[1];
$postString = "user=$user&password=$user";
# Try to authenticate
# -------------------
ok(
$res = $client->_post(
'/', IO::String->new($postString),
length => length($postString),
accept => 'text/html',
),
'Auth query'
);
$match = 'trmsg="' . $code . '"';
ok( $res->[2]->[0] =~ /$match/, "Code is $code" );
#open F, '>../e2e-tests/conf/portal/result.html' or die $!;
#print F $res->[2]->[0];
#close F;
my ( $host, $url, $query ) =
expectForm( $res, '#', undef, 'user', 'oldpassword', 'newpassword',
'confirmpassword' );
ok(
$res->[2]->[0] =~
m%<input name="user" type="hidden" value="$user" />%,
' Hidden user input found'
) or print STDERR Dumper( $res->[2]->[0], 'Hidden user input' );
ok(
$res->[2]->[0] =~
m%<input id="oldpassword" name="oldpassword" type="password" value="$user"%,
' oldpassword input found'
) or print STDERR Dumper( $res->[2]->[0], 'oldpassword input' );
ok(
$res->[2]->[0] =~
m%<input id="staticUser" type="text" readonly class="form-control" value="$user" />%,
' staticUser found'
) or print STDERR Dumper( $res->[2]->[0], 'staticUser' );
ok( $res->[2]->[0] =~ m%<span trspan="passwordPolicyMinSize">%,
' passwordPolicyMinSize' )
or print STDERR Dumper( $res->[2]->[0], 'passwordPolicyMinSize' );
ok( $res->[2]->[0] =~ m%<span trspan="passwordPolicyMinLower">%,
' passwordPolicyMinLower' )
or print STDERR Dumper( $res->[2]->[0], 'passwordPolicyMinLower' );
ok( $res->[2]->[0] =~ m%<span trspan="passwordPolicyMinUpper">%,
' passwordPolicyMinUpper' )
or print STDERR Dumper( $res->[2]->[0], 'passwordPolicyMinUpper' );
ok( $res->[2]->[0] =~ m%<span trspan="passwordPolicyMinDigit">%,
' passwordPolicyMinDigit' )
or print STDERR Dumper( $res->[2]->[0], 'passwordPolicyMinDigit' );
ok( $res->[2]->[0] =~ m%<span trspan="passwordPolicyMinSpeChar">%,
' passwordPolicyMinSpeChar' )
or print STDERR Dumper( $res->[2]->[0], 'passwordPolicyMinSpeChar' );
ok( $res->[2]->[0] !~ m%<span trspan="passwordPolicySpecialChar">%,
' passwordPolicySpecialChar' )
or print STDERR Dumper( $res->[2]->[0], 'passwordPolicySpecialChar' );
ok( $query =~ /user=$user/, "User is $user" )
or explain( $query, "user=$user" );
#$query =~ s/(oldpassword)=/$1=$user/g; -> Now old password is defined #2377
$query =~ s/((?:confirm|new)password)=/$1=Newp1@/g;
ok(
$res = $client->_post(
'/', IO::String->new($query),
length => length($query),
accept => 'text/html',
),
'Post new password'
);
$match = 'trmsg="' . PE_PASSWORD_OK . '"';
ok( $res->[2]->[0] =~ /$match/, 'Password is changed' );
$postString = "user=$user&password=Newp1@";
ok(
$res = $client->_post(
'/', IO::String->new($postString),
length => length($postString),
),
'Auth query'
);
expectCookie($res) or print STDERR Dumper($res);
}
# 2 - TEST PE_PP_GRACE
# -------------------------
$user = 'grace';
$code = "ppGrace";
$postString = "user=$user&password=$user";
# Try to authenticate
# -------------------
ok(
$res = $client->_post(
'/', IO::String->new($postString),
length => length($postString),
accept => 'text/html',
),
'Auth query'
);
$match = 'trspan="' . $code . '"';
ok( $res->[2]->[0] =~ /$match/, 'Grace remaining' );
# 3 - TEST PE_PP_ACCOUNT_LOCKED
# -------------------------
$user = 'lock';
$code = PE_PP_ACCOUNT_LOCKED;
$postString = "user=$user&password=$user";
# Try to authenticate
# -------------------
ok(
$res = $client->_post(
'/', IO::String->new($postString),
length => length($postString),
accept => 'text/html',
),
'Auth query'
);
$match = 'trmsg="' . $code . '"';
ok( $res->[2]->[0] =~ /$match/, 'Account is locked' );
# Try to change anyway
my $query =
'user=lock&oldpassword=lock&newpassword=newp&confirmpassword=newp';
ok(
$res = $client->_post(
'/', IO::String->new($query),
length => length($query),
accept => 'text/html',
),
'Post new password'
);
$match = 'trmsg="' . PE_PASSWORD_OK . '"';
ok( $res->[2]->[0] !~ /$match/s, 'Password is not changed' );
# 4 - TEST PE_PP_PASSWORD_TOO_SHORT
# ---------------------------------
$user = 'short';
$code = PE_PP_PASSWORD_TOO_SHORT;
$postString = "user=$user&password=passwordnottooshort";
# Try to authenticate
# -------------------
ok(
$res = $client->_post(
'/', IO::String->new($postString),
length => length($postString),
accept => 'text/html',
),
'Auth query'
);
my $id = expectCookie($res);
$query =
'oldpassword=passwordnottooshort&newpassword=Te1@&confirmpassword=Te1@';
ok(
$res = $client->_post(
'/',
IO::String->new($query),
cookie => "lemonldap=$id",
accept => 'text/html',
length => length($query),
),
'Change password'
);
$match = 'trmsg="' . PE_PP_PASSWORD_TOO_SHORT . '"';
ok( $res->[2]->[0] =~ /$match/s, 'Password is not changed' );
# Verify that password isn't changed
$client->logout($id);
ok(
$res = $client->_post(
'/', IO::String->new($postString),
length => length($postString),
accept => 'text/html',
),
'Auth query'
);
$id = expectCookie($res);
$query =
'oldpassword=passwordnottooshort&newpassword=Testmore1@&confirmpassword=Testmore1@';
ok(
$res = $client->_post(
'/',
IO::String->new($query),
cookie => "lemonldap=$id",
accept => 'text/html',
length => length($query),
),
'Change password'
);
$match = 'trmsg="' . PE_PASSWORD_OK . '"';
ok( $res->[2]->[0] =~ /$match/s, 'Password is changed' );
}
count($maintests);
clean_sessions();
stopLdapServer() if $ENV{LLNGTESTLDAP};
done_testing( count() );