make tidy

This commit is contained in:
Clément OUDOT 2018-10-12 10:04:03 +02:00
parent f43139321c
commit c5f9a7f95b
39 changed files with 765 additions and 690 deletions

View File

@ -541,9 +541,10 @@ sub authChoiceModules {
my @res; my @res;
foreach my $k ( sort keys %$value ) { foreach my $k ( sort keys %$value ) {
my $data = [ split /;/, $value->{$k} ]; my $data = [ split /;/, $value->{$k} ];
eval {$data->[5] = from_json($data->[5]) if $data->[5] }; eval { $data->[5] = from_json( $data->[5] ) if $data->[5] };
if($@){ if ($@) {
$self->logger->error("Bad value in choice over parameters, deleted ($@)"); $self->logger->error(
"Bad value in choice over parameters, deleted ($@)");
} }
push @res, push @res,
{ {

View File

@ -107,7 +107,7 @@ sub statusInit {
exec $perl_exec, '-MLemonldap::NG::Handler::Lib::Status', exec $perl_exec, '-MLemonldap::NG::Handler::Lib::Status',
# Insert @INC in Perl path # Insert @INC in Perl path
map( { "-I$_" } @INC ), map( {"-I$_"} @INC ),
# Command to launch # Command to launch
'-e', '&Lemonldap::NG::Handler::Lib::Status::run()', '-e', '&Lemonldap::NG::Handler::Lib::Status::run()',

View File

@ -21,7 +21,7 @@ ok(
cookieName => 'lemonldap', cookieName => 'lemonldap',
securedCookie => 0, securedCookie => 0,
https => 0, https => 0,
userLogger => 'Lemonldap::NG::Common::Logger::Null', userLogger => 'Lemonldap::NG::Common::Logger::Null',
} }
), ),
'initialization' 'initialization'

View File

@ -29,7 +29,7 @@ sub init {
cookieName => 'lemonldap', cookieName => 'lemonldap',
securedCookie => 0, securedCookie => 0,
https => 0, https => 0,
logger => 'Lemonldap::NG::Common::Logger::Std', logger => 'Lemonldap::NG::Common::Logger::Std',
%$prms %$prms
); );
ok( ok(

View File

@ -8,17 +8,17 @@ sub types {
'array' => { 'array' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'authParamsText' => { 'authParamsText' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'blackWhiteList' => { 'blackWhiteList' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'bool' => { 'bool' => {
'msgFail' => '__notABoolean__', 'msgFail' => '__notABoolean__',
@ -36,17 +36,17 @@ sub types {
split( /\n/, $@, 0 ) ) split( /\n/, $@, 0 ) )
); );
return $err ? ( 1, "__badExpression__: $err" ) : 1; return $err ? ( 1, "__badExpression__: $err" ) : 1;
} }
}, },
'catAndAppList' => { 'catAndAppList' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'file' => { 'file' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'hostname' => { 'hostname' => {
'form' => 'text', 'form' => 'text',
@ -80,48 +80,48 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
if $_ =~ /exportedvars$/i and defined $conf->{$_}{$val}; if $_ =~ /exportedvars$/i and defined $conf->{$_}{$val};
} }
return 1, "__unknownAttrOrMacro__: $val"; return 1, "__unknownAttrOrMacro__: $val";
} }
}, },
'longtext' => { 'longtext' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'menuApp' => { 'menuApp' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'menuCat' => { 'menuCat' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'oidcmetadatajson' => { 'oidcmetadatajson' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'oidcmetadatajwks' => { 'oidcmetadatajwks' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'oidcOPMetaDataNode' => { 'oidcOPMetaDataNode' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'oidcRPMetaDataNode' => { 'oidcRPMetaDataNode' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'password' => { 'password' => {
'msgFail' => '__malformedValue__', 'msgFail' => '__malformedValue__',
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'pcre' => { 'pcre' => {
'form' => 'text', 'form' => 'text',
@ -132,7 +132,7 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
} }
}; };
return $@ ? ( 0, "__badRegexp__: $@" ) : 1; return $@ ? ( 0, "__badRegexp__: $@" ) : 1;
} }
}, },
'PerlModule' => { 'PerlModule' => {
'form' => 'text', 'form' => 'text',
@ -142,17 +142,17 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
'portalskin' => { 'portalskin' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'portalskinbackground' => { 'portalskinbackground' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'post' => { 'post' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'RSAPrivateKey' => { 'RSAPrivateKey' => {
'test' => sub { 'test' => sub {
@ -160,7 +160,7 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
m[^(?:(?:\-+\s*BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:RSA\s+)PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$]s m[^(?:(?:\-+\s*BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:RSA\s+)PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$]s
? 1 ? 1
: ( 1, '__badPemEncoding__' ); : ( 1, '__badPemEncoding__' );
} }
}, },
'RSAPublicKey' => { 'RSAPublicKey' => {
'test' => sub { 'test' => sub {
@ -168,7 +168,7 @@ m[^(?:(?:\-+\s*BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\n
m[^(?:(?:\-+\s*BEGIN\s+PUBLIC\s+KEY\s*\-+\r?\n)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+PUBLIC\s+KEY\s*\-+)?[\r\n]*)?$]s m[^(?:(?:\-+\s*BEGIN\s+PUBLIC\s+KEY\s*\-+\r?\n)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+PUBLIC\s+KEY\s*\-+)?[\r\n]*)?$]s
? 1 ? 1
: ( 1, '__badPemEncoding__' ); : ( 1, '__badPemEncoding__' );
} }
}, },
'RSAPublicKeyOrCertificate' => { 'RSAPublicKeyOrCertificate' => {
'test' => sub { 'test' => sub {
@ -176,37 +176,37 @@ m[^(?:(?:\-+\s*BEGIN\s+PUBLIC\s+KEY\s*\-+\r?\n)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\
m[^(?:(?:\-+\s*BEGIN\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+\r?\n)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+)?[\r\n]*)?$]s m[^(?:(?:\-+\s*BEGIN\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+\r?\n)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+)?[\r\n]*)?$]s
? 1 ? 1
: ( 1, '__badPemEncoding__' ); : ( 1, '__badPemEncoding__' );
} }
}, },
'rule' => { 'rule' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'samlAssertion' => { 'samlAssertion' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'samlAttribute' => { 'samlAttribute' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'samlIDPMetaDataNode' => { 'samlIDPMetaDataNode' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'samlService' => { 'samlService' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'samlSPMetaDataNode' => { 'samlSPMetaDataNode' => {
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'select' => { 'select' => {
'test' => sub { 'test' => sub {
@ -216,19 +216,19 @@ m[^(?:(?:\-+\s*BEGIN\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+\r?\n)?[a-zA-Z0-9/\+\r\
return $test return $test
? 1 ? 1
: ( 1, "Invalid value '$_[0]' for this select" ); : ( 1, "Invalid value '$_[0]' for this select" );
} }
}, },
'subContainer' => { 'subContainer' => {
'keyTest' => qr/\w/, 'keyTest' => qr/\w/,
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'text' => { 'text' => {
'msgFail' => '__malformedValue__', 'msgFail' => '__malformedValue__',
'test' => sub { 'test' => sub {
1; 1;
} }
}, },
'trool' => { 'trool' => {
'msgFail' => '__authorizedValues__: -1, 0, 1', 'msgFail' => '__authorizedValues__: -1, 0, 1',
@ -1050,7 +1050,7 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
split( /\n/, $@, 0 ) ) split( /\n/, $@, 0 ) )
); );
return $err ? ( 1, "__badExpression__: $err" ) : 1; return $err ? ( 1, "__badExpression__: $err" ) : 1;
} }
}, },
'type' => 'keyTextContainer' 'type' => 'keyTextContainer'
}, },
@ -1219,7 +1219,7 @@ qr/^(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-
and defined $conf->{$_}{$val}; and defined $conf->{$_}{$val};
} }
return 1, "__unknownAttrOrMacro__: $val"; return 1, "__unknownAttrOrMacro__: $val";
} }
}, },
'type' => 'doubleHash' 'type' => 'doubleHash'
}, },
@ -1502,7 +1502,7 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
split( /\n/, $@, 0 ) ) split( /\n/, $@, 0 ) )
); );
return $err ? ( 1, "__badExpression__: $err" ) : 1; return $err ? ( 1, "__badExpression__: $err" ) : 1;
} }
}, },
'type' => 'ruleContainer' 'type' => 'ruleContainer'
}, },

View File

