Merge branch 'v2.0'
This commit is contained in:
commit
d27e4bcc55
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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' );
|
||||
|
||||
|
|
|
@ -279,6 +279,7 @@ sub defaultValues {
|
|||
'samlSPSSODescriptorWantAssertionsSigned' => 1,
|
||||
'securedCookie' => 0,
|
||||
'sfEngine' => '::2F::Engines::Default',
|
||||
'sfRemovedMsg' => 0,
|
||||
'sfRequired' => 0,
|
||||
'showLanguages' => 1,
|
||||
'slaveAuthnLevel' => 2,
|
||||
|
|
|
@ -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( {
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
]
|
||||
},
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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...");
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package Lemonldap::NG::Portal::Register::Custom;
|
||||
|
||||
use strict;
|
||||
use Mouse;
|
||||
|
||||
extends 'Lemonldap::NG::Portal::Register::Base';
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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":"نطاق",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 ?",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 ?",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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] =~
|
||||
|
|
|
@ -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>%,
|
||||
|
|
|
@ -39,6 +39,7 @@ SKIP: {
|
|||
|
||||
$query =~ s/user=/user=rtyler/;
|
||||
$query =~ s/password=/password=rtyler/;
|
||||
|
||||
#$query =~ s/spoofId=/spoofId=/;
|
||||
ok(
|
||||
$res = $client->_post(
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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' )
|
||||
|
|
|
@ -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' )
|
||||
|
|
|
@ -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' )
|
||||
|
|
|
@ -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() );
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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() );
|
|
@ -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() );
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue