Append BruteForceProtection configuration test (#1506)

This commit is contained in:
Christophe Maudoux 2018-10-12 22:30:23 +02:00
parent 36be3bc515
commit 748d25cafc

View File

@ -30,8 +30,7 @@ sub tests {
portalIsInDomain => sub { portalIsInDomain => sub {
return ( return (
1, 1,
( ( index( $conf->{portal}, $conf->{domain} ) > 0
index( $conf->{portal}, $conf->{domain} ) > 0
? '' ? ''
: "Portal seems not to be in the domain $conf->{domain}" : "Portal seems not to be in the domain $conf->{domain}"
) )
@ -43,7 +42,7 @@ sub tests {
# Checking for ending slash # Checking for ending slash
$conf->{portal} .= '/' $conf->{portal} .= '/'
unless ( $conf->{portal} =~ qr#/$# ); unless ( $conf->{portal} =~ qr#/$# );
# Deleting trailing ending slash # Deleting trailing ending slash
my $regex = qr#/+$#; my $regex = qr#/+$#;
@ -61,11 +60,10 @@ sub tests {
} }
return ( return (
1, 1,
( ( @pb
@pb
? 'Virtual hosts ' ? 'Virtual hosts '
. join( ', ', @pb ) . join( ', ', @pb )
. " are not in $conf->{domain} and cross-domain-authentication is not set" . " are not in $conf->{domain} and cross-domain-authentication is not set"
: undef : undef
) )
); );
@ -79,9 +77,9 @@ sub tests {
} }
if (@pb) { if (@pb) {
return ( 0, return ( 0,
'Virtual hosts ' 'Virtual hosts '
. join( ', ', @pb ) . join( ', ', @pb )
. " contain a port, this is not allowed" ); . " contain a port, this is not allowed" );
} }
else { return 1; } else { return 1; }
}, },
@ -94,9 +92,9 @@ sub tests {
} }
if (@pb) { if (@pb) {
return ( 0, return ( 0,
'Virtual hosts ' 'Virtual hosts '
. join( ', ', @pb ) . join( ', ', @pb )
. " must be in lower case" ); . " must be in lower case" );
} }
else { return 1; } else { return 1; }
}, },
@ -104,12 +102,12 @@ sub tests {
# Check if "userDB" and "authentication" are consistent # Check if "userDB" and "authentication" are consistent
authAndUserDBConsistency => sub { authAndUserDBConsistency => sub {
foreach foreach
my $type (qw(Facebook Google OpenID OpenIDConnect SAML WebID)) my $type (qw(Facebook Google OpenID OpenIDConnect SAML WebID))
{ {
return ( 0, return ( 0,
"\"$type\" can not be used as user database without using \"$type\" for authentication" "\"$type\" can not be used as user database without using \"$type\" for authentication"
) )
if ( $conf->{userDB} =~ /$type/ if ($conf->{userDB} =~ /$type/
and $conf->{authentication} !~ /$type/ ); and $conf->{authentication} !~ /$type/ );
} }
return 1; return 1;
@ -119,30 +117,29 @@ sub tests {
checkAttrAndMacros => sub { checkAttrAndMacros => sub {
my @tmp; my @tmp;
foreach my $k ( keys %$conf ) { foreach my $k ( keys %$conf ) {
if ( $k =~ if ( $k
/^(?:openIdSreg_(?:(?:(?:full|nick)nam|languag|postcod|timezon)e|country|gender|email|dob)|whatToTrace)$/ =~ /^(?:openIdSreg_(?:(?:(?:full|nick)nam|languag|postcod|timezon)e|country|gender|email|dob)|whatToTrace)$/
) )
{ {
my $v = $conf->{$k}; my $v = $conf->{$k};
$v =~ s/^$//; $v =~ s/^$//;
next if ( $v =~ /^_/ ); next if ( $v =~ /^_/ );
push @tmp, push @tmp,
$k $k
unless ( unless (
defined( defined(
$conf->{exportedVars}->{$v} $conf->{exportedVars}->{$v}
or defined( $conf->{macros}->{$v} ) or defined( $conf->{macros}->{$v} )
) )
); );
} }
} }
return ( return (
1, 1,
( ( @tmp
@tmp
? 'Values of parameter(s) "' ? 'Values of parameter(s) "'
. join( ', ', @tmp ) . join( ', ', @tmp )
. '" are not defined in exported attributes or macros' . '" are not defined in exported attributes or macros'
: '' : ''
) )
); );
@ -154,18 +151,18 @@ sub tests {
if ( $conf->{userDB} =~ /^Google$/ ) { if ( $conf->{userDB} =~ /^Google$/ ) {
foreach my $k ( keys %{ $conf->{exportedVars} } ) { foreach my $k ( keys %{ $conf->{exportedVars} } ) {
my $v = $conf->{exportedVars}->{$k}; my $v = $conf->{exportedVars}->{$k};
if ( $v !~ Lemonldap::NG::Common::Regexp::GOOGLEAXATTR() ) { if ( $v !~ Lemonldap::NG::Common::Regexp::GOOGLEAXATTR() )
{
push @tmp, $v; push @tmp, $v;
} }
} }
} }
return ( return (
1, 1,
( ( @tmp
@tmp
? 'Values of parameter(s) "' ? 'Values of parameter(s) "'
. join( ', ', @tmp ) . join( ', ', @tmp )
. '" are not exported by Google' . '" are not exported by Google'
: '' : ''
) )
); );
@ -177,7 +174,8 @@ sub tests {
if ( $conf->{userDB} =~ /^OpenID$/ ) { if ( $conf->{userDB} =~ /^OpenID$/ ) {
foreach my $k ( keys %{ $conf->{exportedVars} } ) { foreach my $k ( keys %{ $conf->{exportedVars} } ) {
my $v = $conf->{exportedVars}->{$k}; my $v = $conf->{exportedVars}->{$k};
if ( $v !~ Lemonldap::NG::Common::Regexp::OPENIDSREGATTR() ) if ( $v
!~ Lemonldap::NG::Common::Regexp::OPENIDSREGATTR() )
{ {
push @tmp, $v; push @tmp, $v;
} }
@ -185,11 +183,10 @@ sub tests {
} }
return ( return (
1, 1,
( ( @tmp
@tmp
? 'Values of parameter(s) "' ? 'Values of parameter(s) "'
. join( ', ', @tmp ) . join( ', ', @tmp )
. '" are not exported by OpenID SREG' . '" are not exported by OpenID SREG'
: '' : ''
) )
); );
@ -198,40 +195,40 @@ sub tests {
# Try to use Apache::Session module # Try to use Apache::Session module
testApacheSession => sub { testApacheSession => sub {
my ( $id, %h ); my ( $id, %h );
my $gc = my $gc = $Lemonldap::NG::Handler::PSGI::Main::tsv
$Lemonldap::NG::Handler::PSGI::Main::tsv->{sessionStorageModule}; ->{sessionStorageModule};
return 1 return 1
if ( ( $gc and $gc eq $conf->{globalStorage} ) if ( ( $gc and $gc eq $conf->{globalStorage} )
or $conf->{globalStorage} =~ or $conf->{globalStorage}
/^Lemonldap::NG::Common::Apache::Session::/ ); =~ /^Lemonldap::NG::Common::Apache::Session::/ );
eval "use $conf->{globalStorage}"; eval "use $conf->{globalStorage}";
return ( -1, "Unknown package $conf->{globalStorage}" ) if ($@); return ( -1, "Unknown package $conf->{globalStorage}" ) if ($@);
eval { eval {
tie %h, 'Lemonldap::NG::Common::Apache::Session', undef, tie %h, 'Lemonldap::NG::Common::Apache::Session', undef,
{ {
%{ $conf->{globalStorageOptions} }, %{ $conf->{globalStorageOptions} },
backend => $conf->{globalStorage} backend => $conf->{globalStorage}
}; };
}; };
return ( -1, "Unable to create a session ($@)" ) return ( -1, "Unable to create a session ($@)" )
if ( $@ or not tied(%h) ); if ( $@ or not tied(%h) );
eval { eval {
$h{a} = 1; $h{a} = 1;
$id = $h{_session_id} or return ( -1, 'No _session_id' ); $id = $h{_session_id} or return ( -1, 'No _session_id' );
untie(%h); untie(%h);
tie %h, 'Lemonldap::NG::Common::Apache::Session', $id, tie %h, 'Lemonldap::NG::Common::Apache::Session', $id,
{ {
%{ $conf->{globalStorageOptions} }, %{ $conf->{globalStorageOptions} },
backend => $conf->{globalStorage} backend => $conf->{globalStorage}
}; };
}; };
return ( -1, "Unable to insert data ($@)" ) if ($@); return ( -1, "Unable to insert data ($@)" ) if ($@);
return ( -1, "Unable to recover data stored" ) return ( -1, "Unable to recover data stored" )
unless ( $h{a} == 1 ); unless ( $h{a} == 1 );
eval { tied(%h)->delete; }; eval { tied(%h)->delete; };
return ( -1, "Unable to delete session ($@)" ) if ($@); return ( -1, "Unable to delete session ($@)" ) if ($@);
return ( -1, return ( -1,
'All sessions may be lost and you must restart all your Apache servers' 'All sessions may be lost and you must restart all your Apache servers'
) if ( $gc and $conf->{globalStorage} ne $gc ); ) if ( $gc and $conf->{globalStorage} ne $gc );
return 1; return 1;
}, },
@ -241,9 +238,8 @@ sub tests {
my $cn = $Lemonldap::NG::Handler::PSGI::API::tsv->{cookieName}; my $cn = $Lemonldap::NG::Handler::PSGI::API::tsv->{cookieName};
return ( return (
1, 1,
( ( $cn
$cn and $cn ne $conf->{cookieName}
and $cn ne $conf->{cookieName}
? 'Cookie name has changed, you must restart all your web servers' ? 'Cookie name has changed, you must restart all your web servers'
: () : ()
) )
@ -254,9 +250,9 @@ sub tests {
cookieTTL => sub { cookieTTL => sub {
return 1 unless ( defined $conf->{cookieExpiration} ); return 1 unless ( defined $conf->{cookieExpiration} );
return ( 0, "Cookie TTL must be higher than one minute" ) return ( 0, "Cookie TTL must be higher than one minute" )
unless ( $conf->{cookieExpiration} > 60 ); unless ( $conf->{cookieExpiration} > 60 );
return ( 1, "Cookie TTL should be higher or equal than one hour" ) return ( 1, "Cookie TTL should be higher or equal than one hour" )
unless ( $conf->{cookieExpiration} >= 3600 unless ( $conf->{cookieExpiration} >= 3600
|| $conf->{cookieExpiration} == 0 ); || $conf->{cookieExpiration} == 0 );
# Return # Return
@ -267,8 +263,7 @@ sub tests {
managerProtection => sub { managerProtection => sub {
return ( return (
1, 1,
( ( $conf->{cfgAuthor} eq 'anonymous'
$conf->{cfgAuthor} eq 'anonymous'
? 'Your manager seems to be unprotected' ? 'Your manager seems to be unprotected'
: '' : ''
) )
@ -284,21 +279,21 @@ sub tests {
# Use SMTP # Use SMTP
eval "use Net::SMTP"; eval "use Net::SMTP";
return ( 1, "Net::SMTP module is required to use SMTP server" ) return ( 1, "Net::SMTP module is required to use SMTP server" )
if ($@); if ($@);
# Create SMTP object # Create SMTP object
my $smtp = Net::SMTP->new( $conf->{SMTPServer}, Timeout => 5 ); my $smtp = Net::SMTP->new( $conf->{SMTPServer}, Timeout => 5 );
return ( 1, return ( 1,
"SMTP connection to " . $conf->{SMTPServer} . " failed" ) "SMTP connection to " . $conf->{SMTPServer} . " failed" )
unless ($smtp); unless ($smtp);
# Skip other tests if no authentication # Skip other tests if no authentication
return 1 return 1
unless ( $conf->{SMTPAuthUser} and $conf->{SMTPAuthPass} ); unless ( $conf->{SMTPAuthUser} and $conf->{SMTPAuthPass} );
# Try authentication # Try authentication
return ( 1, "SMTP authentication failed" ) return ( 1, "SMTP authentication failed" )
unless $smtp->auth( $conf->{SMTPAuthUser}, unless $smtp->auth( $conf->{SMTPAuthUser},
$conf->{SMTPAuthPass} ); $conf->{SMTPAuthPass} );
# Return # Return
@ -308,15 +303,14 @@ sub tests {
# SAML entity ID must be uniq # SAML entity ID must be uniq
samlIDPEntityIdUniqueness => sub { samlIDPEntityIdUniqueness => sub {
return 1 return 1
unless ( $conf->{samlIDPMetaDataXML} unless ( $conf->{samlIDPMetaDataXML}
and %{ $conf->{samlIDPMetaDataXML} } ); and %{ $conf->{samlIDPMetaDataXML} } );
my @msg; my @msg;
my $res = 1; my $res = 1;
my %entityIds; my %entityIds;
foreach my $idpId ( keys %{ $conf->{samlIDPMetaDataXML} } ) { foreach my $idpId ( keys %{ $conf->{samlIDPMetaDataXML} } ) {
unless ( unless ( $conf->{samlIDPMetaDataXML}->{$idpId}
$conf->{samlIDPMetaDataXML}->{$idpId}->{samlIDPMetaDataXML} ->{samlIDPMetaDataXML} =~ /entityID=(['"])(.+?)\1/si )
=~ /entityID=(['"])(.+?)\1/si )
{ {
push @msg, "$idpId SAML metadata has no EntityID"; push @msg, "$idpId SAML metadata has no EntityID";
$res = 0; $res = 0;
@ -325,7 +319,7 @@ sub tests {
my $eid = $2; my $eid = $2;
if ( defined $entityIds{$eid} ) { if ( defined $entityIds{$eid} ) {
push @msg, push @msg,
"$idpId and $entityIds{$eid} have the same SAML EntityID"; "$idpId and $entityIds{$eid} have the same SAML EntityID";
$res = 0; $res = 0;
next; next;
} }
@ -335,15 +329,15 @@ sub tests {
}, },
samlSPEntityIdUniqueness => sub { samlSPEntityIdUniqueness => sub {
return 1 return 1
unless ( $conf->{samlSPMetaDataXML} unless ( $conf->{samlSPMetaDataXML}
and %{ $conf->{samlSPMetaDataXML} } ); and %{ $conf->{samlSPMetaDataXML} } );
my @msg; my @msg;
my $res = 1; my $res = 1;
my %entityIds; my %entityIds;
foreach my $spId ( keys %{ $conf->{samlSPMetaDataXML} } ) { foreach my $spId ( keys %{ $conf->{samlSPMetaDataXML} } ) {
unless ( unless (
$conf->{samlSPMetaDataXML}->{$spId}->{samlSPMetaDataXML} =~ $conf->{samlSPMetaDataXML}->{$spId}->{samlSPMetaDataXML}
/entityID=(['"])(.+?)\1/si ) =~ /entityID=(['"])(.+?)\1/si )
{ {
push @msg, "$spId SAML metadata has no EntityID"; push @msg, "$spId SAML metadata has no EntityID";
$res = 0; $res = 0;
@ -352,7 +346,7 @@ sub tests {
my $eid = $2; my $eid = $2;
if ( defined $entityIds{$eid} ) { if ( defined $entityIds{$eid} ) {
push @msg, push @msg,
"$spId and $entityIds{$eid} have the same SAML EntityID"; "$spId and $entityIds{$eid} have the same SAML EntityID";
$res = 0; $res = 0;
next; next;
} }
@ -366,7 +360,7 @@ sub tests {
return 1 unless ( $conf->{authentication} eq 'Combination' ); return 1 unless ( $conf->{authentication} eq 'Combination' );
require Lemonldap::NG::Common::Combination::Parser; require Lemonldap::NG::Common::Combination::Parser;
return ( 0, 'No module declared for combination' ) return ( 0, 'No module declared for combination' )
unless ( $conf->{combModules} and %{ $conf->{combModules} } ); unless ( $conf->{combModules} and %{ $conf->{combModules} } );
my $moduleList; my $moduleList;
foreach my $md ( keys %{ $conf->{combModules} } ) { foreach my $md ( keys %{ $conf->{combModules} } ) {
my $entry = $conf->{combModules}->{$md}; my $entry = $conf->{combModules}->{$md};
@ -377,8 +371,8 @@ sub tests {
); );
} }
eval { eval {
Lemonldap::NG::Common::Combination::Parser->parse( $moduleList, Lemonldap::NG::Common::Combination::Parser->parse(
$conf->{combination} ); $moduleList, $conf->{combination} );
}; };
return ( 0, $@ ) if ($@); return ( 0, $@ ) if ($@);
@ -403,7 +397,7 @@ sub tests {
eval "use Convert::Base32"; eval "use Convert::Base32";
return ( 1, return ( 1,
"Convert::Base32 module is required to enable TOTP" ) "Convert::Base32 module is required to enable TOTP" )
if ($@); if ($@);
} }
# Use U2F # Use U2F
@ -412,7 +406,7 @@ sub tests {
{ {
eval "use Crypt::U2F::Server::Simple"; eval "use Crypt::U2F::Server::Simple";
return ( 1, return ( 1,
"Crypt::U2F::Server::Simple module is required to enable U2F" "Crypt::U2F::Server::Simple module is required to enable U2F"
) if ($@); ) if ($@);
} }
@ -420,7 +414,7 @@ sub tests {
if ( $conf->{yubikey2fActivation} ) { if ( $conf->{yubikey2fActivation} ) {
eval "use Auth::Yubikey_WebClient"; eval "use Auth::Yubikey_WebClient";
return ( 1, return ( 1,
"Auth::Yubikey_WebClient module is required to enable Yubikey" "Auth::Yubikey_WebClient module is required to enable Yubikey"
) if ($@); ) if ($@);
} }
@ -434,7 +428,7 @@ sub tests {
my $w = ""; my $w = "";
foreach ( 'totp', 'u' ) { foreach ( 'totp', 'u' ) {
$w .= uc($_) . "2F is activated twice \n" $w .= uc($_) . "2F is activated twice \n"
if ( $conf->{ $_ . '2fActivation' } eq '1' ); if ( $conf->{ $_ . '2fActivation' } eq '1' );
} }
return ( 1, ( $w ? $w : () ) ); return ( 1, ( $w ? $w : () ) );
}, },
@ -445,10 +439,8 @@ sub tests {
return 1 unless ( defined $conf->{totp2fDigits} ); return 1 unless ( defined $conf->{totp2fDigits} );
return ( return (
1, 1,
( ( ( $conf->{totp2fDigits} == 6
( or $conf->{totp2fDigits} == 8
$conf->{totp2fDigits} == 6
or $conf->{totp2fDigits} == 8
) )
? '' ? ''
: 'TOTP should be 6 or 8 digits long' : 'TOTP should be 6 or 8 digits long'
@ -460,9 +452,9 @@ sub tests {
totp2fParams => sub { totp2fParams => sub {
return 1 unless ( $conf->{totp2fActivation} ); return 1 unless ( $conf->{totp2fActivation} );
return ( 0, 'TOTP range must be defined' ) return ( 0, 'TOTP range must be defined' )
unless ( $conf->{totp2fRange} ); unless ( $conf->{totp2fRange} );
return ( 1, "TOTP interval should be higher than 10s" ) return ( 1, "TOTP interval should be higher than 10s" )
unless ( $conf->{totp2fInterval} > 10 ); unless ( $conf->{totp2fInterval} > 10 );
# Return # Return
return 1; return 1;
@ -473,12 +465,11 @@ sub tests {
yubikey2fParams => sub { yubikey2fParams => sub {
return 1 unless ( $conf->{yubikey2fActivation} ); return 1 unless ( $conf->{yubikey2fActivation} );
return ( 0, "Yubikey client ID and secret key must be set" ) return ( 0, "Yubikey client ID and secret key must be set" )
unless ( defined $conf->{yubikey2fSecretKey} unless ( defined $conf->{yubikey2fSecretKey}
&& defined $conf->{yubikey2fClientID} ); && defined $conf->{yubikey2fClientID} );
return ( return (
1, 1,
( ( ( $conf->{yubikey2fPublicIDSize} == 12 )
( $conf->{yubikey2fPublicIDSize} == 12 )
? '' ? ''
: 'Yubikey public ID size should be 12 digits long' : 'Yubikey public ID size should be 12 digits long'
) )
@ -489,7 +480,7 @@ sub tests {
rest2fVerifyUrl => sub { rest2fVerifyUrl => sub {
return 1 unless ( $conf->{rest2fActivation} ); return 1 unless ( $conf->{rest2fActivation} );
return ( 0, "REST 2F Verify URL must be set" ) return ( 0, "REST 2F Verify URL must be set" )
unless ( defined $conf->{rest2fVerifyUrl} ); unless ( defined $conf->{rest2fVerifyUrl} );
# Return # Return
return 1; return 1;
@ -503,15 +494,16 @@ sub tests {
my $ok = 0; my $ok = 0;
foreach (qw(u totp yubikey)) { foreach (qw(u totp yubikey)) {
$ok ||= $conf->{ $_ . '2fActivation' } $ok ||= $conf->{ $_ . '2fActivation' }
&& $conf->{ $_ . '2fSelfRegistration' }; && $conf->{ $_ . '2fSelfRegistration' };
last if ($ok); last if ($ok);
} }
$ok ||= $conf->{'utotp2fActivation'} $ok ||= $conf->{'utotp2fActivation'}
&& ( $conf->{'u2fSelfRegistration'} && ( $conf->{'u2fSelfRegistration'}
|| $conf->{'totp2fSelfRegistration'} ); || $conf->{'totp2fSelfRegistration'} );
$msg = "A self registrable module should be enabled to require 2FA" $msg
unless ($ok); = "A self registrable module should be enabled to require 2FA"
unless ($ok);
return ( 1, $msg ); return ( 1, $msg );
}, },
@ -520,7 +512,7 @@ sub tests {
ext2fCommands => sub { ext2fCommands => sub {
return 1 unless ( $conf->{ext2fActivation} ); return 1 unless ( $conf->{ext2fActivation} );
return ( 0, "External 2F Send or Validate command must be set" ) return ( 0, "External 2F Send or Validate command must be set" )
unless ( defined $conf->{ext2FSendCommand} unless ( defined $conf->{ext2FSendCommand}
&& defined $conf->{ext2FValidateCommand} ); && defined $conf->{ext2FValidateCommand} );
# Return # Return
@ -531,9 +523,9 @@ sub tests {
formTimeout => sub { formTimeout => sub {
return 1 unless ( defined $conf->{formTimeout} ); return 1 unless ( defined $conf->{formTimeout} );
return ( 0, "XSRF form token TTL must be higher than 30s" ) return ( 0, "XSRF form token TTL must be higher than 30s" )
unless ( $conf->{formTimeout} > 30 ); unless ( $conf->{formTimeout} > 30 );
return ( 1, "XSRF form token TTL should not be higher than 2mn" ) return ( 1, "XSRF form token TTL should not be higher than 2mn" )
if ( $conf->{formTimeout} > 120 ); if ( $conf->{formTimeout} > 120 );
# Return # Return
return 1; return 1;
@ -543,8 +535,11 @@ sub tests {
bruteForceProtection => sub { bruteForceProtection => sub {
return 1 unless ( $conf->{bruteForceProtection} ); return 1 unless ( $conf->{bruteForceProtection} );
return ( 1, return ( 1,
'"History" plugin is required for "BruteForceProtection" plugin' '"History" plugin is required to enable "BruteForceProtection" plugin'
) unless ( $conf->{loginHistoryEnabled} ); ) unless ( $conf->{loginHistoryEnabled} );
return ( 1,
'Number of failed logins must be higher than 2 to enable "BruteForceProtection" plugin'
) unless ( $conf->{failedLoginNumber} > 2 );
# Return # Return
return 1; return 1;