Merge branch 'v2.0'

This commit is contained in:
Xavier 2019-06-15 09:23:50 +02:00
commit d27e4bcc55
64 changed files with 1165 additions and 166 deletions

View File

@ -1,4 +1,4 @@
#
# Regular cron jobs for LemonLDAP::NG Handler
#
17-59/30 * * * * www-data [ -x /usr/share/lemonldap-ng/bin/purgeLocalCache ] && /usr/share/lemonldap-ng/bin/purgeLocalCache
1 * * * * www-data [ -x /usr/share/lemonldap-ng/bin/purgeLocalCache ] && /usr/share/lemonldap-ng/bin/purgeLocalCache

View File

@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "llng-fastcgi-server 1"
.TH llng-fastcgi-server 1 "2019-06-06" "perl v5.28.1" "User Contributed Perl Documentation"
.TH llng-fastcgi-server 1 "2019-06-13" "perl v5.28.1" "User Contributed Perl Documentation"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l

View File

@ -24,7 +24,7 @@ use constant MANAGERSECTION => "manager";
use constant SESSIONSEXPLORERSECTION => "sessionsExplorer";
use constant APPLYSECTION => "apply";
our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|macro)s|o(?:idc(?:RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node)|S(?:erviceMetaDataAuthnContext|torageOptions))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars)|c(?:as(?:S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions)|A(?:ppMetaData(?:(?:ExportedVar|Option)s|Node)|ttributes))|(?:ustomAddParam|ombModule)s)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/;
our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|ingle(?:Session(?:UserByIP)?|(?:UserBy)?IP)|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|kipRenewConfirmation|howLanguages|slByAjax)|o(?:idc(?:ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|RPMetaDataOptions(?:LogoutSessionRequired|BypassConsent|RequirePKCE|Public)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:ErrorOn(?:ExpiredSession|MailNotFound)|DisplayRe(?:setPassword|gister)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|RequireOldPassword|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl)|oginHistoryEnabled)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:User(?:Display(?:PersistentInfo|EmptyValues))?|State|XSS)|orsEnabled|da)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|no(?:tif(?:ication(?:Server)?|y(?:Deleted|Other))|AjaxHook)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|(?:(?:rest(?:Session|Config)|wsdl)Serv|activeTim)er|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|krb(?:RemoveDomain|ByJs)|dbiDynamicHashEnabled|bruteForceProtection)$/;
our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|ingle(?:Session(?:UserByIP)?|(?:UserBy)?IP)|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|kipRenewConfirmation|howLanguages|slByAjax)|o(?:idc(?:ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|RPMetaDataOptions(?:LogoutSessionRequired|BypassConsent|RequirePKCE|Public)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:ErrorOn(?:ExpiredSession|MailNotFound)|DisplayRe(?:setPassword|gister)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|RequireOldPassword|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl)|oginHistoryEnabled)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:User(?:Display(?:PersistentInfo|EmptyValues))?|State|XSS)|orsEnabled|da)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|no(?:tif(?:ication(?:Server)?|y(?:Deleted|Other))|AjaxHook)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|rest(?:(?:Session|Config)Server|ExportSecretKeys)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs)|dbiDynamicHashEnabled|bruteForceProtection)$/;
our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' );

View File