@ -29,15 +29,15 @@ sub types {
# Simple text types # Simple text types
text => { text => {
test => sub {1}, test => sub { 1 },
msgFail => '__malformedValue__', msgFail => '__malformedValue__',
}, },
password => { password => {
test => sub {1}, test => sub { 1 },
msgFail => '__malformedValue__', msgFail => '__malformedValue__',
}, },
longtext => { longtext => {
test => sub {1} test => sub { 1 }
}, },
url => { url => {
form => 'text', form => 'text',
@ -57,7 +57,7 @@ sub types {
pcre => { pcre => {
form => 'text', form => 'text',
test => sub { test => sub {
eval {qr/$_[0]/}; eval { qr/$_[0]/ };
return $@ ? ( 0, "__badRegexp__: $@" ) : (1); return $@ ? ( 0, "__badRegexp__: $@" ) : (1);
}, },
}, },
@ -66,11 +66,11 @@ sub types {
test => sub { test => sub {
my ( $val, $conf ) = @_; my ( $val, $conf ) = @_;
return 1 return 1
if ( defined $conf->{macros}->{$val} if ( defined $conf->{macros}->{$val}
or $val eq '_timezone' ); or $val eq '_timezone' );
foreach ( keys %$conf ) { foreach ( keys %$conf ) {
return 1 return 1
if ( $_ =~ /exportedvars$/i if ( $_ =~ /exportedvars$/i
and defined $conf->{$_}->{$val} ); and defined $conf->{$_}->{$val} );
} }
return ( 1, "__unknownAttrOrMacro__: $val" ); return ( 1, "__unknownAttrOrMacro__: $val" );
@ -102,27 +102,27 @@ sub types {
}, },
subContainer => { subContainer => {
keyTest => qr/\w/, keyTest => qr/\w/,
test => sub {1}, test => sub { 1 },
}, },
select => { select => {
test => sub { test => sub {
my $test = grep ( { $_ eq $_[0] } my $test = grep ( { $_ eq $_[0] }
map ( { $_->{k} } @{ $_[2]->{select} } ) ); map ( { $_->{k} } @{ $_[2]->{select} } ) );
return $test return $test
? 1 ? 1
: ( 1, "Invalid value '$_[0]' for this select" ); : ( 1, "Invalid value '$_[0]' for this select" );
}, },
}, },
# Files type (long text) # Files type (long text)
file => { file => {
test => sub {1} test => sub { 1 }
}, },
RSAPublicKey => { RSAPublicKey => {
test => sub { test => sub {
return ( return (
$_[0] $_[0] =~
=~ /^(?:(?:\-+\s*BEGIN\s+PUBLIC\s+KEY\s*\-+\r?\n)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+PUBLIC\s+KEY\s*\-+)?[\r\n]*)?$/s /^(?:(?:\-+\s*BEGIN\s+PUBLIC\s+KEY\s*\-+\r?\n)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+PUBLIC\s+KEY\s*\-+)?[\r\n]*)?$/s
? (1) ? (1)
: ( 1, '__badPemEncoding__' ) : ( 1, '__badPemEncoding__' )
); );
@ -131,8 +131,8 @@ sub types {
'RSAPublicKeyOrCertificate' => { 'RSAPublicKeyOrCertificate' => {
'test' => sub { 'test' => sub {
return ( return (
$_[0] $_[0] =~
=~ /^(?:(?:\-+\s*BEGIN\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+\r?\n)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+)?[\r\n]*)?$/s /^(?:(?:\-+\s*BEGIN\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+\r?\n)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+)?[\r\n]*)?$/s
? (1) ? (1)
: ( 1, '__badPemEncoding__' ) : ( 1, '__badPemEncoding__' )
); );
@ -141,8 +141,8 @@ sub types {
RSAPrivateKey => { RSAPrivateKey => {
test => sub { test => sub {
return ( return (
$_[0] $_[0] =~
=~ /^(?:(?:\-+\s*BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:RSA\s+)PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$/s /^(?:(?:\-+\s*BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:RSA\s+)PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$/s
? (1) ? (1)
: ( 1, '__badPemEncoding__' ) : ( 1, '__badPemEncoding__' )
); );
@ -150,13 +150,13 @@ sub types {
}, },
authParamsText => { authParamsText => {
test => sub {1} test => sub { 1 }
}, },
blackWhiteList => { blackWhiteList => {
test => sub {1} test => sub { 1 }
}, },
catAndAppList => { catAndAppList => {
test => sub {1} test => sub { 1 }
}, },
keyText => { keyText => {
keyTest => qr/^[a-zA-Z0-9_]+$/, keyTest => qr/^[a-zA-Z0-9_]+$/,
@ -164,52 +164,52 @@ sub types {
msgFail => '__badValue__', msgFail => '__badValue__',
}, },
menuApp => { menuApp => {
test => sub {1} test => sub { 1 }
}, },
menuCat => { menuCat => {
test => sub {1} test => sub { 1 }
}, },
oidcOPMetaDataNode => { oidcOPMetaDataNode => {
test => sub {1} test => sub { 1 }
}, },
oidcRPMetaDataNode => { oidcRPMetaDataNode => {
test => sub {1} test => sub { 1 }
}, },
oidcmetadatajson => { oidcmetadatajson => {
test => sub {1} test => sub { 1 }
}, },
oidcmetadatajwks => { oidcmetadatajwks => {
test => sub {1} test => sub { 1 }
}, },
portalskin => { portalskin => {
test => sub {1} test => sub { 1 }
}, },
portalskinbackground => { portalskinbackground => {
test => sub {1} test => sub { 1 }
}, },
post => { post => {
test => sub {1} test => sub { 1 }
}, },
rule => { rule => {
test => sub {1} test => sub { 1 }
}, },
samlAssertion => { samlAssertion => {
test => sub {1} test => sub { 1 }
}, },
samlAttribute => { samlAttribute => {
test => sub {1} test => sub { 1 }
}, },
samlIDPMetaDataNode => { samlIDPMetaDataNode => {
test => sub {1} test => sub { 1 }
}, },
samlSPMetaDataNode => { samlSPMetaDataNode => {
test => sub {1} test => sub { 1 }
}, },
samlService => { samlService => {
test => sub {1} test => sub { 1 }
}, },
array => { array => {
test => sub {1} test => sub { 1 }
}, },
}; };
} }
@ -221,7 +221,7 @@ sub attributes {
checkTime => { checkTime => {
type => 'int', type => 'int',
documentation => documentation =>
'Timeout to check new configuration in local cache', 'Timeout to check new configuration in local cache',
default => 600, default => 600,
flags => 'hp', flags => 'hp',
}, },
@ -229,7 +229,7 @@ sub attributes {
type => 'array', type => 'array',
documentation => 'Alterable session keys by user itself', documentation => 'Alterable session keys by user itself',
default => default =>
[ '_appsListOrder', '_oidcConnectedRP', '_oidcConsents' ], [ '_appsListOrder', '_oidcConnectedRP', '_oidcConsents' ],
}, },
configStorage => { configStorage => {
type => 'text', type => 'text',
@ -252,14 +252,12 @@ sub attributes {
documentation => 'Enable Cross Domain Authentication', documentation => 'Enable Cross Domain Authentication',
}, },
cfgAuthor => { cfgAuthor => {
type => 'text', type => 'text',
documentation => documentation => 'Name of the author of the current configuration',
'Name of the author of the current configuration',
}, },
cfgAuthorIP => { cfgAuthorIP => {
type => 'text', type => 'text',
documentation => documentation => 'Uploader IP address of the current configuration',
'Uploader IP address of the current configuration',
}, },
cfgDate => { cfgDate => {
type => 'int', type => 'int',
@ -281,7 +279,7 @@ sub attributes {
confirmFormMethod => { confirmFormMethod => {
type => "select", type => "select",
select => select =>
[ { k => 'get', v => 'GET' }, { k => 'post', v => 'POST' }, ], [ { k => 'get', v => 'GET' }, { k => 'post', v => 'POST' }, ],
default => 'post', default => 'post',
documentation => 'HTTP method for confirm page form', documentation => 'HTTP method for confirm page form',
}, },
@ -301,7 +299,7 @@ sub attributes {
infoFormMethod => { infoFormMethod => {
type => "select", type => "select",
select => select =>
[ { k => 'get', v => 'GET' }, { k => 'post', v => 'POST' }, ], [ { k => 'get', v => 'GET' }, { k => 'post', v => 'POST' }, ],
default => 'get', default => 'get',
documentation => 'HTTP method for info page form', documentation => 'HTTP method for info page form',
}, },
@ -316,11 +314,10 @@ sub attributes {
documentation => 'Use javascript for redirections', documentation => 'Use javascript for redirections',
}, },
logoutServices => { logoutServices => {
type => 'keyTextContainer', type => 'keyTextContainer',
help => 'logoutforward.html', help => 'logoutforward.html',
default => {}, default => {},
documentation => documentation => 'Send logout trough GET request to these services',
'Send logout trough GET request to these services',
}, },
maintenance => { maintenance => {
default => 0, default => 0,
@ -358,24 +355,24 @@ sub attributes {
default => '_user', default => '_user',
help => 'monitoring.html', help => 'monitoring.html',
documentation => documentation =>
'Session parameter to display connected user in portal', 'Session parameter to display connected user in portal',
}, },
redirectFormMethod => { redirectFormMethod => {
type => "select", type => "select",
select => select =>
[ { k => 'get', v => 'GET' }, { k => 'post', v => 'POST' }, ], [ { k => 'get', v => 'GET' }, { k => 'post', v => 'POST' }, ],
default => 'get', default => 'get',
documentation => 'HTTP method for redirect page form', documentation => 'HTTP method for redirect page form',
}, },
reloadUrls => { reloadUrls => {
type => 'keyTextContainer', type => 'keyTextContainer',
help => 'configlocation.html#configuration_reload', help => 'configlocation.html#configuration_reload',
keyTest => qr/^$Regexp::Common::URI::RFC2396::host(?::\d+)?$/, keyTest => qr/^$Regexp::Common::URI::RFC2396::host(?::\d+)?$/,
test => $url, test => $url,
msgFail => '__badUrl__', msgFail => '__badUrl__',
documentation => 'URL to call on reload', documentation => 'URL to call on reload',
}, },
portalMainLogo => { portalMainLogo => {
type => 'text', type => 'text',
default => 'common/logos/logo_llng_400px.png', default => 'common/logos/logo_llng_400px.png',
documentation => 'Portal main logo path', documentation => 'Portal main logo path',
@ -407,7 +404,7 @@ sub attributes {
skipRenewConfirmation => { skipRenewConfirmation => {
type => 'bool', type => 'bool',
documentation => documentation =>
'Avoid asking confirmation when an Issuer asks to renew auth', 'Avoid asking confirmation when an Issuer asks to renew auth',
}, },
# Loggers (ini only) # Loggers (ini only)
@ -449,9 +446,9 @@ sub attributes {
# Manager or PSGI protected apps # Manager or PSGI protected apps
protection => { protection => {
type => 'text', type => 'text',
test => qr/^(?:none|authenticate|manager|)$/, test => qr/^(?:none|authenticate|manager|)$/,
msgFail => '__authorizedValues__: none authenticate manager', msgFail => '__authorizedValues__: none authenticate manager',
documentation => 'Manager protection method', documentation => 'Manager protection method',
flags => 'hm', flags => 'hm',
}, },
@ -467,8 +464,7 @@ sub attributes {
keyTest => qr/\w/, keyTest => qr/\w/,
help => 'portalmenu.html#categories_and_applications', help => 'portalmenu.html#categories_and_applications',
default => { default => {
default => default => { catname => 'Default category', type => "category" }
{ catname => 'Default category', type => "category" }
}, },
documentation => 'Applications list', documentation => 'Applications list',
}, },
@ -481,7 +477,7 @@ sub attributes {
type => 'bool', type => 'bool',
default => 0, default => 0,
documentation => documentation =>
'Show error if mail is not found in password reset process', 'Show error if mail is not found in password reset process',
}, },
portalOpenLinkInNewWindow => { portalOpenLinkInNewWindow => {
type => 'bool', type => 'bool',
@ -504,22 +500,26 @@ sub attributes {
documentation => 'Background image of portal skin', documentation => 'Background image of portal skin',
select => [ select => [
{ k => "", v => 'None' }, { k => "", v => 'None' },
{ k => "1280px-Anse_Source_d'Argent_2-La_Digue.jpg", {
k => "1280px-Anse_Source_d'Argent_2-La_Digue.jpg",
v => 'Anse' v => 'Anse'
}, },
{ k => {
"1280px-Autumn-clear-water-waterfall-landscape_-_Virginia_-_ForestWander.jpg", k =>
"1280px-Autumn-clear-water-waterfall-landscape_-_Virginia_-_ForestWander.jpg",
v => 'Waterfall' v => 'Waterfall'
}, },
{ k => "1280px-BrockenSnowedTrees.jpg", v => 'Snowed Trees' }, { k => "1280px-BrockenSnowedTrees.jpg", v => 'Snowed Trees' },
{ k => {
"1280px-Cedar_Breaks_National_Monument_partially.jpg", k => "1280px-Cedar_Breaks_National_Monument_partially.jpg",
v => 'National Monument' v => 'National Monument'
}, },
{ k => "1280px-Parry_Peak_from_Winter_Park.jpg", {
k => "1280px-Parry_Peak_from_Winter_Park.jpg",
v => 'Winter' v => 'Winter'
}, },
{ k => "Aletschgletscher_mit_Pinus_cembra1.jpg", {
k => "Aletschgletscher_mit_Pinus_cembra1.jpg",
v => 'Pinus' v => 'Pinus'
}, },
], ],
@ -565,13 +565,13 @@ sub attributes {
default => 0, default => 0,
type => 'bool', type => 'bool',
documentation => documentation =>
'Enable force to authenticate when displaying portal', 'Enable force to authenticate when displaying portal',
}, },
portalForceAuthnInterval => { portalForceAuthnInterval => {
default => 5, default => 5,
type => 'int', type => 'int',
documentation => documentation =>
'Maximun interval in seconds since last authentifcation to force reauthentication', 'Maximun interval in seconds since last authentifcation to force reauthentication',
}, },
bruteForceProtection => { bruteForceProtection => {
default => 0, default => 0,
@ -582,18 +582,18 @@ sub attributes {
default => 30, default => 30,
type => 'int', type => 'int',
documentation => documentation =>
'Brute force attack protection -> Tempo before try again', 'Brute force attack protection -> Tempo before try again',
}, },
bruteForceProtectionMaxAge => { bruteForceProtectionMaxAge => {
default => 300, default => 300,
type => 'int', type => 'int',
documentation => documentation =>
'Brute force attack protection -> Max age third failed login', 'Brute force attack protection -> Max age third failed login',
}, },
grantSessionRules => { grantSessionRules => {
type => 'grantContainer', type => 'grantContainer',
keyTest => $perlExpr, keyTest => $perlExpr,
test => sub {1}, test => sub { 1 },
documentation => 'Rules to grant sessions', documentation => 'Rules to grant sessions',
}, },
hiddenAttributes => { hiddenAttributes => {
@ -629,7 +629,7 @@ sub attributes {
type => 'text', type => 'text',
default => "'self'", default => "'self'",
documentation => documentation =>
'Authorizated Ajax destination for Content-Security-Policy', 'Authorizated Ajax destination for Content-Security-Policy',
}, },
cspFont => { cspFont => {
type => 'text', type => 'text',
@ -652,7 +652,7 @@ sub attributes {
documentation => 'Regular expression to create a random password', documentation => 'Regular expression to create a random password',
}, },
trustedDomains => trustedDomains =>
{ type => 'text', documentation => 'Trusted domains', }, { type => 'text', documentation => 'Trusted domains', },
storePassword => { storePassword => {
default => 0, default => 0,
type => 'bool', type => 'bool',
@ -788,10 +788,10 @@ sub attributes {
flags => 'hp', flags => 'hp',
}, },
domain => { domain => {
type => 'text', type => 'text',
test => qr/^(?:$Regexp::Common::URI::RFC2396::hostname)?$/, test => qr/^(?:$Regexp::Common::URI::RFC2396::hostname)?$/,
msgFail => '__badDomainName__', msgFail => '__badDomainName__',
default => 'example.com', default => 'example.com',
documentation => 'DNS domain', documentation => 'DNS domain',
flags => 'hp', flags => 'hp',
}, },
@ -886,7 +886,7 @@ sub attributes {
groups => { groups => {
type => 'keyTextContainer', type => 'keyTextContainer',
help => help =>
'exportedvars.html#extend_variables_using_macros_and_groups', 'exportedvars.html#extend_variables_using_macros_and_groups',
test => $perlExpr, test => $perlExpr,
default => {}, default => {},
documentation => 'Groups', documentation => 'Groups',
@ -894,7 +894,7 @@ sub attributes {
macros => { macros => {
type => 'keyTextContainer', type => 'keyTextContainer',
help => help =>
'exportedvars.html#extend_variables_using_macros_and_groups', 'exportedvars.html#extend_variables_using_macros_and_groups',
keyTest => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/, keyTest => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/,
keyMsgFail => '__badMacroName__', keyMsgFail => '__badMacroName__',
test => $perlExpr, test => $perlExpr,
@ -915,7 +915,7 @@ sub attributes {
'Directory' => '/var/lib/lemonldap-ng/sessions/', 'Directory' => '/var/lib/lemonldap-ng/sessions/',
'LockDirectory' => '/var/lib/lemonldap-ng/sessions/lock/', 'LockDirectory' => '/var/lib/lemonldap-ng/sessions/lock/',
'generateModule' => 'generateModule' =>
'Lemonldap::NG::Common::Apache::Session::Generate::SHA256', 'Lemonldap::NG::Common::Apache::Session::Generate::SHA256',
}, },
documentation => 'Session backend module options', documentation => 'Session backend module options',
flags => 'hp', flags => 'hp',
@ -1014,11 +1014,11 @@ sub attributes {
test => sub { test => sub {
my ( $val, $conf ) = @_; my ( $val, $conf ) = @_;
return 1 return 1
if ( defined $conf->{macros}->{$val} if ( defined $conf->{macros}->{$val}
or $val eq '_timezone' ); or $val eq '_timezone' );
foreach ( keys %$conf ) { foreach ( keys %$conf ) {
return 1 return 1
if ( $_ =~ /exportedvars$/i if ( $_ =~ /exportedvars$/i
and defined $conf->{$_}->{$val} ); and defined $conf->{$_}->{$val} );
} }
return ( 1, "__unknownAttrOrMacro__: $val" ); return ( 1, "__unknownAttrOrMacro__: $val" );
@ -1034,10 +1034,9 @@ sub attributes {
documentation => 'Send a mail when password is changed', documentation => 'Send a mail when password is changed',
}, },
portalRequireOldPassword => { portalRequireOldPassword => {
default => 1, default => 1,
type => 'bool', type => 'bool',
documentation => documentation => 'Old password is required to change the password',
'Old password is required to change the password',
}, },
hideOldPassword => { hideOldPassword => {
default => 0, default => 0,
@ -1047,7 +1046,7 @@ sub attributes {
# Mails # Mails
mailBody => mailBody =>
{ type => 'longtext', documentation => 'Custom mail body', }, { type => 'longtext', documentation => 'Custom mail body', },
mailCharset => { mailCharset => {
type => 'text', type => 'text',
default => 'utf-8', default => 'utf-8',
@ -1066,8 +1065,7 @@ sub attributes {
default => 'noreply@example.com', default => 'noreply@example.com',
documentation => 'Sender email', documentation => 'Sender email',
}, },
mailReplyTo => mailReplyTo => { type => 'text', documentation => 'Reply-To address' },
{ type => 'text', documentation => 'Reply-To address' },
mailSessionKey => { mailSessionKey => {
type => 'text', type => 'text',
default => 'mail', default => 'mail',
@ -1090,7 +1088,7 @@ sub attributes {
SMTPServer => { SMTPServer => {
type => 'text', type => 'text',
default => '', default => '',
test => qr/^(?:$Regexp::Common::URI::RFC2396::host(?::\d+)?)?$/, test => qr/^(?:$Regexp::Common::URI::RFC2396::host(?::\d+)?)?$/,
documentation => 'SMTP Server', documentation => 'SMTP Server',
}, },
SMTPPort => { SMTPPort => {
@ -1185,7 +1183,7 @@ sub attributes {
u2fAuthnLevel => { u2fAuthnLevel => {
type => 'int', type => 'int',
documentation => documentation =>
'Authentication level for users authentified by password+U2F' 'Authentication level for users authentified by password+U2F'
}, },
u2fUserCanRemoveKey => { u2fUserCanRemoveKey => {
type => 'bool', type => 'bool',
@ -1207,7 +1205,7 @@ sub attributes {
totp2fAuthnLevel => { totp2fAuthnLevel => {
type => 'int', type => 'int',
documentation => documentation =>
'Authentication level for users authentified by password+TOTP' 'Authentication level for users authentified by password+TOTP'
}, },
totp2fIssuer => { totp2fIssuer => {
type => 'text', type => 'text',
@ -1232,7 +1230,7 @@ sub attributes {
type => 'bool', type => 'bool',
default => 0, default => 0,
documentation => documentation =>
'Display existing TOTP secret in registration form', 'Display existing TOTP secret in registration form',
}, },
totp2fUserCanChangeKey => { totp2fUserCanChangeKey => {
type => 'bool', type => 'bool',
@ -1254,7 +1252,7 @@ sub attributes {
utotp2fAuthnLevel => { utotp2fAuthnLevel => {
type => 'int', type => 'int',
documentation => documentation =>
'Authentication level for users authentified by password+(U2F or TOTP)' 'Authentication level for users authentified by password+(U2F or TOTP)'
}, },
# External second factor # External second factor
@ -1274,7 +1272,7 @@ sub attributes {
ext2fAuthnLevel => { ext2fAuthnLevel => {
type => 'int', type => 'int',
documentation => documentation =>
'Authentication level for users authentified by External second factor' 'Authentication level for users authentified by External second factor'
}, },
ext2fLogo => { ext2fLogo => {
type => 'text', type => 'text',
@ -1314,7 +1312,7 @@ sub attributes {
rest2fAuthnLevel => { rest2fAuthnLevel => {
type => 'int', type => 'int',
documentation => documentation =>
'Authentication level for users authentified by REST second factor' 'Authentication level for users authentified by REST second factor'
}, },
rest2fLogo => { rest2fLogo => {
type => 'text', type => 'text',
@ -1335,7 +1333,7 @@ sub attributes {
yubikey2fAuthnLevel => { yubikey2fAuthnLevel => {
type => 'int', type => 'int',
documentation => documentation =>
'Authentication level for users authentified by Yubikey second factor' 'Authentication level for users authentified by Yubikey second factor'
}, },
yubikey2fClientID => { yubikey2fClientID => {
type => 'text', type => 'text',
@ -1424,7 +1422,7 @@ sub attributes {
exportedAttr => { exportedAttr => {
type => 'text', type => 'text',
documentation => documentation =>
'List of attributes to export by SOAP or REST servers', 'List of attributes to export by SOAP or REST servers',
}, },
wsdlServer => { wsdlServer => {
type => 'bool', type => 'bool',
@ -1452,7 +1450,7 @@ sub attributes {
help => 'writingrulesand_headers.html#rules', help => 'writingrulesand_headers.html#rules',
test => { test => {
keyTest => sub { keyTest => sub {
eval {qr/$_[0]/}; eval { qr/$_[0]/ };
return $@ ? 0 : 1; return $@ ? 0 : 1;
}, },
keyMsgFail => '__badRegexp__', keyMsgFail => '__badRegexp__',
@ -1461,15 +1459,15 @@ sub attributes {
my $s = $val; my $s = $val;
if ( $s =~ s/^logout(?:_(?:sso|app(?:_sso)?))?\s*// ) { if ( $s =~ s/^logout(?:_(?:sso|app(?:_sso)?))?\s*// ) {
return $s =~ m{^(?:https?://.*)?$} return $s =~ m{^(?:https?://.*)?$}
? (1) ? (1)
: ( 0, '__badUrl__' ); : ( 0, '__badUrl__' );
} }
$s =~ s/\b(accept|deny|unprotect|skip)\b/1/g; $s =~ s/\b(accept|deny|unprotect|skip)\b/1/g;
no warnings( 'redefine', 'uninitialized' ); no warnings( 'redefine', 'uninitialized' );
eval $s; eval $s;
my $err = join( '', my $err = join( '',
grep { $_ =~ /Undefined subroutine/ ? () : $_ } grep { $_ =~ /Undefined subroutine/ ? () : $_ }
split( /\n/, $@ ) ); split( /\n/, $@ ) );
return $err ? ( 1, "__badExpression__: $err" ) : (1); return $err ? ( 1, "__badExpression__: $err" ) : (1);
}, },
msgFail => '__badExpression__', msgFail => '__badExpression__',
@ -1495,9 +1493,9 @@ sub attributes {
eval $s; eval $s;
my $err = join( '', my $err = join( '',
grep { $_ =~ /Undefined subroutine/ ? () : $_ } grep { $_ =~ /Undefined subroutine/ ? () : $_ }
split( /\n/, $@ ) ); split( /\n/, $@ ) );
return $err ? ( 1, "__badExpression__: $err" ) : (1); return $err ? ( 1, "__badExpression__: $err" ) : (1);
} }
}, },
documentation => 'Virtualhost headers', documentation => 'Virtualhost headers',
flags => 'h', flags => 'h',
@ -1505,7 +1503,7 @@ sub attributes {
post => { post => {
type => 'postContainer', type => 'postContainer',
help => 'formreplay.html', help => 'formreplay.html',
test => sub {1}, test => sub { 1 },
keyTest => qr/^(?:\*\.)?$Regexp::Common::URI::RFC2396::hostname$/, keyTest => qr/^(?:\*\.)?$Regexp::Common::URI::RFC2396::hostname$/,
keyMsgFail => '__badHostname__', keyMsgFail => '__badHostname__',
documentation => 'Virtualhost urls/Data to post', documentation => 'Virtualhost urls/Data to post',
@ -1603,7 +1601,7 @@ sub attributes {
# CAS IDP # CAS IDP
casAttr => casAttr =>
{ type => 'text', documentation => 'Pivot attribute for CAS', }, { type => 'text', documentation => 'Pivot attribute for CAS', },
casAttributes => { casAttributes => {
type => 'keyTextContainer', type => 'keyTextContainer',
documentation => 'CAS exported attributes', documentation => 'CAS exported attributes',
@ -1762,7 +1760,7 @@ sub attributes {
samlAttributeAuthorityDescriptorAttributeServiceSOAP => { samlAttributeAuthorityDescriptorAttributeServiceSOAP => {
type => 'samlService', type => 'samlService',
default => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' default => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;'
. '#PORTAL#/saml/AA/SOAP;', . '#PORTAL#/saml/AA/SOAP;',
documentation => 'SAML Attribute Authority SOAP', documentation => 'SAML Attribute Authority SOAP',
}, },
samlServicePrivateKeySig => { samlServicePrivateKeySig => {
@ -1803,7 +1801,7 @@ sub attributes {
type => 'bool', type => 'bool',
default => 0, default => 0,
documentation => documentation =>
'Use certificate instead of public key in SAML responses', 'Use certificate instead of public key in SAML responses',
}, },
samlIdPResolveCookie => { samlIdPResolveCookie => {
type => 'text', type => 'text',
@ -1832,7 +1830,7 @@ sub attributes {
type => 'int', type => 'int',
default => 3, default => 3,
documentation => documentation =>
'SAML authn context password protected transport level', 'SAML authn context password protected transport level',
}, },
samlAuthnContextMapTLSClient => { samlAuthnContextMapTLSClient => {
type => 'int', type => 'int',
@ -1901,45 +1899,45 @@ sub attributes {
samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect => { samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect => {
type => 'samlService', type => 'samlService',
default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;' default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;'
. '#PORTAL#/saml/singleSignOn;', . '#PORTAL#/saml/singleSignOn;',
documentation => 'SAML IDP SSO HTTP Redirect', documentation => 'SAML IDP SSO HTTP Redirect',
}, },
samlIDPSSODescriptorSingleSignOnServiceHTTPPost => { samlIDPSSODescriptorSingleSignOnServiceHTTPPost => {
type => 'samlService', type => 'samlService',
default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;' default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;'
. '#PORTAL#/saml/singleSignOn;', . '#PORTAL#/saml/singleSignOn;',
documentation => 'SAML IDP SSO HTTP POST', documentation => 'SAML IDP SSO HTTP POST',
}, },
samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact => { samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact => {
type => 'samlService', type => 'samlService',
default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact;' default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact;'
. '#PORTAL#/saml/singleSignOnArtifact;', . '#PORTAL#/saml/singleSignOnArtifact;',
documentation => 'SAML IDP SSO HTTP Artifact', documentation => 'SAML IDP SSO HTTP Artifact',
}, },
samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect => { samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect => {
type => 'samlService', type => 'samlService',
default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;' default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;'
. '#PORTAL#/saml/singleLogout;' . '#PORTAL#/saml/singleLogout;'
. '#PORTAL#/saml/singleLogoutReturn', . '#PORTAL#/saml/singleLogoutReturn',
documentation => 'SAML IDP SLO HTTP Redirect', documentation => 'SAML IDP SLO HTTP Redirect',
}, },
samlIDPSSODescriptorSingleLogoutServiceHTTPPost => { samlIDPSSODescriptorSingleLogoutServiceHTTPPost => {
type => 'samlService', type => 'samlService',
default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;' default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;'
. '#PORTAL#/saml/singleLogout;' . '#PORTAL#/saml/singleLogout;'
. '#PORTAL#/saml/singleLogoutReturn', . '#PORTAL#/saml/singleLogoutReturn',
documentation => 'SAML IDP SLO HTTP POST', documentation => 'SAML IDP SLO HTTP POST',
}, },
samlIDPSSODescriptorSingleLogoutServiceSOAP => { samlIDPSSODescriptorSingleLogoutServiceSOAP => {
type => 'samlService', type => 'samlService',
default => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' default => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;'
. '#PORTAL#/saml/singleLogoutSOAP;', . '#PORTAL#/saml/singleLogoutSOAP;',
documentation => 'SAML IDP SLO SOAP', documentation => 'SAML IDP SLO SOAP',
}, },
samlIDPSSODescriptorArtifactResolutionServiceArtifact => { samlIDPSSODescriptorArtifactResolutionServiceArtifact => {
type => 'samlAssertion', type => 'samlAssertion',
default => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' default => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;'
. '#PORTAL#/saml/artifact', . '#PORTAL#/saml/artifact',
documentation => 'SAML IDP artifact resolution service', documentation => 'SAML IDP artifact resolution service',
}, },
@ -1979,8 +1977,8 @@ sub attributes {
my $res = 1; my $res = 1;
my %entityIds; my %entityIds;
foreach my $idpId ( keys %$v ) { foreach my $idpId ( keys %$v ) {
unless ( $v->{$idpId}->{samlIDPMetaDataXML} unless ( $v->{$idpId}->{samlIDPMetaDataXML} =~
=~ /entityID="(.+?)"/si ) /entityID="(.+?)"/si )
{ {
push @msg, "$idpId SAML metadata has ne EntityID"; push @msg, "$idpId SAML metadata has ne EntityID";
$res = 0; $res = 0;
@ -1989,7 +1987,7 @@ sub attributes {
my $eid = $1; my $eid = $1;
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;
} }
@ -2040,7 +2038,8 @@ sub attributes {
select => [ select => [
{ k => '', v => '' }, { k => '', v => '' },
{ k => 'kerberos', v => 'Kerberos' }, { k => 'kerberos', v => 'Kerberos' },
{ k => 'password-protected-transport', {
k => 'password-protected-transport',
v => 'Password protected transport' v => 'Password protected transport'
}, },
{ k => 'password', v => 'Password' }, { k => 'password', v => 'Password' },
@ -2152,40 +2151,40 @@ sub attributes {
samlSPSSODescriptorSingleLogoutServiceHTTPRedirect => { samlSPSSODescriptorSingleLogoutServiceHTTPRedirect => {
type => 'samlService', type => 'samlService',
default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;' default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;'
. '#PORTAL#/saml/proxySingleLogout;' . '#PORTAL#/saml/proxySingleLogout;'
. '#PORTAL#/saml/proxySingleLogoutReturn', . '#PORTAL#/saml/proxySingleLogoutReturn',
documentation => 'SAML SP SLO HTTP Redirect', documentation => 'SAML SP SLO HTTP Redirect',
}, },
samlSPSSODescriptorSingleLogoutServiceHTTPPost => { samlSPSSODescriptorSingleLogoutServiceHTTPPost => {
type => 'samlService', type => 'samlService',
default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;' default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;'
. '#PORTAL#/saml/proxySingleLogout;' . '#PORTAL#/saml/proxySingleLogout;'
. '#PORTAL#/saml/proxySingleLogoutReturn', . '#PORTAL#/saml/proxySingleLogoutReturn',
documentation => 'SAML SP SLO HTTP POST', documentation => 'SAML SP SLO HTTP POST',
}, },
samlSPSSODescriptorSingleLogoutServiceSOAP => { samlSPSSODescriptorSingleLogoutServiceSOAP => {
type => 'samlService', type => 'samlService',
default => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' default => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;'
. '#PORTAL#/saml/proxySingleLogoutSOAP;', . '#PORTAL#/saml/proxySingleLogoutSOAP;',
documentation => 'SAML SP SLO SOAP', documentation => 'SAML SP SLO SOAP',
}, },
samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact => { samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact => {
type => 'samlAssertion', type => 'samlAssertion',
default => default =>
'1;0;urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact;' '1;0;urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact;'
. '#PORTAL#/saml/proxySingleSignOnArtifact', . '#PORTAL#/saml/proxySingleSignOnArtifact',
documentation => 'SAML SP ACS HTTP artifact', documentation => 'SAML SP ACS HTTP artifact',
}, },
samlSPSSODescriptorAssertionConsumerServiceHTTPPost => { samlSPSSODescriptorAssertionConsumerServiceHTTPPost => {
type => 'samlAssertion', type => 'samlAssertion',
default => '0;1;urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;' default => '0;1;urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;'
. '#PORTAL#/saml/proxySingleSignOnPost', . '#PORTAL#/saml/proxySingleSignOnPost',
documentation => 'SAML SP ACS HTTP POST', documentation => 'SAML SP ACS HTTP POST',
}, },
samlSPSSODescriptorArtifactResolutionServiceArtifact => { samlSPSSODescriptorArtifactResolutionServiceArtifact => {
type => 'samlAssertion', type => 'samlAssertion',
default => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' default => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;'
. '#PORTAL#/saml/artifact', . '#PORTAL#/saml/artifact',
documentation => 'SAML SP artifact resolution service ', documentation => 'SAML SP artifact resolution service ',
}, },
samlSPMetaDataOptionsNameIDFormat => { samlSPMetaDataOptionsNameIDFormat => {
@ -2277,8 +2276,8 @@ sub attributes {
{ k => 'Demo', v => 'Demonstration' }, { k => 'Demo', v => 'Demonstration' },
{ k => 'Choice', v => 'authChoice' }, { k => 'Choice', v => 'authChoice' },
{ k => 'Combination', v => 'combineMods' }, { k => 'Combination', v => 'combineMods' },
{ k => 'CAS', v => 'Central Authentication Service (CAS)' }, { k => 'CAS', v => 'Central Authentication Service (CAS)' },
{ k => 'OpenID', v => 'OpenID' }, { k => 'OpenID', v => 'OpenID' },
{ k => 'OpenIDConnect', v => 'OpenID Connect' }, { k => 'OpenIDConnect', v => 'OpenID Connect' },
{ k => 'SAML', v => 'SAML v2' }, { k => 'SAML', v => 'SAML v2' },
{ k => 'Proxy', v => 'Proxy' }, { k => 'Proxy', v => 'Proxy' },
@ -2341,7 +2340,7 @@ sub attributes {
type => 'text', type => 'text',
default => 'TOTP,U2F,Yubikey', default => 'TOTP,U2F,Yubikey',
documentation => documentation =>
'Available self-registration modules for second factor', 'Available self-registration modules for second factor',
}, },
# DEMO # DEMO
@ -2414,9 +2413,9 @@ sub attributes {
my $l = shift; my $l = shift;
my (@s) = split( /[\s,]+/, $l ); my (@s) = split( /[\s,]+/, $l );
foreach my $s (@s) { foreach my $s (@s) {
$s $s =~
=~ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?::\d{1,5})?/?.*)$}o m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?::\d{1,5})?/?.*)$}o
or return ( 0, "__badLdapUri__: \"$s\"" ); or return ( 0, "__badLdapUri__: \"$s\"" );
} }
return 1; return 1;
}, },
@ -2474,7 +2473,7 @@ sub attributes {
documentation => 'LDAP filter for mail search' documentation => 'LDAP filter for mail search'
}, },
LDAPFilter => LDAPFilter =>
{ type => 'text', documentation => 'Default LDAP filter' }, { type => 'text', documentation => 'Default LDAP filter' },
AuthLDAPFilter => { AuthLDAPFilter => {
type => 'text', type => 'text',
documentation => 'LDAP filter for auth search' documentation => 'LDAP filter for auth search'
@ -2499,7 +2498,7 @@ sub attributes {
type => 'text', type => 'text',
default => 'dn', default => 'dn',
documentation => documentation =>
'LDAP attribute name in user entry referenced as member in groups', 'LDAP attribute name in user entry referenced as member in groups',
}, },
ldapGroupAttributeNameSearch => { ldapGroupAttributeNameSearch => {
type => 'text', type => 'text',
@ -2510,7 +2509,7 @@ sub attributes {
type => 'text', type => 'text',
default => 'dn', default => 'dn',
documentation => documentation =>
'LDAP attribute name in group entry referenced as member in groups', 'LDAP attribute name in group entry referenced as member in groups',
}, },
ldapTimeout => { ldapTimeout => {
type => 'int', type => 'int',
@ -2637,7 +2636,7 @@ sub attributes {
default => { default => {
proxy => 'http://auth.example.com/sessions', proxy => 'http://auth.example.com/sessions',
ns => ns =>
'http://auth.example.com/Lemonldap/NG/Common/PSGI/SOAPService', 'http://auth.example.com/Lemonldap/NG/Common/PSGI/SOAPService',
}, },
documentation => 'Apache::Session module parameters', documentation => 'Apache::Session module parameters',
}, },
@ -2719,7 +2718,7 @@ sub attributes {
}, },
linkedInUserField => { type => 'text', default => 'emailAddress' }, linkedInUserField => { type => 'text', default => 'emailAddress' },
linkedInScope => linkedInScope =>
{ type => 'text', default => 'r_basicprofile r_emailaddress' }, { type => 'text', default => 'r_basicprofile r_emailaddress' },
# WebID # WebID
webIDAuthnLevel => { webIDAuthnLevel => {
@ -2759,15 +2758,15 @@ sub attributes {
dbiPasswordMailCol => { type => 'text', }, dbiPasswordMailCol => { type => 'text', },
userPivot => { type => 'text', }, userPivot => { type => 'text', },
dbiAuthPasswordHash => dbiAuthPasswordHash =>
{ type => 'text', help => 'authdbi.html#password', }, { type => 'text', help => 'authdbi.html#password', },
dbiDynamicHashEnabled => dbiDynamicHashEnabled =>
{ type => 'bool', help => 'authdbi.html#password', }, { type => 'bool', help => 'authdbi.html#password', },
dbiDynamicHashValidSchemes => dbiDynamicHashValidSchemes =>
{ type => 'text', help => 'authdbi.html#password', }, { type => 'text', help => 'authdbi.html#password', },
dbiDynamicHashValidSaltedSchemes => dbiDynamicHashValidSaltedSchemes =>
{ type => 'text', help => 'authdbi.html#password', }, { type => 'text', help => 'authdbi.html#password', },
dbiDynamicHashNewPasswordScheme => dbiDynamicHashNewPasswordScheme =>
{ type => 'text', help => 'authdbi.html#password', }, { type => 'text', help => 'authdbi.html#password', },
dbiExportedVars => { dbiExportedVars => {
type => 'keyTextContainer', type => 'keyTextContainer',
keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/,
@ -2847,11 +2846,13 @@ sub attributes {
type => 'authChoiceContainer', type => 'authChoiceContainer',
keyTest => qr/^(\d*)?[a-zA-Z0-9_]+$/, keyTest => qr/^(\d*)?[a-zA-Z0-9_]+$/,
keyMsgFail => '__badChoiceKey__', keyMsgFail => '__badChoiceKey__',
test => sub {1}, test => sub { 1 },
select => [ select => [
[ { k => 'Apache', v => 'Apache' }, [
{ k => 'Apache', v => 'Apache' },
{ k => 'AD', v => 'Active Directory' }, { k => 'AD', v => 'Active Directory' },
{ k => 'CAS', {
k => 'CAS',
v => 'Central Authentication Service (CAS)' v => 'Central Authentication Service (CAS)'
}, },
{ k => 'DBI', v => 'Database (DBI)' }, { k => 'DBI', v => 'Database (DBI)' },
@ -2876,8 +2877,10 @@ sub attributes {
{ k => 'WebID', v => 'WebID' }, { k => 'WebID', v => 'WebID' },
{ k => 'Custom', v => 'customModule' }, { k => 'Custom', v => 'customModule' },
], ],
[ { k => 'AD', v => 'Active Directory' }, [
{ k => 'CAS', { k => 'AD', v => 'Active Directory' },
{
k => 'CAS',
v => 'Central Authentication Service (CAS)' v => 'Central Authentication Service (CAS)'
}, },
{ k => 'DBI', v => 'Database (DBI)' }, { k => 'DBI', v => 'Database (DBI)' },
@ -2896,7 +2899,8 @@ sub attributes {
{ k => 'WebID', v => 'WebID' }, { k => 'WebID', v => 'WebID' },
{ k => 'Custom', v => 'customModule' }, { k => 'Custom', v => 'customModule' },
], ],
[ { k => 'AD', v => 'Active Directory' }, [
{ k => 'AD', v => 'Active Directory' },
{ k => 'DBI', v => 'Database (DBI)' }, { k => 'DBI', v => 'Database (DBI)' },
{ k => 'Demo', v => 'Demo' }, { k => 'Demo', v => 'Demo' },
{ k => 'LDAP', v => 'LDAP' }, { k => 'LDAP', v => 'LDAP' },
@ -2916,7 +2920,7 @@ sub attributes {
combModules => { combModules => {
type => 'cmbModuleContainer', type => 'cmbModuleContainer',
keyTest => qr/^\w+$/, keyTest => qr/^\w+$/,
test => sub {1}, test => sub { 1 },
documentation => 'Combination module description', documentation => 'Combination module description',
select => [ select => [
{ k => 'Apache', v => 'Apache' }, { k => 'Apache', v => 'Apache' },
@ -2933,8 +2937,8 @@ sub attributes {
{ k => 'Twitter', v => 'Twitter' }, { k => 'Twitter', v => 'Twitter' },
{ k => 'WebID', v => 'WebID' }, { k => 'WebID', v => 'WebID' },
{ k => 'Demo', v => 'Demonstration' }, { k => 'Demo', v => 'Demonstration' },
{ k => 'CAS', v => 'Central Authentication Service (CAS)' }, { k => 'CAS', v => 'Central Authentication Service (CAS)' },
{ k => 'OpenID', v => 'OpenID' }, { k => 'OpenID', v => 'OpenID' },
{ k => 'OpenIDConnect', v => 'OpenID Connect' }, { k => 'OpenIDConnect', v => 'OpenID Connect' },
{ k => 'SAML', v => 'SAML v2' }, { k => 'SAML', v => 'SAML v2' },
{ k => 'Proxy', v => 'Proxy' }, { k => 'Proxy', v => 'Proxy' },
@ -3045,8 +3049,7 @@ sub attributes {
'loa-4' => 4, 'loa-4' => 4,
'loa-5' => 5, 'loa-5' => 5,
}, },
documentation => documentation => 'OpenID Connect Authentication Context Class Ref',
'OpenID Connect Authentication Context Class Ref',
}, },
oidcServicePrivateKeySig => { type => 'RSAPrivateKey', }, oidcServicePrivateKeySig => { type => 'RSAPrivateKey', },
oidcServicePublicKeySig => { type => 'RSAPublicKey', }, oidcServicePublicKeySig => { type => 'RSAPublicKey', },
@ -3055,10 +3058,9 @@ sub attributes {
documentation => 'OpenID Connect Signature Key ID', documentation => 'OpenID Connect Signature Key ID',
}, },
oidcServiceAllowDynamicRegistration => { oidcServiceAllowDynamicRegistration => {
type => 'bool', type => 'bool',
default => 0, default => 0,
documentation => documentation => 'OpenID Connect allow dynamic client registration',
'OpenID Connect allow dynamic client registration',
}, },
oidcServiceAllowAuthorizationCodeFlow => { oidcServiceAllowAuthorizationCodeFlow => {
type => 'bool', type => 'bool',
@ -3088,12 +3090,12 @@ sub attributes {
oidcOPMetaDataNodes => { oidcOPMetaDataNodes => {
type => 'oidcOPMetaDataNodeContainer', type => 'oidcOPMetaDataNodeContainer',
help => help =>
'authopenidconnect.html#declare_the_openid_connect_provider_in_llng', 'authopenidconnect.html#declare_the_openid_connect_provider_in_llng',
}, },
oidcRPMetaDataNodes => { oidcRPMetaDataNodes => {
type => 'oidcRPMetaDataNodeContainer', type => 'oidcRPMetaDataNodeContainer',
help => help =>
'idpopenidconnect.html#configuration_of_relying_party_in_llng', 'idpopenidconnect.html#configuration_of_relying_party_in_llng',
}, },
oidcOPMetaDataOptions => { type => 'subContainer', }, oidcOPMetaDataOptions => { type => 'subContainer', },
oidcRPMetaDataOptions => { type => 'subContainer', }, oidcRPMetaDataOptions => { type => 'subContainer', },
@ -3115,7 +3117,7 @@ sub attributes {
oidcOPMetaDataOptionsClientID => { type => 'text', }, oidcOPMetaDataOptionsClientID => { type => 'text', },
oidcOPMetaDataOptionsClientSecret => { type => 'password', }, oidcOPMetaDataOptionsClientSecret => { type => 'password', },
oidcOPMetaDataOptionsScope => oidcOPMetaDataOptionsScope =>
{ type => 'text', default => 'openid profile' }, { type => 'text', default => 'openid profile' },
oidcOPMetaDataOptionsDisplay => { oidcOPMetaDataOptionsDisplay => {
type => 'select', type => 'select',
select => [ select => [
@ -3140,10 +3142,9 @@ sub attributes {
default => 'client_secret_post', default => 'client_secret_post',
}, },
oidcOPMetaDataOptionsCheckJWTSignature => oidcOPMetaDataOptionsCheckJWTSignature =>
{ type => 'bool', default => 1 }, { type => 'bool', default => 1 },
oidcOPMetaDataOptionsIDTokenMaxAge => oidcOPMetaDataOptionsIDTokenMaxAge => { type => 'int', default => 30 },
{ type => 'int', default => 30 }, oidcOPMetaDataOptionsUseNonce => { type => 'bool', default => 1 },
oidcOPMetaDataOptionsUseNonce => { type => 'bool', default => 1 },
oidcOPMetaDataOptionsDisplayName => { type => 'text', }, oidcOPMetaDataOptionsDisplayName => { type => 'text', },
oidcOPMetaDataOptionsIcon => { type => 'text', }, oidcOPMetaDataOptionsIcon => { type => 'text', },
oidcOPMetaDataOptionsStoreIDToken => { type => 'bool', default => 0 }, oidcOPMetaDataOptionsStoreIDToken => { type => 'bool', default => 0 },
@ -3182,12 +3183,12 @@ sub attributes {
default => 'HS512', default => 'HS512',
}, },
oidcRPMetaDataOptionsIDTokenExpiration => oidcRPMetaDataOptionsIDTokenExpiration =>
{ type => 'int', default => 3600 }, { type => 'int', default => 3600 },
oidcRPMetaDataOptionsAccessTokenExpiration => oidcRPMetaDataOptionsAccessTokenExpiration =>
{ type => 'int', default => 3600 }, { type => 'int', default => 3600 },
oidcRPMetaDataOptionsRedirectUris => { type => 'text', }, oidcRPMetaDataOptionsRedirectUris => { type => 'text', },
oidcRPMetaDataOptionsExtraClaims => oidcRPMetaDataOptionsExtraClaims =>
{ type => 'keyTextContainer', default => {} }, { type => 'keyTextContainer', default => {} },
oidcRPMetaDataOptionsBypassConsent => { oidcRPMetaDataOptionsBypassConsent => {
type => 'bool', type => 'bool',
help => 'openidconnectclaims.html', help => 'openidconnectclaims.html',

View File

@ -69,7 +69,7 @@ has confChanged => (
); );
# Properties required during build # Properties required during build
has refConf => ( is => 'ro', isa => 'HashRef', required => 1 ); has refConf => ( is => 'ro', isa => 'HashRef', required => 1 );
has req => ( is => 'ro', required => 1 ); has req => ( is => 'ro', required => 1 );
has newConf => ( is => 'rw', isa => 'HashRef' ); has newConf => ( is => 'rw', isa => 'HashRef' );
has tree => ( is => 'rw', isa => 'ArrayRef' ); has tree => ( is => 'rw', isa => 'ArrayRef' );
@ -158,7 +158,7 @@ sub _scanNodes {
hdebug("Looking to $name"); hdebug("Looking to $name");
# subnode # subnode
my $subNodes = $leaf->{nodes} // $leaf->{_nodes}; my $subNodes = $leaf->{nodes} // $leaf->{_nodes};
my $subNodesCond = $leaf->{nodes_cond} // $leaf->{_nodes_cond}; my $subNodesCond = $leaf->{nodes_cond} // $leaf->{_nodes_cond};
################################## ##################################
@ -763,11 +763,12 @@ sub _scanNodes {
foreach my $n (@$subNodes) { foreach my $n (@$subNodes) {
hdebug(" looking at $n subnode"); hdebug(" looking at $n subnode");
if ( ref $n->{data} and ref $n->{data} eq 'ARRAY' ) { if ( ref $n->{data} and ref $n->{data} eq 'ARRAY' ) {
# authChoiceModules # authChoiceModules
if ( $name eq 'authChoiceModules' ) { if ( $name eq 'authChoiceModules' ) {
hdebug(' combModules'); hdebug(' combModules');
$n->{data}->[5] ||= {}; $n->{data}->[5] ||= {};
$n->{data}->[5] = to_json($n->{data}->[5]); $n->{data}->[5] = to_json( $n->{data}->[5] );
} }
$n->{data} = join ';', @{ $n->{data} }; $n->{data} = join ';', @{ $n->{data} };
@ -1059,15 +1060,15 @@ sub _unitTest {
or $attr->{type} =~ /Container$/ ) or $attr->{type} =~ /Container$/ )
{ {
my $keyMsg = $attr->{keyMsgFail} // $type->{keyMsgFail}; my $keyMsg = $attr->{keyMsgFail} // $type->{keyMsgFail};
my $msg = $attr->{msgFail} // $type->{msgFail}; my $msg = $attr->{msgFail} // $type->{msgFail};
$res = 0 $res = 0
unless ( unless (
$self->_execTest( $self->_execTest(
{ {
keyTest => $attr->{keyTest} // $type->{keyTest}, keyTest => $attr->{keyTest} // $type->{keyTest},
keyMsgFail => $attr->{keyMsgFail} keyMsgFail => $attr->{keyMsgFail}
// $type->{keyMsgFail}, // $type->{keyMsgFail},
test => $attr->{test} // $type->{test}, test => $attr->{test} // $type->{test},
msgFail => $attr->{msgFail} // $type->{msgFail}, msgFail => $attr->{msgFail} // $type->{msgFail},
}, },
$conf->{$key}, $conf->{$key},

View File

@ -30,7 +30,8 @@ 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}"
) )
@ -42,7 +43,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#/+$#;
@ -60,10 +61,11 @@ 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
) )
); );
@ -77,9 +79,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; }
}, },
@ -92,9 +94,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; }
}, },
@ -102,12 +104,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;
@ -117,29 +119,30 @@ 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'
: '' : ''
) )
); );
@ -151,18 +154,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'
: '' : ''
) )
); );
@ -174,8 +177,7 @@ 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 if ( $v !~ Lemonldap::NG::Common::Regexp::OPENIDSREGATTR() )
!~ Lemonldap::NG::Common::Regexp::OPENIDSREGATTR() )
{ {
push @tmp, $v; push @tmp, $v;
} }
@ -183,10 +185,11 @@ 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'
: '' : ''
) )
); );
@ -195,40 +198,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 = $Lemonldap::NG::Handler::PSGI::Main::tsv my $gc =
->{sessionStorageModule}; $Lemonldap::NG::Handler::PSGI::Main::tsv->{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;
}, },
@ -238,8 +241,9 @@ 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 (
and $cn ne $conf->{cookieName} $cn
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'
: () : ()
) )
@ -250,9 +254,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
@ -263,7 +267,8 @@ 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'
: '' : ''
) )
@ -279,21 +284,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
@ -303,14 +308,15 @@ 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 ( $conf->{samlIDPMetaDataXML}->{$idpId} unless (
->{samlIDPMetaDataXML} =~ /entityID=(['"])(.+?)\1/si ) $conf->{samlIDPMetaDataXML}->{$idpId}->{samlIDPMetaDataXML}
=~ /entityID=(['"])(.+?)\1/si )
{ {
push @msg, "$idpId SAML metadata has no EntityID"; push @msg, "$idpId SAML metadata has no EntityID";
$res = 0; $res = 0;
@ -319,7 +325,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;
} }
@ -329,15 +335,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;
@ -346,7 +352,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;
} }
@ -360,7 +366,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};
@ -371,8 +377,8 @@ sub tests {
); );
} }
eval { eval {
Lemonldap::NG::Common::Combination::Parser->parse( Lemonldap::NG::Common::Combination::Parser->parse( $moduleList,
$moduleList, $conf->{combination} ); $conf->{combination} );
}; };
return ( 0, $@ ) if ($@); return ( 0, $@ ) if ($@);
@ -397,7 +403,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
@ -406,7 +412,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 ($@);
} }
@ -414,7 +420,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 ($@);
} }
@ -428,7 +434,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 : () ) );
}, },
@ -439,8 +445,10 @@ 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'
@ -452,9 +460,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;
@ -465,11 +473,12 @@ 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'
) )
@ -480,7 +489,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;
@ -494,16 +503,15 @@ 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 $msg = "A self registrable module should be enabled to require 2FA"
= "A self registrable module should be enabled to require 2FA" unless ($ok);
unless ($ok);
return ( 1, $msg ); return ( 1, $msg );
}, },
@ -512,7 +520,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
@ -523,9 +531,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;

View File

@ -24,7 +24,7 @@ delete $forms{restore};
my ( @types, $attr, $tree, $ctrees ); my ( @types, $attr, $tree, $ctrees );
ok( $tree = Lemonldap::NG::Manager::Build::Tree::tree(), 'Get tree' ); ok( $tree = Lemonldap::NG::Manager::Build::Tree::tree(), 'Get tree' );
ok( $ctrees = Lemonldap::NG::Manager::Build::CTrees::cTrees(), 'Get cTrees' ); ok( $ctrees = Lemonldap::NG::Manager::Build::CTrees::cTrees(), 'Get cTrees' );
ok( $attr = Lemonldap::NG::Manager::Build::Attributes::attributes(), ok( $attr = Lemonldap::NG::Manager::Build::Attributes::attributes(),
'Get attributes' ); 'Get attributes' );
$count += 4; $count += 4;

View File

@ -13,11 +13,11 @@ use strict;
use Mouse; use Mouse;
use JSON qw(from_json to_json); use JSON qw(from_json to_json);
use Lemonldap::NG::Portal::Main::Constants qw( use Lemonldap::NG::Portal::Main::Constants qw(
PE_ERROR PE_ERROR
PE_NOTOKEN PE_NOTOKEN
PE_OK PE_OK
PE_SENDRESPONSE PE_SENDRESPONSE
PE_TOKENEXPIRED PE_TOKENEXPIRED
); );
our $VERSION = '2.0.0'; our $VERSION = '2.0.0';
@ -35,8 +35,8 @@ has sfReq => ( is => 'rw' );
has ott => ( has ott => (
is => 'rw', is => 'rw',
default => sub { default => sub {
my $ott = $_[0]->{p} my $ott =
->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken'); $_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
$ott->timeout( $_[0]->{conf}->{formTimeout} ); $ott->timeout( $_[0]->{conf}->{formTimeout} );
return $ott; return $ott;
} }
@ -49,10 +49,12 @@ sub init {
for my $i ( 0 .. 1 ) { for my $i ( 0 .. 1 ) {
foreach ( foreach (
split /,\s*/, split /,\s*/,
$self->conf->{ $i $self->conf->{
$i
? 'available2FSelfRegistration' ? 'available2FSelfRegistration'
: 'available2F' } : 'available2F'
) }
)
{ {
my $prefix = lc($_); my $prefix = lc($_);
$prefix =~ s/2f$//i; $prefix =~ s/2f$//i;
@ -64,10 +66,9 @@ sub init {
# Unless $rule, skip loading # Unless $rule, skip loading
if ( $self->conf->{$ap} ) { if ( $self->conf->{$ap} ) {
$self->logger->debug("Trying to load $_ 2F"); $self->logger->debug("Trying to load $_ 2F");
my $m my $m =
= $self->p->loadPlugin( $self->p->loadPlugin( $i ? "::2F::Register::$_" : "::2F::$_" )
$i ? "::2F::Register::$_" : "::2F::$_" ) or return 0;
or return 0;
# Rule and prefix may be modified by 2F module, reread them # Rule and prefix may be modified by 2F module, reread them
my $rule = $self->conf->{$ap}; my $rule = $self->conf->{$ap};
@ -77,13 +78,13 @@ sub init {
$rule = $self->p->HANDLER->substitute($rule); $rule = $self->p->HANDLER->substitute($rule);
unless ( $rule = $self->p->HANDLER->buildSub($rule) ) { unless ( $rule = $self->p->HANDLER->buildSub($rule) ) {
$self->error( 'External 2F rule error: ' $self->error( 'External 2F rule error: '
. $self->p->HANDLER->tsv->{jail}->error ); . $self->p->HANDLER->tsv->{jail}->error );
return 0; return 0;
} }
# Store module # Store module
push @{ $self->{ $i ? 'sfRModules' : 'sfModules' } }, push @{ $self->{ $i ? 'sfRModules' : 'sfModules' } },
{ p => $prefix, m => $m, r => $rule }; { p => $prefix, m => $m, r => $rule };
} }
else { else {
$self->logger->debug(' -> not enabled'); $self->logger->debug(' -> not enabled');
@ -97,10 +98,10 @@ sub init {
$self->p->HANDLER->substitute( $self->conf->{sfRequired} ) $self->p->HANDLER->substitute( $self->conf->{sfRequired} )
) )
) )
) )
{ {
$self->error( 'Error in sfRequired rule' $self->error( 'Error in sfRequired rule'
. $self->p->HANDLER->tsv->{jail}->error ); . $self->p->HANDLER->tsv->{jail}->error );
return 0; return 0;
} }
@ -161,14 +162,14 @@ sub run {
if ( $self->sfReq->( $req, $req->sessionInfo ) ) { if ( $self->sfReq->( $req, $req->sessionInfo ) ) {
$self->logger->debug("2F is required..."); $self->logger->debug("2F is required...");
$self->logger->debug(" -> Register 2F"); $self->logger->debug(" -> Register 2F");
$req->pdata->{sfRegToken} $req->pdata->{sfRegToken} =
= $self->ott->createToken( $req->sessionInfo ); $self->ott->createToken( $req->sessionInfo );
$self->logger->debug("Just one 2F is enabled"); $self->logger->debug("Just one 2F is enabled");
$self->logger->debug(" -> Redirect to /2fregisters/"); $self->logger->debug(" -> Redirect to /2fregisters/");
$req->response( $req->response(
[ 302, [
[ Location => $self->conf->{portal} . '/2fregisters/' ], 302,
[] [ Location => $self->conf->{portal} . '/2fregisters/' ], []
] ]
); );
return PE_SENDRESPONSE; return PE_SENDRESPONSE;
@ -179,7 +180,7 @@ sub run {
} }
$self->userLogger->info( 'Second factor required for ' $self->userLogger->info( 'Second factor required for '
. $req->sessionInfo->{ $self->conf->{whatToTrace} } ); . $req->sessionInfo->{ $self->conf->{whatToTrace} } );
# Store user data in a token # Store user data in a token
$req->sessionInfo->{_2fRealSession} = $req->id; $req->sessionInfo->{_2fRealSession} = $req->id;
@ -201,10 +202,9 @@ sub run {
$req, $req,
'2fchoice', '2fchoice',
params => { params => {
SKIN => $self->conf->{portalSkin}, SKIN => $self->conf->{portalSkin},
TOKEN => $token, TOKEN => $token,
MODULES => MODULES => [ map { { CODE => $_->prefix, LOGO => $_->logo } } @am ],
[ map { { CODE => $_->prefix, LOGO => $_->logo } } @am ],
CHECKLOGINS => $checkLogins CHECKLOGINS => $checkLogins
} }
); );
@ -230,16 +230,15 @@ sub _choice {
# Restore session # Restore session
unless ( $token = $req->param('token') ) { unless ( $token = $req->param('token') ) {
$self->userLogger->error( $self->userLogger->error( $self->prefix . ' 2F access without token' );
$self->prefix . ' 2F access without token' );
$req->mustRedirect(1); $req->mustRedirect(1);
return $self->p->do( $req, [ sub {PE_NOTOKEN} ] ); return $self->p->do( $req, [ sub { PE_NOTOKEN } ] );
} }
my $session; my $session;
unless ( $session = $self->ott->getToken($token) ) { unless ( $session = $self->ott->getToken($token) ) {
$self->userLogger->info('Token expired'); $self->userLogger->info('Token expired');
return $self->p->do( $req, [ sub {PE_TOKENEXPIRED} ] ); return $self->p->do( $req, [ sub { PE_TOKENEXPIRED } ] );
} }
$req->sessionInfo($session); $req->sessionInfo($session);
@ -254,7 +253,8 @@ sub _choice {
$req->authResult($res); $req->authResult($res);
return $self->p->do( return $self->p->do(
$req, $req,
[ sub {$res}, 'controlUrl', [
sub { $res }, 'controlUrl',
'buildCookie', @{ $self->p->endAuth }, 'buildCookie', @{ $self->p->endAuth },
] ]
); );
@ -269,8 +269,7 @@ sub _redirect {
my $arg = $req->env->{QUERY_STRING}; my $arg = $req->env->{QUERY_STRING};
$self->logger->debug('Call sfEngine _redirect method'); $self->logger->debug('Call sfEngine _redirect method');
return [ return [
302, [ Location => $self->conf->{portal} . ( $arg ? "?$arg" : '' ) ], 302, [ Location => $self->conf->{portal} . ( $arg ? "?$arg" : '' ) ], []
[]
]; ];
} }
@ -300,26 +299,25 @@ sub _displayRegister {
'Looking if ' . $m->{m}->prefix . '2F register is available' ); 'Looking if ' . $m->{m}->prefix . '2F register is available' );
if ( $m->{r}->( $req, $req->userData ) ) { if ( $m->{r}->( $req, $req->userData ) ) {
push @am, push @am,
{ {
CODE => $m->{m}->prefix, CODE => $m->{m}->prefix,
URL => '/2fregisters/' . $m->{m}->prefix, URL => '/2fregisters/' . $m->{m}->prefix,
LOGO => $m->{m}->logo, LOGO => $m->{m}->logo,
}; };
} }
} }
if ( @am == 1 if ( @am == 1
and and not( $req->userData->{_2fDevices} or $req->data->{sfRegRequired} ) )
not( $req->userData->{_2fDevices} or $req->data->{sfRegRequired} ) )
{ {
return [ 302, [ Location => $self->conf->{portal} . $am[0]->{URL} ], return [ 302, [ Location => $self->conf->{portal} . $am[0]->{URL} ],
[] ]; [] ];
} }
my $_2fDevices = $req->userData->{_2fDevices} my $_2fDevices =
? eval { $req->userData->{_2fDevices}
from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } ); ? eval {
} from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } ); }
: undef; : undef;
unless ($_2fDevices) { unless ($_2fDevices) {
$self->logger->debug("No 2F Device found"); $self->logger->debug("No 2F Device found");
@ -365,11 +363,11 @@ sub register {
$self->logger->debug(' -> OK'); $self->logger->debug(' -> OK');
my $name = $m->{m}->prefix; my $name = $m->{m}->prefix;
push @am, push @am,
{ {
name => $name, name => $name,
logo => $m->{m}->logo, logo => $m->{m}->logo,
url => "/2fregisters/$name" url => "/2fregisters/$name"
}; };
} }
} }
return $self->p->sendJSONresponse( $req, \@am ); return $self->p->sendJSONresponse( $req, \@am );
@ -378,12 +376,12 @@ sub register {
sub restoreSession { sub restoreSession {
my ( $self, $req, @path ) = @_; my ( $self, $req, @path ) = @_;
my $token = $req->pdata->{sfRegToken} my $token = $req->pdata->{sfRegToken}
or return [ 302, [ Location => $self->conf->{portal} ], [] ]; or return [ 302, [ Location => $self->conf->{portal} ], [] ];
$req->userData( $self->ott->getToken( $token, 1 ) ); $req->userData( $self->ott->getToken( $token, 1 ) );
$req->data->{sfRegRequired} = 1; $req->data->{sfRegRequired} = 1;
return $req->method eq 'POST' return $req->method eq 'POST'
? $self->register( $req, @path ) ? $self->register( $req, @path )
: $self->_displayRegister( $req, @path ); : $self->_displayRegister( $req, @path );
} }
1; 1;

View File

@ -39,11 +39,12 @@ sub run {
$self->logger->debug("Ext2F checkLogins set") if ($checkLogins); $self->logger->debug("Ext2F checkLogins set") if ($checkLogins);
# Prepare command and launch it # Prepare command and launch it
$self->logger->debug('Launching "Send" external 2F command -> ' . $self->conf->{ext2FSendCommand}); $self->logger->debug( 'Launching "Send" external 2F command -> '
. $self->conf->{ext2FSendCommand} );
if ( my $c = if ( my $c =
$self->launch( $req->sessionInfo, $self->conf->{ext2FSendCommand} ) ) $self->launch( $req->sessionInfo, $self->conf->{ext2FSendCommand} ) )
{ {
$self->logger->error("External send command failed (code $c)"); $self->logger->error("External send command failed (code $c)");
return $self->p->do( $req, [ sub { PE_ERROR } ] ); return $self->p->do( $req, [ sub { PE_ERROR } ] );
} }
@ -72,7 +73,8 @@ sub verify {
} }
# Prepare command and launch it # Prepare command and launch it
$self->logger->debug('Launching "Validate" external 2F command -> ' . $self->conf->{ext2FValidateCommand}); $self->logger->debug( 'Launching "Validate" external 2F command -> '
. $self->conf->{ext2FValidateCommand} );
$self->logger->debug(" code -> $code"); $self->logger->debug(" code -> $code");
if ( my $c = if ( my $c =
$self->launch( $session, $self->conf->{ext2FValidateCommand}, $code ) ) $self->launch( $session, $self->conf->{ext2FValidateCommand}, $code ) )

View File

@ -7,7 +7,8 @@ use JSON qw(from_json to_json);
our $VERSION = '2.0.0'; our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Portal::Main::Plugin', 'Lemonldap::NG::Portal::Lib::U2F'; extends 'Lemonldap::NG::Portal::Main::Plugin',
'Lemonldap::NG::Portal::Lib::U2F';
# INITIALIZATION # INITIALIZATION

View File

@ -16,7 +16,7 @@ extends 'Lemonldap::NG::Portal::Main::Auth',
# INTERFACE # INTERFACE
has opList => ( is => 'rw', default => sub { [] } ); has opList => ( is => 'rw', default => sub { [] } );
has opNumber => ( is => 'rw', default => 0 ); has opNumber => ( is => 'rw', default => 0 );
has path => ( is => 'rw', default => 'oauth2' ); has path => ( is => 'rw', default => 'oauth2' );

View File

@ -174,7 +174,7 @@ sub send_mail {
foreach ( keys %cid ) { foreach ( keys %cid ) {
$message->attach( $message->attach(
Type => "image/" . ( $cid{$_} =~ m/\.(\w+)/ )[0], Type => "image/" . ( $cid{$_} =~ m/\.(\w+)/ )[0],
Id => $_, Id => $_,
Path => $self->p->{templateDir} . "/" . $cid{$_}, Path => $self->p->{templateDir} . "/" . $cid{$_},
); );
} }

View File

@ -16,17 +16,16 @@ sub displayInit {
my ($self) = @_; my ($self) = @_;
$self->skinRules( [] ); $self->skinRules( [] );
if ( $self->conf->{portalSkinRules} ) { if ( $self->conf->{portalSkinRules} ) {
foreach my $skinRule ( sort keys %{ $self->conf->{portalSkinRules} } ) foreach my $skinRule ( sort keys %{ $self->conf->{portalSkinRules} } ) {
{
my $sub = HANDLER->buildSub( HANDLER->substitute($skinRule) ); my $sub = HANDLER->buildSub( HANDLER->substitute($skinRule) );
if ($sub) { if ($sub) {
push @{ $self->skinRules }, push @{ $self->skinRules },
[ $self->conf->{portalSkinRules}->{$skinRule}, $sub ]; [ $self->conf->{portalSkinRules}->{$skinRule}, $sub ];
} }
else { else {
$self->logger->error( $self->logger->error(
qq(Skin rule "$skinRule" returns an error: ) qq(Skin rule "$skinRule" returns an error: )
. HANDLER->tsv->{jail}->error ); . HANDLER->tsv->{jail}->error );
} }
} }
} }
@ -55,7 +54,8 @@ sub display {
AUTH_URL => $req->{data}->{_url}, AUTH_URL => $req->{data}->{_url},
CHOICE_PARAM => $self->conf->{authChoiceParam}, CHOICE_PARAM => $self->conf->{authChoiceParam},
CHOICE_VALUE => $req->data->{_authChoice}, CHOICE_VALUE => $req->data->{_authChoice},
( $req->data->{customScript} (
$req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} ) ? ( CUSTOM_SCRIPT => $req->data->{customScript} )
: () : ()
), ),
@ -79,11 +79,12 @@ sub display {
CHOICE_PARAM => $self->conf->{authChoiceParam}, CHOICE_PARAM => $self->conf->{authChoiceParam},
CHOICE_VALUE => $req->data->{_authChoice}, CHOICE_VALUE => $req->data->{_authChoice},
CHECK_LOGINS => $self->conf->{portalCheckLogins} CHECK_LOGINS => $self->conf->{portalCheckLogins}
&& $req->data->{login}, && $req->data->{login},
ASK_LOGINS => $req->param('checkLogins') || 0, ASK_LOGINS => $req->param('checkLogins') || 0,
CONFIRMKEY => $self->stamp(), CONFIRMKEY => $self->stamp(),
REMEMBER => $req->data->{confirmRemember}, REMEMBER => $req->data->{confirmRemember},
( $req->data->{customScript} (
$req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} ) ? ( CUSTOM_SCRIPT => $req->data->{customScript} )
: () : ()
), ),
@ -106,12 +107,13 @@ sub display {
CHOICE_PARAM => $self->conf->{authChoiceParam}, CHOICE_PARAM => $self->conf->{authChoiceParam},
CHOICE_VALUE => $req->data->{_authChoice}, CHOICE_VALUE => $req->data->{_authChoice},
CHECK_LOGINS => $self->conf->{portalCheckLogins} CHECK_LOGINS => $self->conf->{portalCheckLogins}
&& $req->data->{login}, && $req->data->{login},
ASK_LOGINS => $req->param('checkLogins') || 0, ASK_LOGINS => $req->param('checkLogins') || 0,
CONFIRMKEY => $self->stamp(), CONFIRMKEY => $self->stamp(),
LIST => $req->data->{list} || [], LIST => $req->data->{list} || [],
REMEMBER => $req->data->{confirmRemember}, REMEMBER => $req->data->{confirmRemember},
( $req->data->{customScript} (
$req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} ) ? ( CUSTOM_SCRIPT => $req->data->{customScript} )
: () : ()
), ),
@ -135,7 +137,8 @@ sub display {
FORM_METHOD => $self->conf->{infoFormMethod}, FORM_METHOD => $self->conf->{infoFormMethod},
CHOICE_PARAM => $self->conf->{authChoiceParam}, CHOICE_PARAM => $self->conf->{authChoiceParam},
CHOICE_VALUE => $req->data->{_authChoice}, CHOICE_VALUE => $req->data->{_authChoice},
( $req->data->{customScript} (
$req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} ) ? ( CUSTOM_SCRIPT => $req->data->{customScript} )
: () : ()
), ),
@ -150,14 +153,15 @@ sub display {
my $p = $self->conf->{portal} . $self->conf->{issuerDBOpenIDPath}; my $p = $self->conf->{portal} . $self->conf->{issuerDBOpenIDPath};
$p =~ s#(?<!:)/?\^?/#/#g; $p =~ s#(?<!:)/?\^?/#/#g;
my $id = $req->{sessionInfo} my $id = $req->{sessionInfo}
->{ $self->conf->{openIdAttr} || $self->conf->{whatToTrace} }; ->{ $self->conf->{openIdAttr} || $self->conf->{whatToTrace} };
%templateParams = ( %templateParams = (
MAIN_LOGO => $self->conf->{portalMainLogo}, MAIN_LOGO => $self->conf->{portalMainLogo},
AUTH_ERROR => $self->error, AUTH_ERROR => $self->error,
AUTH_ERROR_TYPE => $req->error_type, AUTH_ERROR_TYPE => $req->error_type,
PROVIDERURI => $p, PROVIDERURI => $p,
MSG => $req->info(), MSG => $req->info(),
( $req->data->{customScript} (
$req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} ) ? ( CUSTOM_SCRIPT => $req->data->{customScript} )
: () : ()
), ),
@ -174,7 +178,8 @@ sub display {
URL => $req->{urldc}, URL => $req->{urldc},
HIDDEN_INPUTS => $self->buildHiddenForm($req), HIDDEN_INPUTS => $self->buildHiddenForm($req),
FORM_METHOD => $req->data->{redirectFormMethod} || 'get', FORM_METHOD => $req->data->{redirectFormMethod} || 'get',
( $req->data->{customScript} (
$req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} ) ? ( CUSTOM_SCRIPT => $req->data->{customScript} )
: () : ()
), ),
@ -188,16 +193,16 @@ sub display {
#utf8::decode($auth_user); #utf8::decode($auth_user);
%templateParams = ( %templateParams = (
MAIN_LOGO => $self->conf->{portalMainLogo}, MAIN_LOGO => $self->conf->{portalMainLogo},
AUTH_USER => AUTH_USER => $req->{sessionInfo}->{ $self->conf->{portalUserAttr} },
$req->{sessionInfo}->{ $self->conf->{portalUserAttr} }, NEWWINDOW => $self->conf->{portalOpenLinkInNewWindow},
NEWWINDOW => $self->conf->{portalOpenLinkInNewWindow},
LOGOUT_URL => $self->conf->{portal} . "?logout=1", LOGOUT_URL => $self->conf->{portal} . "?logout=1",
APPSLIST_ORDER => $req->{sessionInfo}->{'_appsListOrder'}, APPSLIST_ORDER => $req->{sessionInfo}->{'_appsListOrder'},
PING => $self->conf->{portalPingInterval}, PING => $self->conf->{portalPingInterval},
REQUIRE_OLDPASSWORD => $self->conf->{portalRequireOldPassword}, REQUIRE_OLDPASSWORD => $self->conf->{portalRequireOldPassword},
HIDE_OLDPASSWORD => 0, HIDE_OLDPASSWORD => 0,
$self->menu->params($req), $self->menu->params($req),
( $req->data->{customScript} (
$req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} ) ? ( CUSTOM_SCRIPT => $req->data->{customScript} )
: () : ()
), ),
@ -212,7 +217,8 @@ sub display {
CONFIRMKEY => $self->stamp, CONFIRMKEY => $self->stamp,
PORTAL => $self->conf->{portal}, PORTAL => $self->conf->{portal},
URL => $req->data->{_url}, URL => $req->data->{_url},
( $req->data->{customScript} (
$req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} ) ? ( CUSTOM_SCRIPT => $req->data->{customScript} )
: () : ()
), ),
@ -227,7 +233,8 @@ sub display {
CONFIRMKEY => $self->stamp, CONFIRMKEY => $self->stamp,
PORTAL => $self->conf->{portal}, PORTAL => $self->conf->{portal},
URL => $req->data->{_url}, URL => $req->data->{_url},
( $req->data->{customScript} (
$req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} ) ? ( CUSTOM_SCRIPT => $req->data->{customScript} )
: () : ()
), ),
@ -240,14 +247,15 @@ sub display {
or ( not $req->data->{noerror} or ( not $req->data->{noerror}
and $req->userData and $req->userData
and %{ $req->userData } ) and %{ $req->userData } )
) )
{ {
$skinfile = 'error'; $skinfile = 'error';
%templateParams = ( %templateParams = (
MAIN_LOGO => $self->conf->{portalMainLogo}, MAIN_LOGO => $self->conf->{portalMainLogo},
AUTH_ERROR => $req->error, AUTH_ERROR => $req->error,
AUTH_ERROR_TYPE => $req->error_type, AUTH_ERROR_TYPE => $req->error_type,
( $req->data->{customScript} (
$req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} ) ? ( CUSTOM_SCRIPT => $req->data->{customScript} )
: () : ()
), ),
@ -260,21 +268,21 @@ sub display {
my $login = $self->userId($req); my $login = $self->userId($req);
$login = '' if ( $login eq 'anonymous' ); $login = '' if ( $login eq 'anonymous' );
%templateParams = ( %templateParams = (
MAIN_LOGO => $self->conf->{portalMainLogo}, MAIN_LOGO => $self->conf->{portalMainLogo},
AUTH_ERROR => $req->error, AUTH_ERROR => $req->error,
AUTH_ERROR_TYPE => $req->error_type, AUTH_ERROR_TYPE => $req->error_type,
AUTH_URL => $req->{data}->{_url}, AUTH_URL => $req->{data}->{_url},
LOGIN => $login, LOGIN => $login,
CHECK_LOGINS => $self->conf->{portalCheckLogins}, CHECK_LOGINS => $self->conf->{portalCheckLogins},
ASK_LOGINS => $req->param('checkLogins') || 0, ASK_LOGINS => $req->param('checkLogins') || 0,
DISPLAY_RESETPASSWORD => DISPLAY_RESETPASSWORD => $self->conf->{portalDisplayResetPassword},
$self->conf->{portalDisplayResetPassword}, DISPLAY_REGISTER => $self->conf->{portalDisplayRegister},
DISPLAY_REGISTER => $self->conf->{portalDisplayRegister}, MAIL_URL => $self->conf->{mailUrl},
MAIL_URL => $self->conf->{mailUrl}, REGISTER_URL => $self->conf->{registerUrl},
REGISTER_URL => $self->conf->{registerUrl}, HIDDEN_INPUTS => $self->buildHiddenForm($req),
HIDDEN_INPUTS => $self->buildHiddenForm($req), STAYCONNECTED => $self->conf->{stayConnected},
STAYCONNECTED => $self->conf->{stayConnected}, (
( $req->data->{customScript} $req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} ) ? ( CUSTOM_SCRIPT => $req->data->{customScript} )
: () : ()
), ),
@ -306,12 +314,12 @@ sub display {
or $req->{error} == PE_PASSWORDFORMEMPTY or $req->{error} == PE_PASSWORDFORMEMPTY
or ( $req->{error} == PE_PP_PASSWORD_EXPIRED or ( $req->{error} == PE_PP_PASSWORD_EXPIRED
and $self->conf->{ldapAllowResetExpiredPassword} ) and $self->conf->{ldapAllowResetExpiredPassword} )
) )
{ {
%templateParams = ( %templateParams = (
%templateParams, %templateParams,
REQUIRE_OLDPASSWORD => REQUIRE_OLDPASSWORD =>
1, # Old password is required to check user credentials 1, # Old password is required to check user credentials
DISPLAY_FORM => 0, DISPLAY_FORM => 0,
DISPLAY_OPENID_FORM => 0, DISPLAY_OPENID_FORM => 0,
DISPLAY_YUBIKEY_FORM => 0, DISPLAY_YUBIKEY_FORM => 0,
@ -368,8 +376,8 @@ sub display {
# Choose what form to display if not in a loop # Choose what form to display if not in a loop
else { else {
my $displayType my $displayType =
= eval { $self->_authentication->getDisplayType($req) }; eval { $self->_authentication->getDisplayType($req) };
$self->logger->debug("Display type $displayType "); $self->logger->debug("Display type $displayType ");
@ -377,8 +385,7 @@ sub display {
%templateParams, %templateParams,
DISPLAY_FORM => $displayType =~ /\bstandardform\b/ ? 1 DISPLAY_FORM => $displayType =~ /\bstandardform\b/ ? 1
: 0, : 0,
DISPLAY_OPENID_FORM => $displayType =~ /\bopenidform\b/ DISPLAY_OPENID_FORM => $displayType =~ /\bopenidform\b/ ? 1
? 1
: 0, : 0,
DISPLAY_YUBIKEY_FORM => $displayType =~ /\byubikeyform\b/ DISPLAY_YUBIKEY_FORM => $displayType =~ /\byubikeyform\b/
? 1 ? 1
@ -388,10 +395,9 @@ sub display {
module => $displayType eq "logo" module => $displayType eq "logo"
? $self->getModule( $req, 'auth' ) ? $self->getModule( $req, 'auth' )
: "", : "",
AUTH_LOOP => [], AUTH_LOOP => [],
PORTAL_URL => ( PORTAL_URL =>
$displayType eq "logo" ? $self->conf->{portal} : 0 ( $displayType eq "logo" ? $self->conf->{portal} : 0 ),
),
MSG => $req->info(), MSG => $req->info(),
); );
@ -402,8 +408,8 @@ sub display {
} }
# Additional $req param # Additional $req param
%templateParams %templateParams =
= ( %templateParams, %{ $req->{customParameters} // {} }, ); ( %templateParams, %{ $req->{customParameters} // {} }, );
$self->logger->debug("Skin returned: $skinfile"); $self->logger->debug("Skin returned: $skinfile");
return ( $skinfile, \%templateParams ); return ( $skinfile, \%templateParams );
@ -419,16 +425,15 @@ sub staticFile {
require Plack::Util; require Plack::Util;
require Cwd; require Cwd;
require HTTP::Date; require HTTP::Date;
open my $fh, '<:raw', open my $fh, '<:raw', $self->conf->{templatesDir} . "/$file"
$self->conf->{templatesDir} or return $self->sendError( $req,
. "/$file"
or return $self->sendError( $req,
$self->conf->{templatesDir} . "/$file: $!", 403 ); $self->conf->{templatesDir} . "/$file: $!", 403 );
my @stat = stat $file; my @stat = stat $file;
Plack::Util::set_io_path( $fh, Cwd::realpath($file) ); Plack::Util::set_io_path( $fh, Cwd::realpath($file) );
return [ return [
200, 200,
[ 'Content-Type' => $type, [
'Content-Type' => $type,
'Content-Length' => $stat[7], 'Content-Length' => $stat[7],
'Last-Modified' => HTTP::Date::time2str( $stat[9] ) 'Last-Modified' => HTTP::Date::time2str( $stat[9] )
], ],
@ -445,12 +450,11 @@ sub buildHiddenForm {
# Check XSS attacks # Check XSS attacks
next next
if $self->checkXSSAttack( $_, if $self->checkXSSAttack( $_, $req->{portalHiddenFormValues}->{$_} );
$req->{portalHiddenFormValues}->{$_} );
# Build hidden input HTML code # Build hidden input HTML code
$val .= qq{<input type="hidden" name="$_" id="$_" value="} $val .= qq{<input type="hidden" name="$_" id="$_" value="}
. $req->{portalHiddenFormValues}->{$_} . '" />'; . $req->{portalHiddenFormValues}->{$_} . '" />';
} }
return $val; return $val;
@ -521,12 +525,13 @@ sub mkSessionArray {
displayError => $displayError, displayError => $displayError,
fields => [ fields => [
map { { name => $self->conf->{sessionDataToRemember}->{$_} } } map { { name => $self->conf->{sessionDataToRemember}->{$_} } }
@fields @fields
], ],
sessions => [ sessions => [
map { map {
my $session = $_; my $session = $_;
{ user => $session->{user}, {
user => $session->{user},
utime => $session->{_utime}, utime => $session->{_utime},
ip => $session->{ipAddr}, ip => $session->{ipAddr},
values => [ map { { v => $session->{$_} } } @fields ], values => [ map { { v => $session->{$_} } } @fields ],
@ -548,7 +553,7 @@ sub mkOidcConsent {
# Set default RP displayname # Set default RP displayname
foreach my $oidc ( keys %{ $self->conf->{oidcRPMetaDataOptions} } ) { foreach my $oidc ( keys %{ $self->conf->{oidcRPMetaDataOptions} } ) {
$self->conf->{oidcRPMetaDataOptions}->{$oidc} $self->conf->{oidcRPMetaDataOptions}->{$oidc}
->{oidcRPMetaDataOptionsDisplayName} ||= $oidc; ->{oidcRPMetaDataOptionsDisplayName} ||= $oidc;
} }
} }
@ -574,9 +579,9 @@ sub mkOidcConsent {
$self->logger->debug("RP { $rp } Consent found"); $self->logger->debug("RP { $rp } Consent found");
$consents->{$rp}->{epoch} = $_->{epoch}; $consents->{$rp}->{epoch} = $_->{epoch};
$consents->{$rp}->{scope} = $_->{scope}; $consents->{$rp}->{scope} = $_->{scope};
$consents->{$rp}->{displayName} $consents->{$rp}->{displayName} =
= $self->conf->{oidcRPMetaDataOptions}->{$rp} $self->conf->{oidcRPMetaDataOptions}->{$rp}
->{oidcRPMetaDataOptionsDisplayName}; ->{oidcRPMetaDataOptionsDisplayName};
} }
} }
@ -586,7 +591,8 @@ sub mkOidcConsent {
params => { params => {
partners => [ partners => [
map { map {
{ name => $_, {
name => $_,
epoch => $consents->{$_}->{epoch}, epoch => $consents->{$_}->{epoch},
scope => $consents->{$_}->{scope}, scope => $consents->{$_}->{scope},
displayName => $consents->{$_}->{displayName} displayName => $consents->{$_}->{displayName}

View File

@ -84,8 +84,9 @@ sub init {
my ( $self, $args ) = @_; my ( $self, $args ) = @_;
$args ||= {}; $args ||= {};
$self->localConfig( $self->localConfig(
{ %{ Lemonldap::NG::Common::Conf->new( $args->{configStorage} ) {
->getLocalConf('portal') %{ Lemonldap::NG::Common::Conf->new( $args->{configStorage} )
->getLocalConf('portal')
}, },
%$args %$args
} }
@ -109,33 +110,33 @@ sub init {
# Handle requests (other path may be declared in enabled plugins) # Handle requests (other path may be declared in enabled plugins)
$self $self
# "/" or undeclared paths # "/" or undeclared paths
->addUnauthRoute( '*' => 'login', ['GET'] ) ->addUnauthRoute( '*' => 'login', ['GET'] )
->addUnauthRoute( '*' => 'postLogin', ['POST'] ) ->addUnauthRoute( '*' => 'postLogin', ['POST'] )
->addAuthRoute( '*' => 'authenticatedRequest', ['GET'] ) ->addAuthRoute( '*' => 'authenticatedRequest', ['GET'] )
->addAuthRoute( '*' => 'postAuthenticatedRequest', ['POST'] ) ->addAuthRoute( '*' => 'postAuthenticatedRequest', ['POST'] )
# psgi.js # psgi.js
->addUnauthRoute( 'psgi.js' => 'sendJs', ['GET'] ) ->addUnauthRoute( 'psgi.js' => 'sendJs', ['GET'] )
->addAuthRoute( 'psgi.js' => 'sendJs', ['GET'] ) ->addAuthRoute( 'psgi.js' => 'sendJs', ['GET'] )
# portal.css # portal.css
->addUnauthRoute( 'portal.css' => 'sendCss', ['GET'] ) ->addUnauthRoute( 'portal.css' => 'sendCss', ['GET'] )
->addAuthRoute( 'portal.css' => 'sendCss', ['GET'] ) ->addAuthRoute( 'portal.css' => 'sendCss', ['GET'] )
# lmerror # lmerror
->addUnauthRoute( lmerror => { ':code' => 'lmError' }, ['GET'] ) ->addUnauthRoute( lmerror => { ':code' => 'lmError' }, ['GET'] )
->addAuthRoute( lmerror => { ':code' => 'lmError' }, ['GET'] ) ->addAuthRoute( lmerror => { ':code' => 'lmError' }, ['GET'] )
# Core REST API # Core REST API
->addUnauthRoute( ping => 'pleaseAuth', ['GET'] ) ->addUnauthRoute( ping => 'pleaseAuth', ['GET'] )
->addAuthRoute( ping => 'authenticated', ['GET'] ) ->addAuthRoute( ping => 'authenticated', ['GET'] )
# Refresh session # Refresh session
->addAuthRoute( refresh => 'refresh', ['GET'] ) ->addAuthRoute( refresh => 'refresh', ['GET'] )
# Logout # Logout
->addAuthRoute( logout => 'logout', ['GET'] ); ->addAuthRoute( logout => 'logout', ['GET'] );
# Default routes must point to routines declared above # Default routes must point to routines declared above
$self->defaultAuthRoute(''); $self->defaultAuthRoute('');
@ -169,8 +170,8 @@ sub reloadConf {
$self->csp($csp); $self->csp($csp);
# Initialize templateDir # Initialize templateDir
$self->{templateDir} $self->{templateDir} =
= $self->conf->{templateDir} . '/' . $self->conf->{portalSkin}; $self->conf->{templateDir} . '/' . $self->conf->{portalSkin};
unless ( -d $self->{templateDir} ) { unless ( -d $self->{templateDir} ) {
$self->error("Template dir $self->{templateDir} doesn't exist"); $self->error("Template dir $self->{templateDir} doesn't exist");
return $self->fail; return $self->fail;
@ -190,8 +191,8 @@ sub reloadConf {
# Initialize persistent session DB # Initialize persistent session DB
unless ( $self->conf->{persistentStorage} ) { unless ( $self->conf->{persistentStorage} ) {
$self->conf->{persistentStorage} = $self->conf->{globalStorage}; $self->conf->{persistentStorage} = $self->conf->{globalStorage};
$self->conf->{persistentStorageOptions} $self->conf->{persistentStorageOptions} =
= $self->conf->{globalStorageOptions}; $self->conf->{globalStorageOptions};
} }
# Initialize cookie domain # Initialize cookie domain
@ -215,19 +216,19 @@ sub reloadConf {
return $self->fail; return $self->fail;
} }
$mod = $self->conf->{$type} $mod = $self->conf->{$type}
unless ( $self->conf->{$type} eq 'Same' ); unless ( $self->conf->{$type} eq 'Same' );
my $module = '::' . ucfirst($type) . '::' . $mod; my $module = '::' . ucfirst($type) . '::' . $mod;
$module =~ s/Authentication/Auth/; $module =~ s/Authentication/Auth/;
# Launch and initialize module # Launch and initialize module
return $self->fail return $self->fail
unless ( $self->{"_$type"} = $self->loadPlugin($module) ); unless ( $self->{"_$type"} = $self->loadPlugin($module) );
} }
# Load second-factor engine # Load second-factor engine
return $self->fail return $self->fail
unless $self->{_sfEngine} unless $self->{_sfEngine} =
= $self->loadPlugin( $self->conf->{'sfEngine'} ); $self->loadPlugin( $self->conf->{'sfEngine'} );
# Initialize trusted domain regexp # Initialize trusted domain regexp
if ( $self->conf->{trustedDomains} if ( $self->conf->{trustedDomains}
@ -250,8 +251,8 @@ sub reloadConf {
# - $domainlabel.$td # - $domainlabel.$td
# $domainlabel is build looking RFC2396 # $domainlabel is build looking RFC2396
# (see Regexp::Common::URI::RFC2396) # (see Regexp::Common::URI::RFC2396)
$_ $_ =~
=~ s/\*\\\./(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9]\\.)*/g; s/\*\\\./(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9]\\.)*/g;
$re->add("$_"); $re->add("$_");
} }
} }
@ -262,8 +263,8 @@ sub reloadConf {
$self->logger->debug("Vhost $vhost added in trusted domains"); $self->logger->debug("Vhost $vhost added in trusted domains");
$re->add( quotemeta($vhost) ); $re->add( quotemeta($vhost) );
$self->conf->{vhostOptions} ||= {}; $self->conf->{vhostOptions} ||= {};
if ( my $tmp if ( my $tmp =
= $self->conf->{vhostOptions}->{$vhost}->{vhostAliases} ) $self->conf->{vhostOptions}->{$vhost}->{vhostAliases} )
{ {
foreach my $alias ( split /\s+/, $tmp ) { foreach my $alias ( split /\s+/, $tmp ) {
$self->logger->debug( $self->logger->debug(
@ -281,22 +282,22 @@ sub reloadConf {
$self->{"_$type"} = {}; $self->{"_$type"} = {};
if ( $self->conf->{$type} ) { if ( $self->conf->{$type} ) {
for my $name ( sort keys %{ $self->conf->{$type} } ) { for my $name ( sort keys %{ $self->conf->{$type} } ) {
my $sub = HANDLER->buildSub( my $sub =
HANDLER->buildSub(
HANDLER->substitute( $self->conf->{$type}->{$name} ) ); HANDLER->substitute( $self->conf->{$type}->{$name} ) );
if ($sub) { if ($sub) {
$self->{"_$type"}->{$name} = $sub; $self->{"_$type"}->{$name} = $sub;
} }
else { else {
$self->logger->error( "$type $name returns an error: " $self->logger->error( "$type $name returns an error: "
. HANDLER->tsv->{jail}->error ); . HANDLER->tsv->{jail}->error );
} }
} }
} }
} }
$self->{_jsRedirect} $self->{_jsRedirect} =
= HANDLER->buildSub( HANDLER->buildSub( HANDLER->substitute( $self->conf->{jsRedirect} ) )
HANDLER->substitute( $self->conf->{jsRedirect} ) ) or $self->logger->error(
or $self->logger->error(
'jsRedirect returns an error: ' . HANDLER->tsv->{jail}->error ); 'jsRedirect returns an error: ' . HANDLER->tsv->{jail}->error );
# Load plugins # Load plugins
@ -332,7 +333,7 @@ sub loadPlugin {
} }
my $obj; my $obj;
return 0 return 0
unless ( $obj = $self->loadModule("$plugin") ); unless ( $obj = $self->loadModule("$plugin") );
return $self->findEP( $plugin, $obj ); return $self->findEP( $plugin, $obj );
} }
@ -361,7 +362,7 @@ sub findEP {
if ( $obj->can('spRules') ) { if ( $obj->can('spRules') ) {
foreach my $k ( keys %{ $obj->{spRules} } ) { foreach my $k ( keys %{ $obj->{spRules} } ) {
$self->logger->info( $self->logger->info(
"$k is defined more than one time, it can have some bad effect on Menu display" "$k is defined more than one time, it can have some bad effect on Menu display"
) if ( $self->spRules->{$k} ); ) if ( $self->spRules->{$k} );
$self->spRules->{$k} = $obj->{spRules}->{$k}; $self->spRules->{$k} = $obj->{spRules}->{$k};
} }

View File

@ -111,7 +111,7 @@ sub _redirect {
# Restore urldc if auth doesn't need to dial with browser # Restore urldc if auth doesn't need to dial with browser
$self->restoreRequest( $req, $ir ); $self->restoreRequest( $req, $ir );
return $self->run( @_, @path ); return $self->run( @_, @path );
} }
: () : ()
) )
] ]

View File

@ -17,11 +17,11 @@ use strict;
use URI::Escape; use URI::Escape;
# List constants # List constants
sub authProcess {qw(extractFormInfo getUser authenticate)} sub authProcess { qw(extractFormInfo getUser authenticate) }
sub sessionData { sub sessionData {
qw(setAuthSessionInfo setSessionInfo setMacros setGroups setPersistentSessionInfo qw(setAuthSessionInfo setSessionInfo setMacros setGroups setPersistentSessionInfo
setLocalGroups store secondFactor); setLocalGroups store secondFactor);
} }
sub validSession { sub validSession {
@ -56,9 +56,11 @@ sub handler {
if ( $sp or %{ $req->pdata } ) { if ( $sp or %{ $req->pdata } ) {
my %v = ( my %v = (
name => $self->conf->{cookieName} . 'pdata', name => $self->conf->{cookieName} . 'pdata',
( %{ $req->pdata } (
%{ $req->pdata }
? ( value => uri_escape( JSON::to_json( $req->pdata ) ) ) ? ( value => uri_escape( JSON::to_json( $req->pdata ) ) )
: ( value => '', : (
value => '',
expires => 'Wed, 21 Oct 2015 00:00:00 GMT' expires => 'Wed, 21 Oct 2015 00:00:00 GMT'
) )
) )
@ -92,7 +94,8 @@ sub login {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
return $self->do( return $self->do(
$req, $req,
[ 'controlUrl', @{ $self->beforeAuth }, [
'controlUrl', @{ $self->beforeAuth },
$self->authProcess, @{ $self->betweenAuthAndData }, $self->authProcess, @{ $self->betweenAuthAndData },
$self->sessionData, @{ $self->afterData }, $self->sessionData, @{ $self->afterData },
$self->validSession, @{ $self->endAuth }, $self->validSession, @{ $self->endAuth },
@ -104,7 +107,8 @@ sub postLogin {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
return $self->do( return $self->do(
$req, $req,
[ 'restoreArgs', 'controlUrl', [
'restoreArgs', 'controlUrl',
@{ $self->beforeAuth }, $self->authProcess, @{ $self->beforeAuth }, $self->authProcess,
@{ $self->betweenAuthAndData }, $self->sessionData, @{ $self->betweenAuthAndData }, $self->sessionData,
@{ $self->afterData }, $self->validSession, @{ $self->afterData }, $self->validSession,
@ -117,7 +121,8 @@ sub authenticatedRequest {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
return $self->do( return $self->do(
$req, $req,
[ 'importHandlerData', 'controlUrl', [
'importHandlerData', 'controlUrl',
'checkLogout', @{ $self->forAuthUser } 'checkLogout', @{ $self->forAuthUser }
] ]
); );
@ -127,7 +132,8 @@ sub postAuthenticatedRequest {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
return $self->do( return $self->do(
$req, $req,
[ 'importHandlerData', 'restoreArgs', [
'importHandlerData', 'restoreArgs',
'controlUrl', 'checkLogout', 'controlUrl', 'checkLogout',
@{ $self->forAuthUser } @{ $self->forAuthUser }
] ]
@ -145,7 +151,8 @@ sub refresh {
delete $data{$_} unless ( /^_/ or /^(?:startTime)$/ ); delete $data{$_} unless ( /^_/ or /^(?:startTime)$/ );
} }
$req->steps( $req->steps(
[ 'getUser', [
'getUser',
@{ $self->betweenAuthAndData }, @{ $self->betweenAuthAndData },
'setAuthSessionInfo', 'setAuthSessionInfo',
'setSessionInfo', 'setSessionInfo',
@ -163,21 +170,21 @@ sub refresh {
if ($res) { if ($res) {
$req->info( $req->info(
$self->loadTemplate( $self->loadTemplate(
'simpleInfo', 'simpleInfo', params => { trspan => 'rightsReloadNeedsLogout' }
params => { trspan => 'rightsReloadNeedsLogout' }
) )
); );
$req->urldc( $self->conf->{portal} ); $req->urldc( $self->conf->{portal} );
return $self->do( $req, [ sub {PE_INFO} ] ); return $self->do( $req, [ sub { PE_INFO } ] );
} }
return $self->do( $req, [ sub {PE_OK} ] ); return $self->do( $req, [ sub { PE_OK } ] );
} }
sub logout { sub logout {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
return $self->do( return $self->do(
$req, $req,
[ 'controlUrl', @{ $self->beforeLogout }, [
'controlUrl', @{ $self->beforeLogout },
'authLogout', 'deleteSession' 'authLogout', 'deleteSession'
] ]
); );
@ -194,9 +201,9 @@ sub do {
# Update status # Update status
if ( my $p = $self->HANDLER->tsv->{statusPipe} ) { if ( my $p = $self->HANDLER->tsv->{statusPipe} ) {
$p->print(( $req->user ? $req->user : $req->address ) . ' => ' $p->print( ( $req->user ? $req->user : $req->address ) . ' => '
. $req->uri . $req->uri
. " $err\n" ); . " $err\n" );
} }
# Update history # Update history
@ -208,7 +215,8 @@ sub do {
if ( ( $err > 0 and !$req->id ) or $err eq PE_SESSIONNOTGRANTED ) { if ( ( $err > 0 and !$req->id ) or $err eq PE_SESSIONNOTGRANTED ) {
return [ return [
401, 401,
[ 'WWW-Authenticate' => "SSO " . $self->conf->{portal}, [
'WWW-Authenticate' => "SSO " . $self->conf->{portal},
'Access-Control-Allow-Origin' => '*' 'Access-Control-Allow-Origin' => '*'
], ],
[qq'{"result":0,"error":$err}'] [qq'{"result":0,"error":$err}']
@ -224,14 +232,16 @@ sub do {
else { else {
return $self->sendJSONresponse( return $self->sendJSONresponse(
$req, $req,
{ result => 1, {
result => 1,
code => $err code => $err
} }
); );
} }
} }
else { else {
if ( $err if (
$err
and $err != PE_LOGOUT_OK and $err != PE_LOGOUT_OK
and ( and (
$err != PE_REDIRECT $err != PE_REDIRECT
@ -240,7 +250,7 @@ sub do {
and $req->data->{redirectFormMethod} eq 'post' ) and $req->data->{redirectFormMethod} eq 'post' )
or $req->info or $req->info
) )
) )
{ {
my ( $tpl, $prms ) = $self->display($req); my ( $tpl, $prms ) = $self->display($req);
$self->logger->debug("Calling sendHtml with template $tpl"); $self->logger->debug("Calling sendHtml with template $tpl");
@ -258,20 +268,21 @@ sub do {
sub getModule { sub getModule {
my ( $self, $req, $type ) = @_; my ( $self, $req, $type ) = @_;
if (my $mod = { if (
my $mod = {
auth => '_authentication', auth => '_authentication',
user => '_userDB', user => '_userDB',
password => '_passwordDB' password => '_passwordDB'
}->{$type} }->{$type}
) )
{ {
if ( my $sub = $self->$mod->can('name') ) { if ( my $sub = $self->$mod->can('name') ) {
return $sub->( $self->$mod, $req, $type ); return $sub->( $self->$mod, $req, $type );
} }
else { else {
my $s = ref( $self->$mod ); my $s = ref( $self->$mod );
$s $s =~
=~ s/^Lemonldap::NG::Portal::(?:(?:Issuer|UserDB|Auth|Password)::)?//; s/^Lemonldap::NG::Portal::(?:(?:Issuer|UserDB|Auth|Password)::)?//;
return $s; return $s;
} }
} }
@ -288,7 +299,7 @@ sub autoRedirect {
# Set redirection URL if needed # Set redirection URL if needed
$req->{urldc} ||= $self->conf->{portal} $req->{urldc} ||= $self->conf->{portal}
if ( $req->mustRedirect and not( $req->info ) ); if ( $req->mustRedirect and not( $req->info ) );
# Redirection should be made if urldc defined # Redirection should be made if urldc defined
if ( $req->{urldc} ) { if ( $req->{urldc} ) {
@ -298,9 +309,8 @@ sub autoRedirect {
$req->data->{redirectFormMethod} = "get"; $req->data->{redirectFormMethod} = "get";
} }
else { else {
return [ return [ 302,
302, [ Location => $req->{urldc}, @{ $req->respHeaders } ], [] [ Location => $req->{urldc}, @{ $req->respHeaders } ], [] ];
];
} }
} }
my ( $tpl, $prms ) = $self->display($req); my ( $tpl, $prms ) = $self->display($req);
@ -322,7 +332,8 @@ sub getApacheSession {
} }
my $as = Lemonldap::NG::Common::Session->new( my $as = Lemonldap::NG::Common::Session->new(
{ storageModule => $self->conf->{globalStorage}, {
storageModule => $self->conf->{globalStorage},
storageModuleOptions => $self->conf->{globalStorageOptions}, storageModuleOptions => $self->conf->{globalStorageOptions},
cacheModule => $self->conf->{localSessionStorage}, cacheModule => $self->conf->{localSessionStorage},
cacheModuleOptions => $self->conf->{localSessionStorageOptions}, cacheModuleOptions => $self->conf->{localSessionStorageOptions},
@ -336,7 +347,8 @@ sub getApacheSession {
if ( my $err = $as->error ) { if ( my $err = $as->error ) {
$self->lmLog( $self->lmLog(
$err, $err,
( $err =~ /(?:Object does not exist|Invalid session ID)/ (
$err =~ /(?:Object does not exist|Invalid session ID)/
? 'notice' ? 'notice'
: 'error' : 'error'
) )
@ -350,16 +362,17 @@ sub getApacheSession {
} }
my $now = time; my $now = time;
if ( $id if (
$id
and defined $as->data->{_utime} and defined $as->data->{_utime}
and ( and (
$now - $as->data->{_utime} > $self->conf->{timeout} $now - $as->data->{_utime} > $self->conf->{timeout}
or ( $self->conf->{timeoutActivity} or ( $self->conf->{timeoutActivity}
and $as->data->{_lastSeen} and $as->data->{_lastSeen}
and $now - $as->data->{_lastSeen} and $now - $as->data->{_lastSeen} >
> $self->conf->{timeoutActivity} ) $self->conf->{timeoutActivity} )
)
) )
)
{ {
$self->logger->debug("Session $args{kind} $id expired"); $self->logger->debug("Session $args{kind} $id expired");
return; return;
@ -382,7 +395,8 @@ sub getPersistentSession {
$info->{_session_uid} = $uid; $info->{_session_uid} = $uid;
my $ps = Lemonldap::NG::Common::Session->new( my $ps = Lemonldap::NG::Common::Session->new(
{ storageModule => $self->conf->{persistentStorage}, {
storageModule => $self->conf->{persistentStorage},
storageModuleOptions => $self->conf->{persistentStorageOptions}, storageModuleOptions => $self->conf->{persistentStorageOptions},
id => $pid, id => $pid,
force => 1, force => 1,
@ -423,11 +437,10 @@ sub updatePersistentSession {
# Return if no infos to update # Return if no infos to update
return () unless ( ref $infos eq 'HASH' and %$infos ); return () unless ( ref $infos eq 'HASH' and %$infos );
$uid ||= $req->{sessionInfo}->{ $self->conf->{whatToTrace} } $uid ||= $req->{sessionInfo}->{ $self->conf->{whatToTrace} }
|| $req->userData->{ $self->conf->{whatToTrace} }; || $req->userData->{ $self->conf->{whatToTrace} };
$self->logger->debug("Found 'whatToTrace' -> $uid"); $self->logger->debug("Found 'whatToTrace' -> $uid");
unless ($uid) { unless ($uid) {
$self->logger->debug( $self->logger->debug('No uid found, skipping updatePersistentSession');
'No uid found, skipping updatePersistentSession');
return (); return ();
} }
$self->logger->debug("Update $uid persistent session"); $self->logger->debug("Update $uid persistent session");
@ -469,14 +482,14 @@ sub updateSession {
foreach ( keys %$infos ) { foreach ( keys %$infos ) {
$self->logger->debug( $self->logger->debug(
"Update sessionInfo $_ with " . $infos->{$_} ); "Update sessionInfo $_ with " . $infos->{$_} );
$req->{sessionInfo}->{$_} = $self->HANDLER->data->{$_} $req->{sessionInfo}->{$_} = $self->HANDLER->data->{$_} =
= $infos->{$_}; $infos->{$_};
} }
# Update session in global storage with _updateTime # Update session in global storage with _updateTime
$infos->{_updateTime} = strftime( "%Y%m%d%H%M%S", localtime() ); $infos->{_updateTime} = strftime( "%Y%m%d%H%M%S", localtime() );
if ( my $apacheSession if ( my $apacheSession =
= $self->getApacheSession( $id, info => $infos ) ) $self->getApacheSession( $id, info => $infos ) )
{ {
if ( $apacheSession->error ) { if ( $apacheSession->error ) {
$self->logger->error("Cannot update session $id"); $self->logger->error("Cannot update session $id");
@ -559,10 +572,10 @@ sub isTrustedUrl {
sub stamp { sub stamp {
my $self = shift; my $self = shift;
my $res my $res =
= $self->conf->{cipher} $self->conf->{cipher}
? $self->conf->{cipher}->encrypt( time() ) ? $self->conf->{cipher}->encrypt( time() )
: 1; : 1;
$res =~ s/\+/%2B/g; $res =~ s/\+/%2B/g;
return $res; return $res;
} }
@ -694,7 +707,7 @@ sub cookie {
$h{path} ||= '/'; $h{path} ||= '/';
$h{HttpOnly} //= $self->conf->{httpOnly}; $h{HttpOnly} //= $self->conf->{httpOnly};
$h{max_age} //= $self->conf->{cookieExpiration} $h{max_age} //= $self->conf->{cookieExpiration}
if ( $self->conf->{cookieExpiration} ); if ( $self->conf->{cookieExpiration} );
foreach (qw(domain path expires max_age HttpOnly)) { foreach (qw(domain path expires max_age HttpOnly)) {
my $f = $_; my $f = $_;
$f =~ s/_/-/g; $f =~ s/_/-/g;
@ -717,8 +730,8 @@ sub sendHtml {
my ( $self, $req, $template, %args ) = @_; my ( $self, $req, $template, %args ) = @_;
my $res = $self->SUPER::sendHtml( $req, $template, %args ); my $res = $self->SUPER::sendHtml( $req, $template, %args );
push @{ $res->[1] }, push @{ $res->[1] },
'X-XSS-Protection' => '1; mode=block', 'X-XSS-Protection' => '1; mode=block',
'X-Content-Type-Options' => 'nosniff'; 'X-Content-Type-Options' => 'nosniff';
# Set authorized URL for POST # Set authorized URL for POST
my $csp = $self->csp . "form-action 'self'"; my $csp = $self->csp . "form-action 'self'";
@ -732,14 +745,13 @@ sub sendHtml {
if ( defined $url ) { if ( defined $url ) {
$self->logger->debug("Required Params URL : $url"); $self->logger->debug("Required Params URL : $url");
if ( $url =~ s#(https?://[^/]+).*#$1# ) { if ( $url =~ s#(https?://[^/]+).*#$1# ) {
$self->logger->debug( $self->logger->debug("Set CSP form-action with Params URL : $url");
"Set CSP form-action with Params URL : $url");
$csp .= " $url"; $csp .= " $url";
} }
} }
if ( defined $req->{cspFormAction} ) { if ( defined $req->{cspFormAction} ) {
$self->logger->debug( "Set CSP form-action with request URL: " $self->logger->debug(
. $req->{cspFormAction} ); "Set CSP form-action with request URL: " . $req->{cspFormAction} );
$csp .= " " . $req->{cspFormAction}; $csp .= " " . $req->{cspFormAction};
} }
$csp .= ';'; $csp .= ';';
@ -754,7 +766,7 @@ sub sendHtml {
my @url; my @url;
if ( $req->info ) { if ( $req->info ) {
@url = map { s#https?://([^/]+).*#$1#; $_ } @url = map { s#https?://([^/]+).*#$1#; $_ }
( $req->info =~ /<iframe.*?src="(.*?)"/sg ); ( $req->info =~ /<iframe.*?src="(.*?)"/sg );
} }
if (@url) { if (@url) {
$csp .= join( ' ', 'child-src', @url ) . ';'; $csp .= join( ' ', 'child-src', @url ) . ';';
@ -768,16 +780,17 @@ sub sendHtml {
sub sendCss { sub sendCss {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
my $s my $s =
= 'html,body{background:url("' 'html,body{background:url("'
. $self->staticPrefix . $self->staticPrefix
. '/common/backgrounds/' . '/common/backgrounds/'
. $self->conf->{portalSkinBackground} . $self->conf->{portalSkinBackground}
. '") no-repeat center fixed;' . '") no-repeat center fixed;'
. 'background-size:cover;}'; . 'background-size:cover;}';
return [ return [
200, 200,
[ 'Content-Type' => 'text/css', [
'Content-Type' => 'text/css',
'Content-Length' => length($s), 'Content-Length' => length($s),
'Cache-Control' => 'public,max-age=3600', 'Cache-Control' => 'public,max-age=3600',
], ],
@ -799,16 +812,16 @@ sub lmError {
# Error code # Error code
$templateParams{"ERROR$_"} = ( $httpError == $_ ? 1 : 0 ) $templateParams{"ERROR$_"} = ( $httpError == $_ ? 1 : 0 )
foreach ( 403, 404, 500, 502, 503 ); foreach ( 403, 404, 500, 502, 503 );
return $self->sendHtml( $req, 'error', params => \%templateParams ); return $self->sendHtml( $req, 'error', params => \%templateParams );
} }
sub rebuildCookies { sub rebuildCookies {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
my @tmp; my @tmp;
for ( my $i = 0; $i < @{ $req->{respHeaders} }; $i += 2 ) { for ( my $i = 0 ; $i < @{ $req->{respHeaders} } ; $i += 2 ) {
push @tmp, $req->respHeaders->[0], $req->respHeaders->[1] push @tmp, $req->respHeaders->[0], $req->respHeaders->[1]
unless ( $req->respHeaders->[0] eq 'Set-Cookie' ); unless ( $req->respHeaders->[0] eq 'Set-Cookie' );
} }
$req->{respHeaders} = \@tmp; $req->{respHeaders} = \@tmp;
$self->buildCookie($req); $self->buildCookie($req);
@ -831,7 +844,7 @@ sub tplParams {
sub registerLogin { sub registerLogin {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
return return
unless ( $self->conf->{loginHistoryEnabled} unless ( $self->conf->{loginHistoryEnabled}
and defined $req->authResult ); and defined $req->authResult );
my $history = $req->sessionInfo->{_loginHistory} ||= {}; my $history = $req->sessionInfo->{_loginHistory} ||= {};
my $type = ( $req->authResult > 0 ? 'failed' : 'success' ) . 'Login'; my $type = ( $req->authResult > 0 ? 'failed' : 'success' ) . 'Login';
@ -841,17 +854,17 @@ sub registerLogin {
# Gather current login's parameters # Gather current login's parameters
my $login = $self->_sumUpSession( $req->{sessionInfo}, 1 ); my $login = $self->_sumUpSession( $req->{sessionInfo}, 1 );
$login->{error} = $self->error( $req->authResult ) $login->{error} = $self->error( $req->authResult )
if ( $req->authResult ); if ( $req->authResult );
$self->logger->debug(" Current login -> " . $login->{error}) if ( $login->{error} ); $self->logger->debug( " Current login -> " . $login->{error} )
if ( $login->{error} );
# Add current login into history # Add current login into history
unshift @{ $history->{$type} }, $login; unshift @{ $history->{$type} }, $login;
# Forget oldest logins # Forget oldest logins
splice @{ $history->{$type} }, $self->conf->{ $type . "Number" } splice @{ $history->{$type} }, $self->conf->{ $type . "Number" }
if ( if ( scalar @{ $history->{$type} } > $self->conf->{ $type . "Number" } );
scalar @{ $history->{$type} } > $self->conf->{ $type . "Number" } );
# Save into persistent session # Save into persistent session
$self->updatePersistentSession( $req, { _loginHistory => $history, } ); $self->updatePersistentSession( $req, { _loginHistory => $history, } );
@ -862,12 +875,12 @@ sub registerLogin {
# @return hashref # @return hashref
sub _sumUpSession { sub _sumUpSession {
my ( $self, $session, $withoutUser ) = @_; my ( $self, $session, $withoutUser ) = @_;
my $res my $res =
= $withoutUser $withoutUser
? {} ? {}
: { user => $session->{ $self->conf->{whatToTrace} } }; : { user => $session->{ $self->conf->{whatToTrace} } };
$res->{$_} = $session->{$_} $res->{$_} = $session->{$_}
foreach ( "_utime", "ipAddr", foreach ( "_utime", "ipAddr",
keys %{ $self->conf->{sessionDataToRemember} } ); keys %{ $self->conf->{sessionDataToRemember} } );
return $res; return $res;
} }
@ -876,12 +889,12 @@ sub _sumUpSession {
sub loadTemplate { sub loadTemplate {
my ( $self, $name, %prm ) = @_; my ( $self, $name, %prm ) = @_;
$name .= '.tpl'; $name .= '.tpl';
my $file my $file =
= $self->conf->{templateDir} . '/' $self->conf->{templateDir} . '/'
. $self->conf->{portalSkin} . '/' . $self->conf->{portalSkin} . '/'
. $name; . $name;
$file = $self->conf->{templateDir} . '/common/' . $name $file = $self->conf->{templateDir} . '/common/' . $name
unless ( -e $file ); unless ( -e $file );
unless ( -e $file ) { unless ( -e $file ) {
die "Unable to find $name in $self->conf->{templateDir}"; die "Unable to find $name in $self->conf->{templateDir}";
} }

View File

@ -3,11 +3,11 @@ package Lemonldap::NG::Portal::Main::SecondFactor;
use strict; use strict;
use Mouse; use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw( use Lemonldap::NG::Portal::Main::Constants qw(
PE_SENDRESPONSE PE_SENDRESPONSE
PE_OK PE_OK
PE_NOTOKEN PE_NOTOKEN
PE_TOKENEXPIRED PE_TOKENEXPIRED
PE_BADCREDENTIALS PE_BADCREDENTIALS
); );
our $VERSION = '2.0.0'; our $VERSION = '2.0.0';
@ -19,8 +19,8 @@ extends 'Lemonldap::NG::Portal::Main::Plugin';
has ott => ( has ott => (
is => 'rw', is => 'rw',
default => sub { default => sub {
my $ott = $_[0]->{p} my $ott =
->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken'); $_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
$ott->timeout( $_[0]->{conf}->{formTimeout} ); $ott->timeout( $_[0]->{conf}->{formTimeout} );
return $ott; return $ott;
} }
@ -52,8 +52,7 @@ sub _redirect {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
my $arg = $req->env->{QUERY_STRING}; my $arg = $req->env->{QUERY_STRING};
return [ return [
302, [ Location => $self->conf->{portal} . ( $arg ? "?$arg" : '' ) ], 302, [ Location => $self->conf->{portal} . ( $arg ? "?$arg" : '' ) ], []
[]
]; ];
} }
@ -66,16 +65,15 @@ sub _verify {
# Check token # Check token
my $token; my $token;
unless ( $token = $req->param('token') ) { unless ( $token = $req->param('token') ) {
$self->userLogger->error( $self->userLogger->error( $self->prefix . ' 2F access without token' );
$self->prefix . ' 2F access without token' );
$req->mustRedirect(1); $req->mustRedirect(1);
return $self->p->do( $req, [ sub {PE_NOTOKEN} ] ); return $self->p->do( $req, [ sub { PE_NOTOKEN } ] );
} }
my $session; my $session;
unless ( $session = $self->ott->getToken($token) ) { unless ( $session = $self->ott->getToken($token) ) {
$self->userLogger->info('Token expired'); $self->userLogger->info('Token expired');
return $self->p->do( $req, [ sub {PE_TOKENEXPIRED} ] ); return $self->p->do( $req, [ sub { PE_TOKENEXPIRED } ] );
} }
# Launch second factor verification # Launch second factor verification
@ -90,7 +88,7 @@ sub _verify {
$req->{sessionInfo}->{_utime} = delete $req->{sessionInfo}->{_2fUtime}; $req->{sessionInfo}->{_utime} = delete $req->{sessionInfo}->{_2fUtime};
$req->authResult(PE_BADCREDENTIALS); $req->authResult(PE_BADCREDENTIALS);
return $self->p->do( $req, return $self->p->do( $req,
[ $self->p->storeHistory($req), sub {$res} ] ); [ $self->p->storeHistory($req), sub { $res } ] );
} }
# Else restore session # Else restore session
@ -101,15 +99,22 @@ sub _verify {
$self->p->rebuildCookies($req); $self->p->rebuildCookies($req);
$req->mustRedirect(1); $req->mustRedirect(1);
$self->userLogger->notice( $self->prefix $self->userLogger->notice( $self->prefix
. '2F verification for ' . '2F verification for '
. $req->sessionInfo->{ $self->conf->{whatToTrace} } ); . $req->sessionInfo->{ $self->conf->{whatToTrace} } );
if ( my $l = $self->conf->{ $self->prefix . '2fAuthnLevel' } ) { if ( my $l = $self->conf->{ $self->prefix . '2fAuthnLevel' } ) {
$self->p->updateSession( $req, { authenticationLevel => $l } ); $self->p->updateSession( $req, { authenticationLevel => $l } );
} }
$req->authResult(PE_SENDRESPONSE); $req->authResult(PE_SENDRESPONSE);
return $self->p->do( $req, return $self->p->do(
[ @{ $self->p->afterData }, $self->p->validSession, @{ $self->p->endAuth }, sub {PE_OK} ] ); $req,
[
@{ $self->p->afterData },
$self->p->validSession,
@{ $self->p->endAuth },
sub { PE_OK }
]
);
} }
1; 1;

View File

@ -40,11 +40,9 @@ sub run {
return PE_OK if ( $countFailed < 3 ); return PE_OK if ( $countFailed < 3 );
foreach ( 0 .. 2 ) { foreach ( 0 .. 2 ) {
if ( defined $req->sessionInfo->{_loginHistory}->{failedLogin}->[$_] ) if ( defined $req->sessionInfo->{_loginHistory}->{failedLogin}->[$_] ) {
{
push @lastFailedLoginEpoch, push @lastFailedLoginEpoch,
$req->sessionInfo->{_loginHistory}->{failedLogin}->[$_] $req->sessionInfo->{_loginHistory}->{failedLogin}->[$_]->{_utime};
->{_utime};
} }
} }
$self->logger->debug("BruteForceProtection enabled"); $self->logger->debug("BruteForceProtection enabled");
@ -53,7 +51,7 @@ sub run {
$MaxAge = $lastFailedLoginEpoch[0] - $lastFailedLoginEpoch[2]; $MaxAge = $lastFailedLoginEpoch[0] - $lastFailedLoginEpoch[2];
$self->logger->debug(" -> MaxAge = $MaxAge"); $self->logger->debug(" -> MaxAge = $MaxAge");
return PE_OK return PE_OK
if ( $MaxAge > $self->conf->{bruteForceProtectionMaxAge} ); if ( $MaxAge > $self->conf->{bruteForceProtectionMaxAge} );
# Delta between the two last failed logins -> Auth_N - Auth_N-1 # Delta between the two last failed logins -> Auth_N - Auth_N-1
my $delta = time - $lastFailedLoginEpoch[1]; my $delta = time - $lastFailedLoginEpoch[1];
@ -61,7 +59,7 @@ sub run {
# Delta between the two last failed logins < 30s => wait # Delta between the two last failed logins < 30s => wait
return PE_OK return PE_OK
unless ( $delta <= $self->conf->{bruteForceProtectionTempo} ); unless ( $delta <= $self->conf->{bruteForceProtectionTempo} );
# Account locked # Account locked
#shift @{ $req->sessionInfo->{_loginHistory}->{failedLogin} }; #shift @{ $req->sessionInfo->{_loginHistory}->{failedLogin} };

View File

@ -12,7 +12,7 @@ extends 'Lemonldap::NG::Portal::Main::Plugin';
use constant forAuthUser => 'run'; use constant forAuthUser => 'run';
sub init {1} sub init { 1 }
# RUNNING METHOD # RUNNING METHOD
@ -25,8 +25,8 @@ sub run {
$self->logger->debug( "Delta with last Authn -> " . $delta ); $self->logger->debug( "Delta with last Authn -> " . $delta );
$delta <= $self->conf->{portalForceAuthnInterval} $delta <= $self->conf->{portalForceAuthnInterval}
? return PE_OK ? return PE_OK
: return PE_MUSTAUTHN; : return PE_MUSTAUTHN;
} }
} }

View File

@ -3,8 +3,8 @@ package Lemonldap::NG::Portal::Plugins::GrantSession;
use strict; use strict;
use Mouse; use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw( use Lemonldap::NG::Portal::Main::Constants qw(
PE_OK PE_OK
PE_SESSIONNOTGRANTED PE_SESSIONNOTGRANTED
); );
our $VERSION = '2.0.0'; our $VERSION = '2.0.0';
@ -22,11 +22,11 @@ sub init {
$self->logger->debug("GrantRule key -> $_"); $self->logger->debug("GrantRule key -> $_");
$self->logger->debug( $self->logger->debug(
"GrantRule value -> " . $self->conf->{grantSessionRules}->{$_} ); "GrantRule value -> " . $self->conf->{grantSessionRules}->{$_} );
my $rule = $hd->buildSub( my $rule =
$hd->buildSub(
$hd->substitute( $self->conf->{grantSessionRules}->{$_} ) ); $hd->substitute( $self->conf->{grantSessionRules}->{$_} ) );
unless ($rule) { unless ($rule) {
$self->error( $self->error( "Bad grantSession rule " . $hd->tsv->{jail}->error );
"Bad grantSession rule " . $hd->tsv->{jail}->error );
return 0; return 0;
} }
$self->rules->{$_} = $rule; $self->rules->{$_} = $rule;
@ -45,7 +45,7 @@ sub run {
foreach ( sort sortByComment keys %{ $self->rules } ) { foreach ( sort sortByComment keys %{ $self->rules } ) {
$self->logger->debug( "Grant session condition -> " $self->logger->debug( "Grant session condition -> "
. $self->conf->{grantSessionRules}->{$_} ); . $self->conf->{grantSessionRules}->{$_} );
unless ( $self->rules->{$_}->( $req, $req->sessionInfo ) ) { unless ( $self->rules->{$_}->( $req, $req->sessionInfo ) ) {
$req->userData( {} ); $req->userData( {} );
@ -54,7 +54,7 @@ sub run {
if ($1) { if ($1) {
$self->logger->debug("Message -> $1"); $self->logger->debug("Message -> $1");
# Message can contain session data as user attributes or macros # Message can contain session data as user attributes or macros
my $hd = $self->p->HANDLER; my $hd = $self->p->HANDLER;
my $msg = $hd->substitute($1); my $msg = $hd->substitute($1);
unless ( $msg = $hd->buildSub($msg) ) { unless ( $msg = $hd->buildSub($msg) ) {
@ -68,17 +68,17 @@ sub run {
) )
); );
$self->userLogger->error( 'User ' $self->userLogger->error( 'User '
. $req->sessionInfo->{uid} . $req->sessionInfo->{uid}
. " was not granted to open session (rule -> $msg)" ); . " was not granted to open session (rule -> $msg)" );
$req->urldc( $self->conf->{portal} ); $req->urldc( $self->conf->{portal} );
return $req->authResult(PE_SESSIONNOTGRANTED); return $req->authResult(PE_SESSIONNOTGRANTED);
} }
else { else {
$self->userLogger->error( 'User ' $self->userLogger->error( 'User '
. $req->sessionInfo->{uid} . $req->sessionInfo->{uid}
. " was not granted to open session (rule -> " . " was not granted to open session (rule -> "
. $self->conf->{grantSessionRules}->{$_} . $self->conf->{grantSessionRules}->{$_}
. ")" ); . ")" );
$req->urldc( $self->conf->{portal} ); $req->urldc( $self->conf->{portal} );
return $req->authResult(PE_SESSIONNOTGRANTED); return $req->authResult(PE_SESSIONNOTGRANTED);
} }

View File

@ -28,15 +28,15 @@ sub run {
$req->sessionInfo->{_loginHistory}->{successLogin}, $req->sessionInfo->{_loginHistory}->{successLogin},
'lastLogins', 0, 0 ) 'lastLogins', 0, 0 )
: "" : ""
) )
. ("<hr>") . . ("<hr>")
( . (
$req->sessionInfo->{_loginHistory}->{failedLogin} $req->sessionInfo->{_loginHistory}->{failedLogin}
? $self->p->mkSessionArray( ? $self->p->mkSessionArray(
$req->sessionInfo->{_loginHistory}->{failedLogin}, $req->sessionInfo->{_loginHistory}->{failedLogin},
'lastFailedLogins', 0, 1 ) 'lastFailedLogins', 0, 1 )
: "" : ""
) )
); );
unless ( $req->info ) { unless ( $req->info ) {
$req->info( $self->loadTemplate('noHistory') ); $req->info( $self->loadTemplate('noHistory') );

View File

@ -32,7 +32,7 @@ sub createUser {
givenName => $gn, givenName => $gn,
unicodePwd => utf8( unicodePwd => utf8(
chr(34) . $req->data->{registerInfo}->{password} . chr(34) chr(34) . $req->data->{registerInfo}->{password} . chr(34)
)->utf16le(), )->utf16le(),
mail => $req->data->{registerInfo}->{mail}, mail => $req->data->{registerInfo}->{mail},
] ]
); );

View File

@ -9,7 +9,7 @@ sub new {
} }
my $res = $self->{p}->loadModule( $self->{conf}->{customRegister} ); my $res = $self->{p}->loadModule( $self->{conf}->{customRegister} );
unless($res) { unless ($res) {
die 'Unable to load register module ' . $self->{conf}->{customRegister}; die 'Unable to load register module ' . $self->{conf}->{customRegister};
} }
return $res; return $res;

View File

@ -20,7 +20,7 @@ SKIP: {
); );
$dbh->prepare( $dbh->prepare(
q{INSERT INTO notifications VALUES ('dwho','testref','2016-05-30 00:00:00',?,null,null)} q{INSERT INTO notifications VALUES ('dwho','testref','2016-05-30 00:00:00',?,null,null)}
)->execute( )->execute(
'[ '[
{ {
"uid": "dwho", "uid": "dwho",
@ -32,7 +32,7 @@ q{INSERT INTO notifications VALUES ('dwho','testref','2016-05-30 00:00:00',?,nul
"check": ["Accept test"] "check": ["Accept test"]
} }
]' ]'
); );
my $client = LLNG::Manager::Test->new( my $client = LLNG::Manager::Test->new(
{ {

View File

@ -111,8 +111,8 @@ ok(
), ),
'New auth query' 'New auth query'
); );
expectAuthenticatedAs($res,'dwho'); expectAuthenticatedAs( $res, 'dwho' );
ok($res->[2]->[0] =~ /yourApp/s, 'Menu displayed'); ok( $res->[2]->[0] =~ /yourApp/s, 'Menu displayed' );
count(2); count(2);
clean_sessions(); clean_sessions();

View File

@ -16,7 +16,6 @@ my $mailSend = 0;
my $mail2 = 0; my $mail2 = 0;
SKIP: { SKIP: {
eval eval
'require Email::Sender::Simple;use GD::SecurityImage;use Image::Magick;'; 'require Email::Sender::Simple;use GD::SecurityImage;use Image::Magick;';

View File

@ -10,7 +10,8 @@ BEGIN {
my $res; my $res;
my $client = LLNG::Manager::Test->new( my $client = LLNG::Manager::Test->new(
{ ini => { {
ini => {
logLevel => 'error', logLevel => 'error',
authentication => 'Demo', authentication => 'Demo',
userDB => 'Same', userDB => 'Same',
@ -22,7 +23,8 @@ my $client = LLNG::Manager::Test->new(
); );
## First successful connection ## First successful connection
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho'), IO::String->new('user=dwho&password=dwho'),
length => 23, length => 23,
@ -37,7 +39,8 @@ expectRedirection( $res, 'http://auth.example.com/' );
$client->logout($id1); $client->logout($id1);
## Second successful connection ## Second successful connection
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho'), IO::String->new('user=dwho&password=dwho'),
length => 23, length => 23,
@ -52,7 +55,8 @@ expectRedirection( $res, 'http://auth.example.com/' );
$client->logout($id1); $client->logout($id1);
## First failed connection ## First failed connection
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=ohwd'), IO::String->new('user=dwho&password=ohwd'),
length => 23 length => 23
@ -63,7 +67,8 @@ count(1);
expectReject($res); expectReject($res);
## Second failed connection ## Second failed connection
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=ohwd'), IO::String->new('user=dwho&password=ohwd'),
length => 23 length => 23
@ -74,7 +79,8 @@ count(1);
expectReject($res); expectReject($res);
## Third failed connection -> rejected ## Third failed connection -> rejected
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=ohwd'), IO::String->new('user=dwho&password=ohwd'),
length => 23, length => 23,
@ -89,7 +95,8 @@ count(1);
sleep 1; sleep 1;
## Fourth failed connection -> Rejected ## Fourth failed connection -> Rejected
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=ohwd'), IO::String->new('user=dwho&password=ohwd'),
length => 23, length => 23,
@ -104,7 +111,8 @@ count(1);
sleep 2; sleep 2;
## Third successful connection -> Rejected ## Third successful connection -> Rejected
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho'), IO::String->new('user=dwho&password=dwho'),
length => 23, length => 23,
@ -119,7 +127,8 @@ count(1);
sleep 3; sleep 3;
## Fourth successful connection -> Accepted ## Fourth successful connection -> Accepted
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho&checkLogins=1'), IO::String->new('user=dwho&password=dwho&checkLogins=1'),
length => 37, length => 37,
@ -131,7 +140,7 @@ count(1);
$id1 = expectCookie($res); $id1 = expectCookie($res);
ok( $res->[2]->[0] =~ /trspan="lastLogins"/, 'History found' ) ok( $res->[2]->[0] =~ /trspan="lastLogins"/, 'History found' )
or print STDERR Dumper( $res->[2]->[0] ); or print STDERR Dumper( $res->[2]->[0] );
my @c = ( $res->[2]->[0] =~ /<td>127.0.0.1/gs ); my @c = ( $res->[2]->[0] =~ /<td>127.0.0.1/gs );
my @cf = ( $res->[2]->[0] =~ /PE5<\/td>/gs ); my @cf = ( $res->[2]->[0] =~ /PE5<\/td>/gs );

View File

@ -10,7 +10,8 @@ BEGIN {
my $res; my $res;
my $client = LLNG::Manager::Test->new( my $client = LLNG::Manager::Test->new(
{ ini => { {
ini => {
authentication => 'Demo', authentication => 'Demo',
userdb => 'Same', userdb => 'Same',
portalForceAuthn => 1, portalForceAuthn => 1,
@ -19,7 +20,8 @@ my $client = LLNG::Manager::Test->new(
} }
); );
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho'), IO::String->new('user=dwho&password=dwho'),
length => 23 length => 23
@ -33,7 +35,8 @@ count(1);
sleep 3; sleep 3;
ok( $res = $client->_get( ok(
$res = $client->_get(
'/', '/',
cookie => "lemonldap=$id1", cookie => "lemonldap=$id1",
accept => 'text/html', accept => 'text/html',
@ -41,10 +44,11 @@ ok( $res = $client->_get(
'Form ReAuthentification' 'Form ReAuthentification'
); );
ok( $res->[2]->[0] =~ qr%<span trspan="PE87"></span>%, 'Found PE87 code' ) ok( $res->[2]->[0] =~ qr%<span trspan="PE87"></span>%, 'Found PE87 code' )
or print STDERR Dumper( $res->[2]->[0] ); or print STDERR Dumper( $res->[2]->[0] );
count(1); count(1);
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho'), IO::String->new('user=dwho&password=dwho'),
length => 23, length => 23,
@ -56,7 +60,8 @@ expectOK($res);
$id1 = expectCookie($res); $id1 = expectCookie($res);
count(1); count(1);
ok( $res = $client->_get( ok(
$res = $client->_get(
'/', '/',
cookie => "lemonldap=$id1", cookie => "lemonldap=$id1",
accept => 'text/html', accept => 'text/html',
@ -65,7 +70,7 @@ ok( $res = $client->_get(
); );
ok( $res->[2]->[0] =~ qr%<span trspan="yourApps">Your applications</span>%, ok( $res->[2]->[0] =~ qr%<span trspan="yourApps">Your applications</span>%,
'Found applications list' ) 'Found applications list' )
or print STDERR Dumper( $res->[2]->[0] ); or print STDERR Dumper( $res->[2]->[0] );
count(1); count(1);
$client->logout($id1); $client->logout($id1);

View File

@ -10,7 +10,8 @@ BEGIN {
my $res; my $res;
my $client = LLNG::Manager::Test->new( my $client = LLNG::Manager::Test->new(
{ ini => { {
ini => {
logLevel => 'error', logLevel => 'error',
authentication => 'Demo', authentication => 'Demo',
userdb => 'Same', userdb => 'Same',
@ -23,7 +24,8 @@ my $client = LLNG::Manager::Test->new(
} }
); );
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho'), IO::String->new('user=dwho&password=dwho'),
accept => 'text/html', accept => 'text/html',
@ -33,15 +35,16 @@ ok( $res = $client->_post(
); );
count(1); count(1);
ok( $res->[2]->[0] =~ /<h3 trspan="dwho not allowed">dwho not allowed<\/h3>/, ok( $res->[2]->[0] =~ /<h3 trspan="dwho not allowed">dwho not allowed<\/h3>/,
'dwho rejected with custom message and session data' 'dwho rejected with custom message and session data' )
) or print STDERR Dumper( $res->[2]->[0] ); or print STDERR Dumper( $res->[2]->[0] );
count(1); count(1);
ok( $res->[2]->[0] =~ qr%src="/static/common/js/info.(?:min\.)?js"></script>%, ok( $res->[2]->[0] =~ qr%src="/static/common/js/info.(?:min\.)?js"></script>%,
'Found INFO js' 'Found INFO js' )
) or print STDERR Dumper( $res->[2]->[0] ); or print STDERR Dumper( $res->[2]->[0] );
count(1); count(1);
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=rtyler&password=rtyler'), IO::String->new('user=rtyler&password=rtyler'),
length => 27 length => 27
@ -52,7 +55,8 @@ count(1);
expectOK($res); expectOK($res);
expectCookie($res); expectCookie($res);
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=mrsmith&password=mrsmith'), IO::String->new('user=mrsmith&password=mrsmith'),
accept => 'text/html', accept => 'text/html',
@ -61,7 +65,8 @@ ok( $res = $client->_post(
'Auth query' 'Auth query'
); );
count(1); count(1);
ok( $res->[2]->[0] =~ /<span trmsg="4"><\/span><\/div>/, ok(
$res->[2]->[0] =~ /<span trmsg="4"><\/span><\/div>/,
'rtyler rejected with PE_SESSIONNOTGRANTED' 'rtyler rejected with PE_SESSIONNOTGRANTED'
) or print STDERR Dumper( $res->[2]->[0] ); ) or print STDERR Dumper( $res->[2]->[0] );
count(1); count(1);
@ -72,7 +77,8 @@ count(1);
&Lemonldap::NG::Handler::Main::cfgNum( 0, 0 ); &Lemonldap::NG::Handler::Main::cfgNum( 0, 0 );
$client = LLNG::Manager::Test->new( $client = LLNG::Manager::Test->new(
{ ini => { {
ini => {
authentication => 'Demo', authentication => 'Demo',
userdb => 'Same', userdb => 'Same',
grantSessionRules => { '' => '$uid eq "dwho"', } grantSessionRules => { '' => '$uid eq "dwho"', }
@ -80,7 +86,8 @@ $client = LLNG::Manager::Test->new(
} }
); );
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho'), IO::String->new('user=dwho&password=dwho'),
length => 23 length => 23

View File

@ -9,7 +9,8 @@ BEGIN {
my $res; my $res;
my $client = LLNG::Manager::Test->new( my $client = LLNG::Manager::Test->new(
{ ini => { {
ini => {
logLevel => 'error', logLevel => 'error',
authentication => 'Demo', authentication => 'Demo',
userDB => 'Same', userDB => 'Same',
@ -20,7 +21,8 @@ my $client = LLNG::Manager::Test->new(
); );
## First successful connection ## First successful connection
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho&checkLogins=1'), IO::String->new('user=dwho&password=dwho&checkLogins=1'),
length => 37, length => 37,
@ -32,7 +34,7 @@ count(1);
expectOK($res); expectOK($res);
my $id1 = expectCookie($res); my $id1 = expectCookie($res);
ok( $res->[2]->[0] =~ /trspan="lastLogins"/, 'History found' ) ok( $res->[2]->[0] =~ /trspan="lastLogins"/, 'History found' )
or explain( $res->[2]->[0], 'trspan="noHistory"' ); or explain( $res->[2]->[0], 'trspan="noHistory"' );
my @c = ( $res->[2]->[0] =~ /<td>127.0.0.1/gs ); my @c = ( $res->[2]->[0] =~ /<td>127.0.0.1/gs );
# History with 1 successLogin # History with 1 successLogin
@ -47,7 +49,8 @@ expectOK($res);
$client->logout($id1); $client->logout($id1);
## Second successful connection ## Second successful connection
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho&checkLogins=1'), IO::String->new('user=dwho&password=dwho&checkLogins=1'),
length => 37, length => 37,
@ -69,7 +72,8 @@ count(2);
$client->logout($id1); $client->logout($id1);
## First failed connection ## First failed connection
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=ohwd'), IO::String->new('user=dwho&password=ohwd'),
length => 23 length => 23
@ -80,7 +84,8 @@ count(1);
expectReject($res); expectReject($res);
## Second failed connection ## Second failed connection
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=ohwd'), IO::String->new('user=dwho&password=ohwd'),
length => 23 length => 23
@ -91,7 +96,8 @@ count(1);
expectReject($res); expectReject($res);
## Third successful connection ## Third successful connection
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho&checkLogins=1'), IO::String->new('user=dwho&password=dwho&checkLogins=1'),
length => 37, length => 37,

View File

@ -34,13 +34,13 @@ LWP::Protocol::PSGI->register(
my $client = LLNG::Manager::Test->new( my $client = LLNG::Manager::Test->new(
{ {
ini => { ini => {
logLevel => 'error', logLevel => 'error',
rest2fActivation => 1, rest2fActivation => 1,
rest2fInitUrl => 'http://auth.example.com/init', rest2fInitUrl => 'http://auth.example.com/init',
rest2fInitArgs => { name => 'uid' }, rest2fInitArgs => { name => 'uid' },
rest2fVerifyUrl => 'http://auth.example.com/vrfy', rest2fVerifyUrl => 'http://auth.example.com/vrfy',
rest2fVerifyArgs => { code => 'code' }, rest2fVerifyArgs => { code => 'code' },
loginHistoryEnabled => 1, loginHistoryEnabled => 1,
authentication => 'Demo', authentication => 'Demo',
userDB => 'Same', userDB => 'Same',
} }
@ -74,9 +74,8 @@ ok(
); );
my $id = expectCookie($res); my $id = expectCookie($res);
ok( $res->[2]->[0] =~ /trspan="lastLogins"/, 'History found' ) ok( $res->[2]->[0] =~ /trspan="lastLogins"/, 'History found' )
or print STDERR Dumper( $res->[2]->[0] ); or print STDERR Dumper( $res->[2]->[0] );
my @c = ( $res->[2]->[0] =~ /<td>127.0.0.1/gs ); my @c = ( $res->[2]->[0] =~ /<td>127.0.0.1/gs );
ok( @c == 1, 'One entry found' ); ok( @c == 1, 'One entry found' );

View File

@ -9,7 +9,8 @@ use_ok('Lemonldap::NG::Common::FormEncode');
count(1); count(1);
my $client = LLNG::Manager::Test->new( my $client = LLNG::Manager::Test->new(
{ ini => { {
ini => {
logLevel => 'error', logLevel => 'error',
ext2fActivation => 1, ext2fActivation => 1,
ext2FSendCommand => 't/sendOTP.pl -uid $uid', ext2FSendCommand => 't/sendOTP.pl -uid $uid',
@ -26,7 +27,8 @@ my $client = LLNG::Manager::Test->new(
my $res; my $res;
## First failed connection ## First failed connection
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=ohwd'), IO::String->new('user=dwho&password=ohwd'),
length => 23 length => 23
@ -37,7 +39,8 @@ count(1);
expectReject($res); expectReject($res);
## Second failed connection ## Second failed connection
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=ohwd'), IO::String->new('user=dwho&password=ohwd'),
length => 23 length => 23
@ -48,7 +51,8 @@ count(1);
expectReject($res); expectReject($res);
## Third failed connection -> rejected ## Third failed connection -> rejected
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=ohwd'), IO::String->new('user=dwho&password=ohwd'),
length => 23, length => 23,
@ -64,7 +68,8 @@ sleep 2;
# Try to authenticate # Try to authenticate
# ------------------- # -------------------
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho&checkLogins=1'), IO::String->new('user=dwho&password=dwho&checkLogins=1'),
length => 37, length => 37,
@ -74,18 +79,19 @@ ok( $res = $client->_post(
); );
count(1); count(1);
my ( $host, $url, $query ) my ( $host, $url, $query ) =
= expectForm( $res, undef, '/ext2fcheck', 'token', 'code', expectForm( $res, undef, '/ext2fcheck', 'token', 'code', 'checkLogins' );
'checkLogins' );
ok( $res->[2]->[0] ok(
=~ qr%<input name="code" value="" class="form-control" id="extcode" trplaceholder="code">%, $res->[2]->[0] =~
qr%<input name="code" value="" class="form-control" id="extcode" trplaceholder="code">%,
'Found EXTCODE input' 'Found EXTCODE input'
) or print STDERR Dumper( $res->[2]->[0] ); ) or print STDERR Dumper( $res->[2]->[0] );
count(1); count(1);
$query =~ s/code=/code=123456/; $query =~ s/code=/code=123456/;
ok( $res = $client->_post( ok(
$res = $client->_post(
'/ext2fcheck', '/ext2fcheck',
IO::String->new($query), IO::String->new($query),
length => length($query), length => length($query),
@ -102,7 +108,8 @@ sleep 4;
# Try to authenticate again # Try to authenticate again
# ------------------------- # -------------------------
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho&checkLogins=1'), IO::String->new('user=dwho&password=dwho&checkLogins=1'),
length => 37, length => 37,
@ -112,18 +119,19 @@ ok( $res = $client->_post(
); );
count(1); count(1);
( $host, $url, $query ) ( $host, $url, $query ) =
= expectForm( $res, undef, '/ext2fcheck', 'token', 'code', expectForm( $res, undef, '/ext2fcheck', 'token', 'code', 'checkLogins' );
'checkLogins' );
ok( $res->[2]->[0] ok(
=~ qr%<input name="code" value="" class="form-control" id="extcode" trplaceholder="code">%, $res->[2]->[0] =~
qr%<input name="code" value="" class="form-control" id="extcode" trplaceholder="code">%,
'Found EXTCODE input' 'Found EXTCODE input'
) or print STDERR Dumper( $res->[2]->[0] ); ) or print STDERR Dumper( $res->[2]->[0] );
count(1); count(1);
$query =~ s/code=/code=123456/; $query =~ s/code=/code=123456/;
ok( $res = $client->_post( ok(
$res = $client->_post(
'/ext2fcheck', '/ext2fcheck',
IO::String->new($query), IO::String->new($query),
length => length($query), length => length($query),
@ -136,11 +144,11 @@ count(1);
my $id = expectCookie($res); my $id = expectCookie($res);
ok( $res->[2]->[0] =~ /trspan="lastLogins"/, 'History found' ) ok( $res->[2]->[0] =~ /trspan="lastLogins"/, 'History found' )
or print STDERR Dumper( $res->[2]->[0] ); or print STDERR Dumper( $res->[2]->[0] );
count(1); count(1);
my @c = ( $res->[2]->[0] =~ /<td>127.0.0.1/gs ); my @c = ( $res->[2]->[0] =~ /<td>127.0.0.1/gs );
ok( @c == 4, 'Four entries found' ) ok( @c == 4, 'Four entries found' )
or print STDERR Dumper( $res->[2]->[0] ); or print STDERR Dumper( $res->[2]->[0] );
count(1); count(1);
$client->logout($id); $client->logout($id);

View File

@ -9,15 +9,15 @@ use_ok('Lemonldap::NG::Common::FormEncode');
count(1); count(1);
my $client = LLNG::Manager::Test->new( my $client = LLNG::Manager::Test->new(
{ ini => { {
ini => {
logLevel => 'error', logLevel => 'error',
ext2fActivation => 1, ext2fActivation => 1,
ext2FSendCommand => 't/sendOTP.pl -uid $uid', ext2FSendCommand => 't/sendOTP.pl -uid $uid',
ext2FValidateCommand => 't/vrfyOTP.pl -uid $uid -code $code', ext2FValidateCommand => 't/vrfyOTP.pl -uid $uid -code $code',
authentication => 'Demo', authentication => 'Demo',
userDB => 'Same', userDB => 'Same',
grantSessionRules => grantSessionRules => { 'Dwho_notAllowed##Test' => '$uid ne "dwho"' }
{ 'Dwho_notAllowed##Test' => '$uid ne "dwho"' }
} }
} }
); );
@ -26,7 +26,8 @@ my $res;
# Try to authenticate # Try to authenticate
# ------------------- # -------------------
ok( $res = $client->_post( ok(
$res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=dwho'), IO::String->new('user=dwho&password=dwho'),
length => 23, length => 23,
@ -36,17 +37,19 @@ ok( $res = $client->_post(
); );
count(1); count(1);
my ( $host, $url, $query ) my ( $host, $url, $query ) =
= expectForm( $res, undef, '/ext2fcheck', 'token', 'code' ); expectForm( $res, undef, '/ext2fcheck', 'token', 'code' );
ok( $res->[2]->[0] ok(
=~ qr%<input name="code" value="" class="form-control" id="extcode" trplaceholder="code">%, $res->[2]->[0] =~
qr%<input name="code" value="" class="form-control" id="extcode" trplaceholder="code">%,
'Found EXTCODE input' 'Found EXTCODE input'
) or print STDERR Dumper( $res->[2]->[0] ); ) or print STDERR Dumper( $res->[2]->[0] );
count(1); count(1);
$query =~ s/code=/code=123456/; $query =~ s/code=/code=123456/;
ok( $res = $client->_post( ok(
$res = $client->_post(
'/ext2fcheck', '/ext2fcheck',
IO::String->new($query), IO::String->new($query),
length => length($query), length => length($query),
@ -58,12 +61,12 @@ count(1);
ok( $res->[2]->[0] =~ /<h3 trspan="Dwho_notAllowed">Dwho_notAllowed<\/h3>/, ok( $res->[2]->[0] =~ /<h3 trspan="Dwho_notAllowed">Dwho_notAllowed<\/h3>/,
'dwho rejected with custom message' ) 'dwho rejected with custom message' )
or print STDERR Dumper( $res->[2]->[0] ); or print STDERR Dumper( $res->[2]->[0] );
count(1); count(1);
ok( $res->[2]->[0] =~ qr%src="/static/common/js/info.(?:min\.)?js"></script>%, ok( $res->[2]->[0] =~ qr%src="/static/common/js/info.(?:min\.)?js"></script>%,
'Found INFO js' 'Found INFO js' )
) or print STDERR Dumper( $res->[2]->[0] ); or print STDERR Dumper( $res->[2]->[0] );
count(1); count(1);
clean_sessions(); clean_sessions();

View File

@ -2,6 +2,6 @@
use strict; use strict;
use warnings; use warnings;
my ($swt, $user) = @ARGV; my ( $swt, $user ) = @ARGV;
exit !( $swt eq '-uid' && $user eq 'dwho' ); exit !( $swt eq '-uid' && $user eq 'dwho' );

View File

@ -58,6 +58,7 @@ use Data::Dumper;
use LWP::UserAgent; use LWP::UserAgent;
use URI::Escape; use URI::Escape;
use Lemonldap::NG::Common::FormEncode; use Lemonldap::NG::Common::FormEncode;
#use 5.10.0; #use 5.10.0;
no warnings 'redefine'; no warnings 'redefine';
@ -625,7 +626,7 @@ sub _get {
: () : ()
), ),
'REQUEST_METHOD' => $args{method} || 'GET', 'REQUEST_METHOD' => $args{method} || 'GET',
'REQUEST_URI' => $path . ( $args{query} ? "?$args{query}" : '' ), 'REQUEST_URI' => $path . ( $args{query} ? "?$args{query}" : '' ),
( $args{query} ? ( QUERY_STRING => $args{query} ) : () ), ( $args{query} ? ( QUERY_STRING => $args{query} ) : () ),
'SCRIPT_NAME' => '', 'SCRIPT_NAME' => '',
'SERVER_NAME' => 'auth.example.com', 'SERVER_NAME' => 'auth.example.com',
@ -677,10 +678,10 @@ sub _post {
: () : ()
), ),
'REQUEST_METHOD' => $args{method} || 'POST', 'REQUEST_METHOD' => $args{method} || 'POST',
'REQUEST_URI' => $path . ( $args{query} ? "?$args{query}" : '' ), 'REQUEST_URI' => $path . ( $args{query} ? "?$args{query}" : '' ),
'SCRIPT_NAME' => '', 'SCRIPT_NAME' => '',
'SERVER_NAME' => 'auth.example.com', 'SERVER_NAME' => 'auth.example.com',
'SERVER_PORT' => '80', 'SERVER_PORT' => '80',
'SERVER_PROTOCOL' => 'HTTP/1.1', 'SERVER_PROTOCOL' => 'HTTP/1.1',
( $args{custom} ? %{ $args{custom} } : () ), ( $args{custom} ? %{ $args{custom} } : () ),
'psgix.input.buffered' => 0, 'psgix.input.buffered' => 0,

View File

@ -53,7 +53,7 @@ sub mirror {
: () : ()
), ),
'REQUEST_METHOD' => $args{method} || 'GET', 'REQUEST_METHOD' => $args{method} || 'GET',
'REQUEST_URI' => ( $args{path} || '/' ) 'REQUEST_URI' => ( $args{path} || '/' )
. ( $args{query} ? "?$args{query}" : '' ), . ( $args{query} ? "?$args{query}" : '' ),
( $args{query} ? ( QUERY_STRING => $args{query} ) : () ), ( $args{query} ? ( QUERY_STRING => $args{query} ) : () ),
'SCRIPT_NAME' => '', 'SCRIPT_NAME' => '',

View File

@ -2,6 +2,9 @@
use strict; use strict;
use warnings; use warnings;
my ($swt1, $user, $swt2, $code) = @ARGV; my ( $swt1, $user, $swt2, $code ) = @ARGV;
exit !( $swt1 eq '-uid' && $user eq 'dwho' && $swt2 eq '-code' && $code eq '123456' ); exit !($swt1 eq '-uid'
&& $user eq 'dwho'
&& $swt2 eq '-code'
&& $code eq '123456' );