Merge branch 'v2.0'

This commit is contained in:
Xavier Guimard 2019-07-01 20:21:42 +02:00
commit af63d55c08
52 changed files with 1251 additions and 82 deletions

12
COPYING
View File

@ -111,6 +111,18 @@ License: CC-3
Comment: This work, "star1.png", is a derivative of Comment: This work, "star1.png", is a derivative of
"Golden star with red border.png" by ANGELUS, under CC-BYSA-3.0. "Golden star with red border.png" by ANGELUS, under CC-BYSA-3.0.
Files: lemonldap-ng-portal/site/htdocs/static/common/icons/switchcontext_OFF.png
Copyright: Christophe Maudoux <chrmdx@gmail.com>
License: CC-4
Comment: This work, "switchcontext_OFF.png", is a derivative of
"Theater-Masken - Silhouetten und kontur vektoren" by Natasha Sinegina, under CC-BY-4.0.
Files: lemonldap-ng-portal/site/htdocs/static/common/icons/switchcontext_ON.png
Copyright: Christophe Maudoux <chrmdx@gmail.com>
License: CC-4
Comment: This work, "switchcontext_ON.png", is a derivative of
"Theater-Masken - Silhouetten und kontur vektoren" by Natasha Sinegina, under CC-BY-4.0.
Files: lemonldap-ng-portal/site/htdocs/static/common/modules/CustomAuth.png Files: lemonldap-ng-portal/site/htdocs/static/common/modules/CustomAuth.png
Copyright: Christophe Maudoux <chrmdx@gmail.com> Copyright: Christophe Maudoux <chrmdx@gmail.com>
License: CC-3 License: CC-3

View File

@ -326,6 +326,8 @@ status = 0
;hideSignature = 1 ;hideSignature = 1
; Set ServiceToken timeout ; Set ServiceToken timeout
;handlerServiceTokenTTL = 30 ;handlerServiceTokenTTL = 30
; Set Impersonation/ContextSwitching prefix
; impersonationPrefix = real_
useRedirectOnError = 1 useRedirectOnError = 1
; Zimbra Handler parameters ; Zimbra Handler parameters

View File

@ -24,7 +24,7 @@ use constant MANAGERSECTION => "manager";
use constant SESSIONSEXPLORERSECTION => "sessionsExplorer"; use constant SESSIONSEXPLORERSECTION => "sessionsExplorer";
use constant APPLYSECTION => "apply"; 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 $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|fRemovedUseNotif|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 $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|fRemovedUseNotif|howLanguages|slByAjax)|o(?:idc(?:ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|RPMetaDataOptions(?:LogoutSessionRequired|BypassConsent|RequirePKCE|Public)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:User(?:Display(?:PersistentInfo|EmptyValues))?|State|XSS)|o(?:ntextSwitchingStopWithLogout|rsEnabled)|da)|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)|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|d(?:isablePersistentStorage|biDynamicHashEnabled)|rest(?:(?:Session|Config)Server|ExportSecretKeys)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs)|bruteForceProtection)$/;
our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' ); our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' );

View File

@ -18,38 +18,41 @@ sub defaultValues {
'authChoiceParam' => 'lmAuth', 'authChoiceParam' => 'lmAuth',
'authentication' => 'Demo', 'authentication' => 'Demo',
'available2F' => 'UTOTP,TOTP,U2F,REST,Mail2F,Ext2F,Yubikey', 'available2F' => 'UTOTP,TOTP,U2F,REST,Mail2F,Ext2F,Yubikey',
'available2FSelfRegistration' => 'TOTP,U2F,Yubikey', 'available2FSelfRegistration' => 'TOTP,U2F,Yubikey',
'bruteForceProtectionMaxAge' => 300, 'bruteForceProtectionMaxAge' => 300,
'bruteForceProtectionMaxFailed' => 3, 'bruteForceProtectionMaxFailed' => 3,
'bruteForceProtectionTempo' => 30, 'bruteForceProtectionTempo' => 30,
'captcha_mail_enabled' => 1, 'captcha_mail_enabled' => 1,
'captcha_register_enabled' => 1, 'captcha_register_enabled' => 1,
'captcha_size' => 6, 'captcha_size' => 6,
'casAccessControlPolicy' => 'none', 'casAccessControlPolicy' => 'none',
'casAuthnLevel' => 1, 'casAuthnLevel' => 1,
'checkTime' => 600, 'checkTime' => 600,
'checkUserHiddenAttributes' => '_loginHistory hGroups', 'checkUserHiddenAttributes' => '_loginHistory hGroups',
'checkUserIdRule' => 1, 'checkUserIdRule' => 1,
'checkXSS' => 1, 'checkXSS' => 1,
'confirmFormMethod' => 'post', 'confirmFormMethod' => 'post',
'cookieName' => 'lemonldap', 'contextSwitchingIdRule' => 1,
'corsAllow_Credentials' => 'true', 'contextSwitchingRule' => 0,
'corsAllow_Headers' => '*', 'contextSwitchingStopWithLogout' => 1,
'corsAllow_Methods' => 'POST,GET', 'cookieName' => 'lemonldap',
'corsAllow_Origin' => '*', 'corsAllow_Credentials' => 'true',
'corsEnabled' => 1, 'corsAllow_Headers' => '*',
'corsExpose_Headers' => '*', 'corsAllow_Methods' => 'POST,GET',
'corsMax_Age' => '86400', 'corsAllow_Origin' => '*',
'cspConnect' => '\'self\'', 'corsEnabled' => 1,
'cspDefault' => '\'self\'', 'corsExpose_Headers' => '*',
'cspFont' => '\'self\'', 'corsMax_Age' => '86400',
'cspFormAction' => '\'self\'', 'cspConnect' => '\'self\'',
'cspImg' => '\'self\' data:', 'cspDefault' => '\'self\'',
'cspScript' => '\'self\'', 'cspFont' => '\'self\'',
'cspStyle' => '\'self\'', 'cspFormAction' => '\'self\'',
'dbiAuthnLevel' => 2, 'cspImg' => '\'self\' data:',
'dbiExportedVars' => {}, 'cspScript' => '\'self\'',
'demoExportedVars' => { 'cspStyle' => '\'self\'',
'dbiAuthnLevel' => 2,
'dbiExportedVars' => {},
'demoExportedVars' => {
'cn' => 'cn', 'cn' => 'cn',
'mail' => 'mail', 'mail' => 'mail',
'uid' => 'uid' 'uid' => 'uid'

View File

@ -47,6 +47,9 @@ Available actions:
- set <key> <value> : set parameter(s) value(s) - set <key> <value> : set parameter(s) value(s)
- addKey <key> <subkey> <value> : add or set a subkey in a parameter - addKey <key> <subkey> <value> : add or set a subkey in a parameter
- delKey <key> <subkey> : delete subkey of a parameter - delKey <key> <subkey> : delete subkey of a parameter
- save : export configuration to STDOUT
- restore - : import configuration from STDIN
- restore <file> : import configuration from file
See Lemonldap::NG::Common::Cli(3) or Lemonldap::NG::Manager::CLi(3) for more See Lemonldap::NG::Common::Cli(3) or Lemonldap::NG::Manager::CLi(3) for more
}; };

View File

@ -5,7 +5,7 @@
# change 'tests => 1' to 'tests => last_test_to_print'; # change 'tests => 1' to 'tests => last_test_to_print';
use Test::More tests => 22; use Test::More tests => 21;
use Digest::MD5 qw(md5 md5_hex md5_base64); use Digest::MD5 qw(md5 md5_hex md5_base64);
use strict; use strict;
@ -31,10 +31,7 @@ foreach my $i ( 1 .. 17 ) {
$s = join( '', map { chr( int( rand(94) ) + 33 ) } ( 1 .. $i ) ); $s = join( '', map { chr( int( rand(94) ) + 33 ) } ( 1 .. $i ) );
ok( $c->decrypt( $c->encrypt($s) ) eq $s, ok( $c->decrypt( $c->encrypt($s) ) eq $s,
"Test of base64 encrypting with $i characters string" ) "Test of base64 encrypting with $i characters string" )
or diag "Source: $s\nCypher: " or diagErr( $s, $c->decrypt( $c->encrypt($s) ), $c->encrypt($s) );
. $c->encrypt($s)
. "\nUncipher:"
. $c->decrypt( $c->encrypt($s) );
} }
my $data = md5_hex(rand); my $data = md5_hex(rand);
@ -42,13 +39,23 @@ my $secondKey = md5(rand);
ok( ok(
$c->decryptHex( $c->encryptHex( $data, $secondKey ), $secondKey ) eq $data, $c->decryptHex( $c->encryptHex( $data, $secondKey ), $secondKey ) eq $data,
"Test of hexadecimal encrypting" "Test of hexadecimal encrypting"
); )
or diagErr(
$data,
$c->decryptHex( $c->encryptHex( $data, $secondKey ), $secondKey ),
$c->encryptHex( $data, $secondKey )
);
# Test a long value, and replace carriage return by %0A # Test a long value, and replace carriage return by %0A
my $long = "f5a1f72e7ab2f7712855a068af0066f36bfcf2c87e6feb9cf4200da1868e1dfe"; my $long = Lemonldap::NG::Common::Crypto::srandom()->randpattern( "s" x 1000 );
ok( $c->decrypt( $c->encrypt($long) ) eq $long, ok( $c->decrypt( $c->encrypt($long) ) eq $long,
"Test of long value encrypting" ); "Test of long value encrypting" )
ok( or diagErr(
$c->decryptHex( $c->encryptHex($long) ) eq $long, $long,
"Test of long value encrypting (hex)" $c->decryptHex( $c->encryptHex($long) ),
); $c->encryptHex($long)
);
sub diagErr {
diag "Expect: $_[0]\nGet : $_[1]\nCipher: $_[2]";
}

View File

@ -208,6 +208,7 @@ t/11-save-changed-conf-with-confirmation.t
t/12-save-changed-conf.t t/12-save-changed-conf.t
t/14-bad-changes-in-conf.t t/14-bad-changes-in-conf.t
t/15-combination.t t/15-combination.t
t/16-cli.t
t/20-test-coverage.t t/20-test-coverage.t
t/40-sessions.t t/40-sessions.t
t/50-notifications-DBI.t t/50-notifications-DBI.t

View File

@ -926,6 +926,21 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
], ],
'type' => 'select' 'type' => 'select'
}, },
'contextSwitchingIdRule' => {
'default' => 1,
'test' => sub {
return perlExpr(@_);
},
'type' => 'text'
},
'contextSwitchingRule' => {
'default' => 0,
'type' => 'boolOrExpr'
},
'contextSwitchingStopWithLogout' => {
'default' => 1,
'type' => 'bool'
},
'cookieExpiration' => { 'cookieExpiration' => {
'type' => 'int' 'type' => 'int'
}, },
@ -1083,6 +1098,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'test' => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, 'test' => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/,
'type' => 'keyTextContainer' 'type' => 'keyTextContainer'
}, },
'disablePersistentStorage' => {
'default' => 0,
'type' => 'bool'
},
'domain' => { 'domain' => {
'default' => 'example.com', 'default' => 'example.com',
'msgFail' => '__badDomainName__', 'msgFail' => '__badDomainName__',

View File

@ -479,12 +479,14 @@ sub attributes {
type => 'boolOrExpr', type => 'boolOrExpr',
default => 0, default => 0,
documentation => 'Impersonation activation rule', documentation => 'Impersonation activation rule',
flags => 'p',
}, },
impersonationIdRule => { impersonationIdRule => {
type => 'text', type => 'text',
test => sub { return perlExpr(@_) }, test => sub { return perlExpr(@_) },
default => 1, default => 1,
documentation => 'Impersonation identities rule', documentation => 'Impersonation identities rule',
flags => 'p',
}, },
impersonationHiddenAttributes => { impersonationHiddenAttributes => {
type => 'text', type => 'text',
@ -498,6 +500,25 @@ sub attributes {
documentation => 'Skip session empty values', documentation => 'Skip session empty values',
flags => 'p', flags => 'p',
}, },
contextSwitchingRule => {
type => 'boolOrExpr',
default => 0,
documentation => 'Context switching activation rule',
flags => 'p',
},
contextSwitchingIdRule => {
type => 'text',
test => sub { return perlExpr(@_) },
default => 1,
documentation => 'Context switching identities rule',
flags => 'p',
},
contextSwitchingStopWithLogout => {
type => 'bool',
default => 1,
documentation => 'Stop context switching by logout',
flags => 'p',
},
skipRenewConfirmation => { skipRenewConfirmation => {
type => 'bool', type => 'bool',
default => 0, default => 0,
@ -1134,7 +1155,11 @@ sub attributes {
keyMsgFail => '__invalidSessionData__', keyMsgFail => '__invalidSessionData__',
documentation => 'Data to remember in login history', documentation => 'Data to remember in login history',
}, },
disablePersistentStorage => {
default => 0,
type => 'bool',
documentation => 'Enabled persistent storage',
},
# SAML issuer # SAML issuer
issuerDBSAMLActivation => { issuerDBSAMLActivation => {
default => 0, default => 0,

View File

@ -554,9 +554,11 @@ sub tree {
{ {
title => 'persistentSessions', title => 'persistentSessions',
nodes => [ nodes => [
'persistentStorage', 'persistentStorageOptions' 'disablePersistentStorage',
'persistentStorage',
'persistentStorageOptions'
] ]
} },
] ]
}, },
{ {
@ -669,12 +671,22 @@ sub tree {
nodes => [ nodes => [
'impersonationRule', 'impersonationRule',
'impersonationIdRule', 'impersonationIdRule',
'impersonationPrefix',
'impersonationHiddenAttributes', 'impersonationHiddenAttributes',
'impersonationSkipEmptyValues', 'impersonationSkipEmptyValues',
'impersonationMergeSSOgroups', 'impersonationMergeSSOgroups',
] ]
}, },
{
title => 'contextSwitching',
help => 'contextswitching.html',
form => 'simpleInputContainer',
nodes => [
'contextSwitchingRule',
'contextSwitchingIdRule',
'contextSwitchingStopWithLogout',
#'contextSwitchingHiddenAttributes',
]
},
] ]
}, },
{ {

View File

@ -203,15 +203,20 @@ sub save {
sub restore { sub restore {
my ( $self, $file ) = @_; my ( $self, $file ) = @_;
unless ($file) {
die "No file provided. Aborting";
}
require IO::String; require IO::String;
my $conf; my $conf;
if ( $file eq '-' ) { if ( $file eq '-' ) {
$conf = join '', <STDIN>; $conf = join '', <STDIN>;
} }
else { else {
open my $f, $file; die "Unable to read $file" unless ( -r $file );
open( my $f, $file ) or die $!;
$conf = join '', <$f>; $conf = join '', <$f>;
close $f; close $f;
die "Empty or malformed file $file" unless ( $conf =~ /\w/s );
} }
my $res = $self->_post( '/confs/raw', '', IO::String->new($conf), my $res = $self->_post( '/confs/raw', '', IO::String->new($conf),
'application/json', length($conf) ); 'application/json', length($conf) );

View File

@ -643,16 +643,37 @@ sub tests {
return 1; return 1;
}, },
# Warn if Impersonation is enabled without prefix # Warn if Impersonation and ContextSwitching are simultaneously enabled
impersonationPrefix => sub { impersonation => sub {
return 1 unless ( $conf->{impersonationRule} );
return ( 1, return ( 1,
"Impersonation is enabled without real attributes prefix" ) "Impersonation and ContextSwitching are simultaneously enabled" )
unless ( $conf->{impersonationPrefix} ); if ( $conf->{impersonationRule} && $conf->{contextSwitchingRule} );
# Return # Return
return 1; return 1;
}, },
# Warn if persistent storage is disabled with 2FA, History, OIDCConsents and Notifications
persistentStorage => sub {
return 1 unless ( $conf->{disablePersistentStorage} );
return ( 1, "2FA enabled WITHOUT persistent session storage" )
if ( $conf->{totp2fActivation}
|| $conf->{yubikey2fActivation}
|| $conf->{u2fActivation}
|| $conf->{utotp2fActivation} );
return ( 1, "History enabled WITHOUT persistent session storage" )
if ( $conf->{loginHistoryEnabled} );
return ( 1,
"OIDC consents enabled WITHOUT persistent session storage" )
if ( $conf->{portalDisplayOidcConsents} );
return ( 1,
"Notifications enabled WITHOUT persistent session storage" )
if ( $conf->{notification} );
# Return
return 1;
},
}; };
} }

View File

@ -140,6 +140,11 @@
"categoryName":"اسم الفئة", "categoryName":"اسم الفئة",
"cda":"نطاقات متعددة", "cda":"نطاقات متعددة",
"contentSecurityPolicy":"السياسة الأمنية للمحتوى", "contentSecurityPolicy":"السياسة الأمنية للمحتوى",
"contextSwitching":"Switch context anoter user",
"contextSwitchingHiddenAttributes":"Hidden attributes",
"contextSwitchingIdRule":"Identities use rule",
"contextSwitchingRule":"استخدام القاعدة",
"contextSwitchingStopWithLogout":"Stop by logout",
"cspDefault":"القيمة الاعتيادية ", "cspDefault":"القيمة الاعتيادية ",
"cspFormAction":"Form destinations", "cspFormAction":"Form destinations",
"cspImg":"مصدر الصورة", "cspImg":"مصدر الصورة",
@ -253,6 +258,7 @@
"emptyConf":"إعدادات فارغة", "emptyConf":"إعدادات فارغة",
"emptyValueNotAllowed":"القيمة الفارغة غير مسموح بها", "emptyValueNotAllowed":"القيمة الفارغة غير مسموح بها",
"enabled":"مفعلة", "enabled":"مفعلة",
"disablePersistentStorage":"Disable storage",
"enterPassword":"أدخل كلمة المرور (اختياري)", "enterPassword":"أدخل كلمة المرور (اختياري)",
"error":"خطأ", "error":"خطأ",
"errors":"ERRORS", "errors":"ERRORS",
@ -307,7 +313,6 @@
"impersonationIdRule":"Identities use rule", "impersonationIdRule":"Identities use rule",
"impersonationHiddenAttributes":"السمات المخفية", "impersonationHiddenAttributes":"السمات المخفية",
"impersonationMergeSSOgroups":"Merge spoofed and real SSO groups", "impersonationMergeSSOgroups":"Merge spoofed and real SSO groups",
"impersonationPrefix":"Real attributes prefix",
"impersonationSkipEmptyValues":"Skip empty values", "impersonationSkipEmptyValues":"Skip empty values",
"incompleteForm":"الحقول المطلوبة مفقودة", "incompleteForm":"الحقول المطلوبة مفقودة",
"index":"فهرس", "index":"فهرس",

View File

@ -140,6 +140,11 @@
"categoryName":"Category name", "categoryName":"Category name",
"cda":"Mehrere Domains", "cda":"Mehrere Domains",
"contentSecurityPolicy":"Content security policy", "contentSecurityPolicy":"Content security policy",
"contextSwitching":"Switch context anoter user",
"contextSwitchingHiddenAttributes":"Hidden attributes",
"contextSwitchingIdRule":"Identities use rule",
"contextSwitchingRule":"Use rule",
"contextSwitchingStopWithLogout":"Stop by logout",
"cspDefault":"Default value", "cspDefault":"Default value",
"cspFormAction":"Form destinations", "cspFormAction":"Form destinations",
"cspImg":"Image source", "cspImg":"Image source",
@ -252,6 +257,7 @@
"emptyConf":"Empty configuration", "emptyConf":"Empty configuration",
"emptyValueNotAllowed":"Empty value not allowed", "emptyValueNotAllowed":"Empty value not allowed",
"enabled":"Aktiviert", "enabled":"Aktiviert",
"disablePersistentStorage":"Disable storage",
"enterPassword":"Enter password (optional)", "enterPassword":"Enter password (optional)",
"error":"Error", "error":"Error",
"errors":"ERRORS", "errors":"ERRORS",
@ -306,7 +312,6 @@
"impersonationIdRule":"Identities use rule", "impersonationIdRule":"Identities use rule",
"impersonationHiddenAttributes":"Hidden attributes", "impersonationHiddenAttributes":"Hidden attributes",
"impersonationMergeSSOgroups":"Merge spoofed and real SSO groups", "impersonationMergeSSOgroups":"Merge spoofed and real SSO groups",
"impersonationPrefix":"Real attributes prefix",
"impersonationSkipEmptyValues":"Skip empty values", "impersonationSkipEmptyValues":"Skip empty values",
"incompleteForm":"Required fields are missing", "incompleteForm":"Required fields are missing",
"index":"Index", "index":"Index",

View File

@ -140,6 +140,11 @@
"categoryName":"Category name", "categoryName":"Category name",
"cda":"Multiple domains", "cda":"Multiple domains",
"contentSecurityPolicy":"Content security policy", "contentSecurityPolicy":"Content security policy",
"contextSwitching":"Switch context anoter user",
"contextSwitchingHiddenAttributes":"Hidden attributes",
"contextSwitchingIdRule":"Identities use rule",
"contextSwitchingRule":"Use rule",
"contextSwitchingStopWithLogout":"Stop by logout",
"cspDefault":"Default value", "cspDefault":"Default value",
"cspFormAction":"Form destinations", "cspFormAction":"Form destinations",
"cspImg":"Image source", "cspImg":"Image source",
@ -252,6 +257,7 @@
"emptyConf":"Empty configuration", "emptyConf":"Empty configuration",
"emptyValueNotAllowed":"Empty value not allowed", "emptyValueNotAllowed":"Empty value not allowed",
"enabled":"Enabled", "enabled":"Enabled",
"disablePersistentStorage":"Disable storage",
"enterPassword":"Enter password (optional)", "enterPassword":"Enter password (optional)",
"error":"Error", "error":"Error",
"errors":"ERRORS", "errors":"ERRORS",
@ -306,7 +312,6 @@
"impersonationIdRule":"Identities use rule", "impersonationIdRule":"Identities use rule",
"impersonationHiddenAttributes":"Hidden attributes", "impersonationHiddenAttributes":"Hidden attributes",
"impersonationMergeSSOgroups":"Merge spoofed and real SSO groups", "impersonationMergeSSOgroups":"Merge spoofed and real SSO groups",
"impersonationPrefix":"Real attributes prefix",
"impersonationSkipEmptyValues":"Skip empty values", "impersonationSkipEmptyValues":"Skip empty values",
"incompleteForm":"Required fields are missing", "incompleteForm":"Required fields are missing",
"index":"Index", "index":"Index",

View File

@ -140,6 +140,11 @@
"categoryName":"Nom de la catégorie", "categoryName":"Nom de la catégorie",
"cda":"Domaines multiples", "cda":"Domaines multiples",
"contentSecurityPolicy":"Politique de sécurité de contenu", "contentSecurityPolicy":"Politique de sécurité de contenu",
"contextSwitching":"Endossement d'identité",
"contextSwitchingHiddenAttributes":"Attributs masqués",
"contextSwitchingIdRule":"Règle d'utilisation des identités",
"contextSwitchingRule":"Règle d'utilisation",
"contextSwitchingStopWithLogout":"Arrêt par déconnexion",
"cspDefault":"Valeur par défaut", "cspDefault":"Valeur par défaut",
"cspFormAction":"Destinations des formulaires", "cspFormAction":"Destinations des formulaires",
"cspImg":"Sources des images", "cspImg":"Sources des images",
@ -252,6 +257,7 @@
"emptyConf":"Configuration vide", "emptyConf":"Configuration vide",
"emptyValueNotAllowed":"Valeur nulle non accordé", "emptyValueNotAllowed":"Valeur nulle non accordé",
"enabled":"Activé", "enabled":"Activé",
"disablePersistentStorage":"Désactiver le stockage",
"enterPassword":"Entrer le mot de passe (optionnel)", "enterPassword":"Entrer le mot de passe (optionnel)",
"error":"Erreur", "error":"Erreur",
"errors":"ERREURS", "errors":"ERREURS",
@ -306,7 +312,6 @@
"impersonationIdRule":"Règle d'utilisation des identités", "impersonationIdRule":"Règle d'utilisation des identités",
"impersonationHiddenAttributes":"Attributs masqués", "impersonationHiddenAttributes":"Attributs masqués",
"impersonationMergeSSOgroups":"Fusionner les groupes SSO réels et usurpés", "impersonationMergeSSOgroups":"Fusionner les groupes SSO réels et usurpés",
"impersonationPrefix":"Préfix des vrais attributs",
"impersonationSkipEmptyValues":"Ignorer les valeurs nulles", "impersonationSkipEmptyValues":"Ignorer les valeurs nulles",
"incompleteForm":"Des champs requis manquent", "incompleteForm":"Des champs requis manquent",
"index":"Index", "index":"Index",

View File

@ -140,6 +140,11 @@
"categoryName":"Nome della categoria", "categoryName":"Nome della categoria",
"cda":"Domini multipli", "cda":"Domini multipli",
"contentSecurityPolicy":"Politica di protezione dei contenuti", "contentSecurityPolicy":"Politica di protezione dei contenuti",
"contextSwitching":"Switch context anoter user",
"contextSwitchingHiddenAttributes":"Hidden attributes",
"contextSwitchingIdRule":"Identities use rule",
"contextSwitchingRule":"Utilizza la regola",
"contextSwitchingStopWithLogout":"Stop by logout",
"cspDefault":"Valore di default", "cspDefault":"Valore di default",
"cspFormAction":"Formare le destinazioni", "cspFormAction":"Formare le destinazioni",
"cspImg":"Origine immagine", "cspImg":"Origine immagine",
@ -252,6 +257,7 @@
"emptyConf":"Configurazione vuota", "emptyConf":"Configurazione vuota",
"emptyValueNotAllowed":"Valore vuoto non consentito", "emptyValueNotAllowed":"Valore vuoto non consentito",
"enabled":"Abilitato", "enabled":"Abilitato",
"disablePersistentStorage":"Disable storage",
"enterPassword":"Inserisci password (opzionale)", "enterPassword":"Inserisci password (opzionale)",
"error":"Errore", "error":"Errore",
"errors":"ERRORI", "errors":"ERRORI",
@ -306,7 +312,6 @@
"impersonationIdRule":"Le identità usano la regola", "impersonationIdRule":"Le identità usano la regola",
"impersonationHiddenAttributes":"Attributi nascosti", "impersonationHiddenAttributes":"Attributi nascosti",
"impersonationMergeSSOgroups":"Unisci gruppi SSO usurpati e reali", "impersonationMergeSSOgroups":"Unisci gruppi SSO usurpati e reali",
"impersonationPrefix":"Prefisso degli attributi reali",
"impersonationSkipEmptyValues":"Salta valori vuoti", "impersonationSkipEmptyValues":"Salta valori vuoti",
"incompleteForm":"Mancano campi obbligatori", "incompleteForm":"Mancano campi obbligatori",
"index":"Indice", "index":"Indice",

View File

@ -140,6 +140,11 @@
"categoryName":"Tên thể loại", "categoryName":"Tên thể loại",
"cda":"Nhiều tên miền", "cda":"Nhiều tên miền",
"contentSecurityPolicy":"Chính sách bảo mật nội dung", "contentSecurityPolicy":"Chính sách bảo mật nội dung",
"contextSwitching":"Switch context anoter user",
"contextSwitchingHiddenAttributes":"Hidden attributes",
"contextSwitchingIdRule":"Identities use rule",
"contextSwitchingRule":"Quy tắc sử dụng",
"contextSwitchingStopWithLogout":"Stop by logout",
"cspDefault":"Giá trị mặc định", "cspDefault":"Giá trị mặc định",
"cspFormAction":"Form destinations", "cspFormAction":"Form destinations",
"cspImg":"Nguồn ảnh", "cspImg":"Nguồn ảnh",
@ -252,6 +257,7 @@
"emptyConf":"Cấu hình trống rỗng", "emptyConf":"Cấu hình trống rỗng",
"emptyValueNotAllowed":"Không cho phép giá trị trống", "emptyValueNotAllowed":"Không cho phép giá trị trống",
"enabled":"Đã bật", "enabled":"Đã bật",
"disablePersistentStorage":"Disable storage",
"enterPassword":"Nhập mật khẩu (tùy chọn)", "enterPassword":"Nhập mật khẩu (tùy chọn)",
"error":"Lỗi", "error":"Lỗi",
"errors":"ERRORS", "errors":"ERRORS",
@ -306,7 +312,6 @@
"impersonationIdRule":"Identities use rule", "impersonationIdRule":"Identities use rule",
"impersonationHiddenAttributes":"Thuộc tính ẩn", "impersonationHiddenAttributes":"Thuộc tính ẩn",
"impersonationMergeSSOgroups":"Merge spoofed and real SSO groups", "impersonationMergeSSOgroups":"Merge spoofed and real SSO groups",
"impersonationPrefix":"Real attributes prefix",
"impersonationSkipEmptyValues":"Skip empty values", "impersonationSkipEmptyValues":"Skip empty values",
"incompleteForm":"Các trường bắt buộc bị thiếu", "incompleteForm":"Các trường bắt buộc bị thiếu",
"index":"Chỉ mục", "index":"Chỉ mục",

View File

@ -140,6 +140,11 @@
"categoryName":"分类名称", "categoryName":"分类名称",
"cda":"Multiple domains", "cda":"Multiple domains",
"contentSecurityPolicy":"Content security policy", "contentSecurityPolicy":"Content security policy",
"contextSwitching":"Switch context anoter user",
"contextSwitchingHiddenAttributes":"Hidden attributes",
"contextSwitchingIdRule":"Identities use rule",
"contextSwitchingRule":"Use rule",
"contextSwitchingStopWithLogout":"Stop by logout",
"cspDefault":"Default value", "cspDefault":"Default value",
"cspFormAction":"Form destinations", "cspFormAction":"Form destinations",
"cspImg":"Image source", "cspImg":"Image source",
@ -252,6 +257,7 @@
"emptyConf":"Empty configuration", "emptyConf":"Empty configuration",
"emptyValueNotAllowed":"Empty value not allowed", "emptyValueNotAllowed":"Empty value not allowed",
"enabled":"开启", "enabled":"开启",
"disablePersistentStorage":"Disable storage",
"enterPassword":"输入密码(可选)", "enterPassword":"输入密码(可选)",
"error":"错误", "error":"错误",
"errors":"ERRORS", "errors":"ERRORS",
@ -306,7 +312,6 @@
"impersonationIdRule":"Identities use rule", "impersonationIdRule":"Identities use rule",
"impersonationHiddenAttributes":"Hidden attributes", "impersonationHiddenAttributes":"Hidden attributes",
"impersonationMergeSSOgroups":"Merge spoofed and real SSO groups", "impersonationMergeSSOgroups":"Merge spoofed and real SSO groups",
"impersonationPrefix":"Real attributes prefix",
"impersonationSkipEmptyValues":"Skip empty values", "impersonationSkipEmptyValues":"Skip empty values",
"incompleteForm":"Required fields are missing", "incompleteForm":"Required fields are missing",
"index":"Index", "index":"Index",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,38 @@
my $tests;
BEGIN { $tests = 5 }
use Test::More tests => $tests;
use JSON;
use strict;
use_ok('Lemonldap::NG::Common::Cli');
use_ok('Lemonldap::NG::Manager::Cli');
&cleanConfFiles;
SKIP: {
eval 'use Test::Output;';
if ($@) {
skip 'Test::Output is missing, skipping', $tests - 2;
}
my @cmd;
@cmd = ('save');
my $client =
Lemonldap::NG::Manager::Cli->new( iniFile => 't/lemonldap-ng.ini' );
my $res = Capture::Tiny::capture_stdout( sub { $client->run(@cmd) } );
ok( $res =~ /^\s*(\{.*\})\s*$/s, '"save" result looks like JSON' );
eval { JSON::from_json($res) };
ok( not($@), ' result is JSON' ) or diag "error: $@";
close STDIN;
open STDIN, '<', \$res;
@cmd = ( 'restore', '-' );
Test::Output::combined_like( sub { $client->run(@cmd) },
qr/"cfgNum"\s*:\s*"2"/s, 'New config: 2' );
}
&cleanConfFiles;
sub cleanConfFiles {
foreach ( 2 .. $tests - 3 ) {
unlink "t/conf/lmConf-$_.json";
}
}

