Merge branch 'v2.0'

This commit is contained in:
Christophe Maudoux 2019-09-03 22:59:34 +02:00
commit afd915f64c
46 changed files with 387 additions and 72 deletions

View File

@ -93,6 +93,9 @@ logLevel = warn
[configuration]
; confTimeout: maximum time to get configuration (default 10)
;confTimeout = 5
; GLOBAL CONFIGURATION ACCESS TYPE
; (File, REST, SOAP, RDBI/CDBI, LDAP, YAMLFile)
; Set here the parameters needed to access to LemonLDAP::NG configuration.

View File

@ -403,9 +403,12 @@ sub _launch {
my $res;
eval {
local $SIG{ALRM} = sub { die "TIMEOUT\n" };
alarm ($self->{confTimeout} || 10);
$res = &{ $self->{type} . "::$sub" }( $self, @_ );
eval {
alarm( $self->{confTimeout} || 10 );
$res = &{ $self->{type} . "::$sub" }( $self, @_ );
};
alarm 0;
die $@ if $@;
};
$msg .= $@ if $@;
return $res;

View File

@ -103,6 +103,7 @@ sub defaultValues {
'issuerDBOpenIDRule' => 1,
'issuerDBSAMLPath' => '^/saml/',
'issuerDBSAMLRule' => 1,
'issuersTimeout' => 120,
'jsRedirect' => 0,
'krbAuthnLevel' => 3,
'krbRemoveDomain' => 1,

View File

@ -65,6 +65,7 @@ our $issuerParameters = {
issuerDBOpenID => [qw(issuerDBOpenIDActivation issuerDBOpenIDPath issuerDBOpenIDRule openIdIssuerSecret openIdAttr openIdSPList openIdSreg_fullname openIdSreg_nickname openIdSreg_language openIdSreg_postcode openIdSreg_timezone openIdSreg_country openIdSreg_gender openIdSreg_email openIdSreg_dob)],
issuerDBOpenIDConnect => [qw(issuerDBOpenIDConnectActivation issuerDBOpenIDConnectPath issuerDBOpenIDConnectRule)],
issuerDBSAML => [qw(issuerDBSAMLActivation issuerDBSAMLPath issuerDBSAMLRule)],
issuerOptions => [qw(issuersTimeout)],
};
our $samlServiceParameters = [qw(samlEntityID samlServicePrivateKeySig samlServicePrivateKeySigPwd samlServicePublicKeySig samlServicePrivateKeyEnc samlServicePrivateKeyEncPwd samlServicePublicKeyEnc samlServiceUseCertificateInResponse samlServiceSignatureMethod samlNameIDFormatMapEmail samlNameIDFormatMapX509 samlNameIDFormatMapWindows samlNameIDFormatMapKerberos samlAuthnContextMapPassword samlAuthnContextMapPasswordProtectedTransport samlAuthnContextMapTLSClient samlAuthnContextMapKerberos samlOrganizationDisplayName samlOrganizationName samlOrganizationURL samlSPSSODescriptorAuthnRequestsSigned samlSPSSODescriptorWantAssertionsSigned samlSPSSODescriptorSingleLogoutServiceHTTPRedirect samlSPSSODescriptorSingleLogoutServiceHTTPPost samlSPSSODescriptorSingleLogoutServiceSOAP samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact samlSPSSODescriptorAssertionConsumerServiceHTTPPost samlSPSSODescriptorArtifactResolutionServiceArtifact samlIDPSSODescriptorWantAuthnRequestsSigned samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect samlIDPSSODescriptorSingleSignOnServiceHTTPPost samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect samlIDPSSODescriptorSingleLogoutServiceHTTPPost samlIDPSSODescriptorSingleLogoutServiceSOAP samlIDPSSODescriptorArtifactResolutionServiceArtifact samlAttributeAuthorityDescriptorAttributeServiceSOAP samlIdPResolveCookie samlMetadataForceUTF8 samlStorage samlStorageOptions samlRelayStateTimeout samlUseQueryStringSpecific samlCommonDomainCookieActivation samlCommonDomainCookieDomain samlCommonDomainCookieReader samlCommonDomainCookieWriter samlDiscoveryProtocolActivation samlDiscoveryProtocolURL samlDiscoveryProtocolPolicy samlDiscoveryProtocolIsPassive samlOverrideIDPEntityID)];
our $oidcServiceParameters = [qw(oidcServiceMetaDataIssuer oidcServiceMetaDataAuthorizeURI oidcServiceMetaDataTokenURI oidcServiceMetaDataUserInfoURI oidcServiceMetaDataJWKSURI oidcServiceMetaDataRegistrationURI oidcServiceMetaDataIntrospectionURI oidcServiceMetaDataEndSessionURI oidcServiceMetaDataCheckSessionURI oidcServiceMetaDataFrontChannelURI oidcServiceMetaDataBackChannelURI oidcServiceMetaDataAuthnContext oidcServicePrivateKeySig oidcServicePublicKeySig oidcServiceKeyIdSig oidcServiceAllowDynamicRegistration oidcServiceAllowAuthorizationCodeFlow oidcServiceAllowImplicitFlow oidcServiceAllowHybridFlow oidcStorage oidcStorageOptions)];

View File

@ -78,19 +78,18 @@ sub _getCipher {
sub encrypt {
my ( $self, $data, $low ) = @_;
# pad $data so that its length be multiple of 16 bytes
my $l = bytes::length($data) % 16;
$data .= "\0" x ( 16 - $l ) unless ( $l == 0 );
my $iv =
$low
? bytes::substr( Digest::SHA::sha1( rand() . time . {} ), 0, IV_LENGTH )
: $newIv->();
my $hmac = $hash->($data);
$data = $hash->($data) . $data;
# pad $data so that its length be multiple of 16 bytes
my $l = bytes::length($data) % 16;
$data .= "\0" x ( 16 - $l ) unless ( $l == 0 );
eval {
$data =
encode_base64(
$iv . $self->_getCipher->set_iv($iv)->encrypt( $hmac . $data ),
encode_base64( $iv . $self->_getCipher->set_iv($iv)->encrypt($data),
'' );
};
if ($@) {
@ -126,16 +125,16 @@ sub decrypt {
}
my $hmac = bytes::substr( $data, 0, HMAC_LENGTH );
$data = bytes::substr( $data, HMAC_LENGTH );
# Obscure Perl re bug...
$data .= "\0";
$data =~ s/\0*$//;
if ( $hash->($data) ne $hmac ) {
$msg = "Bad MAC";
return undef;
}
else {
$msg = '';
# Obscure Perl re bug...
$data .= "\0";
$data =~ s/\0*$//;
return $data;
}
}

View File

@ -166,19 +166,23 @@ sub _tie_session {
eval {
local $SIG{ALRM} = sub { die "TIMEOUT\n" };
alarm $self->timeout;
eval {
alarm $self->timeout;
# SOAP/REST session module must be directly tied
if ( $self->storageModule =~ /^Lemonldap::NG::Common::Apache::Session/ )
{
tie %h, $self->storageModule, $self->id,
{ %{ $self->options }, %$options, kind => $self->kind };
}
else {
tie %h, 'Lemonldap::NG::Common::Apache::Session', $self->id,
{ %{ $self->options }, %$options };
}
# SOAP/REST session module must be directly tied
if ( $self->storageModule =~
/^Lemonldap::NG::Common::Apache::Session/ )
{
tie %h, $self->storageModule, $self->id,
{ %{ $self->options }, %$options, kind => $self->kind };
}
else {
tie %h, 'Lemonldap::NG::Common::Apache::Session', $self->id,
{ %{ $self->options }, %$options };
}
};
alarm 0;
die $@ if $@;
};
if ( $@ or not tied(%h) ) {

View File

@ -1385,6 +1385,10 @@ qr/^(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-
'default' => 1,
'type' => 'boolOrExpr'
},
'issuersTimeout' => {
'default' => 120,
'type' => 'int'
},
'jsRedirect' => {
'default' => 0,
'type' => 'boolOrExpr'

View File

@ -676,6 +676,11 @@ sub attributes {
type => 'int',
documentation => 'Token timeout for forms',
},
issuersTimeout => {
default => 120,
type => 'int',
documentation => 'Token timeout for issuers',
},
requireToken => {
default => 1,
type => 'boolOrExpr',

View File

@ -505,6 +505,12 @@ sub tree {
'issuerDBGetParameters'
]
},
{
title => 'issuerOptions',
help => 'start.html#options',
form => 'simpleInputContainer',
nodes => ['issuersTimeout']
},
]
},
{
@ -565,7 +571,8 @@ sub tree {
{
title => 'reloadParams',
help => 'configlocation.html#configuration_reload',
nodes => [ 'reloadUrls', 'reloadTimeout', 'dontCompactConf' ]
nodes =>
[ 'reloadUrls', 'reloadTimeout', 'dontCompactConf' ]
},
{
title => 'plugins',

View File

@ -594,7 +594,7 @@ sub tests {
return 1;
},
# Warn if XSRF token TTL is higher than 10s
# Warn if XSRF token TTL is higher than 30s
formTimeout => sub {
return 1 unless ( defined $conf->{formTimeout} );
return ( 0, "XSRF form token TTL must be higher than 30s" )
@ -606,6 +606,18 @@ sub tests {
return 1;
},
# Warn if issuers token TTL is higher than 30s
issuersTimeout => sub {
return 1 unless ( defined $conf->{issuerTimeout} );
return ( 0, "Issuers token TTL must be higher than 30s" )
unless ( $conf->{issuerTimeout} > 30 );
return ( 1, "Issuers token TTL should not be higher than 2mn" )
if ( $conf->{issuerTimeout} > 120 );
# Return
return 1;
},
# Warn if number of password reset retries is null
passwordResetRetries => sub {
return 1 unless ( $conf->{portalDisplayResetPassword} );

View File

@ -348,7 +348,9 @@
"issuerDBOpenIDConnectActivation":"تفعيل",
"issuerDBOpenIDConnectPath":"مسار",
"issuerDBOpenIDConnectRule":"استخدام القاعدة",
"issuerOptions":"Options",
"issuerParams":"وحدات المصدر",
"issuersTimeout":"Issuers timeout",
"jsRedirect":"إعادة توجيه الرسالة",
"jqueryButtonSelector":"زر التحديد ل جي كويري (اختياري)",
"jqueryFormSelector":"تحديد الاستمارة ل جي كويري (اختياري)",

View File

@ -347,7 +347,9 @@
"issuerDBOpenIDConnectActivation":"Activation",
"issuerDBOpenIDConnectPath":"Path",
"issuerDBOpenIDConnectRule":"Use rule",
"issuerOptions":"Options",
"issuerParams":"Issuer modules",
"issuersTimeout":"Issuers timeout",
"jsRedirect":"Redirection message",
"jqueryButtonSelector":"jQuery button selector (optional)",
"jqueryFormSelector":"jQuery form selector (optional)",

View File

@ -347,7 +347,9 @@
"issuerDBOpenIDConnectActivation":"Activation",
"issuerDBOpenIDConnectPath":"Path",
"issuerDBOpenIDConnectRule":"Use rule",
"issuerOptions":"Options",
"issuerParams":"Issuer modules",
"issuersTimeout":"Issuers timeout",
"jsRedirect":"Redirection message",
"jqueryButtonSelector":"jQuery button selector (optional)",
"jqueryFormSelector":"jQuery form selector (optional)",

View File

@ -347,7 +347,9 @@
"issuerDBOpenIDConnectActivation":"Activation",
"issuerDBOpenIDConnectPath":"Chemin",
"issuerDBOpenIDConnectRule":"Règle d'utilisation",
"issuerOptions":"Options",
"issuerParams":"Modules fournisseur",
"issuersTimeout":"Délai de validation pour les fournisseurs",
"jsRedirect":"Message de redirection",
"jqueryButtonSelector":"Sélecteur jQuery du bouton (optionnel)",
"jqueryFormSelector":"Sélecteur jQuery du formulaire (optionnel)",

View File

@ -347,7 +347,9 @@
"issuerDBOpenIDConnectActivation":"Attivazione",
"issuerDBOpenIDConnectPath":"Path",
"issuerDBOpenIDConnectRule":"Utilizza la regola",
"issuerOptions":"Options",
"issuerParams":"Moduli emittenti",
"issuersTimeout":"Issuers timeout",
"jsRedirect":"Messaggio di reindirizzamento",
"jqueryButtonSelector":"Selettore del pulsante jQuery (opzionale)",
"jqueryFormSelector":"Selettore modulo jQuery (opzionale)",

View File

@ -347,7 +347,9 @@
"issuerDBOpenIDConnectActivation":"Kích hoạt",
"issuerDBOpenIDConnectPath":"Đường dẫn",
"issuerDBOpenIDConnectRule":"Quy tắc sử dụng",
"issuerOptions":"Options",
"issuerParams":"Mô-đun của nhà phát hành",
"issuersTimeout":"Issuers timeout",
"jsRedirect":"Thông báo chuyển hướng",
"jqueryButtonSelector":"nút chọn jQuery (tùy chọn)",
"jqueryFormSelector":"trình đơn chọn jQuery (tùy chọn)",

View File

@ -347,7 +347,9 @@
"issuerDBOpenIDConnectActivation":"激活",
"issuerDBOpenIDConnectPath":"Path",
"issuerDBOpenIDConnectRule":"Use rule",
"issuerOptions":"Options",
"issuerParams":"Issuer modules",
"issuersTimeout":"Issuers timeout",
"jsRedirect":"Redirection message",
"jqueryButtonSelector":"jQuery 按钮选择器(可选)",
"jqueryFormSelector":"jQuery form selector (optional)",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -79,11 +79,14 @@ sub run {
$req,
'ext2fcheck',
params => {
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
TOKEN => $token,
PREFIX => $self->prefix,
TARGET => '/' . $self->prefix . '2fcheck',
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
TOKEN => $token,
PREFIX => $self->prefix,
TARGET => '/'
. $self->prefix
. '2fcheck?skin='
. $self->p->getSkin($req),
CHECKLOGINS => $checkLogins
}
);

View File

@ -109,11 +109,14 @@ sub run {
$req,
'ext2fcheck',
params => {
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
TOKEN => $token,
PREFIX => $self->prefix,
TARGET => '/' . $self->prefix . '2fcheck',
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
TOKEN => $token,
PREFIX => $self->prefix,
TARGET => '/'
. $self->prefix
. '2fcheck?skin='
. $self->p->getSkin($req),
LEGEND => 'enterMail2fCode',
CHECKLOGINS => $checkLogins
}

View File

@ -90,11 +90,14 @@ sub run {
$req,
'ext2fcheck',
params => {
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
TOKEN => $token,
PREFIX => $self->prefix,
TARGET => '/' . $self->prefix . '2fcheck',
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
TOKEN => $token,
PREFIX => $self->prefix,
TARGET => '/'
. $self->prefix
. '2fcheck?skin='
. $self->p->getSkin($req),
LEGEND => 'enterRest2fCode',
CHECKLOGINS => $checkLogins
}

View File

@ -64,11 +64,14 @@ sub run {
$req,
'ext2fcheck',
params => {
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
TOKEN => $token,
PREFIX => $self->prefix,
TARGET => '/' . $self->prefix . '2fcheck',
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
TOKEN => $token,
PREFIX => $self->prefix,
TARGET => '/'
. $self->prefix
. '2fcheck?skin='
. $self->p->getSkin($req),
LEGEND => 'enterRadius2fCode',
CHECKLOGINS => $checkLogins
}

View File

@ -108,7 +108,7 @@ sub run {
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
TOKEN => $token,
TARGET => '/yubikey2fcheck',
TARGET => '/yubikey2fcheck?skin=' . $self->p->getSkin($req),
INPUTLOGO => 'yubikey.png',
LEGEND => 'clickOnYubikey',
CHECKLOGINS => $checkLogins

View File

@ -255,6 +255,11 @@ sub loadIDPs {
}
# Add this IDP to Lasso::Server
# TODO: when Lasso issue #35061 is fixed in all distros,
# we could load the metadata into a new LassoProvider, extract the
# parsed-and-decoded entityID from this Provider, and then add the
# Provider into the server with $server->add_provider2, instead of
# decoding HTML entities ourselves below
my $result = $self->addIDP( $self->lassoServer, $idp_metadata );
unless ($result) {
@ -267,6 +272,7 @@ sub loadIDPs {
( $idp_metadata =~ /entityID=(['"])(.+?)\1/si );
# Decode HTML entities from entityID
# TODO: see Lasso comment above
decode_entities($entityID);
my $name = $self->getOrganizationName( $self->lassoServer, $entityID )
@ -356,6 +362,11 @@ sub loadSPs {
}
# Add this SP to Lasso::Server
# TODO: when Lasso issue #35061 is fixed in all distros,
# we could load the metadata into a new LassoProvider, extract the
# parsed-and-decoded entityID from this Provider, and then add the
# Provider into the server with $server->add_provider2, instead of
# decoding HTML entities ourselves below
my $result = $self->addSP( $self->lassoServer, $sp_metadata );
unless ($result) {
@ -367,6 +378,7 @@ sub loadSPs {
my ( $tmp, $entityID ) = ( $sp_metadata =~ /entityID=(['"])(.+?)\1/si );
# Decode HTML entities from entityID
# TODO: see Lasso comment above
decode_entities($entityID);
my $name = $self->getOrganizationName( $self->lassoServer, $entityID )

View File

@ -35,7 +35,8 @@ has _ott => (
lazy => 1,
default => sub {
my $ott = $_[0]->{p}->loadModule('::Lib::OneTimeToken');
$ott->timeout( $_[0]->{conf}->{formTimeout} );
my $timeout = $_[0]->{conf}->{issuersTimeout} // $_[0]->{conf}->{formTimeout};
$ott->timeout( $timeout );
return $ott;
}
);

View File

@ -9,6 +9,8 @@ use Lemonldap::NG::Portal::Main::Constants qw(
PE_PASSWORD_OK
PE_PASSWORD_MISMATCH
PE_PP_MUST_SUPPLY_OLD_PASSWORD
PE_PP_PASSWORD_TOO_SHORT
PE_PP_INSUFFICIENT_PASSWORD_QUALITY
);
extends 'Lemonldap::NG::Portal::Main::Plugin';
@ -52,6 +54,45 @@ sub _modifyPassword {
unless ( $self->confirm( $req, $req->data->{oldpassword} ) );
}
# Min size
if ( $self->conf->{passwordPolicyMinSize}
and length( $req->data->{newpassword} ) <
$self->conf->{passwordPolicyMinSize} )
{
$self->logger->error("Password too short");
return PE_PP_PASSWORD_TOO_SHORT;
}
# Min lower
if ( $self->conf->{passwordPolicyMinLower} ) {
my $lower = 0;
$lower++ while ( $req->data->{newpassword} =~ m/\p{lowercase}/g );
if ( $lower < $self->conf->{passwordPolicyMinLower} ) {
$self->logger->error("Password has not enough lower characters");
return PE_PP_INSUFFICIENT_PASSWORD_QUALITY;
}
}
# Min upper
if ( $self->conf->{passwordPolicyMinUpper} ) {
my $upper = 0;
$upper++ while ( $req->data->{newpassword} =~ m/\p{uppercase}/g );
if ( $upper < $self->conf->{passwordPolicyMinUpper} ) {
$self->logger->error("Password has not enough upper characters");
return PE_PP_INSUFFICIENT_PASSWORD_QUALITY;
}
}
# Min digit
if ( $self->conf->{passwordPolicyMinDigit} ) {
my $digit = 0;
$digit++ while ( $req->data->{newpassword} =~ m/\d/g );
if ( $digit < $self->conf->{passwordPolicyMinDigit} ) {
$self->logger->error("Password has not enough digit characters");
return PE_PP_INSUFFICIENT_PASSWORD_QUALITY;
}
}
# Call password package
my $res = $self->modifyPassword( $req, $req->data->{newpassword} );
if ( $res == PE_PASSWORD_OK ) {

View File

@ -0,0 +1 @@
html,body{height:100%;background:radial-gradient(circle at 50% 0,#fff 0,#ddd 100%) no-repeat scroll 0 0 #ddd}#wrap{min-height:100%;height:auto;margin:0 auto -80px;padding:20px 0 80px}#footer{height:80px;background-color:#fff;background-color:rgba(255,255,255,0.9);text-align:center;padding-top:10px;overflow:hidden}#header img{background-color:#fff;background-color:rgba(255,255,255,0.8);margin-bottom:20px}.card,.navbar-light{background-color:#fff;background-color:rgba(255,255,255,0.9);background-image:none}.login,.password{text-align:center;padding:20px}div.form{margin:0 auto;max-width:330px}div.actions{margin:10px 0 0 0}div.actions a{margin-top:10px}.buttons{text-align:center;margin:10px 0 0 0;cursor:pointer}.btn{white-space:normal}.btn span.fa{padding-right:8px}li.ui-state-active{background-color:#fafafa;background-color:rgba(250,250,250,0.9)}#appslist,#password,#loginHistory,#logout,#oidcConsents{margin-top:20px}div.category{margin:10px 0;cursor:grab}div.application{margin:5px 0;overflow:hidden}div.application a,div.application a:hover{text-decoration:none}p.notifCheck label{margin-left:5px;margin-top:3px;display:inline-block}img.langicon{cursor:pointer}button.idploop{max-width:300px}button.idploop img{max-height:30px}div.oidc_consent_message>ul{text-align:left;list-style:circle}@media(min-width:768px){div.application{height:80px}div.application h4.appname{margin:0}#wrap{margin:0 auto -60px}#footer{height:60px}}.hiddenFrame{border:0;display:hidden;margin:0}.noborder{border:0}.max{width:100%}.link{cursor:pointer}.nodecor:hover,.nodecor:active.nodecor:focus{text-decoration:none}.fa.icon-blue{color:blue}.progress-bar-animated{width:100%}

View File

@ -23,7 +23,7 @@
</div>
</div>
<div class="buttons mt-3">
<a href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1" class="btn btn-primary" role="button">
<a href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1&skin=<TMPL_VAR NAME="SKIN">" class="btn btn-primary" role="button">
<span class="fa fa-home"></span>
<span trspan="cancel">Cancel</span>
</a>

View File

@ -64,7 +64,7 @@
</div>
<div class="buttons">
<a href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1" class="btn btn-primary" role="button">
<a href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1&skin=<TMPL_VAR NAME="SKIN">" class="btn btn-primary" role="button">
<span class="fa fa-home"></span>
<span trspan="goToPortal">Go to portal</span>
</a>

View File

@ -47,12 +47,12 @@
<div class="buttons">
<TMPL_IF RAW_ERROR>
<a href="<TMPL_VAR NAME="PORTAL_URL">2fregisters" class="btn btn-info" role="button">
<a href="<TMPL_VAR NAME="PORTAL_URL">2fregisters?skin=<TMPL_VAR NAME="SKIN">" class="btn btn-info" role="button">
<span class="fa fa-shield"></span>
<span trspan="sfaManager">sfaManager</span>
</a>
</TMPL_IF>
<a href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1<TMPL_IF NAME="AUTH_URL">&url=<TMPL_VAR NAME="AUTH_URL"></TMPL_IF>" class="btn btn-primary" role="button">
<a href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1&skin=<TMPL_VAR NAME="SKIN"><TMPL_IF NAME="AUTH_URL">&url=<TMPL_VAR NAME="AUTH_URL"></TMPL_IF>" class="btn btn-primary" role="button">
<span class="fa fa-home"></span>
<span trspan="goToPortal">Go to portal</span>
</a>

View File

@ -10,6 +10,7 @@
<div class="form">
<input type="hidden" id="token" name="token" value="<TMPL_VAR NAME="TOKEN">" />
<input type="hidden" id="checkLogins" name="checkLogins" value="<TMPL_VAR NAME="CHECKLOGINS">" />
<input type="hidden" name="skin" value="<TMPL_VAR NAME="SKIN">" />
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-lock"></i> </span>
@ -24,7 +25,7 @@
</button>
</div>
<div class="buttons">
<a href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1" class="btn btn-primary" role="button">
<a href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1&skin=<TMPL_VAR NAME="SKIN">" class="btn btn-primary" role="button">
<span class="fa fa-home"></span>
<span trspan="cancel">Cancel</span>
</a>

View File

@ -10,6 +10,7 @@
<div class="form">
<input type="hidden" id="token" name="token" value="<TMPL_VAR NAME="TOKEN">" />
<input type="hidden" id="checkLogins" name="checkLogins" value="<TMPL_VAR NAME="CHECKLOGINS">" />
<input type="hidden" name="skin" value="<TMPL_VAR NAME="SKIN">" />
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-lock"></i> </span>
@ -24,7 +25,7 @@
</button>
</div>
<div class="buttons">
<a href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1" class="btn btn-primary" role="button">
<a href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1&skin=<TMPL_VAR NAME="SKIN">" class="btn btn-primary" role="button">
<span class="fa fa-home"></span>
<span trspan="cancel">Cancel</span>
</a>

View File

@ -43,11 +43,11 @@
</main>
<div class="buttons">
<a href="<TMPL_VAR NAME="PORTAL_URL">2fregisters" class="btn btn-info" role="button">
<a href="<TMPL_VAR NAME="PORTAL_URL">2fregisters?skin=<TMPL_VAR NAME="SKIN">" class="btn btn-info" role="button">
<span class="fa fa-shield"></span>
<span trspan="sfaManager">sfaManager</span>
</a>
<a id="goback" href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1<TMPL_IF NAME="AUTH_URL">&url=<TMPL_VAR NAME="AUTH_URL"></TMPL_IF>" class="btn btn-primary" role="button">
<a id="goback" href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1&skin=<TMPL_VAR NAME="SKIN"><TMPL_IF NAME="AUTH_URL">&url=<TMPL_VAR NAME="AUTH_URL"></TMPL_IF>" class="btn btn-primary" role="button">
<span class="fa fa-home"></span>
<span trspan="goToPortal">Go to portal</span>
</a>

View File

@ -12,6 +12,7 @@
<input type="hidden" id="verify-challenge" name="challenge" value="">
<input type="hidden" id="token" name="token" value="<TMPL_VAR NAME="TOKEN">">
<input type="hidden" id="checkLogins" name="checkLogins" value="<TMPL_VAR NAME="CHECKLOGINS">">
<input type="hidden" name="skin" value="<TMPL_VAR NAME="SKIN">" />
</form>
<script type="application/init">
<TMPL_VAR NAME="DATA">
@ -28,7 +29,7 @@
</div>
<div class="buttons">
<a href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1" class="btn btn-primary" role="button">
<a href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1&skin=<TMPL_VAR NAME="SKIN">" class="btn btn-primary" role="button">
<span class="fa fa-home"></span>
<span trspan="cancel">Cancel</span>
</a>

View File

@ -37,12 +37,12 @@
</main>
<div class="buttons">
<a href="<TMPL_VAR NAME="PORTAL_URL">2fregisters" class="btn btn-info" role="button">
<a href="<TMPL_VAR NAME="PORTAL_URL">2fregisters?skin=<TMPL_VAR NAME="SKIN">" class="btn btn-info" role="button">
<span class="fa fa-shield"></span>
<span trspan="sfaManager">sfaManager</span>
</a>
<a id="goback" href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1<TMPL_IF NAME="AUTH_URL">&url=<TMPL_VAR NAME="AUTH_URL"></TMPL_IF>" class="btn btn-primary" role="button">
<a id="goback" href="<TMPL_VAR NAME="PORTAL_URL">?cancel=1&skin=<TMPL_VAR NAME="SKIN"><TMPL_IF NAME="AUTH_URL">&url=<TMPL_VAR NAME="AUTH_URL"></TMPL_IF>" class="btn btn-primary" role="button">
<span class="fa fa-home"></span>
<span trspan="goToPortal">Go to portal</span>
</a>

View File

@ -0,0 +1,192 @@
use Test::More;
use strict;
use IO::String;
use JSON;
use Lemonldap::NG::Portal::Main::Constants
qw(PE_PP_PASSWORD_TOO_SHORT PE_PP_INSUFFICIENT_PASSWORD_QUALITY);
require 't/test-lib.pm';
my $res;
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
passwordDB => 'Demo',
portalRequireOldPassword => 1,
passwordPolicyMinSize => 6,
passwordPolicyMinLower => 3,
passwordPolicyMinUpper => 3,
passwordPolicyMinDigit => 1,
}
}
);
# Try to authenticate
# -------------------
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23
),
'Auth query'
);
count(1);
expectOK($res);
my $id = expectCookie($res);
# Test min size
# -------------
ok(
$res = $client->_post(
'/',
IO::String->new(
'oldpassword=dwho&newpassword=test&confirmpassword=test'),
cookie => "lemonldap=$id",
accept => 'application/json',
length => 54
),
'Password min size not respected'
);
expectBadRequest($res);
my $json;
ok( $json = eval { from_json( $res->[2]->[0] ) }, 'Response is JSON' )
or print STDERR "$@\n" . Dumper($res);
ok(
$json->{error} == PE_PP_PASSWORD_TOO_SHORT,
'Response is PE_PP_PASSWORD_TOO_SHORT'
) or explain( $json, "error => 29" );
count(3);
ok(
$res = $client->_post(
'/',
IO::String->new(
'oldpassword=dwho&newpassword=TESTis0k&confirmpassword=TESTis0k'),
cookie => "lemonldap=$id",
accept => 'application/json',
length => 62
),
'Password min size respected'
);
expectOK($res);
count(1);
# Test min lower
# --------------
ok(
$res = $client->_post(
'/',
IO::String->new(
'oldpassword=dwho&newpassword=TESTLOWer&confirmpassword=TESTLOWer'),
cookie => "lemonldap=$id",
accept => 'application/json',
length => 64
),
'Password min lower not respected'
);
expectBadRequest($res);
my $json;
ok( $json = eval { from_json( $res->[2]->[0] ) }, 'Response is JSON' )
or print STDERR "$@\n" . Dumper($res);
ok(
$json->{error} == PE_PP_INSUFFICIENT_PASSWORD_QUALITY,
'Response is PE_PP_INSUFFICIENT_PASSWORD_QUALITY'
) or explain( $json, "error => 28" );
count(3);
ok(
$res = $client->_post(
'/',
IO::String->new(
'oldpassword=dwho&newpassword=TESTl0wer&confirmpassword=TESTl0wer'),
cookie => "lemonldap=$id",
accept => 'application/json',
length => 64
),
'Password min lower respected'
);
expectOK($res);
count(1);
# Test min upper
# --------------
ok(
$res = $client->_post(
'/',
IO::String->new(
'oldpassword=dwho&newpassword=testUPper&confirmpassword=testUPper'),
cookie => "lemonldap=$id",
accept => 'application/json',
length => 64
),
'Password min upper not respected'
);
expectBadRequest($res);
my $json;
ok( $json = eval { from_json( $res->[2]->[0] ) }, 'Response is JSON' )
or print STDERR "$@\n" . Dumper($res);
ok(
$json->{error} == PE_PP_INSUFFICIENT_PASSWORD_QUALITY,
'Response is PE_PP_INSUFFICIENT_PASSWORD_QUALITY'
) or explain( $json, "error => 28" );
count(3);
ok(
$res = $client->_post(
'/',
IO::String->new(
'oldpassword=dwho&newpassword=t3stUPPER&confirmpassword=t3stUPPER'),
cookie => "lemonldap=$id",
accept => 'application/json',
length => 64
),
'Password min upper respected'
);
expectOK($res);
count(1);
# Test min digit
# --------------
ok(
$res = $client->_post(
'/',
IO::String->new(
'oldpassword=dwho&newpassword=testDIGIT&confirmpassword=testDIGIT'),
cookie => "lemonldap=$id",
accept => 'application/json',
length => 64
),
'Password min digit not respected'
);
expectBadRequest($res);
my $json;
ok( $json = eval { from_json( $res->[2]->[0] ) }, 'Response is JSON' )
or print STDERR "$@\n" . Dumper($res);
ok(
$json->{error} == PE_PP_INSUFFICIENT_PASSWORD_QUALITY,
'Response is PE_PP_INSUFFICIENT_PASSWORD_QUALITY'
) or explain( $json, "error => 28" );
count(3);
ok(
$res = $client->_post(
'/',
IO::String->new(
'oldpassword=dwho&newpassword=t3stDIGIT&confirmpassword=t3stDIGIT'),
cookie => "lemonldap=$id",
accept => 'application/json',
length => 64
),
'Password min digit respected'
);
expectOK($res);
count(1);
# Test $client->logout
$client->logout($id);
clean_sessions();
done_testing( count() );

View File

@ -65,7 +65,7 @@ ok( $res->[2]->[0] =~ qr%<img src="/static/common/logos/logo_llng_old.png"%,
count(1);
my ( $host, $url, $query ) =
expectForm( $res, undef, '/rest2fcheck', 'token', 'code', 'checkLogins' );
expectForm( $res, undef, '/rest2fcheck?skin=bootstrap', 'token', 'code', 'checkLogins' );
$query =~ s/code=/code=1234/;
ok(

View File

@ -93,7 +93,7 @@ ok(
count(1);
my ( $host, $url, $query ) =
expectForm( $res, undef, '/ext2fcheck', 'token', 'code', 'checkLogins' );
expectForm( $res, undef, '/ext2fcheck?skin=bootstrap', 'token', 'code', 'checkLogins' );
ok(
$res->[2]->[0] =~
@ -134,7 +134,7 @@ ok(
count(1);
( $host, $url, $query ) =
expectForm( $res, undef, '/ext2fcheck', 'token', 'code', 'checkLogins' );
expectForm( $res, undef, '/ext2fcheck?skin=bootstrap', 'token', 'code', 'checkLogins' );
ok(
$res->[2]->[0] =~

View File

@ -36,7 +36,7 @@ ok(
count(1);
my ( $host, $url, $query ) =
expectForm( $res, undef, '/ext2fcheck', 'token', 'code' );
expectForm( $res, undef, '/ext2fcheck?skin=bootstrap', 'token', 'code' );
ok(
$res->[2]->[0] =~

View File

@ -38,7 +38,7 @@ ok(
count(1);
my ( $host, $url, $query ) =
expectForm( $res, undef, '/ext2fcheck', 'token', 'code' );
expectForm( $res, undef, '/ext2fcheck?skin=bootstrap', 'token', 'code' );
ok(
$res->[2]->[0] =~

View File

@ -77,7 +77,7 @@ ok(
);
( $host, $url, $query ) =
expectForm( $res, undef, '/ext2fcheck', 'token', 'code', 'checkLogins' );
expectForm( $res, undef, '/ext2fcheck?skin=bootstrap', 'token', 'code', 'checkLogins' );
ok(
$res->[2]->[0] =~

View File

@ -52,7 +52,7 @@ count(1);
# Only "work" option is available
my ( $host, $url, $query ) =
expectForm( $res, undef, '/work2fcheck', 'token', 'code' );
expectForm( $res, undef, '/work2fcheck?skin=bootstrap', 'token', 'code' );
ok(
$res->[2]->[0] =~
@ -137,7 +137,7 @@ ok(
count(1);
my ( $host, $url, $query ) =
expectForm( $res, undef, '/home2fcheck', 'token', 'code' );
expectForm( $res, undef, '/home2fcheck?skin=bootstrap', 'token', 'code' );
ok(
$res->[2]->[0] =~

View File

@ -35,7 +35,7 @@ ok(
count(1);
my ( $host, $url, $query ) =
expectForm( $res, undef, '/mail2fcheck', 'token', 'code' );
expectForm( $res, undef, '/mail2fcheck?skin=bootstrap', 'token', 'code' );
ok(
$res->[2]->[0] =~

View File

@ -34,7 +34,7 @@ ok(
count(1);
my ( $host, $url, $query ) =
expectForm( $res, undef, '/mail2fcheck', 'token', 'code' );
expectForm( $res, undef, '/mail2fcheck?skin=bootstrap', 'token', 'code' );
ok(
$res->[2]->[0] =~

View File

@ -105,7 +105,7 @@ count(1);
$pdata = expectCookie( $res, 'lemonldappdata' );
( $host, $url, $query ) =
expectForm( $res, undef, '/mail2fcheck', 'token', 'code' );
expectForm( $res, undef, '/mail2fcheck?skin=bootstrap', 'token', 'code' );
ok(
$res->[2]->[0] =~