@ -279,6 +279,7 @@ sub defaultValues {
'samlSPSSODescriptorWantAssertionsSigned' => 1,
'securedCookie' => 0,
'sfEngine' => '::2F::Engines::Default',
'sfRemovedMsg' => 0,
'sfRequired' => 0,
'showLanguages' => 1,
'slaveAuthnLevel' => 2,

View File

@ -201,8 +201,8 @@ sub delete2F {
return $self->sendJSONresponse( $req, { result => 1 } );
}
sub session {
my ( $self, $req, $id, $skey ) = @_;
sub _session {
my ( $self, $raw, $req, $id, $skey ) = @_;
my ( %h, $res );
return $self->sendError( $req, 'Bad request', 400 ) unless ($id);
my $mod = $self->getMod($req)
@ -214,11 +214,13 @@ sub session {
my %session = %{ $apacheSession->data };
foreach my $k ( keys %session ) {
$session{$k} = '**********'
if ( $self->hAttr =~ /\b$k\b/ );
$session{$k} = [ split /$self->separator/o, $session{$k} ]
if ( $session{$k} =~ /$self->separator/o );
unless ($raw) {
foreach my $k ( keys %session ) {
$session{$k} = '**********'
if ( $self->hAttr =~ /\b$k\b/ );
$session{$k} = [ split /$self->separator/o, $session{$k} ]
if ( $session{$k} =~ /$self->separator/o );
}
}
if ($skey) {
@ -237,6 +239,16 @@ sub session {
# TODO: check for utf-8 problems
}
sub session {
my $self = shift;
return $self->_session( 0, @_ );
}
sub rawSession {
my $self = shift;
return $self->_session( 1, @_ );
}
sub getApacheSession {
my ( $self, $mod, $id, $info, $force ) = @_;
my $apacheSession = Lemonldap::NG::Common::Session->new( {

View File

@ -24,7 +24,7 @@ sub fetchId {
@vhosts = grep {
if (/^([\w\-]+)=(.+)$/) {
$serviceHeaders{$1} = $2;
$class->logger->debug( "Found service header: $1 => $2");
$class->logger->debug("Found service header: $1 => $2");
0;
}
else { 1 }

View File

@ -2523,6 +2523,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'default' => 0,
'type' => 'bool'
},
'restExportSecretKeys' => {
'default' => 0,
'type' => 'bool'
},
'restPwdConfirmUrl' => {
'type' => 'url'
},
@ -3193,6 +3197,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'default' => '::2F::Engines::Default',
'type' => 'text'
},
'sfRemovedMsg' => {
'default' => 0,
'type' => 'boolOrExpr'
},
'sfRequired' => {
'default' => 0,
'type' => 'boolOrExpr'

View File

@ -370,7 +370,8 @@ EOF
} keys(%$attributes)
};
$managerAttr = mydump( $managerAttr, 'attributes' );
my $managerSub = Dumper( \&Lemonldap::NG::Manager::Build::Attributes::perlExpr );
my $managerSub =
Dumper( \&Lemonldap::NG::Manager::Build::Attributes::perlExpr );
$managerSub =~ s/\$VAR1 = sub/sub perlExpr/s;
$managerSub =~ s/^\s*(?:use strict;|package .*?;|)\n//gm;
my $managerTypes =

View File

@ -22,7 +22,7 @@ sub perlExpr {
my $err = join( '',
grep { $_ =~ /Undefined subroutine/ ? () : $_ } split( /\n/, $@ ) );
return $err ? ( 1, "__badExpression__: $err" ) : (1);
};
}
my $url = $RE{URI}{HTTP}{ -scheme => "https?" };
$url =~ s/(?<=[^\\])\$/\\\$/g;
@ -1640,6 +1640,12 @@ sub attributes {
type => 'bool',
documentation => 'Enable REST session server',
},
restExportSecretKeys => {
default => 0,
type => 'bool',
documentation =>
'Allow to export secret keys in REST session server',
},
restConfigServer => {
default => 0,
type => 'bool',
@ -2581,6 +2587,12 @@ sub attributes {
help => 'secondfactor.html',
documentation => 'Second factor required',
},
sfRemovedMsg => {
type => 'boolOrExpr',
default => 0,
help => 'secondfactor.html',
documentation => 'Display a message if at leat one expired SF has been removed',
},
available2F => {
type => 'text',
default => 'UTOTP,TOTP,U2F,REST,Mail2F,Ext2F,Yubikey',

View File

@ -576,9 +576,10 @@ sub tree {
help => 'portalservers.html',
form => 'simpleInputContainer',
nodes => [
'wsdlServer', 'restSessionServer',
'restConfigServer', 'soapSessionServer',
'soapConfigServer', 'exportedAttr',
'wsdlServer', 'restSessionServer',
'restExportSecretKeys', 'restConfigServer',
'soapSessionServer', 'soapConfigServer',
'exportedAttr',
]
},
{
@ -762,7 +763,7 @@ sub tree {
'yubikey2fTTL',
],
},
'sfRequired',
'sfRequired', 'sfRemovedMsg',
]
},
{
@ -825,9 +826,12 @@ sub tree {
help => 'security.html#portal',
form => 'simpleInputContainer',
nodes => [
'corsEnabled', 'corsAllow_Credentials',
'corsAllow_Headers', 'corsAllow_Methods',
'corsAllow_Origin', 'corsExpose_Headers',
'corsEnabled',
'corsAllow_Credentials',
'corsAllow_Headers',
'corsAllow_Methods',
'corsAllow_Origin',
'corsExpose_Headers',
'corsMax_Age',
]
},

View File

@ -70,7 +70,7 @@ has confChanged => (
);
# 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 newConf => ( is => 'rw', isa => 'HashRef' );
has tree => ( is => 'rw', isa => 'ArrayRef' );
@ -160,7 +160,7 @@ sub _scanNodes {
hdebug("Looking to $name");
# subnode
my $subNodes = $leaf->{nodes} // $leaf->{_nodes};
my $subNodes = $leaf->{nodes} // $leaf->{_nodes};
my $subNodesCond = $leaf->{nodes_cond} // $leaf->{_nodes_cond};
##################################
@ -1101,14 +1101,14 @@ sub _unitTest {
or $attr->{type} =~ /Container$/ )
{
my $keyMsg = $attr->{keyMsgFail} // $type->{keyMsgFail};
my $msg = $attr->{msgFail} // $type->{msgFail};
my $msg = $attr->{msgFail} // $type->{msgFail};
$res = 0
unless (
$self->_execTest( {
keyTest => $attr->{keyTest} // $type->{keyTest},
keyTest => $attr->{keyTest} // $type->{keyTest},
keyMsgFail => $attr->{keyMsgFail}
// $type->{keyMsgFail},
test => $attr->{test} // $type->{test},
test => $attr->{test} // $type->{test},
msgFail => $attr->{msgFail} // $type->{msgFail},
},
$conf->{$key},

View File

@ -207,6 +207,7 @@
"dbiAuthPassword":"كلمة المرور",
"dbiAuthPasswordCol":"اسم حقل كلمة المرور",
"dbiAuthPasswordHash":"هاش المخطط",
"dbiAuthPasswordHash":"Hash scheme",
"dbiDynamicHash":"dynamic hashing",
"dbiDynamicHashEnabled":"dynamic hash activation",
"dbiDynamicHashValidSchemes":"Supported non-salted schemes",
@ -622,8 +623,8 @@
"portalCheckLogins":"تحقق من آخر تسجيلات دخول",
"portalCustomization":"التخصيص",
"portalDisplayAppslist":"قائمة التطبيقات",
"portalDisplayChangePassword":"تغيير كلمة المرور",
"portalDisplayFavApps":"Activation rule",
"portalDisplayChangePassword":"تغيير كلمة المرور",
"portalDisplayLoginHistory":"سجل تسجيل الدخول",
"portalDisplayLogout":"تسجيل الخروج",
"portalDisplayOidcConsents":"OIDC Consents",
@ -701,6 +702,7 @@
"rest2fLogo":"شعار",
"rest2fVerifyArgs":"Verify Arguments",
"rest2fVerifyUrl":"Verify URL",
"restExportSecretKeys":"Export secret attributes in REST",
"restParams":"معايير ريست",
"restPwdConfirmUrl":"عنوان اليو آر إل لتأكيد كلمة المرور",
"restPwdModifyUrl":"عنوان اليو آر إل لتغيير كلمة المرور",
@ -732,6 +734,7 @@
"sessionTitle":"محتوى الجلسة",
"sfaTitle":"Second Factors Authentication",
"sfRequired":"Require 2FA",
"sfRemovedMsg":"Display a message if an expired SF is removed",
"show":"عرض",
"showHelp":"عرض المساعدة",
"showLanguages":"Show languages choice",
@ -1006,4 +1009,4 @@
"samlRelayStateTimeout":"تناوب حالة مهلة الجلسة ",
"samlUseQueryStringSpecific":"استخدام أسلوب query_string المعين",
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
}
}

View File

@ -622,8 +622,8 @@
"portalCheckLogins":"Check last logins",
"portalCustomization":"Customization",
"portalDisplayAppslist":"Applications list",
"portalDisplayChangePassword":"Password change",
"portalDisplayFavApps":"Activation rule",
"portalDisplayChangePassword":"Password change",
"portalDisplayLoginHistory":"Login History",
"portalDisplayLogout":"Logout",
"portalDisplayOidcConsents":"OIDC Consents",
@ -701,6 +701,7 @@
"rest2fLogo":"Logo",
"rest2fVerifyArgs":"Verify Arguments",
"rest2fVerifyUrl":"Verify URL",
"restExportSecretKeys":"Export secret attributes in REST",
"restParams":"REST parameters",
"restPwdConfirmUrl":"Password confirmation URL",
"restPwdModifyUrl":"Password change URL",
@ -732,6 +733,7 @@
"sessionTitle":"Session content",
"sfaTitle":"Second Factors Authentication",
"sfRequired":"Require 2FA",
"sfRemovedMsg":"Display a message if an expired SF is removed",
"show":"Show",
"showHelp":"Show help",
"showLanguages":"Show languages choice",
@ -1006,4 +1008,4 @@
"samlRelayStateTimeout":"RelayState session timeout",
"samlUseQueryStringSpecific":"Use specific query_string method",
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
}
}

View File

@ -701,6 +701,7 @@
"rest2fLogo":"Logo",
"rest2fVerifyArgs":"Verify Arguments",
"rest2fVerifyUrl":"Verify URL",
"restExportSecretKeys":"Export secret attributes in REST",
"restParams":"REST parameters",
"restPwdConfirmUrl":"Password confirmation URL",
"restPwdModifyUrl":"Password change URL",
@ -732,6 +733,7 @@
"sessionTitle":"Session content",
"sfaTitle":"Second Factors Authentication",
"sfRequired":"Require 2FA",
"sfRemovedMsg":"Display a message if an expired SF is removed",
"show":"Show",
"showHelp":"Show help",
"showLanguages":"Show languages choice",

View File

@ -701,6 +701,7 @@
"rest2fLogo":"Logo",
"rest2fVerifyArgs":"Arguments de vérification",
"rest2fVerifyUrl":"URL de vérification",
"restExportSecretKeys":"Exporter les attributs secrets en REST",
"restParams":"Paramètres REST",
"restPwdConfirmUrl":"URL de confirmation de mot-de-passe",
"restPwdModifyUrl":"URL de modification de mot-de-passe",
@ -732,6 +733,7 @@
"sessionTitle":"Contenu de la session",
"sfaTitle":"Seconds Facteurs d'Authentification",
"sfRequired":"Exiger 2FA",
"sfRemovedMsg":"Afficher un message si un SF expiré est supprimé",
"show":"Montrer",
"showHelp":"Montrer l'aide",
"showLanguages":"Afficher le choix des langues",

View File

@ -701,6 +701,7 @@
"rest2fLogo":"Logo",
"rest2fVerifyArgs":"Verifica Argomenti",
"rest2fVerifyUrl":"Verifica UR",
"restExportSecretKeys":"Export secret attributes in REST",
"restParams":"Parametri REST",
"restPwdConfirmUrl":"URL di conferma password",
"restPwdModifyUrl":"URL di modifica password",
@ -732,6 +733,7 @@
"sessionTitle":"Contenuto della sessione",
"sfaTitle":"Autenticazione a due fattori",
"sfRequired":"Richiedi 2FA",
"sfRemovedMsg":"Display a message if an expired SF is removed",
"show":"Mostra",
"showHelp":"Mostra aiuto",
"showLanguages":"Mostra la scelta delle lingue",
@ -1006,4 +1008,4 @@
"samlRelayStateTimeout":"Timeout di sessione di RelayState",
"samlUseQueryStringSpecific":"Utilizza il metodo specifico query_string",
"samlOverrideIDPEntityID":"Sostituisci l'ID entità quando agisce come IDP"
}
}

View File

@ -58,7 +58,6 @@
"authChoiceParam":"Tham số URL",
"authentication":"Mô đun xác thực",
"authenticationNeeded":"Xác thực cần thiết",
"portalDisplayFavApps":"Activation rule",
"authenticationLevel":"Mức xác thực",
"authenticationTitle":"Xác thực",
"AuthLDAPFilter":"Bộ lọc xác thực",
@ -623,6 +622,7 @@
"portalCheckLogins":"Kiểm tra đăng nhập lần cuối",
"portalCustomization":"Tùy chỉnh",
"portalDisplayAppslist":"Danh sách ứng dụng",
"portalDisplayFavApps":"Activation rule",
"portalDisplayChangePassword":"Thay đổi mật khẩu",
"portalDisplayLoginHistory":"Lịch sử đăng nhập",
"portalDisplayLogout":"Đăng xuất",
@ -701,6 +701,7 @@
"rest2fLogo":"Logo",
"rest2fVerifyArgs":"Verify Arguments",
"rest2fVerifyUrl":"Verify URL",
"restExportSecretKeys":"Export secret attributes in REST",
"restParams":"Tham số REST",
"restPwdConfirmUrl":"URL xác nhận mật khẩu",
"restPwdModifyUrl":"URL thay đổi mật khẩu",
@ -732,6 +733,7 @@
"sessionTitle":"Nội dung phiên",
"sfaTitle":"Second Factors Authentication",
"sfRequired":"Require 2FA",
"sfRemovedMsg":"Display a message if an expired SF is removed",
"show":"Hiển thị",
"showHelp":"Hiển thị trợ giúp",
"showLanguages":"Show languages choice",
@ -1006,4 +1008,4 @@
"samlRelayStateTimeout":"Thời gian hết hạn phiên RelayState ",
"samlUseQueryStringSpecific":"Sử dụng phương pháp query_string cụ thể",
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
}
}

View File

@ -701,6 +701,7 @@
"rest2fLogo":"Logo",
"rest2fVerifyArgs":"Verify Arguments",
"rest2fVerifyUrl":"Verify URL",
"restExportSecretKeys":"Export secret attributes in REST",
"restParams":"REST parameters",
"restPwdConfirmUrl":"Password confirmation URL",
"restPwdModifyUrl":"Password change URL",
@ -732,6 +733,7 @@
"sessionTitle":"Session content",
"sfaTitle":"Second Factors Authentication",
"sfRequired":"Require 2FA",
"sfRemovedMsg":"Display a message if an expired SF is removed",
"show":"Show",
"showHelp":"Show help",
"showLanguages":"Show languages choice",
@ -1006,4 +1008,4 @@
"samlRelayStateTimeout":"RelayState session timeout",
"samlUseQueryStringSpecific":"Use specific query_string method",
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -54,7 +54,8 @@ my @notManagedAttributes = (
# Other ini-only prms
'configStorage', 'status', 'localStorageOptions', 'localStorage',
'max2FDevices', 'max2FDevicesNameLength', 'checkTime',
'mySessionAuthorizedRWKeys', 'handlerInternalCache', 'handlerServiceTokenTTL'
'mySessionAuthorizedRWKeys', 'handlerInternalCache',
'handlerServiceTokenTTL'
);
# Words used either as attribute name and node title

View File

@ -416,6 +416,7 @@ t/01-CSP-and-CORS-headers.t
t/01-pdata.t
t/02-Password-Demo.t
t/03-XSS-protection.t
t/04-language-selection.t
t/19-Auth-Null.t
t/20-Auth-and-password-DBI-dynamic-hash.t
t/20-Auth-and-password-DBI.t
@ -478,11 +479,13 @@ t/34-Auth-Proxy-and-REST-Server.t
t/34-Auth-Proxy-and-SOAP-Server.t
t/35-My-session.t
t/35-REST-config-backend.t
t/35-REST-export-password.t
t/35-REST-sessions-with-AuthBasic-handler.t
t/35-REST-sessions-with-REST-server.t
t/35-SOAP-config-backend.t
t/35-SOAP-sessions-with-SOAP-server.t
t/36-Combination-Kerberos-or-Demo.t
t/36-Combination-with-Choice.t
t/36-Combination-with-over.t
t/36-Combination-with-token.t
t/36-Combination.t
@ -549,8 +552,11 @@ t/68-Impersonation.t
t/69-FavApps.t
t/70-2F-TOTP-8.t
t/70-2F-TOTP-with-History.t
t/70-2F-TOTP-with-TTL-and-msg.t
t/70-2F-TOTP-with-TTL.t
t/71-2F-U2F-with-History.t
t/71-2F-U2F-with-TTL-and-msg.t
t/71-2F-U2F-with-TTL.t
t/71-2F-U2F.t
t/72-2F-REST-with-History.t
t/73-2F-UTOTP-TOTP-and-U2F-with-History.t

View File

@ -18,6 +18,7 @@ use Lemonldap::NG::Portal::Main::Constants qw(
PE_OK
PE_SENDRESPONSE
PE_TOKENEXPIRED
PE_INFO
);
our $VERSION = '2.1.0';
@ -26,11 +27,10 @@ extends 'Lemonldap::NG::Portal::Main::Plugin';
# INITIALIZATION
has sfModules => ( is => 'rw', default => sub { [] } );
has sfModules => ( is => 'rw', default => sub { [] } );
has sfRModules => ( is => 'rw', default => sub { [] } );
has sfReq => ( is => 'rw' );
has sfReq => ( is => 'rw' );
has sfMsg => ( is => 'rw' );
has ott => (
is => 'rw',
@ -106,6 +106,19 @@ sub init {
return 0;
}
unless (
$self->sfMsg(
$self->p->HANDLER->buildSub(
$self->p->HANDLER->substitute( $self->conf->{sfRemovedMsg} )
)
)
)
{
$self->error( 'Error in sfRemovedMsg rule'
. $self->p->HANDLER->tsv->{jail}->error );
return 0;
}
# Enable REST request only if more than 1 2F module is enabled
if ( @{ $self->{sfModules} } > 1 ) {
$self->addUnauthRoute( '2fchoice' => '_choice', ['POST'] );
@ -155,49 +168,54 @@ sub run {
$self->logger->error("Bad encoding in _2fDevices: $@");
return PE_ERROR;
}
$self->logger->debug(" -> 2F Device(s) found");
# Initialize 2FDevices TTL
my $u2fTTL = $self->conf->{u2fTTL}
&& $self->conf->{u2fTTL} > 0 ? $self->conf->{u2fTTL} : 0;
my $totpTTL = $self->conf->{totp2fTTL}
&& $self->conf->{totp2fTTL} > 0 ? $self->conf->{totp2fTTL} : 0;
my $ubkTTL = $self->conf->{yubikey2fTTL}
&& $self->conf->{yubikey2fTTL} > 0 ? $self->conf->{yubikey2fTTL} : 0;
# Processing if at least one TTL is set
if ( $u2fTTL || $totpTTL || $ubkTTL ) {
my $now = time();
my $removed = 0;
$self->logger->debug("Looking for expired 2F device(s)...");
foreach my $device (@$_2fDevices) {
foreach my $type (qw(TOTP U2F UBK)) {
if ( $device->{type} eq $type ) {
if ( eval( '$' . lc($type) . 'TTL' )
&& $now - $device->{epoch} >
eval( '$' . lc($type) . 'TTL' ) )
{
$self->logger->debug(
"Remove $type -> $device->{name} / $device->{epoch}"
);
$self->userLogger->info("Remove expired $type");
$device->{type} = 'EXPIRED';
$removed++;
last;
}
}
}
}
if ($removed) {
my $now = time();
my $removed = 0;
$self->logger->debug("Looking for expired 2F device(s)...");
foreach my $device (@$_2fDevices) {
my $type = lc( $device->{type} );
$type =~ s/2f$//i;
$type = 'yubikey' if $type eq 'ubk';
my $ttl = $self->conf->{ $type . '2fTTL' };
if ( $ttl and $ttl > 0 and $now - $device->{epoch} > $ttl ) {
$self->logger->debug(
"Found $removed EXPIRED 2F device(s) => Update persistent session"
"Remove $device->{type} -> $device->{name} / $device->{epoch}"
);
$self->userLogger->notice(
" -> $removed EXPIRED 2F device(s) removed");
@$_2fDevices =
map { $_->{type} =~ /\bEXPIRED\b/ ? () : $_ } @$_2fDevices;
$self->p->updatePersistentSession( $req,
{ _2fDevices => to_json($_2fDevices) } );
$self->userLogger->info("Remove expired $device->{type}");
$device->{type} = 'EXPIRED';
$removed++;
}
}
if ($removed) {
$self->logger->debug(
"Found $removed EXPIRED 2F device(s) => Update persistent session"
);
$self->userLogger->notice(
" -> $removed EXPIRED 2F device(s) removed");
@$_2fDevices =
map { $_->{type} =~ /\bEXPIRED\b/ ? () : $_ } @$_2fDevices;
$self->p->updatePersistentSession( $req,
{ _2fDevices => to_json($_2fDevices) } );
# Display message if required
if ( $self->sfMsg->( $req, $req->sessionInfo ) ) {
$req->info(
$self->loadTemplate(
'simpleInfo',
(
$removed > 1
? (
params => {
trspan => "expired2Fremoved, $removed"
}
)
: ( params => { trspan => "oneExpired2Fremoved" } )
)
)
);
return PE_INFO;
}
}
}

View File

@ -1009,6 +1009,11 @@ sub token {
# Get code session
my $code = $req->param('code');
unless ($code) {
$self->logger->error("No code found on token endpoint");
return $self->p->sendError( $req, 'invalid_request', 400 );
}
$self->logger->debug("OpenID Connect Code: $code");
my $codeSession = $self->getOpenIDConnectSession($code);

View File

@ -230,8 +230,10 @@ sub getForm {
# Get displayType for this module
no strict 'refs';
my $displayType = "Lemonldap::NG::Portal::Auth::${auth}"
->can('getForm')->( $self, $req );
my $displayType = eval {
"Lemonldap::NG::Portal::Auth::${auth}"
->can('getForm')->( $self, $req );
} || 'logo';
$self->logger->debug( 'Display type '
. ( ref $displayType ? '[ref]' : $displayType )

View File

@ -12,7 +12,7 @@ use Data::Dumper;
use constant CommonPrms => {
MAIN_LOGO => 'portalMainLogo',
LANGS => 'showLanguages',
LANGS => 'showLanguages',
};
has skinRules => ( is => 'rw' );
@ -40,7 +40,8 @@ sub displayInit {
$self->logger->debug(
"FavApps activation rule -> " . $self->conf->{portalDisplayFavApps} );
my $rule =
HANDLER->buildSub( HANDLER->substitute( $self->conf->{portalDisplayFavApps} ) );
HANDLER->buildSub(
HANDLER->substitute( $self->conf->{portalDisplayFavApps} ) );
unless ($rule) {
$self->error(
"Bad FavApps activation rule -> " . HANDLER->tsv->{jail}->error );
@ -317,9 +318,10 @@ sub display {
$filter->{LOGIN_FORM} = $form;
}
}
# Common parameters
foreach (keys %{CommonPrms()}){
$templateParams{$_} = $self->conf->{CommonPrms->{$_}};
foreach ( keys %{ CommonPrms() } ) {
$templateParams{$_} = $self->conf->{ CommonPrms->{$_} };
}
$self->logger->debug("Calling sendHtml with template $skinfile");

View File

@ -244,7 +244,8 @@ sub _buildCategoryHash {
if ( scalar keys %$apphash > 0 ) {
foreach my $appkey (
sort {
($apphash->{$a}->{order} || 0) <=> ($apphash->{$b}->{order} || 0)
( $apphash->{$a}->{order} || 0 )
<=> ( $apphash->{$b}->{order} || 0 )
or $a cmp $b
}
keys %$apphash
@ -258,7 +259,8 @@ sub _buildCategoryHash {
# Display subcategories
foreach my $catkey (
sort {
($cathash->{$a}->{order} || 0) <=> ($cathash->{$b}->{order} || 0)
( $cathash->{$a}->{order} || 0 )
<=> ( $cathash->{$b}->{order} || 0 )
or $a cmp $b
}
grep { not /^(?:catname|type|options|order)$/ } keys %$cathash
@ -312,7 +314,8 @@ sub _buildApplicationHash {
if ( scalar keys %$subapphash > 0 ) {
foreach my $appkey (
sort {
($subapphash->{$a}->{order} || 0) <=> ( $subapphash->{$b}->{order} || 0 )
( $subapphash->{$a}->{order} || 0 )
<=> ( $subapphash->{$b}->{order} || 0 )
or $a cmp $b
}
keys %$subapphash

View File

@ -794,7 +794,7 @@ sub sendHtml {
if ( $self->conf->{corsEnabled} ) {
push @{ $res->[1] }, @cors;
$self->logger->debug(
"Apply following CORS policy : " . Data::Dumper::Dumper(\@cors) );
"Apply following CORS policy : " . Data::Dumper::Dumper( \@cors ) );
}
# Set authorized URL for POST

View File

@ -125,7 +125,7 @@ sub run {
}
# Merging SSO Groups and hGroups & dedup
$spoofSession->{groups} ||= '';
$spoofSession->{groups} ||= '';
$spoofSession->{hGroups} ||= {};
if ( $self->{conf}->{impersonationMergeSSOgroups} ) {
$self->userLogger->warn("MERGING SSO groups and hGroups...");

View File

@ -134,7 +134,13 @@ sub init {
# Methods inherited from Lemonldap::NG::Common::Session::REST
$self->addUnauthRoute(
sessions => { ':sessionType' => 'session' },
sessions => {
':sessionType' => (
$self->conf->{restExportSecretKeys}
? 'rawSession'
: 'session'
)
},
['GET']
);
$self->addUnauthRoute(

View File

@ -384,7 +384,7 @@ sub deleteSession {
my ( $self, $req, $id ) = @_;
die('id parameter is required') unless ($id);
my $session = $self->p->getApacheSession($id, kind => '');
my $session = $self->p->getApacheSession( $id, kind => '' );
return 0 unless ($session);

View File

@ -1,6 +1,7 @@
package Lemonldap::NG::Portal::Register::Custom;
use strict;
use Mouse;
extends 'Lemonldap::NG::Portal::Register::Base';

View File

@ -1,4 +1,4 @@
#
# Regular cron jobs for LemonLDAP::NG Portal
#
*/10 * * * * __APACHEUSER__ [ -x __BINDIR__/purgeCentralCache ] && __BINDIR__/purgeCentralCache
7 * * * * __APACHEUSER__ [ -x __BINDIR__/purgeCentralCache ] && __BINDIR__/purgeCentralCache

View File

@ -131,7 +131,7 @@
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"يرجى استخدام يوبي كي الخاص بك",
"errorMsg":"رسالة خاطئة",
"yourFavApps":"Favorite applications",
"expired2Fremoved":"%s expired 2F devices have been removed!",
"fillTheForm":"Fill the form",
"firstName":"الاسم الاول",
"forbidden":"Access FORBIDDEN",
@ -175,6 +175,7 @@
"oidcConsent":"التطبيق ٪s هل ترغب في معرفة:",
"oidcConsents":"OIDC consents",
"oidcConsentsFull":"OpenID Connect consents",
"oneExpired2Fremoved":"An expired 2F device has been removed!",
"openidAp":"هل توافق على تقديم الإعدادات التالية؟",
"openIdExample":"فمثلا:http://myopenid.org/toto",
"openidExchange":"هل تريد مصادقة نفسك على٪ s؟",
@ -202,7 +203,6 @@
"removeOtherSessions":"إزالة الجلسات الأخرى",
"resendConfirmMail":"هل تريد إعادة إرسال رسالة التأكيد؟",
"resentConfirm":"هل تريد إعادة إرسال رسالة التأكيد؟",
"resetFavApps":"Reset my favorite Apps.",
"resetPwd":"إعادة تعيين كلمة المرور الخاصة بي",
"rightsReloadNeedsLogout":" إعادة تحميل الحقوق تحتاج إلى تسجيل الخروج وتسجيل الدخول مرة أخرى",
"scope":"نطاق",

View File

@ -131,7 +131,7 @@
"enterTotpCode":"Gebe den TOTP Code ein",
"enterYubikey":"Benutze bitte deinen Yubikey",
"errorMsg":"Fehlermeldung",
"yourFavApps":"Favorite applications",
"expired2Fremoved":"%s expired 2F devices have been removed!",
"fillTheForm":"Fülle das Formular aus",
"firstName":"Vorname",
"forbidden":"Access FORBIDDEN",
@ -175,6 +175,7 @@
"oidcConsent":"Die Anwendung %s möchte wissen:",
"oidcConsents":"OIDC consents",
"oidcConsentsFull":"OpenID Connect consents",
"oneExpired2Fremoved":"An expired 2F device has been removed!",
"openidAp":"Stimmst du folgenden Parametern zu ?",
"openIdExample":"zum Beispiel: http://myopenid.org/toto",
"openidExchange":"Willst du dich bei %s authentifizieren ?",
@ -202,7 +203,6 @@
"removeOtherSessions":"Andere Sitzungen löschen",
"resendConfirmMail":"Bestätigungsmail erneuert senden ?",
"resentConfirm":"Möchtest du, dass die Bestätigungsmail erneut gesendet wird ?",
"resetFavApps":"Reset my favorite Apps.",
"resetPwd":"Mein Passwort zurücksetzen",
"rightsReloadNeedsLogout":"Zum Neuladen der Rechte musst du dich ab- und wieder anmelden",
"scope":"Scope",

View File

@ -131,7 +131,7 @@
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Please use your Yubikey",
"errorMsg":"Error Message",
"yourFavApps":"Favorite applications",
"expired2Fremoved":"%s expired 2F devices have been removed!",
"fillTheForm":"Fill the form",
"firstName":"First name",
"forbidden":"Access FORBIDDEN",
@ -175,6 +175,7 @@
"oidcConsent":"The application %s would like to know:",
"oidcConsents": "OIDC consents",
"oidcConsentsFull":"OpenID Connect consents",
"oneExpired2Fremoved":"An expired 2F device has been removed!",
"openidAp":"Do you agree to provide the following parameters?",
"openIdExample":"for example:http://myopenid.org/toto",
"openidExchange":"Do you want to authenticate yourself on %s ?",

View File

@ -131,7 +131,7 @@
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Please use your Yubikey",
"errorMsg":"Error Message",
"yourFavApps":"Favorite applications",
"expired2Fremoved":"%s expired 2F devices have been removed!",
"fillTheForm":"Fill the form",
"firstName":"First name",
"forbidden":"Access FORBIDDEN",
@ -175,6 +175,7 @@
"oidcConsent":"The application %s would like to know:",
"oidcConsents":"OIDC consents",
"oidcConsentsFull":"OpenID Connect consents",
"oneExpired2Fremoved":"An expired 2F device has been removed!",
"openidAp":"Do you agree to provide the following parameters?",
"openIdExample":"for example:http://myopenid.org/toto",
"openidExchange":"Do you want to authenticate yourself on %s ?",
@ -202,7 +203,6 @@
"removeOtherSessions":"Remove other sessions",
"resendConfirmMail":"Resend confirmation mail?",
"resentConfirm":"Do you want the confirmation mail to be resent?",
"resetFavApps":"Reset my favorite Apps.",
"resetPwd":"Reset my password",
"rightsReloadNeedsLogout":"Rights reloads need to logout and login again",
"scope":"Scope",

View File

@ -131,7 +131,7 @@
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Please use your Yubikey",
"errorMsg":"Virhe viesti",
"yourFavApps":"Favorite applications",
"expired2Fremoved":"%s expired 2F devices have been removed!",
"fillTheForm":"Fill the form",
"firstName":"Etunimi",
"forbidden":"Access FORBIDDEN",
@ -175,6 +175,7 @@
"oidcConsent":"The application %s would like to know:",
"oidcConsents":"OIDC consents",
"oidcConsentsFull":"OpenID Connect consents",
"oneExpired2Fremoved":"An expired 2F device has been removed!",
"openidAp":"Do you agree to provide the following parameters?",
"openIdExample":"for example:http://myopenid.org/toto",
"openidExchange":"Do you want to authenticate yourself on %s ?",
@ -202,7 +203,6 @@
"removeOtherSessions":"Remove other sessions",
"resendConfirmMail":"Uudelleen lähetä vahvistus sähköposti?",
"resentConfirm":"Do you want the confirmation mail to be resent?",
"resetFavApps":"Reset my favorite Apps.",
"resetPwd":"Palauta salasanani?",
"rightsReloadNeedsLogout":"Rights reloads need to logout and login again",
"scope":"Scope",

View File

@ -131,7 +131,7 @@
"enterTotpCode":"Entrez le code TOTP",
"enterYubikey":"Utilisez votre Yubikey",
"errorMsg":"Message d'erreur",
"yourFavApps":"Applications favorites",
"expired2Fremoved":"%s seconds facteurs expirés ont été supprimés !",
"fillTheForm":"Remplissez le formulaire",
"forbidden":"Accès INTERDIT",
"firstName":"Prénom",
@ -175,6 +175,7 @@
"oidcConsent":"L'application %s voudrait connaître :",
"oidcConsents": "Accords OIDC",
"oidcConsentsFull":"Accords OpenID Connect",
"oneExpired2Fremoved":"Un second facteur expiré a été supprimé !",
"openidAp":"Consentez-vous à communiquer les paramètres suivants ?",
"openIdExample":"par exemple : http://myopenid.org/toto",
"openidExchange":"Souhaitez-vous vous identifier sur le site %s ?",

View File

@ -131,7 +131,7 @@
"enterTotpCode":"Inserisci il codice TOTP",
"enterYubikey":"Utilizza il tuo Yubikey",
"errorMsg":"Messaggio di errore",
"yourFavApps":"Favorite applications",
"expired2Fremoved":"%s expired 2F devices have been removed!",
"fillTheForm":"Compila il modulo",
"firstName":"Nome",
"forbidden":"Accesso VIETATO",
@ -175,6 +175,7 @@
"oidcConsent":"L'applicazione %s vorrebbe sapere:",
"oidcConsents":"Consensi OIDC",
"oidcConsentsFull":"Consensi OpenID Connect",
"oneExpired2Fremoved":"An expired 2F device has been removed!",
"openidAp":"Accetti di fornire i seguenti parametri?",
"openIdExample":"per esempio:http://myopenid.org/toto",
"openidExchange":"Vuoi autenticarti su% s?",
@ -202,7 +203,6 @@
"removeOtherSessions":"Rimuovere altre sessioni",
"resendConfirmMail":"Inviare nuovamente mail di conferma?",
"resentConfirm":"Vuoi inviare di nuovo la mail di conferma?",
"resetFavApps":"Reset my favorite Apps.",
"resetPwd":"Reimpostare la password",
"rightsReloadNeedsLogout":"Le ricariche dei diritti necessitano di disconnettersi e di riconnettersi",
"scope":"Ambito",

View File

@ -131,7 +131,7 @@
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Please use your Yubikey",
"errorMsg":"Error Message",
"yourFavApps":"Favorite applications",
"expired2Fremoved":"%s expired 2F devices have been removed!",
"fillTheForm":"Fill the form",
"firstName":"First name",
"forbidden":"Access FORBIDDEN",
@ -175,6 +175,7 @@
"oidcConsent":"The application %s would like to know:",
"oidcConsents":"OIDC consents",
"oidcConsentsFull":"OpenID Connect consents",
"oneExpired2Fremoved":"An expired 2F device has been removed!",
"openidAp":"Do you agree to provide the following parameters?",
"openIdExample":"for example:http://myopenid.org/toto",
"openidExchange":"Do you want to authenticate yourself on %s ?",
@ -202,7 +203,6 @@
"removeOtherSessions":"Remove other sessions",
"resendConfirmMail":"Resend confirmation mail?",
"resentConfirm":"Do you want the confirmation mail to be resent?",
"resetFavApps":"Reset my favorite Apps.",
"resetPwd":"Reset my password",
"rightsReloadNeedsLogout":"Rights reloads need to logout and login again",
"scope":"Scope",

View File

@ -131,7 +131,7 @@
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Please use your Yubikey",
"errorMsg":"Error Message",
"yourFavApps":"Favorite applications",
"expired2Fremoved":"%s expired 2F devices have been removed!",
"fillTheForm":"Fill the form",
"firstName":"First name",
"forbidden":"Access FORBIDDEN",
@ -175,6 +175,7 @@
"oidcConsent":"The application %s would like to know:",
"oidcConsents":"OIDC consents",
"oidcConsentsFull":"OpenID Connect consents",
"oneExpired2Fremoved":"An expired 2F device has been removed!",
"openidAp":"Do you agree to provide the following parameters?",
"openIdExample":"for example:http://myopenid.org/toto",
"openidExchange":"Do you want to authenticate yourself on %s ?",
@ -202,7 +203,6 @@
"removeOtherSessions":"Remove other sessions",
"resendConfirmMail":"Resend confirmation mail?",
"resentConfirm":"Do you want the confirmation mail to be resent?",
"resetFavApps":"Reset my favorite Apps.",
"resetPwd":"Reset my password",
"rightsReloadNeedsLogout":"Rights reloads need to logout and login again",
"scope":"Scope",

View File

@ -131,7 +131,7 @@
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Please use your Yubikey",
"errorMsg":"Error Message",
"yourFavApps":"Favorite applications",
"expired2Fremoved":"%s expired 2F devices have been removed!",
"fillTheForm":"Fill the form",
"firstName":"First name",
"forbidden":"Access FORBIDDEN",
@ -175,6 +175,7 @@
"oidcConsent":"The application %s would like to know:",
"oidcConsents":"OIDC consents",
"oidcConsentsFull":"OpenID Connect consents",
"oneExpired2Fremoved":"An expired 2F device has been removed!",
"openidAp":"Do you agree to provide the following parameters?",
"openIdExample":"for example:http://myopenid.org/toto",
"openidExchange":"Do you want to authenticate yourself on %s ?",
@ -202,7 +203,6 @@
"removeOtherSessions":"Remove other sessions",
"resendConfirmMail":"Resend confirmation mail?",
"resentConfirm":"Do you want the confirmation mail to be resent?",
"resetFavApps":"Reset my favorite Apps.",
"resetPwd":"Reset my password",
"rightsReloadNeedsLogout":"Rights reloads need to logout and login again",
"scope":"Scope",

View File

@ -131,7 +131,7 @@
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"Vui lòng sử dụng Yubikey của bạn",
"errorMsg":"Thông báo lỗi",
"yourFavApps":"Favorite applications",
"expired2Fremoved":"%s expired 2F devices have been removed!",
"fillTheForm":"Fill the form",
"firstName":"Tên",
"forbidden":"Access FORBIDDEN",
@ -175,6 +175,7 @@
"oidcConsent":"Ứng dụng %s muốn biết:",
"oidcConsents":"OIDC consents",
"oidcConsentsFull":"OpenID Connect consents",
"oneExpired2Fremoved":"An expired 2F device has been removed!",
"openidAp":"Bạn đồng ý cung cấp các thông số sau?",
"openIdExample":"ví dụ: http: //myopenid.org/toto",
"openidExchange":"Bạn có muốn chứng thực mình trên%s không?",
@ -202,7 +203,6 @@
"removeOtherSessions":"Xóa các phiên khác",
"resendConfirmMail":"Gửi lại thư xác nhận?",
"resentConfirm":"Bạn có muốn gửi lại thư xác nhận không?",
"resetFavApps":"Reset my favorite Apps.",
"resetPwd":"Đặt lại mật khẩu của tôi",
"rightsReloadNeedsLogout":"Tải lại quyền cần đăng xuất và đăng nhập lại",
"scope":"Phạm vi",

View File

@ -131,7 +131,7 @@
"enterTotpCode":"Enter TOTP code",
"enterYubikey":"请使用您的Yubikey",
"errorMsg":"错误消息",
"yourFavApps":"Favorite applications",
"expired2Fremoved":"%s expired 2F devices have been removed!",
"fillTheForm":"Fill the form",
"firstName":"名",
"forbidden":"Access FORBIDDEN",
@ -175,6 +175,7 @@
"oidcConsent":"The application %s would like to know:",
"oidcConsents":"OIDC consents",
"oidcConsentsFull":"OpenID Connect consents",
"oneExpired2Fremoved":"An expired 2F device has been removed!",
"openidAp":"您是否同意提供以下参数?",
"openIdExample":"例如http://myopenid.org/toto",
"openidExchange":"Do you want to authenticate yourself on %s ?",
@ -202,7 +203,6 @@
"removeOtherSessions":"移除其他会话",
"resendConfirmMail":"重新发送确认邮件?",
"resentConfirm":"您想确认邮件被重新发送吗?",
"resetFavApps":"Reset my favorite Apps.",
"resetPwd":"重置我的密码",
"rightsReloadNeedsLogout":"重新加载权限需要登出并且再次登录",
"scope":"Scope",

View File

@ -8,8 +8,11 @@ BEGIN {
my ( $client, $res, $id );
$client = LLNG::Manager::Test->new(
{ ini => { logLevel => 'error', restSessionServer => 1, useSafeJail => 1 }, } );
$client = LLNG::Manager::Test->new( {
ini =>
{ logLevel => 'error', restSessionServer => 1, useSafeJail => 1 },
}
);
# Try to authenticate
# -------------------
@ -24,12 +27,12 @@ ok(
count(1);
expectOK($res);
$id = expectCookie($res);
ok( $res = $client->_get("/sessions/global/$id"), 'Get session' );
count(1);
expectOK($res);
ok( $res = eval { JSON::from_json( $res->[2]->[0] ) }, ' GET JSON' )
or print STDERR $@;
or print STDERR $@;
count(1);
ok( $res->{_language} eq 'en', 'Default value for _language' );
count(1);
@ -49,17 +52,16 @@ ok(
count(1);
expectOK($res);
$id = expectCookie($res);
ok( $res = $client->_get("/sessions/global/$id"), 'Get session' );
count(1);
expectOK($res);
ok( $res = eval { JSON::from_json( $res->[2]->[0] ) }, ' GET JSON' )
or print STDERR $@;
or print STDERR $@;
count(1);
ok( $res->{_language} eq 'fr', 'Correct value for _language' );
count(1);
# Test logout
$client->logout($id);

View File

@ -31,8 +31,9 @@ SKIP: {
authChoiceParam => 'test',
authChoiceModules => {
ldap => 'LDAP;LDAP;LDAP',
sql => 'DBI;DBI;DBI',
ldap => 'LDAP;LDAP;LDAP',
sql => 'DBI;DBI;DBI',
slave => 'Slave;LDAP;LDAP',
},
dbiAuthChain => 'dbi:SQLite:dbname=t/userdb.db',
@ -47,6 +48,11 @@ SKIP: {
ldapBase => 'ou=users,dc=example,dc=com',
managerDn => 'cn=admin,dc=example,dc=com',
managerPassword => 'admin',
slaveUserHeader => 'My-Test',
slaveExportedVars => {
name => 'Name',
}
}
}
);

View File

@ -205,7 +205,7 @@ SKIP: {
),
'Post SAML response to SP'
);
ok($res->[2]->[0] =~ /trmsg="56"/, 'Found SLO error');
ok( $res->[2]->[0] =~ /trmsg="56"/, 'Found SLO error' );
}
count($maintests);

View File

@ -0,0 +1,163 @@
use lib 'inc';
use Test::More;
use strict;
use IO::String;
use LWP::UserAgent;
use LWP::Protocol::PSGI;
BEGIN {
require 't/test-lib.pm';
}
my $debug = 'error';
my ( $issuer, $sp, $res, $spId );
my %handlerOR = ( issuer => [], sp => [] );
# Redefine LWP methods for tests
LWP::Protocol::PSGI->register(
sub {
my $req = Plack::Request->new(@_);
ok(
$req->uri =~ m#http://auth.idp.com(.*?)(?:\?(.*))?$#,
' @ REST request (' . $req->method . " $1)"
);
count(1);
my $url = $1;
my $query = $2;
my $res;
if ( $req->method =~ /^(post|put)$/i ) {
my $mth = '_' . lc($1);
my $s = $req->content;
ok(
$res = $issuer->$mth(
$url,
IO::String->new($s),
length => length($s),
type => $req->header('Content-Type'),
),
' Post request'
);
count(1);
expectOK($res);
}
elsif ( $req->method =~ /^(get|delete)$/i ) {
my $mth = '_' . lc($1);
ok(
$res = $issuer->$mth(
$url,
accept => $req->header('Accept'),
cookie => $req->header('Cookie'),
query => $query,
),
' Execute request'
);
ok( ( $res->[0] == 200 or $res->[0] == 400 ),
' Response is 200 or 400' )
or explain( $res->[0], '200 or 400' );
count(2);
}
pass(' @ END OF REST REQUEST');
count(1);
return $res;
}
);
ok( $issuer = issuer(), 'Issuer portal' );
$handlerOR{issuer} = \@Lemonldap::NG::Handler::Main::_onReload;
switch ('sp');
&Lemonldap::NG::Handler::Main::cfgNum( 0, 0 );
ok( $sp = sp(), 'SP portal' );
$handlerOR{sp} = \@Lemonldap::NG::Handler::Main::_onReload;
count(2);
# Simple SP access
ok(
$res = $sp->_get(
'/', accept => 'text/html',
),
'Unauth SP request'
);
expectOK($res);
# Try to auth
ok(
$res = $sp->_post(
'/', IO::String->new('user=french&password=french'),
length => 27,
accept => 'text/html'
),
'Post user/password'
);
count(2);
expectRedirection( $res, 'http://auth.sp.com' );
$spId = expectCookie($res);
# Test auth
ok( $res = $sp->_get( '/', cookie => "lemonldap=$spId" ), 'Auth test' );
count(1);
expectOK($res);
# Test other REST queries
switch ('issuer');
# Session key
ok( $res = $issuer->_get("/sessions/global/$spId/[_session_id,_password]"),
'Some session keys' );
expectOK($res);
ok( $res = eval { JSON::from_json( $res->[2]->[0] ) }, ' GET JSON' )
or print STDERR $@;
ok( $res->{_session_id} eq $spId, ' Good ID' )
or explain( $res, "_session_id => $spId" );
ok( $res->{_password} eq 'french', ' Password is exported' )
or explain( $res, '_password => french' );
count(4);
clean_sessions();
done_testing( count() );
sub switch {
my $type = shift;
@Lemonldap::NG::Handler::Main::_onReload = @{
$handlerOR{$type};
};
}
sub issuer {
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
templatesDir => 'site/htdocs/static',
domain => 'idp.com',
portal => 'http://auth.idp.com',
authentication => 'Demo',
userDB => 'Same',
restSessionServer => 1,
restExportSecretKeys => 1,
}
}
);
}
sub sp {
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'sp.com',
portal => 'http://auth.sp.com',
authentication => 'Demo',
userDB => 'Same',
globalStorage => 'Lemonldap::NG::Common::Apache::Session::REST',
globalStorageOptions => {
baseUrl => 'http://auth.idp.com/sessions/global/'
},
persistentStorage =>
'Lemonldap::NG::Common::Apache::Session::REST',
persistentStorageOptions => {
baseUrl => 'http://auth.idp.com/sessions/persistent/'
},
storePassword => 1,
},
}
);
}

View File

@ -0,0 +1,118 @@
use Test::More;
use strict;
use IO::String;
require 't/test-lib.pm';
my $res;
my $maintests = 0;
my $client;
eval { unlink 't/userdb.db' };
SKIP: {
eval { require DBI; require DBD::SQLite; };
if ($@) {
skip 'DBD::SQLite not found', $maintests;
}
my $dbh = DBI->connect("dbi:SQLite:dbname=t/userdb.db");
$dbh->do('CREATE TABLE users (user text,password text,name text)');
$dbh->do("INSERT INTO users VALUES ('dvador','dvador','Test user 1')");
$dbh->do("INSERT INTO users VALUES ('rtyler','rtyler','Test user 1')");
$client = iniCmb('[Dm] or [Ch]');
expectCookie( try('dwho') );
expectCookie( try( 'dvador', 'sql' ) );
$client = iniCmb('[Dm] and [Ch]');
expectCookie( try( 'rtyler', 'sql' ) );
expectCookie( try( 'dwho', 'demo' ) );
expectReject( try( 'dwho', 'sql' ) );
$client = iniCmb('if($env->{HTTP_X} eq "dwho") then [Dm] else [Ch]');
expectCookie( try('dwho') );
expectCookie( try( 'dvador', 'sql' ) );
$client = iniCmb(
'if($env->{HTTP_X} eq "rtyler") then [Dm] and [Ch] else if($env->{HTTP_X} eq "dvador") then [Ch] else [Ch]'
);
expectCookie( try( 'rtyler', 'sql' ) );
expectCookie( try( 'dvador', 'sql' ) );
expectCookie( try( 'dwho', 'demo' ) );
expectReject( try( 'dwho', 'sql' ) );
}
count($maintests);
clean_sessions();
eval { unlink 't/userdb.db' };
done_testing( count() );
sub try {
my $user = shift;
my $s = "user=$user&password=$user";
$s .= "&test=$_[0]" if (@_);
my $res;
ok(
$res = $client->_post(
'/', IO::String->new($s),
length => length($s),
custom => { HTTP_X => $user }
),
" Try to connect with login $user"
);
count(1);
return $res;
}
sub iniCmb {
my $expr = shift;
&Lemonldap::NG::Handler::Main::cfgNum( 0, 0 );
if (
my $res = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
useSafeJail => 1,
authentication => 'Combination',
userDB => 'Same',
combination => $expr,
combModules => {
Dm => {
for => 0,
type => 'Demo',
},
Ch => {
for => 0,
type => 'Choice',
},
},
dbiAuthChain => 'dbi:SQLite:dbname=t/userdb.db',
dbiAuthUser => '',
dbiAuthPassword => '',
dbiAuthTable => 'users',
dbiAuthLoginCol => 'user',
dbiAuthPasswordCol => 'password',
dbiAuthPasswordHash => '',
dbiExportedVars => {},
demoExportedVars => {},
authChoiceParam => 'test',
authChoiceModules => {
sql => 'DBI;DBI;DBI',
slave => 'Slave;DBI;DBI',
demo => 'Demo;Demo;Demo',
},
slaveUserHeader => 'My-Test',
slaveExportedVars => {
name => 'Name',
}
}
}
)
)
{
pass(qq'Expression loaded: "$expr"');
count(1);
return $res;
}
}

View File

@ -189,7 +189,8 @@ ok( $attributes2{'_updateTime'} =~ /^\d{14}$/, 'Timestamp found' )
or print STDERR Dumper( \%attributes2 );
count(3);
ok( $attributes2{_updateTime} - $attributes{_updateTime} >= 3, '_updateTime has been updated' )
ok( $attributes2{_updateTime} - $attributes{_updateTime} >= 3,
'_updateTime has been updated' )
or print STDERR Dumper( \%attributes2 );
count(1);

View File

@ -68,11 +68,9 @@ ok(
);
count(1);
expectOK($res);
ok(
$res->[2]->[0] =~
m%<span trspan="connectedAs">Connected as</span> dwho%,
'Connected as Dwho'
) or print STDERR Dumper( $res->[2]->[0] );
ok( $res->[2]->[0] =~ m%<span trspan="connectedAs">Connected as</span> dwho%,
'Connected as Dwho' )
or print STDERR Dumper( $res->[2]->[0] );
count(1);
# Refresh rights
@ -101,11 +99,9 @@ ok(
count(1);
expectOK($res);
ok(
$res->[2]->[0] =~
m%<span trspan="connectedAs">Connected as</span> dwho%,
'Connected as Dwho'
) or print STDERR Dumper( $res->[2]->[0] );
ok( $res->[2]->[0] =~ m%<span trspan="connectedAs">Connected as</span> dwho%,
'Connected as Dwho' )
or print STDERR Dumper( $res->[2]->[0] );
count(1);
# Log out request

View File

@ -105,7 +105,8 @@ count(1);
( $host, $url, $query ) =
expectForm( $res, undef, '/checkuser', 'user', 'url', 'token' );
ok( $res->[2]->[0] =~ m%<span trspan="checkUserComputeSession">%, 'Found trspan="checkUserComputeSession"' )
ok( $res->[2]->[0] =~ m%<span trspan="checkUserComputeSession">%,
'Found trspan="checkUserComputeSession"' )
or explain( $res->[2]->[0], 'trspan="checkUserComputeSession"' );
ok(
$res->[2]->[0] =~

View File

@ -105,10 +105,9 @@ count(1);
( $host, $url, $query ) =
expectForm( $res, undef, '/checkuser', 'user', 'url', 'token' );
ok(
$res->[2]->[0] =~ m%<span trspan="checkUserComputeSession">%,
'Found trspan="checkUserComputeSession"'
) or explain( $res->[2]->[0], 'trspan="checkUserComputeSession"' );
ok( $res->[2]->[0] =~ m%<span trspan="checkUserComputeSession">%,
'Found trspan="checkUserComputeSession"' )
or explain( $res->[2]->[0], 'trspan="checkUserComputeSession"' );
ok(
$res->[2]->[0] =~
m%<div class="alert alert-success"><b><span trspan="allowed"></span></b></div>%,

View File

@ -39,6 +39,7 @@ SKIP: {
$query =~ s/user=/user=rtyler/;
$query =~ s/password=/password=rtyler/;
#$query =~ s/spoofId=/spoofId=/;
ok(
$res = $client->_post(

View File

@ -329,7 +329,8 @@ ok( scalar keys %attributes == 33, 'Found 33 attributes' )
or print STDERR ( keys %attributes < 33 )
? "Missing attributes -> " . scalar keys %attributes
: "Too much attributes -> " . scalar keys %attributes;
ok( $attributes{'_auth'} eq 'Demo', '_auth' ) or print STDERR Dumper( \%attributes );
ok( $attributes{'_auth'} eq 'Demo', '_auth' )
or print STDERR Dumper( \%attributes );
ok( $attributes{'_httpSession'}, '_httpSession' )
or print STDERR Dumper( \%attributes );
ok( $attributes{'uid'}, 'uid' ) or print STDERR Dumper( \%attributes );

View File

@ -61,11 +61,9 @@ ok(
);
count(1);
expectOK($res);
ok(
$res->[2]->[0] =~
m%<span trspan="connectedAs">Connected as</span> dwho%,
'Connected as dwho'
) or print STDERR Dumper( $res->[2]->[0] );
ok( $res->[2]->[0] =~ m%<span trspan="connectedAs">Connected as</span> dwho%,
'Connected as dwho' )
or print STDERR Dumper( $res->[2]->[0] );
expectAuthenticatedAs( $res, 'dwho' );
count(1);
@ -103,8 +101,7 @@ count(1);
( $host, $url, $query ) =
expectForm( $res, undef, '/checkuser', 'user', 'url' );
ok( $res->[2]->[0] =~ m%<span trspan="checkUser">%,
'Found trspan="checkUser"' )
ok( $res->[2]->[0] =~ m%<span trspan="checkUser">%, 'Found trspan="checkUser"' )
or explain( $res->[2]->[0], 'trspan="checkUser"' );
ok(
$res->[2]->[0] =~
@ -130,11 +127,14 @@ ok( $res->[2]->[0] =~ m%<td class="align-middle">dwho</td>%, 'Found dwho' )
or explain( $res->[2]->[0], 'Header Value: dwho' );
ok( $res->[2]->[0] =~ m%<td class="align-middle">su</td>%, 'Found su' )
or explain( $res->[2]->[0], 'SSO Groups: su' );
ok( $res->[2]->[0] =~ m%<td class="align-middle">su_test</td>%, 'Found su_test' )
ok( $res->[2]->[0] =~ m%<td class="align-middle">su_test</td>%,
'Found su_test' )
or explain( $res->[2]->[0], 'SSO Groups: su_test' );
ok( $res->[2]->[0] !~ m%<td class="align-middle">_test_</td>%, 'NOT found _test_' )
ok( $res->[2]->[0] !~ m%<td class="align-middle">_test_</td>%,
'NOT found _test_' )
or explain( $res->[2]->[0], 'SSO Groups: _test_' );
ok( $res->[2]->[0] !~ m%<td class="align-middle">test_su</td>%, 'NOT found test_su' )
ok( $res->[2]->[0] !~ m%<td class="align-middle">test_su</td>%,
'NOT found test_su' )
or explain( $res->[2]->[0], 'SSO Groups: test_su' );
ok( $res->[2]->[0] =~ m%<td class="align-middle">_whatToTrace</td>%,
'Found _whatToTrace' )

View File

@ -61,11 +61,9 @@ ok(
);
count(1);
expectOK($res);
ok(
$res->[2]->[0] =~
m%<span trspan="connectedAs">Connected as</span> dwho%,
'Connected as dwho'
) or print STDERR Dumper( $res->[2]->[0] );
ok( $res->[2]->[0] =~ m%<span trspan="connectedAs">Connected as</span> dwho%,
'Connected as dwho' )
or print STDERR Dumper( $res->[2]->[0] );
expectAuthenticatedAs( $res, 'dwho' );
count(1);
@ -139,11 +137,14 @@ ok( $res->[2]->[0] =~ m%<td class="align-middle">dwho</td>%, 'Found dwho' )
or explain( $res->[2]->[0], 'Header Value: dwho' );
ok( $res->[2]->[0] =~ m%<td class="align-middle">su</td>%, 'Found su' )
or explain( $res->[2]->[0], 'SSO Groups: su' );
ok( $res->[2]->[0] =~ m%<td class="align-middle">su_test</td>%, 'Found su_test' )
ok( $res->[2]->[0] =~ m%<td class="align-middle">su_test</td>%,
'Found su_test' )
or explain( $res->[2]->[0], 'SSO Groups: su_test' );
ok( $res->[2]->[0] !~ m%<td class="align-middle">_test_</td>%, 'NOT found _test_' )
ok( $res->[2]->[0] !~ m%<td class="align-middle">_test_</td>%,
'NOT found _test_' )
or explain( $res->[2]->[0], 'SSO Groups: _test_' );
ok( $res->[2]->[0] =~ m%<td class="align-middle">test_su</td>%, 'Found test_su' )
ok( $res->[2]->[0] =~ m%<td class="align-middle">test_su</td>%,
'Found test_su' )
or explain( $res->[2]->[0], 'SSO Groups: test_su' );
ok( $res->[2]->[0] =~ m%<td class="align-middle">_whatToTrace</td>%,
'Found _whatToTrace' )

View File

@ -325,8 +325,9 @@ count(16);
my %attributes = map /<td class="text-left">(.+)?<\/td>/g, $res->[2]->[0];
ok( keys %attributes == 31, 'Found 31 attributes' )
or print STDERR "Missing attributes -> " . scalar %attributes;
ok( $attributes{'_auth'} eq 'Demo', '_auth' ) or print STDERR Dumper( \%attributes );
ok( $attributes{'uid'}, 'uid' ) or print STDERR Dumper( \%attributes );
ok( $attributes{'_auth'} eq 'Demo', '_auth' )
or print STDERR Dumper( \%attributes );
ok( $attributes{'uid'}, 'uid' ) or print STDERR Dumper( \%attributes );
ok( $attributes{'testPrefix__auth'}, 'testPrefix__auth' )
or print STDERR Dumper( \%attributes );
ok( $attributes{'testPrefix_uid'} eq 'rtyler', 'testPrefix_uid' )

View File

@ -0,0 +1,154 @@
use Test::More;
use strict;
use IO::String;
require 't/test-lib.pm';
my $maintests = 20;
SKIP: {
eval { require Convert::Base32 };
if ($@) {
skip 'Convert::Base32 is missing', $maintests;
}
require Lemonldap::NG::Common::TOTP;
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
totp2fSelfRegistration => 1,
totp2fActivation => 1,
totp2fTTL => 2,
sfRemovedMsg => '$uid eq "dwho"',
portalMainLogo => 'common/logos/logo_llng_old.png',
}
}
);
my $res;
# Try to authenticate
# -------------------
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23
),
'Auth query'
);
my $id = expectCookie($res);
# TOTP form
ok(
$res = $client->_get(
'/2fregisters',
cookie => "lemonldap=$id",
accept => 'text/html',
),
'Form registration'
);
expectRedirection( $res, qr#/2fregisters/totp$# );
ok(
$res = $client->_get(
'/2fregisters/totp',
cookie => "lemonldap=$id",
accept => 'text/html',
),
'Form registration'
);
ok( $res->[2]->[0] =~ /totpregistration\.(?:min\.)?js/, 'Found TOTP js' );
ok(
$res->[2]->[0] =~ qr%<img src="/static/common/logos/logo_llng_old.png"%,
'Found custom Main Logo'
) or print STDERR Dumper( $res->[2]->[0] );
# JS query
ok(
$res = $client->_post(
'/2fregisters/totp/getkey', IO::String->new(''),
cookie => "lemonldap=$id",
length => 0,
),
'Get new key'
);
eval { $res = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), 'Content is JSON' )
or explain( $res->[2]->[0], 'JSON content' );
my ( $key, $token );
ok( $key = $res->{secret}, 'Found secret' );
ok( $token = $res->{token}, 'Found token' );
$key = Convert::Base32::decode_base32($key);
# Post code
my $code;
ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ),
'Code' );
ok( $code =~ /^\d{6}$/, 'Code contains 6 digits' );
my $s = "code=$code&token=$token&TOTPName=myTOTP";
ok(
$res = $client->_post(
'/2fregisters/totp/verify',
IO::String->new($s),
length => length($s),
cookie => "lemonldap=$id",
),
'Post code'
);
eval { $res = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), 'Content is JSON' )
or explain( $res->[2]->[0], 'JSON content' );
ok( $res->{result} == 1, 'Key is registered' );
# Try to sign-in
$client->logout($id);
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'Auth query'
);
my ( $host, $url, $query ) =
expectForm( $res, undef, '/totp2fcheck', 'token' );
ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ),
'Code' );
$query =~ s/code=/code=$code/;
ok(
$res = $client->_post(
'/totp2fcheck', IO::String->new($query),
length => length($query),
),
'Post code'
);
$id = expectCookie($res);
$client->logout($id);
diag 'Waiting';
sleep 3;
# Try to sign-in
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'Auth query'
);
expectOK($res);
ok(
$res->[2]->[0] =~
qr%<h3 trspan="oneExpired2Fremoved">oneExpired2Fremoved</h3>%,
'Found expired 2F message'
) or print STDERR Dumper( $res->[2]->[0] );
my $c = getCookies($res);
ok( not(%$c), 'No cookie' );
}
count($maintests);
clean_sessions();
done_testing( count() );

View File

@ -3,7 +3,7 @@ use strict;
use IO::String;
require 't/test-lib.pm';
my $maintests = 17;
my $maintests = 18;
SKIP: {
eval { require Convert::Base32 };
@ -59,7 +59,6 @@ SKIP: {
$res->[2]->[0] =~ qr%<img src="/static/common/logos/logo_llng_old.png"%,
'Found custom Main Logo'
) or print STDERR Dumper( $res->[2]->[0] );
count(1);
# JS query
ok(
@ -139,6 +138,7 @@ SKIP: {
);
$id = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
$client->logout($id);
}
count($maintests);

View File

@ -0,0 +1,233 @@
use Test::More;
use strict;
use IO::String;
require 't/test-lib.pm';
my $maintests = 21;
SKIP: {
eval { require Crypt::U2F::Server; require Authen::U2F::Tester };
if ( $@ or $Crypt::U2F::Server::VERSION < 0.42 ) {
skip 'Missing libraries', $maintests;
}
use_ok('Lemonldap::NG::Common::FormEncode');
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
u2fSelfRegistration => 1,
u2fActivation => 1,
portalMainLogo => 'common/logos/logo_llng_old.png',
totp2fTTL => 2,
u2fTTL => 2,
sfRemovedMsg => 1,
}
}
);
my $res;
# Try to authenticate
# -------------------
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23
),
'Auth query'
);
my $id = expectCookie($res);
# U2F form
ok(
$res = $client->_get(
'/2fregisters',
cookie => "lemonldap=$id",
accept => 'text/html',
),
'Form registration'
);
expectRedirection( $res, qr#/2fregisters/u$# );
ok(
$res = $client->_get(
'/2fregisters/u',
cookie => "lemonldap=$id",
accept => 'text/html',
),
'Form registration'
);
ok( $res->[2]->[0] =~ /u2fregistration\.(?:min\.)?js/, 'Found U2F js' );
ok(
$res->[2]->[0] =~ qr%<img src="/static/common/logos/logo_llng_old.png"%,
'Found custom Main Logo'
) or print STDERR Dumper( $res->[2]->[0] );
# Ajax registration request
ok(
$res = $client->_post(
'/2fregisters/u/register', IO::String->new(''),
accept => 'application/json',
cookie => "lemonldap=$id",
length => 0,
),
'Get registration challenge'
);
expectOK($res);
my $data;
eval { $data = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), ' Content is JSON' )
or explain( [ $@, $res->[2] ], 'JSON content' );
ok( ( $data->{challenge} and $data->{appId} ), ' Get challenge and appId' )
or explain( $data, 'challenge and appId' );
# Build U2F tester
my $tester = Authen::U2F::Tester->new(
certificate => Crypt::OpenSSL::X509->new_from_string(
'-----BEGIN CERTIFICATE-----
MIIB6DCCAY6gAwIBAgIJAJKuutkN2sAfMAoGCCqGSM49BAMCME8xCzAJBgNVBAYT
AlVTMQ4wDAYDVQQIDAVUZXhhczEaMBgGA1UECgwRVW50cnVzdGVkIFUyRiBPcmcx
FDASBgNVBAMMC3ZpcnR1YWwtdTJmMB4XDTE4MDMyODIwMTc1OVoXDTI3MTIyNjIw
MTc1OVowTzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRowGAYDVQQKDBFV
bnRydXN0ZWQgVTJGIE9yZzEUMBIGA1UEAwwLdmlydHVhbC11MmYwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAAQTij+9mI1FJdvKNHLeSQcOW4ob3prvIXuEGJMrQeJF
6OYcgwxrVqsmNMl5w45L7zx8ryovVOti/mtqkh2pQjtpo1MwUTAdBgNVHQ4EFgQU
QXKKf+rrZwA4WXDCU/Vebe4gYXEwHwYDVR0jBBgwFoAUQXKKf+rrZwA4WXDCU/Ve
be4gYXEwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAiCdOEmw5
hknzHR1FoyFZKRrcJu17a1PGcqTFMJHTC70CIHeCZ8KVuuMIPjoofQd1l1E221rv
RJY1Oz1fUNbrIPsL
-----END CERTIFICATE-----', Crypt::OpenSSL::X509::FORMAT_PEM()
),
key => Crypt::PK::ECC->new(
\'-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOdbZw1swQIL+RZoDQ9zwjWY5UjA1NO81WWjwbmznUbgoAoGCCqGSM49
AwEHoUQDQgAEE4o/vZiNRSXbyjRy3kkHDluKG96a7yF7hBiTK0HiRejmHIMMa1ar
JjTJecOOS+88fK8qL1TrYv5rapIdqUI7aQ==
-----END EC PRIVATE KEY-----'
),
);
my $r = $tester->register( $data->{appId}, $data->{challenge} );
ok( $r->is_success, ' Good challenge value' )
or diag( $r->error_message );
my $registrationData = JSON::to_json( {
clientData => $r->client_data,
errorCode => 0,
registrationData => $r->registration_data,
version => "U2F_V2"
}
);
my ( $host, $url, $query );
$query = Lemonldap::NG::Common::FormEncode::build_urlencoded(
registration => $registrationData,
challenge => $res->[2]->[0],
);
ok(
$res = $client->_post(
'/2fregisters/u/registration', IO::String->new($query),
length => length($query),
accept => 'application/json',
cookie => "lemonldap=$id",
),
'Push registration data'
);
expectOK($res);
eval { $data = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), ' Content is JSON' )
or explain( [ $@, $res->[2] ], 'JSON content' );
ok( $data->{result} == 1, 'Key is registered' )
or explain( $data, '"result":1' );
# Try to sign-in
$client->logout($id);
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'Auth query'
);
( $host, $url, $query ) = expectForm( $res, undef, '/u2fcheck', 'token' );
# Get challenge
ok( $res->[2]->[0] =~ /^.*"keyHandle".*$/m, ' get keyHandle' );
$data = $&;
eval { $data = JSON::from_json($data) };
ok( not($@), ' Content is JSON' )
or explain( [ $@, $data ], 'JSON content' );
# Build U2F signature
$r =
$tester->sign( $data->{appId}, $data->{challenge},
$data->{registeredKeys}->[0]->{keyHandle} );
ok( $r->is_success, ' Good challenge value' )
or diag( $r->error_message );
my $sign = JSON::to_json( {
errorCode => 0,
signatureData => $r->signature_data,
clientData => $r->client_data,
keyHandle => $data->{registeredKeys}->[0]->{keyHandle},
}
);
$sign =
Lemonldap::NG::Common::FormEncode::build_urlencoded( signature => $sign );
$query =~ s/signature=/$sign/e;
$query =~ s/challenge=/challenge=$data->{challenge}/;
# POST result
ok(
$res = $client->_post(
'/u2fcheck',
IO::String->new($query),
length => length($query),
),
'Push U2F signature'
);
# See https://github.com/mschout/perl-authen-u2f-tester/issues/2
if ( $Authen::U2F::Tester::VERSION >= 0.03 ) {
$id = expectCookie($res);
$client->logout($id);
}
else {
count(1);
pass(
'Authen::2F::Tester-0.02 signatures are not recognized by Yubico library'
);
}
diag 'Waiting';
sleep 3;
# Try to sign-in
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'Auth query'
);
expectOK($res);
ok(
$res->[2]->[0] =~
qr%<h3 trspan="oneExpired2Fremoved">oneExpired2Fremoved</h3>%,
'Found expired 2F message'
) or print STDERR Dumper( $res->[2]->[0] );
my $c = getCookies($res);
ok( not(%$c), 'No cookie' );
# $id = expectCookie($res);
# expectRedirection( $res, 'http://auth.example.com/' );
# $client->logout($id);
}
count($maintests);
clean_sessions();
done_testing( count() );

View File

@ -0,0 +1,222 @@
use Test::More;
use strict;
use IO::String;
require 't/test-lib.pm';
my $maintests = 19;
SKIP: {
eval { require Crypt::U2F::Server; require Authen::U2F::Tester };
if ( $@ or $Crypt::U2F::Server::VERSION < 0.42 ) {
skip 'Missing libraries', $maintests;
}
use_ok('Lemonldap::NG::Common::FormEncode');
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
u2fSelfRegistration => 1,
u2fActivation => 1,
portalMainLogo => 'common/logos/logo_llng_old.png',
totp2fTTL => 2,
u2fTTL => 2,
}
}
);
my $res;
# Try to authenticate
# -------------------
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23
),
'Auth query'
);
my $id = expectCookie($res);
# U2F form
ok(
$res = $client->_get(
'/2fregisters',
cookie => "lemonldap=$id",
accept => 'text/html',
),
'Form registration'
);
expectRedirection( $res, qr#/2fregisters/u$# );
ok(
$res = $client->_get(
'/2fregisters/u',
cookie => "lemonldap=$id",
accept => 'text/html',
),
'Form registration'
);
ok( $res->[2]->[0] =~ /u2fregistration\.(?:min\.)?js/, 'Found U2F js' );
ok(
$res->[2]->[0] =~ qr%<img src="/static/common/logos/logo_llng_old.png"%,
'Found custom Main Logo'
) or print STDERR Dumper( $res->[2]->[0] );
# Ajax registration request
ok(
$res = $client->_post(
'/2fregisters/u/register', IO::String->new(''),
accept => 'application/json',
cookie => "lemonldap=$id",
length => 0,
),
'Get registration challenge'
);
expectOK($res);
my $data;
eval { $data = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), ' Content is JSON' )
or explain( [ $@, $res->[2] ], 'JSON content' );
ok( ( $data->{challenge} and $data->{appId} ), ' Get challenge and appId' )
or explain( $data, 'challenge and appId' );
# Build U2F tester
my $tester = Authen::U2F::Tester->new(
certificate => Crypt::OpenSSL::X509->new_from_string(
'-----BEGIN CERTIFICATE-----
MIIB6DCCAY6gAwIBAgIJAJKuutkN2sAfMAoGCCqGSM49BAMCME8xCzAJBgNVBAYT
AlVTMQ4wDAYDVQQIDAVUZXhhczEaMBgGA1UECgwRVW50cnVzdGVkIFUyRiBPcmcx
FDASBgNVBAMMC3ZpcnR1YWwtdTJmMB4XDTE4MDMyODIwMTc1OVoXDTI3MTIyNjIw
MTc1OVowTzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRowGAYDVQQKDBFV
bnRydXN0ZWQgVTJGIE9yZzEUMBIGA1UEAwwLdmlydHVhbC11MmYwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAAQTij+9mI1FJdvKNHLeSQcOW4ob3prvIXuEGJMrQeJF
6OYcgwxrVqsmNMl5w45L7zx8ryovVOti/mtqkh2pQjtpo1MwUTAdBgNVHQ4EFgQU
QXKKf+rrZwA4WXDCU/Vebe4gYXEwHwYDVR0jBBgwFoAUQXKKf+rrZwA4WXDCU/Ve
be4gYXEwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAiCdOEmw5
hknzHR1FoyFZKRrcJu17a1PGcqTFMJHTC70CIHeCZ8KVuuMIPjoofQd1l1E221rv
RJY1Oz1fUNbrIPsL
-----END CERTIFICATE-----', Crypt::OpenSSL::X509::FORMAT_PEM()
),
key => Crypt::PK::ECC->new(
\'-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOdbZw1swQIL+RZoDQ9zwjWY5UjA1NO81WWjwbmznUbgoAoGCCqGSM49
AwEHoUQDQgAEE4o/vZiNRSXbyjRy3kkHDluKG96a7yF7hBiTK0HiRejmHIMMa1ar
JjTJecOOS+88fK8qL1TrYv5rapIdqUI7aQ==
-----END EC PRIVATE KEY-----'
),
);
my $r = $tester->register( $data->{appId}, $data->{challenge} );
ok( $r->is_success, ' Good challenge value' )
or diag( $r->error_message );
my $registrationData = JSON::to_json( {
clientData => $r->client_data,
errorCode => 0,
registrationData => $r->registration_data,
version => "U2F_V2"
}
);
my ( $host, $url, $query );
$query = Lemonldap::NG::Common::FormEncode::build_urlencoded(
registration => $registrationData,
challenge => $res->[2]->[0],
);
ok(
$res = $client->_post(
'/2fregisters/u/registration', IO::String->new($query),
length => length($query),
accept => 'application/json',
cookie => "lemonldap=$id",
),
'Push registration data'
);
expectOK($res);
eval { $data = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), ' Content is JSON' )
or explain( [ $@, $res->[2] ], 'JSON content' );
ok( $data->{result} == 1, 'Key is registered' )
or explain( $data, '"result":1' );
# Try to sign-in
$client->logout($id);
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'Auth query'
);
( $host, $url, $query ) = expectForm( $res, undef, '/u2fcheck', 'token' );
# Get challenge
ok( $res->[2]->[0] =~ /^.*"keyHandle".*$/m, ' get keyHandle' );
$data = $&;
eval { $data = JSON::from_json($data) };
ok( not($@), ' Content is JSON' )
or explain( [ $@, $data ], 'JSON content' );
# Build U2F signature
$r =
$tester->sign( $data->{appId}, $data->{challenge},
$data->{registeredKeys}->[0]->{keyHandle} );
ok( $r->is_success, ' Good challenge value' )
or diag( $r->error_message );
my $sign = JSON::to_json( {
errorCode => 0,
signatureData => $r->signature_data,
clientData => $r->client_data,
keyHandle => $data->{registeredKeys}->[0]->{keyHandle},
}
);
$sign =
Lemonldap::NG::Common::FormEncode::build_urlencoded( signature => $sign );
$query =~ s/signature=/$sign/e;
$query =~ s/challenge=/challenge=$data->{challenge}/;
# POST result
ok(
$res = $client->_post(
'/u2fcheck',
IO::String->new($query),
length => length($query),
),
'Push U2F signature'
);
# See https://github.com/mschout/perl-authen-u2f-tester/issues/2
if ( $Authen::U2F::Tester::VERSION >= 0.03 ) {
$id = expectCookie($res);
$client->logout($id);
}
else {
count(1);
pass(
'Authen::2F::Tester-0.02 signatures are not recognized by Yubico library'
);
}
diag 'Waiting';
sleep 3;
# Try to sign-in
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'Auth query'
);
$id = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
$client->logout($id);
}
count($maintests);
clean_sessions();
done_testing( count() );

View File

@ -187,7 +187,8 @@ JjTJecOOS+88fK8qL1TrYv5rapIdqUI7aQ==
# See https://github.com/mschout/perl-authen-u2f-tester/issues/2
if ( $Authen::U2F::Tester::VERSION >= 0.03 ) {
expectCookie($res);
$id = expectCookie($res);
$client->logout($id);
}
else {
count(1);