View File

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

View File

@ -1348,6 +1348,12 @@
"id": "persistentSessions", "id": "persistentSessions",
"title": "persistentSessions", "title": "persistentSessions",
"nodes": [{ "nodes": [{
"default": 0,
"id": "disablePersistentStorage",
"title": "disablePersistentStorage",
"type": "bool",
"data": 0
}, {
"id": "persistentStorage", "id": "persistentStorage",
"title": "persistentStorage", "title": "persistentStorage",
"data": "Apache::Session::File" "data": "Apache::Session::File"

View File

@ -1348,6 +1348,12 @@
"id": "persistentSessions", "id": "persistentSessions",
"title": "persistentSessions", "title": "persistentSessions",
"nodes": [{ "nodes": [{
"default": 0,
"id": "disablePersistentStorage",
"title": "disablePersistentStorage",
"type": "bool",
"data": 0
}, {
"id": "persistentStorage", "id": "persistentStorage",
"title": "persistentStorage", "title": "persistentStorage",
"data": "Apache::Session::File" "data": "Apache::Session::File"

View File

@ -102,6 +102,7 @@ lib/Lemonldap/NG/Portal/Plugins/BruteForceProtection.pm
lib/Lemonldap/NG/Portal/Plugins/CDA.pm lib/Lemonldap/NG/Portal/Plugins/CDA.pm
lib/Lemonldap/NG/Portal/Plugins/CheckState.pm lib/Lemonldap/NG/Portal/Plugins/CheckState.pm
lib/Lemonldap/NG/Portal/Plugins/CheckUser.pm lib/Lemonldap/NG/Portal/Plugins/CheckUser.pm
lib/Lemonldap/NG/Portal/Plugins/ContextSwitching.pm
lib/Lemonldap/NG/Portal/Plugins/FavApps.pm lib/Lemonldap/NG/Portal/Plugins/FavApps.pm
lib/Lemonldap/NG/Portal/Plugins/ForceAuthn.pm lib/Lemonldap/NG/Portal/Plugins/ForceAuthn.pm
lib/Lemonldap/NG/Portal/Plugins/GrantSession.pm lib/Lemonldap/NG/Portal/Plugins/GrantSession.pm
@ -262,6 +263,8 @@ site/htdocs/static/common/icons/ok.png
site/htdocs/static/common/icons/sfa_manager.png site/htdocs/static/common/icons/sfa_manager.png
site/htdocs/static/common/icons/star0.png site/htdocs/static/common/icons/star0.png
site/htdocs/static/common/icons/star1.png site/htdocs/static/common/icons/star1.png
site/htdocs/static/common/icons/switchcontext_OFF.png
site/htdocs/static/common/icons/switchcontext_ON.png
site/htdocs/static/common/icons/vcard_edit.png site/htdocs/static/common/icons/vcard_edit.png
site/htdocs/static/common/icons/warning.png site/htdocs/static/common/icons/warning.png
site/htdocs/static/common/it.png site/htdocs/static/common/it.png
@ -339,6 +342,7 @@ site/templates/bootstrap/checklogins.tpl
site/templates/bootstrap/checkuser.tpl site/templates/bootstrap/checkuser.tpl
site/templates/bootstrap/choice.tpl site/templates/bootstrap/choice.tpl
site/templates/bootstrap/confirm.tpl site/templates/bootstrap/confirm.tpl
site/templates/bootstrap/contextSwitching.tpl
site/templates/bootstrap/customfooter.tpl site/templates/bootstrap/customfooter.tpl
site/templates/bootstrap/customhead.tpl site/templates/bootstrap/customhead.tpl
site/templates/bootstrap/customheader.tpl site/templates/bootstrap/customheader.tpl
@ -499,6 +503,7 @@ t/37-SAML-SP-GET-to-OIDC-OP.t
t/37-SAML-SP-POST-to-CAS-server-with-Choice.t t/37-SAML-SP-POST-to-CAS-server-with-Choice.t
t/37-SAML-SP-POST-to-CAS-server.t t/37-SAML-SP-POST-to-CAS-server.t
t/37-SAML-SP-POST-to-OIDC-OP.t t/37-SAML-SP-POST-to-OIDC-OP.t
t/38-No-persistent-session.t
t/40-Notifications-JSON-DBI.t t/40-Notifications-JSON-DBI.t
t/40-Notifications-JSON-File-with-token.t t/40-Notifications-JSON-File-with-token.t
t/40-Notifications-JSON-File.t t/40-Notifications-JSON-File.t
@ -544,6 +549,8 @@ t/67-CheckUser-with-Global-token.t
t/67-CheckUser-with-issuer-SAML-POST.t t/67-CheckUser-with-issuer-SAML-POST.t
t/67-CheckUser-with-token.t t/67-CheckUser-with-token.t
t/67-CheckUser.t t/67-CheckUser.t
t/68-ContextSwitching-with-Logout.t
t/68-ContextSwitching.t
t/68-Impersonation-with-doubleCookies.t t/68-Impersonation-with-doubleCookies.t
t/68-Impersonation-with-filtered-merge.t t/68-Impersonation-with-filtered-merge.t
t/68-Impersonation-with-History.t t/68-Impersonation-with-History.t

View File

@ -119,6 +119,16 @@ sub params {
$self->p->_sfEngine->display2fRegisters( $req, $req->userData ); $self->p->_sfEngine->display2fRegisters( $req, $req->userData );
$self->logger->debug("Display 2fRegisters link") if $res{sfaManager}; $self->logger->debug("Display 2fRegisters link") if $res{sfaManager};
# Display ContextSwitching link only if allowed
my $cswPlugin = $self->p->loadedModules->{
'Lemonldap::NG::Portal::Plugins::ContextSwitching'};
$res{contextSwitching} =
$cswPlugin
? $cswPlugin->displaySwitchContext( $req, $req->userData )
: '';
$self->logger->debug("Display SwitchContext link -> $res{contextSwitching}")
if $res{contextSwitching};
return %res; return %res;
} }

View File

@ -28,6 +28,7 @@ our @pList = (
checkUser => '::Plugins::CheckUser', checkUser => '::Plugins::CheckUser',
impersonationRule => '::Plugins::Impersonation', impersonationRule => '::Plugins::Impersonation',
portalDisplayFavApps => '::Plugins::FavApps', portalDisplayFavApps => '::Plugins::FavApps',
contextSwitchingRule => '::Plugins::ContextSwitching',
); );
##@method list enabledPlugins ##@method list enabledPlugins

View File

@ -405,7 +405,7 @@ sub getApacheSession {
sub getPersistentSession { sub getPersistentSession {
my ( $self, $uid, $info ) = @_; my ( $self, $uid, $info ) = @_;
return unless defined $uid; return unless ( defined $uid and !$self->conf->{disablePersistentStorage} );
# Compute persistent identifier # Compute persistent identifier
my $pid = $self->_md5hash($uid); my $pid = $self->_md5hash($uid);
@ -452,7 +452,8 @@ sub updatePersistentSession {
my ( $self, $req, $infos, $uid, $id ) = @_; my ( $self, $req, $infos, $uid, $id ) = @_;
# Return if no infos to update # Return if no infos to update
return () unless ( ref $infos eq 'HASH' and %$infos ); return () unless ( ref $infos eq 'HASH' and %$infos and !$self->conf->{disablePersistentStorage} );
$uid ||= $req->{sessionInfo}->{ $self->conf->{whatToTrace} } $uid ||= $req->{sessionInfo}->{ $self->conf->{whatToTrace} }
|| $req->userData->{ $self->conf->{whatToTrace} }; || $req->userData->{ $self->conf->{whatToTrace} };
$self->logger->debug("Found 'whatToTrace' -> $uid"); $self->logger->debug("Found 'whatToTrace' -> $uid");

View File

@ -29,6 +29,7 @@ has ott => (
} }
); );
has idRule => ( is => 'rw', default => sub { 1 } ); has idRule => ( is => 'rw', default => sub { 1 } );
has sorted => ( is => 'rw', default => sub { 0 } );
sub hAttr { sub hAttr {
$_[0]->{conf}->{checkUserHiddenAttributes} . ' ' $_[0]->{conf}->{checkUserHiddenAttributes} . ' '
@ -52,7 +53,8 @@ sub init {
return 0; return 0;
} }
$self->idRule($rule); $self->idRule($rule);
$self->sorted( $self->conf->{impersonationRule}
|| $self->conf->{contextSwitchingRule} );
return 1; return 1;
} }
@ -430,7 +432,7 @@ sub _splitAttributes {
} }
# Sort real and spoofed attributes if required # Sort real and spoofed attributes if required
if ( $self->conf->{impersonationRule} ) { if ( $self->sorted ) {
$self->logger->debug('Dispatching real and spoofed attributes...'); $self->logger->debug('Dispatching real and spoofed attributes...');
my ( $realAttrs, $spoofedAttrs ) = ( [], [] ); my ( $realAttrs, $spoofedAttrs ) = ( [], [] );
my $prefix = "$self->{conf}->{impersonationPrefix}"; my $prefix = "$self->{conf}->{impersonationPrefix}";

View File

@ -0,0 +1,227 @@
package Lemonldap::NG::Portal::Plugins::ContextSwitching;
use strict;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants
qw( PE_OK PE_REDIRECT PE_BADCREDENTIALS PE_IMPERSONATION_SERVICE_NOT_ALLOWED PE_MALFORMEDUSER );
our $VERSION = '2.0.6';
extends
qw(Lemonldap::NG::Portal::Main::Plugin Lemonldap::NG::Portal::Lib::_tokenRule);
# INITIALIZATION
has ott => (
is => 'rw',
lazy => 1,
default => sub {
my $ott =
$_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
$ott->timeout( $_[0]->{conf}->{formTimeout} );
return $ott;
}
);
has rule => ( is => 'rw', default => sub { 1 } );
has idRule => ( is => 'rw', default => sub { 1 } );
sub init {
my ($self) = @_;
my $hd = $self->p->HANDLER;
$self->addAuthRoute( switchcontext => 'run', ['POST'] );
$self->addAuthRoute( switchcontext => 'display', ['GET'] );
# Parse activation rule
$self->logger->debug(
'ContextSwitching rule -> ' . $self->conf->{contextSwitchingRule} );
my $rule =
$hd->buildSub( $hd->substitute( $self->conf->{contextSwitchingRule} ) );
unless ($rule) {
$self->error(
'Bad contextSwitching rule -> ' . $hd->tsv->{jail}->error );
return 0;
}
$self->rule($rule);
# Parse identity rule
$self->logger->debug( "ContextSwitching identities rule -> "
. $self->conf->{contextSwitchingIdRule} );
$rule =
$hd->buildSub( $hd->substitute( $self->conf->{contextSwitchingIdRule} ) );
unless ($rule) {
$self->error( "Bad contextSwitching identities rule -> "
. $hd->tsv->{jail}->error );
return 0;
}
$self->idRule($rule);
return 1;
}
# RUNNING METHOD
sub display {
my ( $self, $req ) = @_;
# Check access rules
unless ( $self->rule->( $req, $req->userData )
|| $req->userData->{"$self->{conf}->{impersonationPrefix}_session_id"} )
{
$self->userLogger->error('Context switching service not authorized');
return $self->p->do( $req,
[ sub { PE_IMPERSONATION_SERVICE_NOT_ALLOWED } ] );
}
if ( $req->userData->{"$self->{conf}->{impersonationPrefix}_session_id"} ) {
$self->logger->debug('Request to stop ContextSwitching');
if ( $self->conf->{contextSwitchingStopWithLogout} ) {
$self->logger->debug('Send logout request');
return $self->p->do( $req,
[ @{ $self->p->beforeLogout }, 'authLogout', 'deleteSession' ]
);
}
else {
$req = $self->_abortImpersonation( $req, 0 );
$self->p->updateSession( $req, $req->userData );
return $self->p->do( $req, [ sub { PE_REDIRECT } ] );
}
}
# Display form
my $params = {
PORTAL => $self->conf->{portal},
MAIN_LOGO => $self->conf->{portalMainLogo},
LANGS => $self->conf->{showLanguages},
MSG => 'contextSwitching_ON',
ALERTE => 'alert-danger',
LOGIN => '',
SPOOFID => $self->conf->{contextSwitchingRule},
TOKEN => (
$self->ottRule->( $req, {} )
? $self->ott->createToken()
: ''
)
};
return $self->p->sendHtml( $req, 'contextSwitching', params => $params, );
}
sub run {
my ( $self, $req ) = @_;
my $statut = PE_OK;
my $spoofId = $req->param('spoofId') || ''; # ContextSwitching required ?
# Check activation rule
unless ( $self->rule->( $req, $req->userData ) ) {
$self->userLogger->warn('Context switching service not authorized');
$spoofId = '';
return $self->p->do( $req,
[ sub { PE_IMPERSONATION_SERVICE_NOT_ALLOWED } ] );
}
# ContextSwitching required -> Check user Id
if ( $spoofId && $spoofId ne $req->{user} ) {
$self->logger->debug("Spoof Id: $spoofId");
unless ( $spoofId =~ /$self->{conf}->{userControl}/o ) {
$self->userLogger->warn('Malformed spoofed Id');
$self->logger->debug(
"Context switching tried with spoofed Id: $spoofId");
return $self->p->do( $req, [ sub { PE_MALFORMEDUSER } ] );
}
}
else {
$self->logger->debug("No context switching required");
$req->urldc( $self->conf->{portal} );
return $self->p->do( $req, [ sub { PE_OK } ] );
}
# Create spoofed session
$req = $self->_switchContext( $req, $spoofId );
if ( $req->error ) {
if ( $req->error == PE_BADCREDENTIALS ) {
$statut = PE_MALFORMEDUSER;
}
else {
$statut = $req->error;
}
}
# Main session
$self->p->updateSession( $req, $req->sessionInfo );
return $self->p->do( $req, [ sub { $statut } ] );
}
sub _switchContext {
my ( $self, $req, $spoofId ) = @_;
my $realSessionId = $req->userData->{_session_id};
my $raz = 0;
$req->{user} = $spoofId;
# Search user in database & create session
$req->steps( [ 'getUser', $self->p->sessionData, 'buildCookie' ] );
if ( my $error = $self->p->process($req) ) {
if ( $error == PE_BADCREDENTIALS ) {
$self->userLogger->warn(
'ContextSwitching requested for an unvalid user ('
. $req->{user}
. ")" );
}
$self->logger->debug("Process returned error: $error");
$req->error($error);
$raz = 1;
}
# Check identity rule if ContextSwitching required
unless ( $self->idRule->( $req, $req->sessionInfo ) ) {
$self->userLogger->warn(
'ContextSwitching requested for an unvalid user ('
. $req->{user}
. ")" );
$self->logger->debug('Identity NOT authorized');
$req->error(PE_MALFORMEDUSER); # Hide error to preserve protected Id
$raz = 1;
}
$req->sessionInfo->{"$self->{conf}->{impersonationPrefix}_session_id"} =
$realSessionId;
return $raz ? $self->_abortImpersonation( $req, 1 ) : $req;
}
sub _abortImpersonation {
my ( $self, $req, $abort ) = @_;
my $type = $abort ? 'sessionInfo' : 'userData';
my $realSessionId =
$req->{$type}->{"$self->{conf}->{impersonationPrefix}_session_id"};
my $session = $self->p->getApacheSession($realSessionId)->data;
if ($abort) {
$self->logger->debug('ABORT ContextSwitching');
$self->userLogger->notice('ABORT ContextSwitching');
$self->p->updateSession( $req, { '_session_kind' => 'SPOOF' } );
}
else {
$self->logger->debug('STOP ContextSwitching');
$self->userLogger->notice('STOP ContextSwitching');
$self->p->deleteSession($req);
}
# Restore real session
$req->{$type} = {%$session};
$req->{user} = $session->{_user};
$req->urldc( $self->conf->{portal} );
$req->id($realSessionId);
$self->p->buildCookie($req);
delete $req->{$type}->{"$self->{conf}->{impersonationPrefix}_session_id"};
return $req;
}
sub displaySwitchContext {
my ( $self, $req ) = @_;
return 'OFF'
if $req->userData->{"$self->{conf}->{impersonationPrefix}_session_id"};
return 'ON' if $self->rule->( $req, $req->userData );
}
1;

View File

@ -37,13 +37,13 @@ sub init {
$self->rule($rule); $self->rule($rule);
# Parse identity rule # Parse identity rule
$self->logger->debug( "Impersonation identity rule -> " $self->logger->debug( "Impersonation identities rule -> "
. $self->conf->{impersonationIdRule} ); . $self->conf->{impersonationIdRule} );
$rule = $rule =
$hd->buildSub( $hd->substitute( $self->conf->{impersonationIdRule} ) ); $hd->buildSub( $hd->substitute( $self->conf->{impersonationIdRule} ) );
unless ($rule) { unless ($rule) {
$self->error( $self->error(
"Bad impersonation identity rule -> " . $hd->tsv->{jail}->error ); "Bad impersonation identities rule -> " . $hd->tsv->{jail}->error );
return 0; return 0;
} }
$self->idRule($rule); $self->idRule($rule);
@ -71,7 +71,7 @@ sub run {
if ( $spoofId eq $req->{user} ); if ( $spoofId eq $req->{user} );
unless ( $spoofId =~ /$self->{conf}->{userControl}/o ) { unless ( $spoofId =~ /$self->{conf}->{userControl}/o ) {
$self->userLogger->error('Malformed spoofed Id'); $self->userLogger->warn('Malformed spoofed Id');
$self->logger->debug("Impersonation tried with spoofed Id: $spoofId"); $self->logger->debug("Impersonation tried with spoofed Id: $spoofId");
$spoofId = $req->{user}; $spoofId = $req->{user};
$statut = PE_MALFORMEDUSER; $statut = PE_MALFORMEDUSER;
@ -81,7 +81,7 @@ sub run {
if ( $spoofId ne $req->{user} ) { if ( $spoofId ne $req->{user} ) {
$self->logger->debug("Spoof Id: $spoofId / Real Id: $req->{user}"); $self->logger->debug("Spoof Id: $spoofId / Real Id: $req->{user}");
unless ( $self->rule->( $req, $req->sessionInfo ) ) { unless ( $self->rule->( $req, $req->sessionInfo ) ) {
$self->userLogger->error('Impersonation service not authorized'); $self->userLogger->warn('Impersonation service not authorized');
$spoofId = $req->{user}; $spoofId = $req->{user};
$statut = PE_IMPERSONATION_SERVICE_NOT_ALLOWED; $statut = PE_IMPERSONATION_SERVICE_NOT_ALLOWED;
} }
@ -90,12 +90,11 @@ sub run {
# Fill spoof session # Fill spoof session
my ( $realSession, $spoofSession ) = ( {}, {} ); my ( $realSession, $spoofSession ) = ( {}, {} );
$self->logger->debug("Rename real attributes..."); $self->logger->debug("Rename real attributes...");
my $spk = '';
foreach my $k ( keys %{ $req->{sessionInfo} } ) { foreach my $k ( keys %{ $req->{sessionInfo} } ) {
if ( $self->{conf}->{impersonationSkipEmptyValues} ) { if ( $self->{conf}->{impersonationSkipEmptyValues} ) {
next unless defined $req->{sessionInfo}->{$k}; next unless defined $req->{sessionInfo}->{$k};
} }
$spk = "$self->{conf}->{impersonationPrefix}$k"; my $spk = "$self->{conf}->{impersonationPrefix}$k";
unless ( $self->hAttr =~ /\b$k\b/ unless ( $self->hAttr =~ /\b$k\b/
|| $k =~ /^(?:_imp|token|_type)\w*\b/ ) || $k =~ /^(?:_imp|token|_type)\w*\b/ )
{ {
@ -120,7 +119,7 @@ sub run {
$self->logger->debug("Populating spoof session..."); $self->logger->debug("Populating spoof session...");
foreach (qw (_auth _userDB)) { foreach (qw (_auth _userDB)) {
$self->logger->debug("Processing $_..."); $self->logger->debug("Processing $_...");
$spk = "$self->{conf}->{impersonationPrefix}$_"; my $spk = "$self->{conf}->{impersonationPrefix}$_";
$spoofSession->{$_} = $realSession->{$spk}; $spoofSession->{$_} = $realSession->{$spk};
} }
@ -217,7 +216,7 @@ sub _userData {
'Impersonation requested for an unvalid user (' 'Impersonation requested for an unvalid user ('
. $req->{user} . $req->{user}
. ")" ); . ")" );
$self->logger->debug('Identity not authorized'); $self->logger->debug('Identity NOT authorized');
$raz = 1; $raz = 1;
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

View File

@ -143,6 +143,8 @@
"groups_sso":"SSO GROUPS", "groups_sso":"SSO GROUPS",
"headers":"HEADERS", "headers":"HEADERS",
"id":"Id", "id":"Id",
"contextSwitching_ON":"Impersonate another user",
"contextSwitching_OFF":"Stop impersonation",
"imSure":"انا متاكد", "imSure":"انا متاكد",
"info":"معلومات", "info":"معلومات",
"ipAddr":"عنوان الأي بي", "ipAddr":"عنوان الأي بي",
@ -219,6 +221,7 @@
"SSOSessionInactive":"جلسة الدخول الموحد غير نشطة", "SSOSessionInactive":"جلسة الدخول الموحد غير نشطة",
"stayConnected":"ابق على اتصال على هذا الجهاز", "stayConnected":"ابق على اتصال على هذا الجهاز",
"submit":"قدم", "submit":"قدم",
"switchContext":"Switch context",
"totpExistingKey":"A TOTP secret already exists", "totpExistingKey":"A TOTP secret already exists",
"touchU2fDevice":"يرجى لمس جهاز U2F وامض الآن.", "touchU2fDevice":"يرجى لمس جهاز U2F وامض الآن.",
"touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.", "touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.",

View File

@ -143,6 +143,8 @@
"groups_sso":"SSO GROUPS", "groups_sso":"SSO GROUPS",
"headers":"HEADERS", "headers":"HEADERS",
"id":"ID", "id":"ID",
"contextSwitching_ON":"Impersonate another user",
"contextSwitching_OFF":"Stop impersonation",
"imSure":"Ich bin sicher", "imSure":"Ich bin sicher",
"info":"Information", "info":"Information",
"ipAddr":"IP Adresse", "ipAddr":"IP Adresse",
@ -219,6 +221,7 @@
"SSOSessionInactive":"SSO Sitzung inaktiv", "SSOSessionInactive":"SSO Sitzung inaktiv",
"stayConnected":"Auf diesem Gerät verbunden bleiben", "stayConnected":"Auf diesem Gerät verbunden bleiben",
"submit":"Absenden", "submit":"Absenden",
"switchContext":"Switch context",
"totpExistingKey":"Es existiert bereits ein TOTP-Secret", "totpExistingKey":"Es existiert bereits ein TOTP-Secret",
"touchU2fDevice":"Please touch the flashing U2F device now.", "touchU2fDevice":"Please touch the flashing U2F device now.",
"touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.", "touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.",

View File

@ -143,6 +143,8 @@
"groups_sso":"SSO GROUPS", "groups_sso":"SSO GROUPS",
"headers":"HEADERS", "headers":"HEADERS",
"id":"Id", "id":"Id",
"contextSwitching_ON":"Impersonate another user",
"contextSwitching_OFF":"Stop impersonation",
"imSure":"I'm sure", "imSure":"I'm sure",
"info":"Information", "info":"Information",
"ipAddr":"IP address", "ipAddr":"IP address",
@ -219,6 +221,7 @@
"SSOSessionInactive":"SSO session inactive", "SSOSessionInactive":"SSO session inactive",
"stayConnected": "Stay connected on this device", "stayConnected": "Stay connected on this device",
"submit":"Submit", "submit":"Submit",
"switchContext":"Switch context",
"totpExistingKey":"A TOTP secret already exists", "totpExistingKey":"A TOTP secret already exists",
"touchU2fDevice": "Please touch the flashing U2F device now.", "touchU2fDevice": "Please touch the flashing U2F device now.",
"touchU2fDeviceOrEnterTotp": "Please touch the flashing U2F device or enter TOTP code.", "touchU2fDeviceOrEnterTotp": "Please touch the flashing U2F device or enter TOTP code.",

View File

@ -143,6 +143,8 @@
"groups_sso":"SSO GROUPS", "groups_sso":"SSO GROUPS",
"headers":"HEADERS", "headers":"HEADERS",
"id":"Id", "id":"Id",
"contextSwitching_ON":"Impersonate another user",
"contextSwitching_OFF":"Stop impersonation",
"imSure":"I'm sure", "imSure":"I'm sure",
"info":"Information", "info":"Information",
"ipAddr":"IP address", "ipAddr":"IP address",
@ -219,6 +221,7 @@
"SSOSessionInactive":"SSO session inactive", "SSOSessionInactive":"SSO session inactive",
"stayConnected":"Stay connected on this device", "stayConnected":"Stay connected on this device",
"submit":"Submit", "submit":"Submit",
"switchContext":"Switch context",
"totpExistingKey":"A TOTP secret already exists", "totpExistingKey":"A TOTP secret already exists",
"touchU2fDevice":"Please touch the flashing U2F device now.", "touchU2fDevice":"Please touch the flashing U2F device now.",
"touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.", "touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.",

View File

@ -143,6 +143,8 @@
"groups_sso":"SSO GROUPS", "groups_sso":"SSO GROUPS",
"headers":"HEADERS", "headers":"HEADERS",
"id":"Id", "id":"Id",
"contextSwitching_ON":"Impersonate another user",
"contextSwitching_OFF":"Stop impersonation",
"imSure":"Olen varma", "imSure":"Olen varma",
"info":"Information", "info":"Information",
"ipAddr":"IP-osoite", "ipAddr":"IP-osoite",
@ -219,6 +221,7 @@
"SSOSessionInactive":"SSO session inactive", "SSOSessionInactive":"SSO session inactive",
"stayConnected":"Stay connected on this device", "stayConnected":"Stay connected on this device",
"submit":"Lähetä", "submit":"Lähetä",
"switchContext":"Switch context",
"totpExistingKey":"A TOTP secret already exists", "totpExistingKey":"A TOTP secret already exists",
"touchU2fDevice":"Please touch the flashing U2F device now.", "touchU2fDevice":"Please touch the flashing U2F device now.",
"touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.", "touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.",

View File

@ -143,6 +143,8 @@
"groups_sso":"GROUPES SSO", "groups_sso":"GROUPES SSO",
"headers":"ENTETES", "headers":"ENTETES",
"id":"Id", "id":"Id",
"contextSwitching_ON":"Endosser l'identité d'un autre utilisateur",
"contextSwitching_OFF":"Stopper l'endossement",
"imSure":"Je suis sûr", "imSure":"Je suis sûr",
"info":"Information", "info":"Information",
"ipAddr":"Adresse IP", "ipAddr":"Adresse IP",
@ -219,6 +221,7 @@
"SSOSessionInactive":"Session SSO inactive", "SSOSessionInactive":"Session SSO inactive",
"stayConnected": "Rester connecté sur cet appareil", "stayConnected": "Rester connecté sur cet appareil",
"submit":"Envoyer", "submit":"Envoyer",
"switchContext":"Changer de contexte",
"totpExistingKey":"Un secret TOTP existe déjà !!!", "totpExistingKey":"Un secret TOTP existe déjà !!!",
"touchU2fDevice": "Posez votre doigt sur le périphérique U2F", "touchU2fDevice": "Posez votre doigt sur le périphérique U2F",
"touchU2fDeviceOrEnterTotp": "Posez votre doigt sur le périphérique U2F ou entrez le code TOTP", "touchU2fDeviceOrEnterTotp": "Posez votre doigt sur le périphérique U2F ou entrez le code TOTP",

View File

@ -143,6 +143,8 @@
"groups_sso":"GRUPPI SSO", "groups_sso":"GRUPPI SSO",
"headers":"INTESTAZIONI", "headers":"INTESTAZIONI",
"id":"Id", "id":"Id",
"contextSwitching_ON":"Impersonate another user",
"contextSwitching_OFF":"Stop impersonation",
"imSure":"Sono sicuro", "imSure":"Sono sicuro",
"info":"Informazioni", "info":"Informazioni",
"ipAddr":"Indirizzo IP", "ipAddr":"Indirizzo IP",
@ -219,6 +221,7 @@
"SSOSessionInactive":"Sessione SSO inattiva", "SSOSessionInactive":"Sessione SSO inattiva",
"stayConnected":"Resta connesso su questo dispositivo", "stayConnected":"Resta connesso su questo dispositivo",
"submit":"Invia", "submit":"Invia",
"switchContext":"Switch context",
"totpExistingKey":"Un segreto TOTP esiste già", "totpExistingKey":"Un segreto TOTP esiste già",
"touchU2fDevice":"Adesso tocca il dispositivo U2F lampeggiante.", "touchU2fDevice":"Adesso tocca il dispositivo U2F lampeggiante.",
"touchU2fDeviceOrEnterTotp":"Tocca il dispositivo U2F lampeggiante o inserisci il codice TOTP.", "touchU2fDeviceOrEnterTotp":"Tocca il dispositivo U2F lampeggiante o inserisci il codice TOTP.",

View File

@ -143,6 +143,8 @@
"groups_sso":"SSO GROUPS", "groups_sso":"SSO GROUPS",
"headers":"HEADERS", "headers":"HEADERS",
"id":"Id", "id":"Id",
"contextSwitching_ON":"Impersonate another user",
"contextSwitching_OFF":"Stop impersonation",
"imSure":"I'm sure", "imSure":"I'm sure",
"info":"Information", "info":"Information",
"ipAddr":"IP address", "ipAddr":"IP address",
@ -219,6 +221,7 @@
"SSOSessionInactive":"SSO session inactive", "SSOSessionInactive":"SSO session inactive",
"stayConnected":"Stay connected on this device", "stayConnected":"Stay connected on this device",
"submit":"Submit", "submit":"Submit",
"switchContext":"Switch context",
"totpExistingKey":"A TOTP secret already exists", "totpExistingKey":"A TOTP secret already exists",
"touchU2fDevice":"Please touch the flashing U2F device now.", "touchU2fDevice":"Please touch the flashing U2F device now.",
"touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.", "touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.",

View File

@ -143,6 +143,8 @@
"groups_sso":"SSO GROUPS", "groups_sso":"SSO GROUPS",
"headers":"HEADERS", "headers":"HEADERS",
"id":"Id", "id":"Id",
"contextSwitching_ON":"Impersonate another user",
"contextSwitching_OFF":"Stop impersonation",
"imSure":"I'm sure", "imSure":"I'm sure",
"info":"Information", "info":"Information",
"ipAddr":"IP address", "ipAddr":"IP address",
@ -219,6 +221,7 @@
"SSOSessionInactive":"SSO session inactive", "SSOSessionInactive":"SSO session inactive",
"stayConnected":"Stay connected on this device", "stayConnected":"Stay connected on this device",
"submit":"Submit", "submit":"Submit",
"switchContext":"Switch context",
"totpExistingKey":"A TOTP secret already exists", "totpExistingKey":"A TOTP secret already exists",
"touchU2fDevice":"Please touch the flashing U2F device now.", "touchU2fDevice":"Please touch the flashing U2F device now.",
"touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.", "touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.",

View File

@ -143,6 +143,8 @@
"groups_sso":"SSO GROUPS", "groups_sso":"SSO GROUPS",
"headers":"HEADERS", "headers":"HEADERS",
"id":"Id", "id":"Id",
"contextSwitching_ON":"Impersonate another user",
"contextSwitching_OFF":"Stop impersonation",
"imSure":"I'm sure", "imSure":"I'm sure",
"info":"Information", "info":"Information",
"ipAddr":"IP address", "ipAddr":"IP address",
@ -219,6 +221,7 @@
"SSOSessionInactive":"SSO session inactive", "SSOSessionInactive":"SSO session inactive",
"stayConnected":"Stay connected on this device", "stayConnected":"Stay connected on this device",
"submit":"Submit", "submit":"Submit",
"switchContext":"Switch context",
"totpExistingKey":"A TOTP secret already exists", "totpExistingKey":"A TOTP secret already exists",
"touchU2fDevice":"Please touch the flashing U2F device now.", "touchU2fDevice":"Please touch the flashing U2F device now.",
"touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.", "touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.",

View File

@ -143,6 +143,8 @@
"groups_sso":"SSO GROUPS", "groups_sso":"SSO GROUPS",
"headers":"HEADERS", "headers":"HEADERS",
"id":"Id", "id":"Id",
"contextSwitching_ON":"Impersonate another user",
"contextSwitching_OFF":"Stop impersonation",
"imSure":"Tôi chắc chắn", "imSure":"Tôi chắc chắn",
"info":"Thông tin", "info":"Thông tin",
"ipAddr":"Địa chỉ IP", "ipAddr":"Địa chỉ IP",
@ -219,6 +221,7 @@
"SSOSessionInactive":"Phiên SSO không hoạt động", "SSOSessionInactive":"Phiên SSO không hoạt động",
"stayConnected":"Giữ kết nối trên thiết bị này", "stayConnected":"Giữ kết nối trên thiết bị này",
"submit":"Gửi", "submit":"Gửi",
"switchContext":"Switch context",
"totpExistingKey":"A TOTP secret already exists", "totpExistingKey":"A TOTP secret already exists",
"touchU2fDevice":"Vui lòng chạm vào thiết bị U2F nhấp nháy ngay bây giờ.", "touchU2fDevice":"Vui lòng chạm vào thiết bị U2F nhấp nháy ngay bây giờ.",
"touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.", "touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.",

View File

@ -143,6 +143,8 @@
"groups_sso":"SSO GROUPS", "groups_sso":"SSO GROUPS",
"headers":"HEADERS", "headers":"HEADERS",
"id":"Id", "id":"Id",
"contextSwitching_ON":"Impersonate another user",
"contextSwitching_OFF":"Stop impersonation",
"imSure":"我确认", "imSure":"我确认",
"info":"信息", "info":"信息",
"ipAddr":"IP 地址", "ipAddr":"IP 地址",
@ -219,6 +221,7 @@
"SSOSessionInactive":"SSO session inactive", "SSOSessionInactive":"SSO session inactive",
"stayConnected":"在该项设备上保持连接", "stayConnected":"在该项设备上保持连接",
"submit":"提交", "submit":"提交",
"switchContext":"Switch context",
"totpExistingKey":"A TOTP secret already exists", "totpExistingKey":"A TOTP secret already exists",
"touchU2fDevice":"Please touch the flashing U2F device now.", "touchU2fDevice":"Please touch the flashing U2F device now.",
"touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.", "touchU2fDeviceOrEnterTotp":"Please touch the flashing U2F device or enter TOTP code.",

View File

@ -0,0 +1,38 @@
<TMPL_INCLUDE NAME="header.tpl">
<div id="errorcontent" class="container">
<!--
<div class="message message-positive alert"><span trspan="<TMPL_VAR NAME="MSG">"></span></div>
-->
<div class="alert <TMPL_VAR NAME="ALERTE"> alert"><div class="text-center"><span trspan="<TMPL_VAR NAME="MSG">"></span></div></div>
<form id="contextSwitching" action="/switchcontext" method="post" class="password" role="form">
<div class="buttons">
<TMPL_IF NAME="TOKEN">
<input type="hidden" name="token" value="<TMPL_VAR NAME="TOKEN">" />
</TMPL_IF>
<TMPL_INCLUDE NAME="impersonation.tpl">
<button type="submit" class="btn btn-success">
<span class="fa fa-random"></span>
<span trspan="switchContext">switchContext</span>
</button>
</div>
</form>
<div class="buttons">
<!--
<button type="submit" class="btn btn-success">
<span class="fa fa-sign-in"></span>
<span trspan="search">Search</span>
</button>
-->
<a href="<TMPL_VAR NAME="PORTAL_URL">" class="btn btn-primary" role="button">
<span class="fa fa-home"></span>
<span trspan="goToPortal">Go to portal</span>
</a>
</div>
</div>
</div>
<TMPL_INCLUDE NAME="footer.tpl">

View File

@ -72,6 +72,12 @@
<span trspan="sfaManager">sfaManager</span> <span trspan="sfaManager">sfaManager</span>
</a></li> </a></li>
</TMPL_IF> </TMPL_IF>
<TMPL_IF NAME="contextSwitching">
<li class="dropdown-item"><a href="/switchcontext" class="nav-link">
<img src="<TMPL_VAR NAME="STATIC_PREFIX">common/icons/switchcontext_<TMPL_VAR NAME="contextSwitching">.png" width="20" height="20" alt="refresh" />
<span trspan="contextSwitching_<TMPL_VAR NAME="contextSwitching">">contextSwitching_<TMPL_VAR NAME="contextSwitching"></span>
</a></li>
</TMPL_IF>
<li class="dropdown-item"><a href="/refresh" class="nav-link"> <li class="dropdown-item"><a href="/refresh" class="nav-link">
<img src="<TMPL_VAR NAME="STATIC_PREFIX">common/icons/arrow_refresh.png" width="16" height="16" alt="refresh" /> <img src="<TMPL_VAR NAME="STATIC_PREFIX">common/icons/arrow_refresh.png" width="16" height="16" alt="refresh" />
<span trspan="refreshrights">Refresh</span> <span trspan="refreshrights">Refresh</span>

View File

@ -0,0 +1,155 @@
use Test::More;
use strict;
use IO::String;
require 't/test-lib.pm';
my $maintests = 18;
SKIP: {
eval { require Convert::Base32 };
if ($@) {
skip 'Convert::Base32 is missing', $maintests;
}
eval { require Authen::OATH };
if ($@) {
skip 'Authen::OATH is missing', $maintests;
}
require Lemonldap::NG::Common::TOTP;
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
totp2fSelfRegistration => 1,
totp2fActivation => 1,
totp2fDigits => 8,
totp2fTTL => -1,
loginHistoryEnabled => 1,
disablePersistentStorage => 1,
}
}
);
my $res;
# Try to authenticate 1
# ---------------------
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23
),
'Auth query'
);
my $id = expectCookie($res);
$client->logout($id);
# Try to authenticate 2
# ---------------------
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23
),
'Auth query'
);
$id = expectCookie($res);
$client->logout($id);
# Try to authenticate 3
# ---------------------
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho&checkLogins=1'),
length => 37,
accept => 'text/html',
),
'Auth query'
);
$id = expectCookie($res);
ok( $res->[2]->[0] =~ /trspan="lastLogins"/, 'History found' )
or print STDERR Dumper( $res->[2]->[0] );
my @c = ( $res->[2]->[0] =~ /<td>127.0.0.1/gs );
ok( @c == 1, ' -> NO history : only one entry found' );
# 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' );
# 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, 8 ),
'Code' );
ok( $code =~ /^\d{8}$/, 'Code contains 8 digits' );
my $s = "code=$code&token=$token";
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'
);
# NO 2FA stored
$id = expectCookie($res);
$client->logout($id);
}
count($maintests);
clean_sessions();
done_testing( count() );

View File

@ -0,0 +1,148 @@
use Test::More;
use strict;
use IO::String;
BEGIN {
require 't/test-lib.pm';
}
my $res;
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
authentication => 'Demo',
userDB => 'Same',
loginHistoryEnabled => 0,
brutForceProtection => 0,
portalMainLogo => 'common/logos/logo_llng_old.png',
requireToken => 0,
checkUser => 1,
impersonationPrefix => 'testPrefix_',
securedCookie => 0,
https => 0,
checkUserDisplayPersistentInfo => 0,
checkUserDisplayEmptyValues => 0,
contextSwitchingRule => 1,
contextSwitchingIdRule => 1,
contextSwitchingStopWithLogout => 1,
}
}
);
##
## Try to authenticate
ok(
$res = $client->_post(
'/',
IO::String->new('user=rtyler&password=rtyler'),
length => 27,
accept => 'text/html',
),
'Auth query'
);
count(1);
my $id = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
# Get Menu
# ------------------------
ok(
$res = $client->_get(
'/',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'Get Menu',
);
count(1);
expectOK($res);
ok( $res->[2]->[0] =~ m%<span trspan="connectedAs">Connected as</span> rtyler%,
'Connected as rtyler' )
or print STDERR Dumper( $res->[2]->[0] );
expectAuthenticatedAs( $res, 'rtyler' );
ok( $res->[2]->[0] =~ m%<span trspan="contextSwitching_ON">contextSwitching_ON</span>%,
'Connected as rtyler' )
or print STDERR Dumper( $res->[2]->[0] );
count(2);
# ContextSwitching form -> PE_OK
# ------------------------
ok(
$res = $client->_get(
'/switchcontext',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'ContextSwitching form',
);
count(1);
my ( $host, $url, $query ) =
expectForm( $res, undef, '/switchcontext', 'spoofId' );
ok( $res->[2]->[0] =~ m%<span trspan="contextSwitching_ON">%, 'Found trspan="contextSwitching_ON"' )
or explain( $res->[2]->[0], 'trspan="contextSwitching_ON"' );
$query =~ s/spoofId=/spoofId=dwho/;
ok(
$res = $client->_post(
'/switchcontext',
IO::String->new($query),
cookie => "lemonldap=$id",
length => length($query),
accept => 'text/html',
),
'POST switchcontext'
);
$id = expectCookie($res);
ok(
$res = $client->_get(
'/',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'Get Menu',
);
count(3);
expectAuthenticatedAs( $res, 'dwho' );
ok( $res->[2]->[0] =~ m%<span trspan="contextSwitching_OFF">%, 'Found trspan="contextSwitching_OFF"' )
or explain( $res->[2]->[0], 'trspan="contextSwitching_OFF"' );
ok(
$res = $client->_get(
'/checkuser',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'CheckUser form',
);
count(2);
( $host, $url, $query ) =
expectForm( $res, undef, '/checkuser', 'user', 'url' );
ok( $res->[2]->[0] =~ m%<span trspan="checkUser">%, 'Found trspan="checkUser"' )
or explain( $res->[2]->[0], 'trspan="checkUser"' );
ok( $res->[2]->[0] =~ m%<td scope="row">_user</td>%, 'Found attribute _user' )
or explain( $res->[2]->[0], 'Attribute _user' );
ok( $res->[2]->[0] =~ m%<td scope="row">dwho</td>%, 'Found value dwho' )
or explain( $res->[2]->[0], 'Value dwho' );
ok( $res->[2]->[0] =~ m%<td scope="row">mail</td>%, 'Found attribute mail' )
or explain( $res->[2]->[0], 'Attribute mail' );
ok( $res->[2]->[0] =~ m%<td scope="row">testPrefix__session_id</td>%, 'Found spoofed _id_session' )
or explain( $res->[2]->[0], 'Spoofed _id_session' );
count(5);
# Stop ContextSwitching
# ------------------------
ok(
$res = $client->_get(
'/switchcontext',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'Stop context switching',
);
ok( $res->[2]->[0] =~ /trmsg="-7"/, 'Found logout message' );
count(2);
clean_sessions();
done_testing( count() );

View File

@ -0,0 +1,343 @@
use Test::More;
use strict;
use IO::String;
BEGIN {
require 't/test-lib.pm';
}
my $res;
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
authentication => 'Demo',
userDB => 'Same',
loginHistoryEnabled => 0,
brutForceProtection => 0,
portalMainLogo => 'common/logos/logo_llng_old.png',
requireToken => 0,
checkUser => 0,
impersonationPrefix => 'testPrefix_',
securedCookie => 0,
https => 0,
checkUserDisplayPersistentInfo => 0,
checkUserDisplayEmptyValues => 0,
contextSwitchingRule => '$uid eq "dwho"',
contextSwitchingIdRule => '$uid ne "msmith"',
contextSwitchingStopWithLogout => 0,
}
}
);
##
## Try to authenticate with a user not authorized to switch context
ok(
$res = $client->_post(
'/',
IO::String->new('user=rtyler&password=rtyler'),
length => 27,
accept => 'text/html',
),
'Auth query'
);
count(1);
my $id = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
# Get Menu
# ------------------------
ok(
$res = $client->_get(
'/',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'Get Menu',
);
count(1);
expectOK($res);
ok(
$res->[2]->[0] =~ m%<span trspan="connectedAs">Connected as</span> rtyler%,
'Connected as rtyler'
) or print STDERR Dumper( $res->[2]->[0] );
expectAuthenticatedAs( $res, 'rtyler' );
ok( $res->[2]->[0] !~ m%contextSwitching_ON%, 'Connected as dwho' )
or print STDERR Dumper( $res->[2]->[0] );
count(2);
$client->logout($id);
##
## Try to authenticate with a user authorized to switch context
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'Auth query'
);
count(1);
$id = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
# Get Menu
# ------------------------
ok(
$res = $client->_get(
'/',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'Get Menu',
);
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] );
expectAuthenticatedAs( $res, 'dwho' );
ok(
$res->[2]->[0] =~
m%<span trspan="contextSwitching_ON">contextSwitching_ON</span>%,
'Connected as dwho'
) or print STDERR Dumper( $res->[2]->[0] );
count(2);
# ContextSwitching form -> PE_MALFORMEDUSER
# ------------------------
ok(
$res = $client->_get(
'/switchcontext',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'ContextSwitching form',
);
count(1);
my ( $host, $url, $query ) =
expectForm( $res, undef, '/switchcontext', 'spoofId' );
ok( $res->[2]->[0] =~ m%<span trspan="contextSwitching_ON">%,
'Found trspan="contextSwitching_ON"' )
or explain( $res->[2]->[0], 'trspan="contextSwitching_ON"' );
count(1);
$query =~ s/spoofId=/spoofId=msmith/;
ok(
$res = $client->_post(
'/switchcontext',
IO::String->new($query),
cookie => "lemonldap=$id",
length => length($query),
accept => 'text/html',
),
'POST switchcontext'
);
ok( $res->[2]->[0] =~ m%<span trmsg="40">%, 'PE_MALFORMEDUSER' )
or explain( $res->[2]->[0], 'PE_MALFORMEDUSER' );
count(2);
# ContextSwitching form -> PE_MALFORMEDUSER
# ------------------------
ok(
$res = $client->_get(
'/switchcontext',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'ContextSwitching form',
);
count(1);
( $host, $url, $query ) =
expectForm( $res, undef, '/switchcontext', 'spoofId' );
ok( $res->[2]->[0] =~ m%<span trspan="contextSwitching_ON">%,
'Found trspan="contextSwitching_ON"' )
or explain( $res->[2]->[0], 'trspan="contextSwitching_ON"' );
count(1);
$query =~ s/spoofId=/spoofId=</;
ok(
$res = $client->_post(
'/switchcontext',
IO::String->new($query),
cookie => "lemonldap=$id",
length => length($query),
accept => 'text/html',
),
'POST switchcontext'
);
ok( $res->[2]->[0] =~ m%<span trmsg="40">%, 'PE_MALFORMEDUSER' )
or explain( $res->[2]->[0], 'PE_MALFORMEDUSER' );
count(2);
# ContextSwitching form -> PE_MALFORMEDUSER
# ------------------------
ok(
$res = $client->_get(
'/switchcontext',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'ContextSwitching form',
);
count(1);
( $host, $url, $query ) =
expectForm( $res, undef, '/switchcontext', 'spoofId' );
ok( $res->[2]->[0] =~ m%<span trspan="contextSwitching_ON">%,
'Found trspan="contextSwitching_ON"' )
or explain( $res->[2]->[0], 'trspan="contextSwitching_ON"' );
count(1);
$query =~ s/spoofId=/spoofId=darkVador/;
ok(
$res = $client->_post(
'/switchcontext',
IO::String->new($query),
cookie => "lemonldap=$id",
length => length($query),
accept => 'text/html',
),
'POST switchcontext'
);
ok( $res->[2]->[0] =~ m%<span trmsg="40">%, 'PE_MALFORMEDUSER' )
or explain( $res->[2]->[0], 'PE_MALFORMEDUSER' );
count(2);
# ContextSwitching form -> No impersonation required
# ------------------------
ok(
$res = $client->_get(
'/switchcontext',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'ContextSwitching form',
);
count(1);
( $host, $url, $query ) =
expectForm( $res, undef, '/switchcontext', 'spoofId' );
ok( $res->[2]->[0] =~ m%<span trspan="contextSwitching_ON">%,
'Found trspan="contextSwitching_ON"' )
or explain( $res->[2]->[0], 'trspan="contextSwitching_ON"' );
$query =~ s/spoofId=/spoofId=dwho/;
ok(
$res = $client->_post(
'/switchcontext',
IO::String->new($query),
cookie => "lemonldap=$id",
length => length($query),
accept => 'text/html',
),
'POST switchcontext'
);
ok(
$res = $client->_get(
'/',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'Get Menu',
);
ok( $res->[2]->[0] =~ m%<span trspan="contextSwitching_ON">%,
'Found trspan="contextSwitching_ON"' )
or explain( $res->[2]->[0], 'trspan="contextSwitching_ON"' );
count(4);
expectAuthenticatedAs( $res, 'dwho' );
# ContextSwitching form -> PE_OK
# ------------------------
ok(
$res = $client->_get(
'/switchcontext',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'ContextSwitching form',
);
count(1);
( $host, $url, $query ) =
expectForm( $res, undef, '/switchcontext', 'spoofId' );
ok( $res->[2]->[0] =~ m%<span trspan="contextSwitching_ON">%,
'Found trspan="contextSwitching_ON"' )
or explain( $res->[2]->[0], 'trspan="contextSwitching_ON"' );
$query =~ s/spoofId=/spoofId=rtyler/;
ok(
$res = $client->_post(
'/switchcontext',
IO::String->new($query),
cookie => "lemonldap=$id",
length => length($query),
accept => 'text/html',
),
'POST switchcontext'
);
# Refresh cookie value
$id = expectCookie($res);
ok(
$res = $client->_get(
'/',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'Get Menu',
);
count(3);
expectAuthenticatedAs( $res, 'rtyler' );
ok( $res->[2]->[0] =~ m%<span trspan="contextSwitching_OFF">%,
'Found trspan="contextSwitching_ON"' )
or explain( $res->[2]->[0], 'trspan="contextSwitching_OFF"' );
ok(
$res = $client->_get(
'/switchcontext',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'Stop context switching',
);
# Refresh cookie value
$id = expectCookie($res);
ok(
$res = $client->_get(
'/',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'Get Menu',
);
count(3);
expectAuthenticatedAs( $res, 'dwho' );
ok( $res->[2]->[0] =~ m%<span trspan="contextSwitching_ON">%,
'Found trspan="contextSwitching_ON"' )
or explain( $res->[2]->[0], 'trspan="contextSwitching_ON"' );
count(1);
# Log out request
# ------------------------
ok(
$res = $client->_get(
'/',
query => 'logout=1',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'Get Menu',
);
count(1);
expectOK($res);
ok(
$res->[2]->[0] =~
m%<div class="message message-positive alert"><span trmsg="-7"></span></div>%,
'Dwho has been well disconnected'
) or print STDERR Dumper( $res->[2]->[0] );
count(1);
clean_sessions();
done_testing( count() );

View File

@ -49,7 +49,11 @@
# Main package # Main package
#============================================================================== #==============================================================================
Name: lemonldap-ng Name: lemonldap-ng
<<<<<<< HEAD
Version: 2.1.0 Version: 2.1.0
=======
Version: 2.0.5
>>>>>>> v2.0
Release: %{?pre_release:0.}1%{?pre_release:.%{pre_release}}%{?dist} Release: %{?pre_release:0.}1%{?pre_release:.%{pre_release}}%{?dist}
Summary: LemonLDAP-NG WebSSO Summary: LemonLDAP-NG WebSSO
License: GPLv2+ License: GPLv2+
@ -124,7 +128,7 @@ BuildRequires: perl(IO::Select)
BuildRequires: perl(IO::Socket::INET) BuildRequires: perl(IO::Socket::INET)
BuildRequires: perl(IO::String) BuildRequires: perl(IO::String)
BuildRequires: perl(JSON) BuildRequires: perl(JSON)
%if 0%{?fedora} >= 29 %if 0%{?fedora}
BuildRequires: perl(Lasso) BuildRequires: perl(Lasso)
BuildRequires: perl(Glib) BuildRequires: perl(Glib)
%endif %endif
@ -194,16 +198,13 @@ Requires: lemonldap-ng-test = %{version}-%{release}
%if 0%{?fedora} %if 0%{?fedora}
%{?perl_default_filter} %{?perl_default_filter}
%if 0%{?fedora} < 29
%global __requires_exclude perl\\(Lasso
%endif
%endif %endif
%description %description
LemonLdap::NG is a modular Web-SSO based on Apache::Session modules. It LemonLdap::NG is a modular Web-SSO based on Apache::Session modules. It
simplifies the build of a protected area with a few changes in the simplifies the build of a protected area with a few changes in the
application. It manages both authentication and authorization and provides application. It manages both authentication and authorization and provides
headers for accounting. headers for accounting.
So you can have a full AAA protection for your web space as described below. So you can have a full AAA protection for your web space as described below.
#============================================================================== #==============================================================================