Merge branch 'v2.0'
This commit is contained in:
commit
d2d9988b61
6
COPYING
6
COPYING
|
@ -96,6 +96,12 @@ Comment: downloaded from
|
|||
.
|
||||
Author is unknown and license may be W3C or public-domain
|
||||
|
||||
Files: lemonldap-ng-portal/site/htdocs/static/common/modules/GitHub.png
|
||||
Copyright: GitHub
|
||||
License: MIT
|
||||
Comment: downloaded from
|
||||
https://commons.wikimedia.org/wiki/File:Octicons-mark-github.svg
|
||||
|
||||
Files: lemonldap-ng-portal/site/htdocs/static/bootstrap/u2f.png
|
||||
Copyright: Bautsch <https://commons.wikimedia.org/wiki/User:Bautsch>
|
||||
License: CC0-1.0
|
||||
|
|
|
@ -62,6 +62,12 @@
|
|||
DocumentRoot __MANAGERAPIDIR__
|
||||
|
||||
<Location />
|
||||
|
||||
# By default, access to this VHost is denied
|
||||
# If you want to enable the manager APIs, you MUST
|
||||
# implement a robust authentication scheme to protect this
|
||||
# VHost since LemonLDAP::NG provides no protection to the
|
||||
# Manager APIs yet
|
||||
Require all denied
|
||||
|
||||
<IfModule mod_deflate.c>
|
||||
|
|
|
@ -62,6 +62,12 @@
|
|||
DocumentRoot __MANAGERAPIDIR__
|
||||
|
||||
<Location />
|
||||
|
||||
# By default, access to this VHost is denied
|
||||
# If you want to enable the manager APIs, you MUST
|
||||
# implement a robust authentication scheme to protect this
|
||||
# VHost since LemonLDAP::NG provides no protection to the
|
||||
# Manager APIs yet
|
||||
<IfVersion >= 2.3>
|
||||
Require all denied
|
||||
</IfVersion>
|
||||
|
|
|
@ -62,6 +62,12 @@
|
|||
DocumentRoot __MANAGERAPIDIR__
|
||||
|
||||
<Location />
|
||||
|
||||
# By default, access to this VHost is denied
|
||||
# If you want to enable the manager APIs, you MUST
|
||||
# implement a robust authentication scheme to protect this
|
||||
# VHost since LemonLDAP::NG provides no protection to the
|
||||
# Manager APIs yet
|
||||
Order Deny,Allow
|
||||
Deny from all
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
server {
|
||||
listen __PORT__;
|
||||
listen [::]:__PORT__;
|
||||
server_name manager-api.__DNSDOMAIN__;
|
||||
root __MANAGERAPIDIR__;
|
||||
# Use "lm_app" format to get username in nginx.log (see nginx-lmlog.conf)
|
||||
|
@ -36,8 +37,14 @@ server {
|
|||
# Uncomment this if you use https only
|
||||
#add_header Strict-Transport-Security "max-age=15768000";
|
||||
|
||||
# Access control
|
||||
# By default, access to this VHost is denied
|
||||
# If you want to enable the manager APIs, you MUST
|
||||
# implement a robust authentication scheme to protect this
|
||||
# VHost since LemonLDAP::NG provides no protection to the
|
||||
# Manager APIs yet
|
||||
#
|
||||
#allow 127.0.0.0/8;
|
||||
#allow ::1/128;
|
||||
deny all;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ include __CONFDIR__/nginx-lmlog.conf;
|
|||
|
||||
server {
|
||||
listen __PORT__;
|
||||
listen [::]:__PORT__;
|
||||
server_name reload.__DNSDOMAIN__;
|
||||
root /var/www/html;
|
||||
|
||||
|
@ -31,7 +32,8 @@ server {
|
|||
#real_ip_header X-Forwarded-For;
|
||||
|
||||
location = /reload {
|
||||
allow 127.0.0.1;
|
||||
allow 127.0.0.1/8;
|
||||
allow ::1/128;
|
||||
deny all;
|
||||
|
||||
# FastCGI configuration
|
||||
|
@ -55,7 +57,8 @@ server {
|
|||
|
||||
# Uncomment this if status is enabled
|
||||
#location = /status {
|
||||
# allow 127.0.0.1;
|
||||
# allow 127.0.0.1/8;
|
||||
# allow ::1/128;
|
||||
# deny all;
|
||||
# # FastCGI configuration
|
||||
# include /etc/nginx/fastcgi_params;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
server {
|
||||
listen __PORT__;
|
||||
listen [::]:__PORT__;
|
||||
server_name manager.__DNSDOMAIN__;
|
||||
root __MANAGERSITEDIR__;
|
||||
# Use "lm_app" format to get username in nginx.log (see nginx-lmlog.conf)
|
||||
|
@ -43,6 +44,7 @@ server {
|
|||
index manager.psgi;
|
||||
try_files $uri $uri/ =404;
|
||||
allow 127.0.0.0/8;
|
||||
allow ::1/128;
|
||||
deny all;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ upstream llng_portal_upstream {
|
|||
|
||||
server {
|
||||
listen __PORT__;
|
||||
listen [::]:__PORT__;
|
||||
server_name auth.__DNSDOMAIN__;
|
||||
root __PORTALSITEDIR__;
|
||||
# Use "lm_app" format to get username in nginx.log (see nginx-lmlog.conf)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
server {
|
||||
listen __PORT__;
|
||||
listen [::]:__PORT__;
|
||||
server_name test1.__DNSDOMAIN__ test2.__DNSDOMAIN__;
|
||||
root __TESTDIR__;
|
||||
|
||||
|
@ -113,7 +114,8 @@ server {
|
|||
}
|
||||
|
||||
#location = /status {
|
||||
# allow 127.0.0.1;
|
||||
# allow 127.0.0.1/8;
|
||||
# allow ::1/128;
|
||||
# deny all;
|
||||
# include /etc/nginx/fastcgi_params;
|
||||
# fastcgi_pass unix:__FASTCGISOCKDIR__/llng-fastcgi.sock;
|
||||
|
|
|
@ -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(?:S(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar|Macro)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option|Macro)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|c(?:as(?:A(?:ppMetaData(?:(?:ExportedVar|Option|Macro)s|Node)|ttributes)|S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions))|(?:ustom(?:Plugins|Add)Param|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|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|AllowOffline|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:User(?:Display(?:Empty(?:Header|Value)s|PersistentInfo))?|State|XSS)|o(?:ntextSwitchingStopWithLogout|mpactConf|rsEnabled)|da)|p(?:ortal(?:Display(?:Re(?:setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|RequireOldPassword|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|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)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|re(?:st(?:(?:Session|Config)Server|ExportSecretKeys)|freshSessions)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|g(?:roupsBeforeMacros|lobalLogoutTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs))$/;
|
||||
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)|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|kipRenewConfirmation|fRemovedUseNotif|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:Allow(?:PasswordGrant|Offline)|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|RequireOldPassword|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|o(?:ntextSwitchingStopWithLogout|mpactConf|rsEnabled)|heck(?:State|User|XSS)|da)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|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)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|re(?:st(?:(?:Session|Config)Server|ExportSecretKeys)|freshSessions)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|g(?:roupsBeforeMacros|lobalLogoutTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs))$/;
|
||||
|
||||
our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' );
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ sub defaultValues {
|
|||
'activeTimer' => 1,
|
||||
'ADPwdExpireWarning' => 0,
|
||||
'ADPwdMaxAge' => 0,
|
||||
'apacheAuthnLevel' => 4,
|
||||
'apacheAuthnLevel' => 3,
|
||||
'applicationList' => {
|
||||
'default' => {
|
||||
'catname' => 'Default category',
|
||||
|
@ -36,6 +36,9 @@ sub defaultValues {
|
|||
'http://auth.example.com/certificateReset',
|
||||
'certificateResetByMailValidityDelay' => 0,
|
||||
'checkTime' => 600,
|
||||
'checkUserDisplayEmptyHeaders' => 0,
|
||||
'checkUserDisplayEmptyValues' => 0,
|
||||
'checkUserDisplayPersistentInfo' => 0,
|
||||
'checkUserHiddenAttributes' => '_loginHistory _session_id hGroups',
|
||||
'checkUserIdRule' => 1,
|
||||
'checkXSS' => 1,
|
||||
|
@ -80,6 +83,9 @@ sub defaultValues {
|
|||
'failedLoginNumber' => 5,
|
||||
'favAppsMaxNumber' => 3,
|
||||
'formTimeout' => 120,
|
||||
'githubAuthnLevel' => 1,
|
||||
'githubScope' => 'user:email',
|
||||
'githubUserField' => 'login',
|
||||
'globalLogoutRule' => 0,
|
||||
'globalLogoutTimer' => 1,
|
||||
'globalStorage' => 'Apache::Session::File',
|
||||
|
@ -326,6 +332,9 @@ sub defaultValues {
|
|||
'sfRemovedNotifTitle' => 'Second factor notification',
|
||||
'sfRequired' => 0,
|
||||
'showLanguages' => 1,
|
||||
'singleIP' => 0,
|
||||
'singleSession' => 0,
|
||||
'singleUserByIP' => 0,
|
||||
'slaveAuthnLevel' => 2,
|
||||
'slaveExportedVars' => {},
|
||||
'SMTPServer' => '',
|
||||
|
|
|
@ -733,7 +733,7 @@ sub sfExtra {
|
|||
$tmp->{id} = "sfExtra/$mod";
|
||||
$tmp->{type} = 'sfExtra';
|
||||
$tmp->{data}->{$_} = $val->{$mod}->{$_}
|
||||
foreach (qw(type rule logo label));
|
||||
foreach (qw(type rule logo level label));
|
||||
my $over = $val->{$mod}->{over} // {};
|
||||
$tmp->{data}->{over} = [ map { [ $_, $over->{$_} ] } keys %$over ];
|
||||
push @$res, $tmp;
|
||||
|
|
|
@ -27,7 +27,7 @@ our $specialNodeKeys = '(?:(?:(?:saml(?:ID|S)|oidc[OR])P|cas(?:App|Srv))MetaData
|
|||
our $casAppMetaDataNodeKeys = 'casAppMetaData(?:Options(?:UserAttribut|Servic|Rul)e|(?:ExportedVar|Macro)s)';
|
||||
our $casSrvMetaDataNodeKeys = 'casSrvMetaData(?:Options(?:ProxiedServices|DisplayName|SortNumber|Gateway|Renew|Icon|Url)|ExportedVars)';
|
||||
our $oidcOPMetaDataNodeKeys = 'oidcOPMetaData(?:Options(?:C(?:lient(?:Secret|ID)|heckJWTSignature|onfigurationURI)|S(?:toreIDToken|ortNumber|cope)|TokenEndpointAuthMethod|(?:JWKSTimeou|Promp)t|I(?:DTokenMaxAge|con)|U(?:iLocales|seNonce)|Display(?:Name)?|AcrValues|MaxAge)|ExportedVars|J(?:SON|WKS))';
|
||||
our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:A(?:(?:uthorizationCode|ccessToken)Expiration|llowOffline)|I(?:DToken(?:ForceClaims|Expiration|SignAlg)|con)|R(?:e(?:directUris|freshToken|quirePKCE)|ule)|Logout(?:SessionRequired|Type|Url)|P(?:ostLogoutRedirectUris|ublic)|OfflineSessionExpiration|Client(?:Secret|ID)|BypassConsent|DisplayName|ExtraClaims|UserIDAttr)|(?:ExportedVar|Macro)s)';
|
||||
our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:A(?:(?:uthorizationCode|ccessToken)Expiration|llow(?:PasswordGrant|Offline)|dditionalAudiences)|I(?:DToken(?:ForceClaims|Expiration|SignAlg)|con)|R(?:e(?:directUris|freshToken|quirePKCE)|ule)|Logout(?:SessionRequired|Type|Url)|P(?:ostLogoutRedirectUris|ublic)|OfflineSessionExpiration|Client(?:Secret|ID)|BypassConsent|DisplayName|ExtraClaims|UserIDAttr)|(?:ExportedVar|Macro)s)';
|
||||
our $samlIDPMetaDataNodeKeys = 'samlIDPMetaData(?:Options(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|EncryptionMod|UserAttribut|DisplayNam)e|S(?:ignS[LS]OMessage|toreSAMLToken|[LS]OBinding|ortNumber)|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Re(?:questedAuthnContext|solutionRule|layStateURL)|Force(?:Authn|UTF8)|I(?:sPassive|con)|NameIDFormat)|ExportedAttributes|XML)';
|
||||
our $samlSPMetaDataNodeKeys = 'samlSPMetaData(?:Options(?:N(?:ameID(?:SessionKey|Format)|otOnOrAfterTimeout)|S(?:essionNotOnOrAfterTimeout|ignS[LS]OMessage)|(?:CheckS[LS]OMessageSignatur|OneTimeUs|Rul)e|En(?:ableIDPInitiatedURL|cryptionMode)|ForceUTF8)|(?:ExportedAttribute|Macro)s|XML)';
|
||||
our $virtualHostKeys = '(?:vhost(?:A(?:uthnLevel|liases)|(?:Maintenanc|Typ)e|ServiceTokenTTL|Https|Port)|(?:exportedHeader|locationRule)s|post)';
|
||||
|
@ -42,6 +42,7 @@ our $authParameters = {
|
|||
dbiParams => [qw(dbiAuthnLevel dbiExportedVars dbiAuthChain dbiAuthUser dbiAuthPassword dbiUserChain dbiUserUser dbiUserPassword dbiAuthTable dbiUserTable dbiAuthLoginCol dbiAuthPasswordCol dbiPasswordMailCol userPivot dbiAuthPasswordHash dbiDynamicHashEnabled dbiDynamicHashValidSchemes dbiDynamicHashValidSaltedSchemes dbiDynamicHashNewPasswordScheme)],
|
||||
demoParams => [qw(demoExportedVars)],
|
||||
facebookParams => [qw(facebookAuthnLevel facebookExportedVars facebookAppId facebookAppSecret facebookUserField)],
|
||||
githubParams => [qw(githubAuthnLevel githubClientID githubClientSecret githubUserField githubScope)],
|
||||
gpgParams => [qw(gpgAuthnLevel gpgDb)],
|
||||
kerberosParams => [qw(krbAuthnLevel krbKeytab krbByJs krbRemoveDomain)],
|
||||
ldapParams => [qw(ldapAuthnLevel ldapExportedVars ldapServer ldapPort ldapBase managerDn managerPassword ldapTimeout ldapVersion ldapRaw LDAPFilter AuthLDAPFilter mailLDAPFilter ldapSearchDeref ldapGroupBase ldapGroupObjectClass ldapGroupAttributeName ldapGroupAttributeNameUser ldapGroupAttributeNameSearch ldapGroupDecodeSearchedValue ldapGroupRecursive ldapGroupAttributeNameGroup ldapPpolicyControl ldapSetPassword ldapChangePasswordAsUser ldapPwdEnc ldapUsePasswordResetAttribute ldapPasswordResetAttribute ldapPasswordResetAttributeValue ldapAllowResetExpiredPassword ldapITDS)],
|
||||
|
|
|
@ -72,6 +72,7 @@ sub encodings { $_[0]->env->{HTTP_ACCEPT_ENCODING} }
|
|||
sub languages { $_[0]->env->{HTTP_ACCEPT_LANGUAGE} }
|
||||
sub authorization { $_[0]->env->{HTTP_AUTHORIZATION} }
|
||||
sub hostname { $_[0]->env->{HTTP_HOST} }
|
||||
sub origin { $_[0]->env->{HTTP_ORIGIN} }
|
||||
sub referer { $_[0]->env->{REFERER} }
|
||||
sub query_string { $_[0]->env->{QUERY_STRING} }
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ sub run {
|
|||
my ( $class, $req, $rule, $protection ) = @_;
|
||||
my $uri = $req->{env}->{REQUEST_URI};
|
||||
my $cn = $class->tsv->{cookieName};
|
||||
my ( $id, $ret, $session );
|
||||
my ( $id, $session );
|
||||
if ( $uri =~ s/[\?&;]${cn}cda=(\w+)$//oi ) {
|
||||
if ( $id = $class->fetchId($req)
|
||||
and $session = $class->retrieveSession( $req, $id ) )
|
||||
|
@ -48,10 +48,8 @@ sub run {
|
|||
return $class->REDIRECT;
|
||||
}
|
||||
}
|
||||
( $ret, $session ) =
|
||||
$class->Lemonldap::NG::Handler::Main::run( $req, $rule, $protection );
|
||||
|
||||
return $ret;
|
||||
return $class->Lemonldap::NG::Handler::Main::run( $req, $rule,
|
||||
$protection );
|
||||
}
|
||||
|
||||
## @rmethod protected hash getCDAInfos(id)
|
||||
|
|
|
@ -172,7 +172,9 @@ sub tplParams {
|
|||
sub javascript {
|
||||
my ( $self, $req ) = @_;
|
||||
my $res = eval {
|
||||
$self->hLoadedPlugins->{viewer}->diffRule->( $req, $req->{userData} );
|
||||
$self->hLoadedPlugins->{viewer}
|
||||
&& $self->hLoadedPlugins->{viewer}
|
||||
->diffRule->( $req, $req->{userData} );
|
||||
} || 0;
|
||||
print STDERR $@ if $@;
|
||||
my $impPrefix = $self->{impersonationPrefix} || 'real_';
|
||||
|
|
|
@ -353,7 +353,7 @@ sub _checkType {
|
|||
|
||||
return {
|
||||
res => "ko",
|
||||
code => 405,
|
||||
code => 400,
|
||||
msg =>
|
||||
"Invalid input: Type \"$type\" does not exist. Allowed values for type are: \"U2F\", \"TOTP\" or \"UBK\""
|
||||
}
|
||||
|
|
|
@ -20,17 +20,16 @@ sub _isSimpleKeyValueHash {
|
|||
return 1;
|
||||
}
|
||||
|
||||
sub _setDefaultValues {
|
||||
my ( $self, $attrs, $rootNode ) = @_;
|
||||
sub _getDefaultValues {
|
||||
my ( $self, $rootNode ) = @_;
|
||||
my @allAttrs = $self->_listAttributes($rootNode);
|
||||
my $defaultAttrs = Lemonldap::NG::Manager::Build::Attributes::attributes();
|
||||
my $attrs = {};
|
||||
|
||||
foreach $attr (@allAttrs) {
|
||||
unless ( defined $attrs->{$attr} ) {
|
||||
$attrs->{$attr} = $defaultAttrs->{$attr}->{default}
|
||||
if ( defined $defaultAttrs->{$attr}
|
||||
&& defined $defaultAttrs->{$attr}->{default} );
|
||||
}
|
||||
$attrs->{$attr} = $defaultAttrs->{$attr}->{default}
|
||||
if ( defined $defaultAttrs->{$attr}
|
||||
&& defined $defaultAttrs->{$attr}->{default} );
|
||||
}
|
||||
|
||||
return $attrs;
|
||||
|
@ -47,7 +46,7 @@ sub _hasAllowedAttributes {
|
|||
msg => "Invalid input: Attribute $attribute is not a string."
|
||||
};
|
||||
}
|
||||
unless ( grep { /^$attribute$/ } @allowedAttributes ) {
|
||||
unless ( grep { $_ eq $attribute } @allowedAttributes ) {
|
||||
return {
|
||||
res => "ko",
|
||||
msg => "Invalid input: Attribute $attribute does not exist."
|
||||
|
@ -76,4 +75,41 @@ sub _listNodeAttributes {
|
|||
return @attributes;
|
||||
}
|
||||
|
||||
sub _translateOptionApiToConf {
|
||||
my ( $self, $optionName, $prefix ) = @_;
|
||||
|
||||
# For consistency
|
||||
$optionName =~ s/^clientId$/clientID/;
|
||||
|
||||
return $prefix . "MetaDataOptions" . ( ucfirst $optionName );
|
||||
}
|
||||
|
||||
sub _translateOptionConfToApi {
|
||||
my ( $self, $optionName ) = @_;
|
||||
$optionName =~ s/^(\w+)MetaDataOptions//;
|
||||
|
||||
$optionName = lcfirst $optionName;
|
||||
|
||||
# iDToken looks ugly
|
||||
$optionName =~ s/^iDToken/IDToken/;
|
||||
|
||||
# For consistency
|
||||
$optionName =~ s/^clientID/clientId/;
|
||||
return $optionName;
|
||||
}
|
||||
|
||||
sub _getRegexpFromPattern {
|
||||
my ( $self, $pattern ) = @_;
|
||||
return unless ( $pattern =~ /[\w\.\-\*]+/ );
|
||||
|
||||
# . is allowed, and must be escaped
|
||||
$pattern =~ s/\./\\\./g;
|
||||
$pattern =~ s/\*/\.\*/g;
|
||||
|
||||
# anchor string, unless * was provided
|
||||
$pattern = "^$pattern\$" if ( $pattern =~ /\*/ );
|
||||
|
||||
return qr/$pattern/;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -7,6 +7,7 @@ package Lemonldap::NG::Manager::Api;
|
|||
use 5.10.0;
|
||||
use utf8;
|
||||
use Mouse;
|
||||
use Lemonldap::NG::Manager::Conf::Parser;
|
||||
|
||||
extends 'Lemonldap::NG::Manager::Api::Common';
|
||||
|
||||
|
@ -40,9 +41,14 @@ sub findOidcRpByConfKey {
|
|||
: ( defined $req->params('pattern') ? $req->params('pattern') : undef )
|
||||
);
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: pattern is missing', 405 )
|
||||
return $self->sendError( $req, 'Invalid input: pattern is missing', 400 )
|
||||
unless ( defined $pattern );
|
||||
|
||||
unless ( $pattern = $self->_getRegexpFromPattern($pattern) ) {
|
||||
return $self->sendError( $req, 'Invalid input: pattern is invalid',
|
||||
400 );
|
||||
}
|
||||
|
||||
$self->logger->debug(
|
||||
"[API] Find OIDC RPs by confKey regexp $pattern requested");
|
||||
|
||||
|
@ -67,7 +73,7 @@ sub findOidcRpByClientId {
|
|||
)
|
||||
);
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: clientId is missing', 405 )
|
||||
return $self->sendError( $req, 'Invalid input: clientId is missing', 400 )
|
||||
unless ( defined $clientId );
|
||||
|
||||
$self->logger->debug("[API] Find OIDC RPs by clientId $clientId requested");
|
||||
|
@ -87,15 +93,31 @@ sub addOidcRp {
|
|||
my ( $self, $req ) = @_;
|
||||
my $add = $req->jsonBodyToObj;
|
||||
|
||||
return $self->sendError( $req, "Invalid input: " . $req->error, 405 )
|
||||
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
|
||||
unless ($add);
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: confKey is missing', 405 )
|
||||
return $self->sendError( $req, 'Invalid input: confKey is missing', 400 )
|
||||
unless ( defined $add->{confKey} );
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: clientId is missing', 405 )
|
||||
return $self->sendError( $req, 'Invalid input: confKey is not a string',
|
||||
400 )
|
||||
if ( ref $add->{confKey} );
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: clientId is missing', 400 )
|
||||
unless ( defined $add->{clientId} );
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: clientId is not a string',
|
||||
400 )
|
||||
if ( ref $add->{clientId} );
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: redirectUris is missing',
|
||||
400 )
|
||||
unless ( defined $add->{redirectUris} );
|
||||
|
||||
return $self->sendError( $req,
|
||||
'Invalid input: redirectUris must be an array', 400 )
|
||||
unless ( ref( $add->{redirectUris} ) eq "ARRAY" );
|
||||
|
||||
$self->logger->debug(
|
||||
"[API] Add OIDC RP with confKey $add->{confKey} and clientId $add->{clientId} requested"
|
||||
);
|
||||
|
@ -106,25 +128,29 @@ sub addOidcRp {
|
|||
return $self->sendError(
|
||||
$req,
|
||||
"Invalid input: An OIDC RP with confKey $add->{confKey} already exists",
|
||||
405
|
||||
409
|
||||
) if ( defined $self->_getOidcRpByConfKey( $conf, $add->{confKey} ) );
|
||||
|
||||
return $self->sendError(
|
||||
$req,
|
||||
"Invalid input: An OIDC RP with clientId $add->{clientId} already exists",
|
||||
405
|
||||
409
|
||||
) if ( defined $self->_getOidcRpByClientId( $conf, $add->{clientId} ) );
|
||||
|
||||
$add->{options} = {} unless ( defined $add->{options} );
|
||||
$add->{options}->{oidcRPMetaDataOptionsClientID} = $add->{clientId};
|
||||
$add->{options}->{clientId} = $add->{clientId};
|
||||
$add->{options}->{redirectUris} = $add->{redirectUris};
|
||||
|
||||
my $res = $self->_pushOidcRp( $conf, $add->{confKey}, $add, 1 );
|
||||
|
||||
return $self->sendError( $req, $res->{msg}, 405 )
|
||||
return $self->sendError( $req, $res->{msg}, 400 )
|
||||
unless ( $res->{res} eq 'ok' );
|
||||
|
||||
return $self->sendJSONresponse( $req,
|
||||
{ message => "Successful operation" } );
|
||||
return $self->sendJSONresponse(
|
||||
$req,
|
||||
{ message => "Successful operation" },
|
||||
code => 201
|
||||
);
|
||||
}
|
||||
|
||||
sub updateOidcRp {
|
||||
|
@ -134,7 +160,7 @@ sub updateOidcRp {
|
|||
|
||||
my $update = $req->jsonBodyToObj;
|
||||
|
||||
return $self->sendError( $req, "Invalid input: " . $req->error, 405 )
|
||||
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
|
||||
unless ($update);
|
||||
|
||||
$self->logger->debug(
|
||||
|
@ -154,16 +180,15 @@ sub updateOidcRp {
|
|||
# check if new clientID exists already
|
||||
my $res = $self->_isNewOidcRpClientIdUnique( $conf, $confKey, $update );
|
||||
|
||||
return $self->sendError( $req, $res->{msg}, 405 )
|
||||
return $self->sendError( $req, $res->{msg}, 409 )
|
||||
unless ( $res->{res} eq 'ok' );
|
||||
|
||||
$res = $self->_pushOidcRp( $conf, $confKey, $update, 0 );
|
||||
|
||||
return $self->sendError( $req, $res->{msg}, 405 )
|
||||
return $self->sendError( $req, $res->{msg}, 400 )
|
||||
unless ( $res->{res} eq 'ok' );
|
||||
|
||||
return $self->sendJSONresponse( $req,
|
||||
{ message => "Successful operation" } );
|
||||
return $self->sendJSONresponse( $req, undef, code => 204 );
|
||||
}
|
||||
|
||||
sub replaceOidcRp {
|
||||
|
@ -173,11 +198,31 @@ sub replaceOidcRp {
|
|||
|
||||
my $replace = $req->jsonBodyToObj;
|
||||
|
||||
return $self->sendError( $req, "Invalid input: " . $req->error, 405 )
|
||||
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
|
||||
unless ($replace);
|
||||
return $self->sendError( $req, 'Invalid input: clientId is missing', 405 )
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: confKey is missing', 400 )
|
||||
unless ( defined $replace->{confKey} );
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: confKey is not a string',
|
||||
400 )
|
||||
if ( ref $replace->{confKey} );
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: clientId is missing', 400 )
|
||||
unless ( defined $replace->{clientId} );
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: clientId is not a string',
|
||||
400 )
|
||||
if ( ref $replace->{clientId} );
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: redirectUris is missing',
|
||||
400 )
|
||||
unless ( defined $replace->{redirectUris} );
|
||||
|
||||
return $self->sendError( $req,
|
||||
'Invalid input: redirectUris must be an array', 400 )
|
||||
unless ( ref( $replace->{redirectUris} ) eq "ARRAY" );
|
||||
|
||||
$self->logger->debug(
|
||||
"[API] OIDC RP $confKey configuration replace requested");
|
||||
|
||||
|
@ -192,15 +237,18 @@ sub replaceOidcRp {
|
|||
|
||||
# check if new clientID exists already
|
||||
my $res = $self->_isNewOidcRpClientIdUnique( $conf, $confKey, $replace );
|
||||
return $self->sendError( $req, $res->{msg}, 405 )
|
||||
return $self->sendError( $req, $res->{msg}, 409 )
|
||||
unless ( $res->{res} eq 'ok' );
|
||||
|
||||
$replace->{options} = {} unless ( defined $replace->{options} );
|
||||
$replace->{options}->{clientId} = $replace->{clientId};
|
||||
$replace->{options}->{redirectUris} = $replace->{redirectUris};
|
||||
|
||||
$res = $self->_pushOidcRp( $conf, $confKey, $replace, 1 );
|
||||
return $self->sendError( $req, $res->{msg}, 405 )
|
||||
return $self->sendError( $req, $res->{msg}, 400 )
|
||||
unless ( $res->{res} eq 'ok' );
|
||||
|
||||
return $self->sendJSONresponse( $req,
|
||||
{ message => "Successful operation" } );
|
||||
return $self->sendJSONresponse( $req, undef, code => 204 );
|
||||
}
|
||||
|
||||
sub deleteOidcRp {
|
||||
|
@ -222,12 +270,12 @@ sub deleteOidcRp {
|
|||
delete $conf->{oidcRPMetaDataOptions}->{$confKey};
|
||||
delete $conf->{oidcRPMetaDataExportedVars}->{$confKey};
|
||||
delete $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey};
|
||||
delete $conf->{oidcRPMetaDataMacros}->{$confKey};
|
||||
|
||||
# Save configuration
|
||||
$self->_confAcc->saveConf($conf);
|
||||
|
||||
return $self->sendJSONresponse( $req,
|
||||
{ message => "Successful operation" } );
|
||||
return $self->sendJSONresponse( $req, undef, code => 204 );
|
||||
}
|
||||
|
||||
sub _getOidcRpByConfKey {
|
||||
|
@ -244,16 +292,37 @@ sub _getOidcRpByConfKey {
|
|||
my $exportedVars = $conf->{oidcRPMetaDataExportedVars}->{$confKey};
|
||||
|
||||
# Get extra claim
|
||||
my $extraClaim = $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey};
|
||||
my $extraClaims = $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey};
|
||||
|
||||
# Get macros
|
||||
my $macros = $conf->{oidcRPMetaDataMacros}->{$confKey} || {};
|
||||
|
||||
# Get options
|
||||
my $options = $conf->{oidcRPMetaDataOptions}->{$confKey};
|
||||
my $options = {};
|
||||
for
|
||||
my $configOption ( keys %{ $conf->{oidcRPMetaDataOptions}->{$confKey} } )
|
||||
{
|
||||
# redirectUris is handled as an array
|
||||
if ( $configOption eq "oidcRPMetaDataOptionsRedirectUris" ) {
|
||||
$options->{ $self->_translateOptionConfToApi($configOption) } = [
|
||||
split(
|
||||
/\s+/,
|
||||
$conf->{oidcRPMetaDataOptions}->{$confKey}->{$configOption}
|
||||
)
|
||||
];
|
||||
}
|
||||
else {
|
||||
$options->{ $self->_translateOptionConfToApi($configOption) } =
|
||||
$conf->{oidcRPMetaDataOptions}->{$confKey}->{$configOption};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
confKey => $confKey,
|
||||
clientId => $clientId,
|
||||
exportedVars => $exportedVars,
|
||||
extraClaim => $extraClaim,
|
||||
extraClaims => $extraClaims,
|
||||
macros => $macros,
|
||||
options => $options
|
||||
};
|
||||
}
|
||||
|
@ -275,7 +344,7 @@ sub _isNewOidcRpClientIdUnique {
|
|||
my $curClientId = $self->_getOidcRpByConfKey( $conf, $confKey )->{clientId};
|
||||
my $newClientId =
|
||||
$oidcRp->{clientId}
|
||||
|| $oidcRp->{options}->{oidcRPMetaDataOptionsClientID}
|
||||
|| $oidcRp->{options}->{clientId}
|
||||
|| "";
|
||||
if ( $newClientId ne '' && $newClientId ne $curClientId ) {
|
||||
return {
|
||||
|
@ -292,23 +361,46 @@ sub _isNewOidcRpClientIdUnique {
|
|||
sub _pushOidcRp {
|
||||
my ( $self, $conf, $confKey, $push, $replace ) = @_;
|
||||
|
||||
my $translatedOptions = {};
|
||||
if ($replace) {
|
||||
$conf->{oidcRPMetaDataOptions}->{$confKey} = {};
|
||||
$conf->{oidcRPMetaDataExportedVars}->{$confKey} = {};
|
||||
$conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey} = {};
|
||||
$push->{options} =
|
||||
$self->_setDefaultValues( $push->{options}, 'oidcRPMetaDataNode' );
|
||||
$conf->{oidcRPMetaDataMacros}->{$confKey} = {};
|
||||
$translatedOptions = $self->_getDefaultValues('oidcRPMetaDataNode');
|
||||
}
|
||||
|
||||
if ( defined $push->{options} ) {
|
||||
my $res = $self->_hasAllowedAttributes( $push->{options},
|
||||
|
||||
foreach ( keys %{ $push->{options} } ) {
|
||||
|
||||
# redirectUris is handled as an array
|
||||
if ( $_ eq 'redirectUris' ) {
|
||||
my $option = $push->{options}->{$_};
|
||||
return {
|
||||
res => 'ko',
|
||||
msg => "Invalid input: redirectUris is not an array"
|
||||
}
|
||||
unless ( ref($option) eq "ARRAY" );
|
||||
|
||||
$translatedOptions->{ $self->_translateOptionApiToConf( $_,
|
||||
'oidcRP' ) } = join( ' ', @{ $push->{options}->{$_} } );
|
||||
}
|
||||
else {
|
||||
$translatedOptions->{ $self->_translateOptionApiToConf( $_,
|
||||
'oidcRP' ) } = $push->{options}->{$_};
|
||||
}
|
||||
}
|
||||
|
||||
my $res = $self->_hasAllowedAttributes( $translatedOptions,
|
||||
'oidcRPMetaDataNode' );
|
||||
return $res unless ( $res->{res} eq 'ok' );
|
||||
|
||||
foreach ( keys %{ $push->{options} } ) {
|
||||
foreach ( keys %{$translatedOptions} ) {
|
||||
$conf->{oidcRPMetaDataOptions}->{$confKey}->{$_} =
|
||||
$push->{options}->{$_};
|
||||
$translatedOptions->{$_};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$conf->{oidcRPMetaDataOptions}->{$confKey}->{oidcRPMetaDataOptionsClientID}
|
||||
|
@ -331,22 +423,54 @@ sub _pushOidcRp {
|
|||
}
|
||||
}
|
||||
|
||||
if ( defined $push->{extraClaim} ) {
|
||||
if ( $self->_isSimpleKeyValueHash( $push->{extraClaim} ) ) {
|
||||
foreach ( keys %{ $push->{extraClaim} } ) {
|
||||
$conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey}->{$_} =
|
||||
$push->{extraClaim}->{$_};
|
||||
if ( defined $push->{macros} ) {
|
||||
if ( $self->_isSimpleKeyValueHash( $push->{macros} ) ) {
|
||||
foreach ( keys %{ $push->{macros} } ) {
|
||||
$conf->{oidcRPMetaDataMacros}->{$confKey}->{$_} =
|
||||
$push->{macros}->{$_};
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
res => 'ko',
|
||||
msg =>
|
||||
"Invalid input: extraClaim is not a hash object with \"key\":\"value\" attributes"
|
||||
"Invalid input: macros is not a hash object with \"key\":\"value\" attributes"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if ( defined $push->{extraClaims} ) {
|
||||
if ( $self->_isSimpleKeyValueHash( $push->{extraClaims} ) ) {
|
||||
foreach ( keys %{ $push->{extraClaims} } ) {
|
||||
$conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey}->{$_} =
|
||||
$push->{extraClaims}->{$_};
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
res => 'ko',
|
||||
msg =>
|
||||
"Invalid input: extraClaims is not a hash object with \"key\":\"value\" attributes"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
# Test new configuration
|
||||
my $parser = Lemonldap::NG::Manager::Conf::Parser->new( {
|
||||
refConf => $self->_confAcc->getConf,
|
||||
newConf => $conf,
|
||||
req => {},
|
||||
}
|
||||
);
|
||||
unless ( $parser->testNewConf( $self->p ) ) {
|
||||
return {
|
||||
res => 'ko',
|
||||
code => 400,
|
||||
msg => "Configuration error: "
|
||||
. join( ". ", map { $_->{message} } @{ $parser->errors } ),
|
||||
};
|
||||
}
|
||||
|
||||
# Save configuration
|
||||
$self->_confAcc->saveConf($conf);
|
||||
|
||||
|
|
|
@ -38,9 +38,14 @@ sub findSamlSpByConfKey {
|
|||
: ( defined $req->params('pattern') ? $req->params('pattern') : undef )
|
||||
);
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: pattern is missing', 405 )
|
||||
return $self->sendError( $req, 'Invalid input: pattern is missing', 400 )
|
||||
unless ( defined $pattern );
|
||||
|
||||
unless ( $pattern = $self->_getRegexpFromPattern($pattern) ) {
|
||||
return $self->sendError( $req, 'Invalid input: pattern is invalid',
|
||||
400 );
|
||||
}
|
||||
|
||||
$self->logger->debug(
|
||||
"[API] Find SAML SPs by confKey regexp $pattern requested");
|
||||
|
||||
|
@ -63,7 +68,7 @@ sub findSamlSpByEntityId {
|
|||
)
|
||||
);
|
||||
|
||||
return $self->sendError( $req, 'entityId is missing', 405 )
|
||||
return $self->sendError( $req, 'entityId is missing', 400 )
|
||||
unless ( defined $entityId );
|
||||
|
||||
$self->logger->debug("[API] Find SAML SPs by entityId $entityId requested");
|
||||
|
@ -82,19 +87,19 @@ sub addSamlSp {
|
|||
my ( $self, $req ) = @_;
|
||||
my $add = $req->jsonBodyToObj;
|
||||
|
||||
return $self->sendError( $req, "Invalid input: " . $req->error, 405 )
|
||||
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
|
||||
unless ($add);
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: confKey is missing', 405 )
|
||||
return $self->sendError( $req, 'Invalid input: confKey is missing', 400 )
|
||||
unless ( defined $add->{confKey} );
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: metadata is missing', 405 )
|
||||
return $self->sendError( $req, 'Invalid input: metadata is missing', 400 )
|
||||
unless ( defined $add->{metadata} );
|
||||
|
||||
my $entityId = $self->_readSamlSpEntityId( $add->{metadata} );
|
||||
|
||||
return $self->sendError( $req,
|
||||
'Invalid input: entityID is missing in metadata', 405 )
|
||||
'Invalid input: entityID is missing in metadata', 400 )
|
||||
unless ( defined $entityId );
|
||||
|
||||
$self->logger->debug(
|
||||
|
@ -107,20 +112,23 @@ sub addSamlSp {
|
|||
return $self->sendError(
|
||||
$req,
|
||||
"Invalid input: A SAML SP with confKey $add->{confKey} already exists",
|
||||
405
|
||||
409
|
||||
) if ( defined $self->_getSamlSpByConfKey( $conf, $add->{confKey} ) );
|
||||
|
||||
return $self->sendError( $req,
|
||||
"Invalid input: A SAML SP with entityID $entityId already exists", 405 )
|
||||
"Invalid input: A SAML SP with entityID $entityId already exists", 409 )
|
||||
if ( defined $self->_getSamlSpByEntityId( $conf, $entityId ) );
|
||||
|
||||
my $res = $self->_pushSamlSp( $conf, $add->{confKey}, $add, 1 );
|
||||
|
||||
return $self->sendError( $req, $res->{msg}, 405 )
|
||||
return $self->sendError( $req, $res->{msg}, 400 )
|
||||
unless ( $res->{res} eq 'ok' );
|
||||
|
||||
return $self->sendJSONresponse( $req,
|
||||
{ message => "Successful operation" } );
|
||||
return $self->sendJSONresponse(
|
||||
$req,
|
||||
{ message => "Successful operation" },
|
||||
code => 201
|
||||
);
|
||||
}
|
||||
|
||||
sub replaceSamlSp {
|
||||
|
@ -131,10 +139,10 @@ sub replaceSamlSp {
|
|||
|
||||
my $replace = $req->jsonBodyToObj;
|
||||
|
||||
return $self->sendError( $req, "Invalid input: " . $req->error, 405 )
|
||||
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
|
||||
unless ($replace);
|
||||
|
||||
return $self->sendError( $req, 'Invalid input: metadata is missing', 405 )
|
||||
return $self->sendError( $req, 'Invalid input: metadata is missing', 400 )
|
||||
unless ( defined $replace->{metadata} );
|
||||
|
||||
$self->logger->debug(
|
||||
|
@ -152,16 +160,15 @@ sub replaceSamlSp {
|
|||
# check if new entityId exists already
|
||||
my $res = $self->_isNewSamlSpEntityIdUnique( $conf, $confKey, $replace );
|
||||
|
||||
return $self->sendError( $req, $res->{msg}, 405 )
|
||||
return $self->sendError( $req, $res->{msg}, 409 )
|
||||
unless ( $res->{res} eq 'ok' );
|
||||
|
||||
$res = $self->_pushSamlSp( $conf, $confKey, $replace, 1 );
|
||||
|
||||
return $self->sendError( $req, $res->{msg}, 405 )
|
||||
return $self->sendError( $req, $res->{msg}, 400 )
|
||||
unless ( $res->{res} eq 'ok' );
|
||||
|
||||
return $self->sendJSONresponse( $req,
|
||||
{ message => "Successful operation" } );
|
||||
return $self->sendJSONresponse( $req, undef, code => 204 );
|
||||
}
|
||||
|
||||
sub updateSamlSp {
|
||||
|
@ -172,7 +179,7 @@ sub updateSamlSp {
|
|||
|
||||
my $update = $req->jsonBodyToObj;
|
||||
|
||||
return $self->sendError( $req, "Invalid input: " . $req->error, 405 )
|
||||
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
|
||||
unless ($update);
|
||||
|
||||
$self->logger->debug(
|
||||
|
@ -192,17 +199,16 @@ sub updateSamlSp {
|
|||
# check if new entityId exists already
|
||||
$res = $self->_isNewSamlSpEntityIdUnique( $conf, $confKey, $update );
|
||||
|
||||
return $self->sendError( $req, $res->{msg}, 405 )
|
||||
return $self->sendError( $req, $res->{msg}, 409 )
|
||||
unless ( $res->{res} eq 'ok' );
|
||||
|
||||
}
|
||||
|
||||
$res = $self->_pushSamlSp( $conf, $confKey, $update, 0 );
|
||||
return $self->sendError( $req, $res->{msg}, 405 )
|
||||
return $self->sendError( $req, $res->{msg}, 400 )
|
||||
unless ( $res->{res} eq 'ok' );
|
||||
|
||||
return $self->sendJSONresponse( $req,
|
||||
{ message => "Successful operation" } );
|
||||
return $self->sendJSONresponse( $req, undef, code => 204 );
|
||||
}
|
||||
|
||||
sub deleteSamlSp {
|
||||
|
@ -225,12 +231,12 @@ sub deleteSamlSp {
|
|||
delete $conf->{samlSPMetaDataXML}->{$confKey};
|
||||
delete $conf->{samlSPMetaDataOptions}->{$confKey};
|
||||
delete $conf->{samlSPMetaDataExportedAttributes}->{$confKey};
|
||||
delete $conf->{samlSPMetaDataMacros}->{$confKey};
|
||||
|
||||
# Save configuration
|
||||
$self->_confAcc->saveConf($conf);
|
||||
|
||||
return $self->sendJSONresponse( $req,
|
||||
{ message => "Successful operation" } );
|
||||
return $self->sendJSONresponse( $req, undef, code => 204 );
|
||||
}
|
||||
|
||||
sub _getSamlSpByConfKey {
|
||||
|
@ -243,12 +249,21 @@ sub _getSamlSpByConfKey {
|
|||
my $metadata = $conf->{samlSPMetaDataXML}->{$confKey}->{samlSPMetaDataXML};
|
||||
|
||||
# Get options
|
||||
my $options = $conf->{samlSPMetaDataOptions}->{$confKey};
|
||||
my $options = {};
|
||||
for my $confOption ( keys %{ $conf->{samlSPMetaDataOptions}->{$confKey} } )
|
||||
{
|
||||
$options->{ $self->_translateOptionConfToApi($confOption) } =
|
||||
$conf->{samlSPMetaDataOptions}->{$confKey}->{$confOption};
|
||||
}
|
||||
|
||||
# Get macros
|
||||
my $macros = $conf->{samlSPMetaDataMacros}->{$confKey} || {};
|
||||
|
||||
my $samlSp = {
|
||||
confKey => $confKey,
|
||||
metadata => $metadata,
|
||||
exportedAttributes => {},
|
||||
macros => $macros,
|
||||
options => $options
|
||||
};
|
||||
|
||||
|
@ -344,25 +359,48 @@ sub _readSamlSpExportedAttributes {
|
|||
sub _pushSamlSp {
|
||||
my ( $self, $conf, $confKey, $push, $replace ) = @_;
|
||||
|
||||
my $translatedOptions = {};
|
||||
if ($replace) {
|
||||
$conf->{samlSPMetaDataXML}->{$confKey} = {};
|
||||
$conf->{samlSPMetaDataOptions}->{$confKey} = {};
|
||||
$conf->{samlSPMetaDataMacros}->{$confKey} = {};
|
||||
$conf->{samlSPMetaDataExportedAttributes}->{$confKey} = {};
|
||||
$push->{options} =
|
||||
$self->_setDefaultValues( $push->{options}, 'samlSPMetaDataNode' );
|
||||
$translatedOptions = $self->_getDefaultValues('samlSPMetaDataNode');
|
||||
}
|
||||
|
||||
$conf->{samlSPMetaDataXML}->{$confKey}->{samlSPMetaDataXML} =
|
||||
$push->{metadata};
|
||||
|
||||
if ( defined $push->{options} ) {
|
||||
my $res = $self->_hasAllowedAttributes( $push->{options},
|
||||
'samlSPMetaDataNode' );
|
||||
return $res unless ( $res->{res} eq 'ok' );
|
||||
|
||||
foreach ( keys %{ $push->{options} } ) {
|
||||
$translatedOptions->{ $self->_translateOptionApiToConf( $_,
|
||||
'samlSP' ) } = $push->{options}->{$_};
|
||||
}
|
||||
|
||||
my $res = $self->_hasAllowedAttributes( $translatedOptions,
|
||||
'samlSPMetaDataNode' );
|
||||
return $res unless ( $res->{res} eq 'ok' );
|
||||
foreach ( keys %{$translatedOptions} ) {
|
||||
$conf->{samlSPMetaDataOptions}->{$confKey}->{$_} =
|
||||
$push->{options}->{$_};
|
||||
$translatedOptions->{$_};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( defined $push->{macros} ) {
|
||||
if ( $self->_isSimpleKeyValueHash( $push->{macros} ) ) {
|
||||
foreach ( keys %{ $push->{macros} } ) {
|
||||
$conf->{samlSPMetaDataMacros}->{$confKey}->{$_} =
|
||||
$push->{macros}->{$_};
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
res => 'ko',
|
||||
msg =>
|
||||
"Invalid input: macros is not a hash object with \"key\":\"value\" attributes"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -376,6 +414,22 @@ sub _pushSamlSp {
|
|||
$res->{exportedAttributes};
|
||||
}
|
||||
|
||||
# Test new configuration
|
||||
my $parser = Lemonldap::NG::Manager::Conf::Parser->new( {
|
||||
refConf => $self->_confAcc->getConf,
|
||||
newConf => $conf,
|
||||
req => {},
|
||||
}
|
||||
);
|
||||
unless ( $parser->testNewConf( $self->p ) ) {
|
||||
return {
|
||||
res => 'ko',
|
||||
code => 400,
|
||||
msg => "Configuration error: "
|
||||
. join( ". ", map { $_->{message} } @{ $parser->errors } ),
|
||||
};
|
||||
}
|
||||
|
||||
# Save configuration
|
||||
$self->_confAcc->saveConf($conf);
|
||||
|
||||
|
|
|
@ -224,6 +224,7 @@ m[^(?:(?:\-+\s*BEGIN\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+\r?\n)?[a-zA-Z0-9/\+\r\
|
|||
},
|
||||
'select' => {
|
||||
'test' => sub {
|
||||
return 0, 'Value is not a scalar' if ref $_[0];
|
||||
my $test = grep( { $_ eq $_[0]; }
|
||||
map( { $_->{'k'}; } @{ $_[2]{'select'}; } ) );
|
||||
return $test
|
||||
|
@ -271,7 +272,7 @@ sub attributes {
|
|||
'type' => 'int'
|
||||
},
|
||||
'apacheAuthnLevel' => {
|
||||
'default' => 4,
|
||||
'default' => 3,
|
||||
'type' => 'int'
|
||||
},
|
||||
'applicationList' => {
|
||||
|
@ -314,6 +315,10 @@ sub attributes {
|
|||
'k' => 'Facebook',
|
||||
'v' => 'Facebook'
|
||||
},
|
||||
{
|
||||
'k' => 'GitHub',
|
||||
'v' => 'GitHub'
|
||||
},
|
||||
{
|
||||
'k' => 'GPG',
|
||||
'v' => 'GPG'
|
||||
|
@ -509,6 +514,10 @@ sub attributes {
|
|||
'k' => 'Facebook',
|
||||
'v' => 'Facebook'
|
||||
},
|
||||
{
|
||||
'k' => 'GitHub',
|
||||
'v' => 'GitHub'
|
||||
},
|
||||
{
|
||||
'k' => 'GPG',
|
||||
'v' => 'GPG'
|
||||
|
@ -842,15 +851,15 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
|
|||
},
|
||||
'checkUserDisplayEmptyHeaders' => {
|
||||
'default' => 0,
|
||||
'type' => 'bool'
|
||||
'type' => 'boolOrExpr'
|
||||
},
|
||||
'checkUserDisplayEmptyValues' => {
|
||||
'default' => 0,
|
||||
'type' => 'bool'
|
||||
'type' => 'boolOrExpr'
|
||||
},
|
||||
'checkUserDisplayPersistentInfo' => {
|
||||
'default' => 0,
|
||||
'type' => 'bool'
|
||||
'type' => 'boolOrExpr'
|
||||
},
|
||||
'checkUserHiddenAttributes' => {
|
||||
'default' => '_loginHistory _session_id hGroups',
|
||||
|
@ -894,6 +903,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
|
|||
'k' => 'Facebook',
|
||||
'v' => 'Facebook'
|
||||
},
|
||||
{
|
||||
'k' => 'GitHub',
|
||||
'v' => 'GitHub'
|
||||
},
|
||||
{
|
||||
'k' => 'GPG',
|
||||
'v' => 'GPG'
|
||||
|
@ -1301,6 +1314,24 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
|
|||
'default' => 120,
|
||||
'type' => 'int'
|
||||
},
|
||||
'githubAuthnLevel' => {
|
||||
'default' => 1,
|
||||
'type' => 'int'
|
||||
},
|
||||
'githubClientID' => {
|
||||
'type' => 'text'
|
||||
},
|
||||
'githubClientSecret' => {
|
||||
'type' => 'password'
|
||||
},
|
||||
'githubScope' => {
|
||||
'default' => 'user:email',
|
||||
'type' => 'text'
|
||||
},
|
||||
'githubUserField' => {
|
||||
'default' => 'login',
|
||||
'type' => 'text'
|
||||
},
|
||||
'globalLogoutCustomParam' => {
|
||||
'type' => 'text'
|
||||
},
|
||||
|
@ -2098,10 +2129,17 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
|
|||
'oidcRPMetaDataOptionsAccessTokenExpiration' => {
|
||||
'type' => 'int'
|
||||
},
|
||||
'oidcRPMetaDataOptionsAdditionalAudiences' => {
|
||||
'type' => 'text'
|
||||
},
|
||||
'oidcRPMetaDataOptionsAllowOffline' => {
|
||||
'default' => 0,
|
||||
'type' => 'bool'
|
||||
},
|
||||
'oidcRPMetaDataOptionsAllowPasswordGrant' => {
|
||||
'default' => 0,
|
||||
'type' => 'bool'
|
||||
},
|
||||
'oidcRPMetaDataOptionsAuthorizationCodeExpiration' => {
|
||||
'type' => 'int'
|
||||
},
|
||||
|
@ -2120,6 +2158,7 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
|
|||
},
|
||||
'oidcRPMetaDataOptionsExtraClaims' => {
|
||||
'default' => {},
|
||||
'keyTest' => qr/^[\x21\x23-\x5B\x5D-\x7E]+$/,
|
||||
'type' => 'keyTextContainer'
|
||||
},
|
||||
'oidcRPMetaDataOptionsIcon' => {
|
||||
|
@ -2129,7 +2168,8 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
|
|||
'type' => 'int'
|
||||
},
|
||||
'oidcRPMetaDataOptionsIDTokenForceClaims' => {
|
||||
'type' => 'bool'
|
||||
'default' => 0,
|
||||
'type' => 'bool'
|
||||
},
|
||||
'oidcRPMetaDataOptionsIDTokenSignAlg' => {
|
||||
'default' => 'HS512',
|
||||
|
@ -2173,10 +2213,6 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
|
|||
'select' => [ {
|
||||
'k' => 'front',
|
||||
'v' => 'Front Channel'
|
||||
},
|
||||
{
|
||||
'k' => 'back',
|
||||
'v' => 'Back Channel'
|
||||
}
|
||||
],
|
||||
'type' => 'select'
|
||||
|
@ -2246,7 +2282,8 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
|
|||
'type' => 'keyTextContainer'
|
||||
},
|
||||
'oidcServiceDynamicRegistrationExtraClaims' => {
|
||||
'type' => 'keyTextContainer'
|
||||
'keyTest' => qr/^[\x21\x23-\x5B\x5D-\x7E]+$/,
|
||||
'type' => 'keyTextContainer'
|
||||
},
|
||||
'oidcServiceIDTokenExpiration' => {
|
||||
'default' => 3600,
|
||||
|
@ -3593,19 +3630,15 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
|
|||
},
|
||||
'singleIP' => {
|
||||
'default' => 0,
|
||||
'type' => 'bool'
|
||||
'type' => 'boolOrExpr'
|
||||
},
|
||||
'singleSession' => {
|
||||
'default' => 0,
|
||||
'type' => 'bool'
|
||||
},
|
||||
'singleSessionUserByIP' => {
|
||||
'default' => 0,
|
||||
'type' => 'bool'
|
||||
'type' => 'boolOrExpr'
|
||||
},
|
||||
'singleUserByIP' => {
|
||||
'default' => 0,
|
||||
'type' => 'bool'
|
||||
'type' => 'boolOrExpr'
|
||||
},
|
||||
'skipRenewConfirmation' => {
|
||||
'default' => 0,
|
||||
|
|
|
@ -25,7 +25,8 @@ sub perlExpr {
|
|||
$Lemonldap::NG::Common::Safelib::functions );
|
||||
$cpt->reval("BEGIN { 'warnings'->unimport; } $val");
|
||||
my $err = join( '',
|
||||
grep { $_ =~ /(?:Undefined subroutine|Devel::StackTrace)/ ? () : $_ } split( /\n/, $@ ) );
|
||||
grep { $_ =~ /(?:Undefined subroutine|Devel::StackTrace)/ ? () : $_ }
|
||||
split( /\n/, $@ ) );
|
||||
return $err ? ( -1, "__badExpression__: $err" ) : (1);
|
||||
}
|
||||
|
||||
|
@ -54,8 +55,8 @@ sub types {
|
|||
msgFail => '__badUrl__',
|
||||
},
|
||||
PerlModule => {
|
||||
form => 'text',
|
||||
test => qr/^(?:[a-zA-Z][a-zA-Z0-9]*)*(?:::[a-zA-Z][a-zA-Z0-9]*)*$/,
|
||||
form => 'text',
|
||||
test => qr/^(?:[a-zA-Z][a-zA-Z0-9]*)*(?:::[a-zA-Z][a-zA-Z0-9]*)*$/,
|
||||
msgFail => '__badPerlPackageName__',
|
||||
},
|
||||
hostname => {
|
||||
|
@ -115,6 +116,7 @@ sub types {
|
|||
},
|
||||
select => {
|
||||
test => sub {
|
||||
return ( 0, "Value is not a scalar" ) if ref( $_[0] );
|
||||
my $test = grep ( { $_ eq $_[0] }
|
||||
map ( { $_->{k} } @{ $_[2]->{select} } ) );
|
||||
return $test
|
||||
|
@ -466,20 +468,20 @@ sub attributes {
|
|||
},
|
||||
checkUserDisplayPersistentInfo => {
|
||||
default => 0,
|
||||
type => 'bool',
|
||||
documentation => 'Display persistent session info',
|
||||
type => 'boolOrExpr',
|
||||
documentation => 'Display persistent session info rule',
|
||||
flags => 'p',
|
||||
},
|
||||
checkUserDisplayEmptyValues => {
|
||||
default => 0,
|
||||
type => 'bool',
|
||||
documentation => 'Display session empty values',
|
||||
type => 'boolOrExpr',
|
||||
documentation => 'Display session empty values rule',
|
||||
flags => 'p',
|
||||
},
|
||||
checkUserDisplayEmptyHeaders => {
|
||||
default => 0,
|
||||
type => 'bool',
|
||||
documentation => 'Display empty headers',
|
||||
type => 'boolOrExpr',
|
||||
documentation => 'Display empty headers rule',
|
||||
flags => 'p',
|
||||
},
|
||||
globalLogoutRule => {
|
||||
|
@ -811,21 +813,22 @@ sub attributes {
|
|||
'Brute force attack protection -> Max allowed failed login',
|
||||
},
|
||||
bruteForceProtectionMaxLockTime => {
|
||||
default => 900,
|
||||
type => 'int',
|
||||
documentation =>
|
||||
'Brute force attack protection -> Max lock time',
|
||||
default => 900,
|
||||
type => 'int',
|
||||
documentation => 'Brute force attack protection -> Max lock time',
|
||||
},
|
||||
bruteForceProtectionIncrementalTempo => {
|
||||
default => 0,
|
||||
help => 'bruteforceprotection.html',
|
||||
type => 'bool',
|
||||
documentation => 'Enable incremental lock time for brute force attack protection',
|
||||
default => 0,
|
||||
help => 'bruteforceprotection.html',
|
||||
type => 'bool',
|
||||
documentation =>
|
||||
'Enable incremental lock time for brute force attack protection',
|
||||
},
|
||||
bruteForceProtectionLockTimes => {
|
||||
type => 'text',
|
||||
default => '5 15 60 300 600',
|
||||
documentation => 'Incremental lock time values for brute force attack protection',
|
||||
type => 'text',
|
||||
default => '5 15 60 300 600',
|
||||
documentation =>
|
||||
'Incremental lock time values for brute force attack protection',
|
||||
},
|
||||
grantSessionRules => {
|
||||
type => 'grantContainer',
|
||||
|
@ -1978,24 +1981,19 @@ sub attributes {
|
|||
},
|
||||
singleSession => {
|
||||
default => 0,
|
||||
type => 'bool',
|
||||
type => 'boolOrExpr',
|
||||
documentation => 'Allow only one session per user',
|
||||
},
|
||||
singleIP => {
|
||||
default => 0,
|
||||
type => 'bool',
|
||||
type => 'boolOrExpr',
|
||||
documentation => 'Allow only one session per IP',
|
||||
},
|
||||
singleUserByIP => {
|
||||
default => 0,
|
||||
type => 'bool',
|
||||
type => 'boolOrExpr',
|
||||
documentation => 'Allow only one user per IP',
|
||||
},
|
||||
singleSessionUserByIP => {
|
||||
default => 0,
|
||||
type => 'bool',
|
||||
documentation => 'Allow only one session per user on an IP',
|
||||
},
|
||||
|
||||
# REST server
|
||||
restSessionServer => {
|
||||
|
@ -2914,6 +2912,7 @@ sub attributes {
|
|||
{ k => 'AD', v => 'Active Directory' },
|
||||
{ k => 'DBI', v => 'Database (DBI)' },
|
||||
{ k => 'Facebook', v => 'Facebook' },
|
||||
{ k => 'GitHub', v => 'GitHub' },
|
||||
{ k => 'GPG', v => 'GPG' },
|
||||
{ k => 'Kerberos', v => 'Kerberos' },
|
||||
{ k => 'LDAP', v => 'LDAP' },
|
||||
|
@ -3425,6 +3424,17 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
|
|||
linkedInScope =>
|
||||
{ type => 'text', default => 'r_liteprofile r_emailaddress' },
|
||||
|
||||
# GitHub
|
||||
githubAuthnLevel => {
|
||||
type => 'int',
|
||||
default => 1,
|
||||
documentation => 'GitHub authentication level',
|
||||
},
|
||||
githubClientID => { type => 'text', },
|
||||
githubClientSecret => { type => 'password', },
|
||||
githubScope => { type => 'text', default => 'user:email' },
|
||||
githubUserField => { type => 'text', default => 'login' },
|
||||
|
||||
# WebID
|
||||
webIDAuthnLevel => {
|
||||
type => 'int',
|
||||
|
@ -3485,7 +3495,7 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
|
|||
# Apache
|
||||
apacheAuthnLevel => {
|
||||
type => 'int',
|
||||
default => 4,
|
||||
default => 3,
|
||||
documentation => 'Apache authentication level',
|
||||
},
|
||||
|
||||
|
@ -3571,6 +3581,7 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
|
|||
{ k => 'DBI', v => 'Database (DBI)' },
|
||||
{ k => 'Demo', v => 'Demo' },
|
||||
{ k => 'Facebook', v => 'Facebook' },
|
||||
{ k => 'GitHub', v => 'GitHub' },
|
||||
{ k => 'GPG', v => 'GPG' },
|
||||
{ k => 'Kerberos', v => 'Kerberos' },
|
||||
{ k => 'LDAP', v => 'LDAP' },
|
||||
|
@ -3643,6 +3654,7 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
|
|||
{ k => 'AD', v => 'Active Directory' },
|
||||
{ k => 'DBI', v => 'Database (DBI)' },
|
||||
{ k => 'Facebook', v => 'Facebook' },
|
||||
{ k => 'GitHub', v => 'GitHub' },
|
||||
{ k => 'GPG', v => 'GPG' },
|
||||
{ k => 'Kerberos', v => 'Kerberos' },
|
||||
{ k => 'LDAP', v => 'LDAP' },
|
||||
|
@ -3836,7 +3848,8 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
|
|||
'OpenID Connect exported variables for dynamic registration',
|
||||
},
|
||||
oidcServiceDynamicRegistrationExtraClaims => {
|
||||
type => 'keyTextContainer',
|
||||
type => 'keyTextContainer',
|
||||
keyTest => qr/^[\x21\x23-\x5B\x5D-\x7E]+$/,
|
||||
documentation =>
|
||||
'OpenID Connect extra claims for dynamic registration',
|
||||
},
|
||||
|
@ -3950,14 +3963,20 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
|
|||
],
|
||||
default => 'HS512',
|
||||
},
|
||||
oidcRPMetaDataOptionsIDTokenExpiration => { type => 'int' },
|
||||
oidcRPMetaDataOptionsIDTokenForceClaims => { type => 'bool' },
|
||||
oidcRPMetaDataOptionsIDTokenExpiration => { type => 'int' },
|
||||
oidcRPMetaDataOptionsIDTokenForceClaims =>
|
||||
{ type => 'bool', default => 0 },
|
||||
oidcRPMetaDataOptionsAdditionalAudiences =>
|
||||
{ type => 'text' },
|
||||
oidcRPMetaDataOptionsAccessTokenExpiration => { type => 'int' },
|
||||
oidcRPMetaDataOptionsAuthorizationCodeExpiration => { type => 'int' },
|
||||
oidcRPMetaDataOptionsOfflineSessionExpiration => { type => 'int' },
|
||||
oidcRPMetaDataOptionsRedirectUris => { type => 'text', },
|
||||
oidcRPMetaDataOptionsExtraClaims =>
|
||||
{ type => 'keyTextContainer', default => {} },
|
||||
oidcRPMetaDataOptionsExtraClaims => {
|
||||
type => 'keyTextContainer',
|
||||
keyTest => qr/^[\x21\x23-\x5B\x5D-\x7E]+$/,
|
||||
default => {}
|
||||
},
|
||||
oidcRPMetaDataOptionsBypassConsent => {
|
||||
type => 'bool',
|
||||
help => 'openidconnectclaims.html',
|
||||
|
@ -3972,7 +3991,8 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
|
|||
type => 'select',
|
||||
select => [
|
||||
{ k => 'front', v => 'Front Channel' },
|
||||
{ k => 'back', v => 'Back Channel' },
|
||||
#TODO #1194
|
||||
# { k => 'back', v => 'Back Channel' },
|
||||
],
|
||||
default => 'front',
|
||||
documentation => 'Logout type',
|
||||
|
@ -3997,6 +4017,12 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
|
|||
default => 0,
|
||||
documentation => 'Allow offline access',
|
||||
},
|
||||
oidcRPMetaDataOptionsAllowPasswordGrant => {
|
||||
type => 'bool',
|
||||
default => 0,
|
||||
documentation =>
|
||||
'Allow OAuth2 Resource Owner Password Credentials Grant',
|
||||
},
|
||||
oidcRPMetaDataOptionsRefreshToken => {
|
||||
type => 'bool',
|
||||
default => 0,
|
||||
|
|
|
@ -191,27 +191,49 @@ sub cTrees {
|
|||
'oidcRPMetaDataOptionsExtraClaims',
|
||||
{
|
||||
title => 'oidcRPMetaDataOptions',
|
||||
help => 'idpopenidconnect.html#options',
|
||||
nodes => [ {
|
||||
title => 'oidcRPMetaDataOptionsAuthentication',
|
||||
title => 'oidcRPMetaDataOptionsBasic',
|
||||
form => 'simpleInputContainer',
|
||||
nodes => [
|
||||
'oidcRPMetaDataOptionsClientID',
|
||||
'oidcRPMetaDataOptionsClientSecret',
|
||||
'oidcRPMetaDataOptionsPublic',
|
||||
'oidcRPMetaDataOptionsRequirePKCE',
|
||||
'oidcRPMetaDataOptionsRedirectUris',
|
||||
]
|
||||
},
|
||||
{
|
||||
title => 'oidcRPMetaDataOptionsAdvanced',
|
||||
form => 'simpleInputContainer',
|
||||
nodes => [
|
||||
'oidcRPMetaDataOptionsBypassConsent',
|
||||
'oidcRPMetaDataOptionsUserIDAttr',
|
||||
'oidcRPMetaDataOptionsIDTokenForceClaims',
|
||||
'oidcRPMetaDataOptionsAdditionalAudiences',
|
||||
'oidcRPMetaDataOptionsRefreshToken',
|
||||
]
|
||||
},
|
||||
{
|
||||
title => 'security',
|
||||
form => 'simpleInputContainer',
|
||||
nodes => [
|
||||
'oidcRPMetaDataOptionsIDTokenSignAlg',
|
||||
'oidcRPMetaDataOptionsRequirePKCE',
|
||||
'oidcRPMetaDataOptionsAllowOffline',
|
||||
'oidcRPMetaDataOptionsAllowPasswordGrant',
|
||||
'oidcRPMetaDataOptionsRule',
|
||||
]
|
||||
},
|
||||
{
|
||||
title => 'oidcRPMetaDataOptionsTimeouts',
|
||||
form => 'simpleInputContainer',
|
||||
nodes => [
|
||||
'oidcRPMetaDataOptionsAuthorizationCodeExpiration',
|
||||
'oidcRPMetaDataOptionsIDTokenExpiration',
|
||||
'oidcRPMetaDataOptionsAccessTokenExpiration',
|
||||
'oidcRPMetaDataOptionsOfflineSessionExpiration',
|
||||
]
|
||||
},
|
||||
'oidcRPMetaDataOptionsUserIDAttr',
|
||||
'oidcRPMetaDataOptionsIDTokenSignAlg',
|
||||
'oidcRPMetaDataOptionsIDTokenExpiration',
|
||||
'oidcRPMetaDataOptionsIDTokenForceClaims',
|
||||
'oidcRPMetaDataOptionsAccessTokenExpiration',
|
||||
'oidcRPMetaDataOptionsAuthorizationCodeExpiration',
|
||||
'oidcRPMetaDataOptionsAllowOffline',
|
||||
'oidcRPMetaDataOptionsRefreshToken',
|
||||
'oidcRPMetaDataOptionsOfflineSessionExpiration',
|
||||
'oidcRPMetaDataOptionsRedirectUris',
|
||||
'oidcRPMetaDataOptionsBypassConsent',
|
||||
{
|
||||
title => 'logout',
|
||||
form => 'simpleInputContainer',
|
||||
|
@ -222,7 +244,6 @@ sub cTrees {
|
|||
'oidcRPMetaDataOptionsLogoutSessionRequired',
|
||||
]
|
||||
},
|
||||
'oidcRPMetaDataOptionsRule',
|
||||
]
|
||||
},
|
||||
'oidcRPMetaDataMacros',
|
||||
|
|
|
@ -314,6 +314,16 @@ sub tree {
|
|||
'linkedInScope'
|
||||
]
|
||||
},
|
||||
{
|
||||
title => 'githubParams',
|
||||
help => 'authgithub.html',
|
||||
form => 'simpleInputContainer',
|
||||
nodes => [
|
||||
'githubAuthnLevel', 'githubClientID',
|
||||
'githubClientSecret', 'githubUserField',
|
||||
'githubScope'
|
||||
]
|
||||
},
|
||||
{
|
||||
title => 'combinationParams',
|
||||
help => 'authcombination.html',
|
||||
|
@ -569,7 +579,7 @@ sub tree {
|
|||
form => 'simpleInputContainer',
|
||||
nodes => [
|
||||
'singleSession', 'singleIP',
|
||||
'singleUserByIP', 'singleSessionUserByIP',
|
||||
'singleUserByIP',
|
||||
'notifyDeleted', 'notifyOther'
|
||||
]
|
||||
},
|
||||
|
@ -720,8 +730,11 @@ sub tree {
|
|||
title => 'globalLogout',
|
||||
help => 'globallogout.html',
|
||||
form => 'simpleInputContainer',
|
||||
nodes =>
|
||||
[ 'globalLogoutRule', 'globalLogoutTimer','globalLogoutCustomParam' ],
|
||||
nodes => [
|
||||
'globalLogoutRule',
|
||||
'globalLogoutTimer',
|
||||
'globalLogoutCustomParam'
|
||||
],
|
||||
},
|
||||
{
|
||||
title => 'stateCheck',
|
||||
|
|
|
@ -831,7 +831,7 @@ sub _scanNodes {
|
|||
foreach my $node ( @{ $leaf->{nodes} } ) {
|
||||
my $tmp;
|
||||
$tmp->{$_} = $node->{data}->{$_}
|
||||
foreach (qw(type rule logo label));
|
||||
foreach (qw(type rule logo level label));
|
||||
$tmp->{over} = {};
|
||||
foreach ( @{ $node->{data}->{over} } ) {
|
||||
$tmp->{over}->{ $_->[0] } = $_->[1];
|
||||
|
@ -1131,10 +1131,25 @@ sub _unitTest {
|
|||
next;
|
||||
}
|
||||
|
||||
if ( $attr->{type} and $attr->{type} eq 'subContainer' ) {
|
||||
# Vhost, CAS, SAML, OIDC options
|
||||
if ( $key =~
|
||||
/^(?^:(?:(?:(?:saml(?:ID|S)|oidc[OR])P|cas(?:App|Srv))MetaData|vhost)Options)$/
|
||||
)
|
||||
{
|
||||
|
||||
# TODO Recursive for SAML/OIDC nodes
|
||||
# Iterate on vhost names, or saml/cas/oidc configuration keys
|
||||
for my $vhost ( keys %{ $conf->{$key} } ) {
|
||||
my $options = $conf->{$key}->{$vhost};
|
||||
if ( ref($options) eq "HASH" ) {
|
||||
|
||||
# Recurse on option list,
|
||||
# FIXME this does check for oidcRPMetaDataOptionsXXX appearing under
|
||||
# samlSPMetadataOptions
|
||||
$res = 0 unless $self->_unitTest( $options, $localConf, "$key/$vhost/" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
# Check if key exists
|
||||
|
|
|
@ -13,8 +13,11 @@ require Lemonldap::NG::Common::Notifications;
|
|||
|
||||
use feature 'state';
|
||||
|
||||
extends 'Lemonldap::NG::Manager::Plugin',
|
||||
'Lemonldap::NG::Common::Conf::AccessLib';
|
||||
extends qw(
|
||||
Lemonldap::NG::Manager::Plugin
|
||||
Lemonldap::NG::Common::Conf::AccessLib
|
||||
Lemonldap::NG::Common::PSGI::Router
|
||||
);
|
||||
|
||||
our $VERSION = '2.1.0';
|
||||
|
||||
|
@ -271,7 +274,7 @@ sub newNotification {
|
|||
return $self->sendError( $req, undef, 200 );
|
||||
}
|
||||
|
||||
$json->{reference} =~ s/_/-/g; # Remove underscores (#2135)
|
||||
$json->{reference} =~ s/_/-/g; # Remove underscores (#2135)
|
||||
|
||||
foreach my $r (qw(uid reference xml)) {
|
||||
return $self->sendError( $req, "Missing $r", 200 )
|
||||
|
|
|
@ -339,6 +339,7 @@ llapp.controller 'TreeCtrl', [
|
|||
type: ''
|
||||
rule: ''
|
||||
logo: ''
|
||||
level: ''
|
||||
label: ''
|
||||
over: []
|
||||
|
||||
|
|
|
@ -320,6 +320,9 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
|
|||
for key, value of l
|
||||
if !key.match /^(_utime|ipAddr|error)$/
|
||||
cv += ", #{key} : #{value}"
|
||||
tab = cv.split ', '
|
||||
tab.sort()
|
||||
cv = tab.join ', '
|
||||
tmp.push
|
||||
t: l._utime
|
||||
title: $scope.localeDate l._utime
|
||||
|
@ -331,6 +334,9 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
|
|||
for key, value of l
|
||||
if !key.match /^(_utime|ipAddr|error)$/
|
||||
cv += ", #{key} : #{value}"
|
||||
tab = cv.split ', '
|
||||
tab.sort()
|
||||
cv = tab.join ', '
|
||||
tmp.push
|
||||
t: l._utime
|
||||
title: $scope.localeDate l._utime
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="12%" trspan="name"></th>
|
||||
<th width="11%" trspan="type"></th>
|
||||
<th width="33%" trspan="label"></th>
|
||||
<th width="11%" trspan="logo"></th>
|
||||
<th width="33%" trspan="rule"></th>
|
||||
<th trspan="name"></th>
|
||||
<th trspan="type"></th>
|
||||
<th trspan="label"></th>
|
||||
<th trspan="logo"></th>
|
||||
<th trspan="level"></th>
|
||||
<th trspan="rule"></th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -29,6 +30,9 @@
|
|||
<td>
|
||||
<input class="form-control" ng-model="currentNode.data.logo" />
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control" ng-model="currentNode.data.level" />
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control" ng-model="currentNode.data.rule" />
|
||||
</td>
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="12%" trspan="name"></th>
|
||||
<th width="11%" trspan="type"></th>
|
||||
<th width="33%" trspan="label"></th>
|
||||
<th width="11%" trspan="logo"></th>
|
||||
<th width="33%" trspan="rule"></th>
|
||||
<th trspan="name"></th>
|
||||
<th trspan="type"></th>
|
||||
<th trspan="label"></th>
|
||||
<th trspan="logo"></th>
|
||||
<th trspan="level"></th>
|
||||
<th trspan="rule"></th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -29,6 +30,9 @@
|
|||
<td>
|
||||
<input class="form-control" ng-model="s.data.logo" />
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control" ng-model="s.data.level" />
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control" ng-model="s.data.rule" />
|
||||
</td>
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -426,116 +426,155 @@ function templates(tpl,key) {
|
|||
"title" : "oidcRPMetaDataOptionsPublic",
|
||||
"type" : "bool"
|
||||
},
|
||||
{
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRedirectUris",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRedirectUris",
|
||||
"title" : "oidcRPMetaDataOptionsRedirectUris"
|
||||
}
|
||||
],
|
||||
"id" : "oidcRPMetaDataOptionsBasic",
|
||||
"title" : "oidcRPMetaDataOptionsBasic",
|
||||
"type" : "simpleInputContainer"
|
||||
},
|
||||
{
|
||||
"_nodes" : [
|
||||
{
|
||||
"default" : 0,
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsBypassConsent",
|
||||
"help" : "openidconnectclaims.html",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsBypassConsent",
|
||||
"title" : "oidcRPMetaDataOptionsBypassConsent",
|
||||
"type" : "bool"
|
||||
},
|
||||
{
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsUserIDAttr",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsUserIDAttr",
|
||||
"title" : "oidcRPMetaDataOptionsUserIDAttr"
|
||||
},
|
||||
{
|
||||
"default" : 0,
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsIDTokenForceClaims",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsIDTokenForceClaims",
|
||||
"title" : "oidcRPMetaDataOptionsIDTokenForceClaims",
|
||||
"type" : "bool"
|
||||
},
|
||||
{
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAdditionalAudiences",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAdditionalAudiences",
|
||||
"title" : "oidcRPMetaDataOptionsAdditionalAudiences"
|
||||
},
|
||||
{
|
||||
"default" : 0,
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRefreshToken",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRefreshToken",
|
||||
"title" : "oidcRPMetaDataOptionsRefreshToken",
|
||||
"type" : "bool"
|
||||
}
|
||||
],
|
||||
"id" : "oidcRPMetaDataOptionsAdvanced",
|
||||
"title" : "oidcRPMetaDataOptionsAdvanced",
|
||||
"type" : "simpleInputContainer"
|
||||
},
|
||||
{
|
||||
"_nodes" : [
|
||||
{
|
||||
"default" : "HS512",
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsIDTokenSignAlg",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsIDTokenSignAlg",
|
||||
"select" : [
|
||||
{
|
||||
"k" : "none",
|
||||
"v" : "None"
|
||||
},
|
||||
{
|
||||
"k" : "HS256",
|
||||
"v" : "HS256"
|
||||
},
|
||||
{
|
||||
"k" : "HS384",
|
||||
"v" : "HS384"
|
||||
},
|
||||
{
|
||||
"k" : "HS512",
|
||||
"v" : "HS512"
|
||||
},
|
||||
{
|
||||
"k" : "RS256",
|
||||
"v" : "RS256"
|
||||
},
|
||||
{
|
||||
"k" : "RS384",
|
||||
"v" : "RS384"
|
||||
},
|
||||
{
|
||||
"k" : "RS512",
|
||||
"v" : "RS512"
|
||||
}
|
||||
],
|
||||
"title" : "oidcRPMetaDataOptionsIDTokenSignAlg",
|
||||
"type" : "select"
|
||||
},
|
||||
{
|
||||
"default" : 0,
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRequirePKCE",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRequirePKCE",
|
||||
"title" : "oidcRPMetaDataOptionsRequirePKCE",
|
||||
"type" : "bool"
|
||||
},
|
||||
{
|
||||
"default" : 0,
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAllowOffline",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAllowOffline",
|
||||
"title" : "oidcRPMetaDataOptionsAllowOffline",
|
||||
"type" : "bool"
|
||||
},
|
||||
{
|
||||
"default" : 0,
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAllowPasswordGrant",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAllowPasswordGrant",
|
||||
"title" : "oidcRPMetaDataOptionsAllowPasswordGrant",
|
||||
"type" : "bool"
|
||||
},
|
||||
{
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRule",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRule",
|
||||
"title" : "oidcRPMetaDataOptionsRule"
|
||||
}
|
||||
],
|
||||
"id" : "oidcRPMetaDataOptionsAuthentication",
|
||||
"title" : "oidcRPMetaDataOptionsAuthentication",
|
||||
"id" : "security",
|
||||
"title" : "security",
|
||||
"type" : "simpleInputContainer"
|
||||
},
|
||||
{
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsUserIDAttr",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsUserIDAttr",
|
||||
"title" : "oidcRPMetaDataOptionsUserIDAttr"
|
||||
},
|
||||
{
|
||||
"default" : "HS512",
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsIDTokenSignAlg",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsIDTokenSignAlg",
|
||||
"select" : [
|
||||
"_nodes" : [
|
||||
{
|
||||
"k" : "none",
|
||||
"v" : "None"
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAuthorizationCodeExpiration",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAuthorizationCodeExpiration",
|
||||
"title" : "oidcRPMetaDataOptionsAuthorizationCodeExpiration",
|
||||
"type" : "int"
|
||||
},
|
||||
{
|
||||
"k" : "HS256",
|
||||
"v" : "HS256"
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsIDTokenExpiration",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsIDTokenExpiration",
|
||||
"title" : "oidcRPMetaDataOptionsIDTokenExpiration",
|
||||
"type" : "int"
|
||||
},
|
||||
{
|
||||
"k" : "HS384",
|
||||
"v" : "HS384"
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAccessTokenExpiration",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAccessTokenExpiration",
|
||||
"title" : "oidcRPMetaDataOptionsAccessTokenExpiration",
|
||||
"type" : "int"
|
||||
},
|
||||
{
|
||||
"k" : "HS512",
|
||||
"v" : "HS512"
|
||||
},
|
||||
{
|
||||
"k" : "RS256",
|
||||
"v" : "RS256"
|
||||
},
|
||||
{
|
||||
"k" : "RS384",
|
||||
"v" : "RS384"
|
||||
},
|
||||
{
|
||||
"k" : "RS512",
|
||||
"v" : "RS512"
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsOfflineSessionExpiration",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsOfflineSessionExpiration",
|
||||
"title" : "oidcRPMetaDataOptionsOfflineSessionExpiration",
|
||||
"type" : "int"
|
||||
}
|
||||
],
|
||||
"title" : "oidcRPMetaDataOptionsIDTokenSignAlg",
|
||||
"type" : "select"
|
||||
},
|
||||
{
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsIDTokenExpiration",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsIDTokenExpiration",
|
||||
"title" : "oidcRPMetaDataOptionsIDTokenExpiration",
|
||||
"type" : "int"
|
||||
},
|
||||
{
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsIDTokenForceClaims",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsIDTokenForceClaims",
|
||||
"title" : "oidcRPMetaDataOptionsIDTokenForceClaims",
|
||||
"type" : "bool"
|
||||
},
|
||||
{
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAccessTokenExpiration",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAccessTokenExpiration",
|
||||
"title" : "oidcRPMetaDataOptionsAccessTokenExpiration",
|
||||
"type" : "int"
|
||||
},
|
||||
{
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAuthorizationCodeExpiration",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAuthorizationCodeExpiration",
|
||||
"title" : "oidcRPMetaDataOptionsAuthorizationCodeExpiration",
|
||||
"type" : "int"
|
||||
},
|
||||
{
|
||||
"default" : 0,
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAllowOffline",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAllowOffline",
|
||||
"title" : "oidcRPMetaDataOptionsAllowOffline",
|
||||
"type" : "bool"
|
||||
},
|
||||
{
|
||||
"default" : 0,
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRefreshToken",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRefreshToken",
|
||||
"title" : "oidcRPMetaDataOptionsRefreshToken",
|
||||
"type" : "bool"
|
||||
},
|
||||
{
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsOfflineSessionExpiration",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsOfflineSessionExpiration",
|
||||
"title" : "oidcRPMetaDataOptionsOfflineSessionExpiration",
|
||||
"type" : "int"
|
||||
},
|
||||
{
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRedirectUris",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRedirectUris",
|
||||
"title" : "oidcRPMetaDataOptionsRedirectUris"
|
||||
},
|
||||
{
|
||||
"default" : 0,
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsBypassConsent",
|
||||
"help" : "openidconnectclaims.html",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsBypassConsent",
|
||||
"title" : "oidcRPMetaDataOptionsBypassConsent",
|
||||
"type" : "bool"
|
||||
"id" : "oidcRPMetaDataOptionsTimeouts",
|
||||
"title" : "oidcRPMetaDataOptionsTimeouts",
|
||||
"type" : "simpleInputContainer"
|
||||
},
|
||||
{
|
||||
"_nodes" : [
|
||||
|
@ -557,10 +596,6 @@ function templates(tpl,key) {
|
|||
{
|
||||
"k" : "front",
|
||||
"v" : "Front Channel"
|
||||
},
|
||||
{
|
||||
"k" : "back",
|
||||
"v" : "Back Channel"
|
||||
}
|
||||
],
|
||||
"title" : "oidcRPMetaDataOptionsLogoutType",
|
||||
|
@ -577,13 +612,9 @@ function templates(tpl,key) {
|
|||
"id" : "logout",
|
||||
"title" : "logout",
|
||||
"type" : "simpleInputContainer"
|
||||
},
|
||||
{
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRule",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRule",
|
||||
"title" : "oidcRPMetaDataOptionsRule"
|
||||
}
|
||||
],
|
||||
"help" : "idpopenidconnect.html#options",
|
||||
"id" : "oidcRPMetaDataOptions",
|
||||
"title" : "oidcRPMetaDataOptions"
|
||||
},
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
(function(){var e;(e=angular.module("llApp",["ngAria"])).provider("$translator",function(){var e,t,n,r,a,i,o,s,l,u,c,g;if(g={},decodeURIComponent(document.cookie).match(/llnglanguage=(\w+)/))g.lang=RegExp.$1;else if(navigator){for(r=[],a=[],l=[navigator.language],navigator.languages&&(l=navigator.languages),t=0,i=l.length;t<i;t++)for(s=l[t],console.log("Navigator lang",s),n=0,o=(c=window.availableLanguages).length;n<o;n++)e=c[n],console.log(" Available lang",e),u=new RegExp("^"+e+"-?"),s.match(u)?(console.log(" Matching lang =",e),r.push(e)):e.substring(0,1)===s.substring(0,1)&&a.push(e);g.lang=r[0]?r[0]:a[0]?a[0]:"en"}else g.lang="en";return console.log("Selected lang ->",g.lang),g.deferredTr=[],g.translationFields={},g.translate=function(e){return g.translationFields[e]&&(e=g.translationFields[e]),e},g.translateField=function(e,t){return g.translate(e[t])},g.translateP=function(e){return e&&g.translationFields.portal&&(e=e.replace(/__(\w+)__/g,function(e,t){return g.translate(t)})),e},this.$get=["$q","$http",function(t,n){return g.last="",g.init=function(e){var i;return e=e||g.lang,(i=new Date).setTime(i.getTime()+2592e6),document.cookie="llnglanguage="+e+"; expires="+i.toUTCString()+"; path=/",i=t.defer(),g.last!==e?(g.last=e,n.get(window.staticPrefix+"languages/"+e+".json").then(function(e){var t,n,r,a;for(g.translationFields=e.data,n=0,r=(a=g.deferredTr).length;n<r;n++)(t=a[n]).e[t.f](g.translationFields[t.m]);return g.deferredTr=[],i.resolve("Translation files loaded")},function(e){return i.reject("")})):i.resolve("No change"),i.promise},g}],this}),e.directive("trspan",["$translator",function(r){return{restrict:"A",replace:!1,transclude:!0,scope:{trspan:"@"},link:function(e,t,n){return r.translationFields.portal?n.trspan=r.translate(n.trspan):r.deferredTr.push({e:t,f:"text",m:n.trspan}),t.text(n.trspan)},template:""}}]),e.provider("$htmlParams",function(){return this.$get=function(){var n;return n={},{set:function(e,t){return n[e]=t},menu:function(){return n.menu},params:function(){return n.params}}},this}),e.directive("script",["$htmlParams",function(a){return{restrict:"E",terminal:!0,compile:function(e,t){var n,r;if(t.type&&(r=t.type.match(/text\/(menu|parameters)/)))try{return a.set(r[1],JSON.parse(e[0].text))}catch(e){n=e,console.log("Parsing error:",n)}}}}]),e.controller("ModalInstanceCtrl",["$scope","$uibModalInstance","elem","set","init",function(a,e,t,n,r){var i,o;return null,a.elem=t,a.set=n,a.result=r,a.staticPrefix=window.staticPrefix,i=t("currentNode"),a.translateP=t("translateP"),i&&(o=i.data,a.currentNode=i),a.ok=function(){return n("result",a.result),e.close(!0)},a.cancel=function(){return i&&(a.currentNode.data=o),e.dismiss("cancel")},a.inSelect=function(e){var t,n,r;for(t=0,n=(r=a.currentNode.select).length;t<n;t++)if(r[t].k===e)return!0;return!1}}]),e.directive("onReadFile",["$parse",function(a){return{restrict:"A",scope:!1,link:function(n,e,t){var r;return r=a(t.onReadFile),e.on("change",function(e){var t;return(t=new FileReader).onload=function(e){return n.$apply(function(){return r(n,{$fileContent:e.target.result})})},t.readAsText((e.srcElement||e.target).files[0])})}}}]),e.directive("resizer",["$document",function(i){var o,s;return s=o=null,function(e,t,r){var n,a;return t.on("mousedown",function(e){return"vertical"===r.resizer?s=$(r.resizerRight).width()+$(r.resizerLeft).width():o=$(r.resizerTop).height()+$(r.resizerBottom).height(),e.preventDefault(),i.on("mousemove",n),i.on("mouseup",a)}),n=function(e){var t,n;return"vertical"===r.resizer?(t=e.pageX,r.resizerMax&&t>r.resizerMax&&(t=parseInt(r.resizerMax)),$(r.resizerLeft).css({width:t+"px"}),$(r.resizerRight).css({width:s-t+"px"})):(n=e.pageY-$("#navbar").height(),$(r.resizerTop).css({height:n+"px"}),$(r.resizerBottom).css({height:o-n+"px"}))},a=function(){return i.unbind("mousemove",n),i.unbind("mouseup",a)}}}]),e.factory("$lmhttp",["$q","$location",function(t,e){return{responseError:function(e){return 401===e.status&&window.portal?window.location=window.portal+"?url="+window.btoa(window.location).replace(/\//,"_"):t.reject(e)}}}]),e.config(["$httpProvider",function(e){return e.interceptors.push("$lmhttp")}])}).call(this);
|
||||
(function(){var e;(e=angular.module("llApp",["ngAria"])).provider("$translator",function(){var e,t,n,r,a,i,o,s,l,u,c,g;if(g={},decodeURIComponent(document.cookie).match(/llnglanguage=(\w+)/))g.lang=RegExp.$1;else if(navigator){for(r=[],a=[],l=[navigator.language],navigator.languages&&(l=navigator.languages),t=0,i=l.length;t<i;t++)for(s=l[t],console.log("Navigator lang",s),n=0,o=(c=window.availableLanguages).length;n<o;n++)e=c[n],console.log(" Available lang",e),u=new RegExp("^"+e+"-?"),s.match(u)?(console.log(" Matching lang =",e),r.push(e)):e.substring(0,1)===s.substring(0,1)&&a.push(e);g.lang=r[0]?r[0]:a[0]?a[0]:"en"}else g.lang="en";return console.log("Selected lang ->",g.lang),g.deferredTr=[],g.translationFields={},g.translate=function(e){return g.translationFields[e]&&(e=g.translationFields[e]),e},g.translateField=function(e,t){return g.translate(e[t])},g.translateP=function(e){return e&&g.translationFields.portal&&(e=e.replace(/__(\w+)__/g,function(e,t){return g.translate(t)})),e},this.$get=["$q","$http",function(t,n){return g.last="",g.init=function(e){var i;return e||(e=g.lang),(i=new Date).setTime(i.getTime()+2592e6),document.cookie="llnglanguage="+e+"; expires="+i.toUTCString()+"; path=/",i=t.defer(),g.last!==e?(g.last=e,n.get(window.staticPrefix+"languages/"+e+".json").then(function(e){var t,n,r,a;for(g.translationFields=e.data,n=0,r=(a=g.deferredTr).length;n<r;n++)(t=a[n]).e[t.f](g.translationFields[t.m]);return g.deferredTr=[],i.resolve("Translation files loaded")},function(e){return i.reject("")})):i.resolve("No change"),i.promise},g}],this}),e.directive("trspan",["$translator",function(r){return{restrict:"A",replace:!1,transclude:!0,scope:{trspan:"@"},link:function(e,t,n){return r.translationFields.portal?n.trspan=r.translate(n.trspan):r.deferredTr.push({e:t,f:"text",m:n.trspan}),t.text(n.trspan)},template:""}}]),e.provider("$htmlParams",function(){return this.$get=function(){var n;return n={},{set:function(e,t){return n[e]=t},menu:function(){return n.menu},params:function(){return n.params}}},this}),e.directive("script",["$htmlParams",function(a){return{restrict:"E",terminal:!0,compile:function(e,t){var n,r;if(t.type&&(r=t.type.match(/text\/(menu|parameters)/)))try{return a.set(r[1],JSON.parse(e[0].text))}catch(e){n=e,console.log("Parsing error:",n)}}}}]),e.controller("ModalInstanceCtrl",["$scope","$uibModalInstance","elem","set","init",function(a,e,t,n,r){var i,o;return null,a.elem=t,a.set=n,a.result=r,a.staticPrefix=window.staticPrefix,i=t("currentNode"),a.translateP=t("translateP"),i&&(o=i.data,a.currentNode=i),a.ok=function(){return n("result",a.result),e.close(!0)},a.cancel=function(){return i&&(a.currentNode.data=o),e.dismiss("cancel")},a.inSelect=function(e){var t,n,r;for(t=0,n=(r=a.currentNode.select).length;t<n;t++)if(r[t].k===e)return!0;return!1}}]),e.directive("onReadFile",["$parse",function(a){return{restrict:"A",scope:!1,link:function(n,e,t){var r;return r=a(t.onReadFile),e.on("change",function(e){var t;return(t=new FileReader).onload=function(e){return n.$apply(function(){return r(n,{$fileContent:e.target.result})})},t.readAsText((e.srcElement||e.target).files[0])})}}}]),e.directive("resizer",["$document",function(i){var o,s;return s=o=null,function(e,t,r){var n,a;return t.on("mousedown",function(e){return"vertical"===r.resizer?s=$(r.resizerRight).width()+$(r.resizerLeft).width():o=$(r.resizerTop).height()+$(r.resizerBottom).height(),e.preventDefault(),i.on("mousemove",n),i.on("mouseup",a)}),n=function(e){var t,n;return"vertical"===r.resizer?(t=e.pageX,r.resizerMax&&t>r.resizerMax&&(t=parseInt(r.resizerMax)),$(r.resizerLeft).css({width:t+"px"}),$(r.resizerRight).css({width:s-t+"px"})):(n=e.pageY-$("#navbar").height(),$(r.resizerTop).css({height:n+"px"}),$(r.resizerBottom).css({height:o-n+"px"}))},a=function(){return i.unbind("mousemove",n),i.unbind("mouseup",a)}}}]),e.factory("$lmhttp",["$q","$location",function(t,e){return{responseError:function(e){return 401===e.status&&window.portal?window.location=window.portal+"?url="+window.btoa(window.location).replace(/\//,"_"):t.reject(e)}}}]),e.config(["$httpProvider",function(e){return e.interceptors.push("$lmhttp")}])}).call(this);
|
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
|||
// Generated by CoffeeScript 1.12.7
|
||||
// Generated by CoffeeScript 1.12.8
|
||||
|
||||
/*
|
||||
LemonLDAP::NG Manager client
|
||||
|
@ -394,6 +394,7 @@ This file contains:
|
|||
type: '',
|
||||
rule: '',
|
||||
logo: '',
|
||||
level: '',
|
||||
label: '',
|
||||
over: []
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -228,7 +228,7 @@
|
|||
$scope.displaySession = function(scope) {
|
||||
var sessionId, transformSession;
|
||||
transformSession = function(session) {
|
||||
var _insert, array, attr, attrs, category, cv, element, epoch, i, id, j, k, key, l, len, len1, len2, len3, len4, len5, m, name, o, oidcConsent, p, real, ref, ref1, res, sfDevice, spoof, subres, time, title, tmp, value;
|
||||
var _insert, array, attr, attrs, category, cv, element, epoch, i, id, j, k, key, l, len, len1, len2, len3, len4, len5, m, name, o, oidcConsent, p, real, ref, ref1, res, sfDevice, spoof, subres, tab, time, title, tmp, value;
|
||||
_insert = function(re, title) {
|
||||
var key, reg, tmp, value;
|
||||
tmp = [];
|
||||
|
@ -374,6 +374,9 @@
|
|||
cv += ", " + key + " : " + value;
|
||||
}
|
||||
}
|
||||
tab = cv.split(', ');
|
||||
tab.sort();
|
||||
cv = tab.join(', ');
|
||||
tmp.push({
|
||||
t: l._utime,
|
||||
title: $scope.localeDate(l._utime),
|
||||
|
@ -392,6 +395,9 @@
|
|||
cv += ", " + key + " : " + value;
|
||||
}
|
||||
}
|
||||
tab = cv.split(', ');
|
||||
tab.sort();
|
||||
cv = tab.join(', ');
|
||||
tmp.push({
|
||||
t: l._utime,
|
||||
title: $scope.localeDate(l._utime),
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -319,6 +319,12 @@
|
|||
"forms":"إستمارات",
|
||||
"friendlyName":"اسم مألوف",
|
||||
"generalParameters":"المعاييرالعامة",
|
||||
"githubAuthnLevel":"مستوى إثبات الهوية",
|
||||
"githubClientID":"Client ID",
|
||||
"githubClientSecret":"Client secret",
|
||||
"githubParams":"GitHub Parameters",
|
||||
"githubScope":"Scope",
|
||||
"githubUserField":"Field containing user identifier",
|
||||
"globalLogout":"Global logout",
|
||||
"globalLogoutCustomParam":"Custom parameter",
|
||||
"globalLogoutRule":"تفعيل",
|
||||
|
@ -430,6 +436,7 @@
|
|||
"ldapTimeout":"مهلة",
|
||||
"ldapUsePasswordResetAttribute":"استخدام سمة إعادة الضبط",
|
||||
"ldapVersion":"الإصدار",
|
||||
"level":"Level",
|
||||
"linkedInAuthnLevel":"مستوى إثبات الهوية",
|
||||
"linkedInClientID":"معرف العميل",
|
||||
"linkedInClientSecret":"سرالعميل",
|
||||
|
@ -559,7 +566,10 @@
|
|||
"oidcOPMetaDataJWKS":"بيانات JWKS",
|
||||
"oidcOPMetaDataNode":" أوبين أيدي كونيكت بروفيدر",
|
||||
"oidcOPMetaDataOptions":"الخيارات",
|
||||
"oidcRPMetaDataOptionsAuthentication":"إثبات الهوية",
|
||||
"oidcRPMetaDataOptionsBasic":"Basique",
|
||||
"oidcRPMetaDataOptionsAdditionalAudiences":"Additional audiences",
|
||||
"oidcRPMetaDataOptionsAdvanced":"Avancé",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Expiration",
|
||||
"oidcRPMetaDataOptionsAllowOffline":"Allow offline access",
|
||||
"oidcOPMetaDataOptionsCheckJWTSignature":"توقيع",
|
||||
"oidcOPMetaDataOptionsClientID":"معرف العميل",
|
||||
|
@ -591,6 +601,7 @@
|
|||
"oidcRPMetaDataNode":"الأطراف المعتمد لي أوبين أيدي كونيكت",
|
||||
"oidcRPMetaDataOptions":"الخيارات",
|
||||
"oidcRPMetaDataOptionsAccessTokenExpiration":"انتهاء صلاحية التوكن",
|
||||
"oidcRPMetaDataOptionsAllowPasswordGrant":"Allow OAuth2.0 Password Grant",
|
||||
"oidcRPMetaDataOptionsAuthorizationCodeExpiration":"Authorization Code expiration",
|
||||
"oidcRPMetaDataOptionsBypassConsent":"تخطى الموافقة ",
|
||||
"oidcRPMetaDataOptionsClientID":"معرف العميل",
|
||||
|
@ -746,7 +757,7 @@
|
|||
"radius2fSecret":"سر مشترك",
|
||||
"radius2fUsernameSessionKey":"Session key containing login",
|
||||
"radius2fTimeout":"Authentication timeout",
|
||||
"radius2fAuthnLevel":"Authentication level",
|
||||
"radius2fAuthnLevel":"مستوى إثبات الهوية",
|
||||
"radius2fLogo":"شعار",
|
||||
"radius2fLabel":"Label",
|
||||
"radiusAuthnLevel":"مستوى إثبات الهوية",
|
||||
|
@ -803,7 +814,7 @@
|
|||
"rule":"القاعدة",
|
||||
"ruleAuthnLevel":"مستوى إثبات الهوية واجب",
|
||||
"rules":"القواعد",
|
||||
"rulesAuthnLevel":"Required auth levels",
|
||||
"rulesAuthnLevel":"مستوى إثبات الهوية واجب",
|
||||
"Same":"نفسه",
|
||||
"sameSite":"Cookie SameSite value",
|
||||
"save":"حفظ",
|
||||
|
@ -825,23 +836,22 @@
|
|||
"sessionStartedAt":"بدأت الجلسة",
|
||||
"sessionStorage":"تخزين الجلسات",
|
||||
"sessionTitle":"محتوى الجلسة",
|
||||
"sfaTitle":"Second Factors Authentication",
|
||||
"sfaTitle":"Second factors authentication",
|
||||
"sfExtra":"Additional second factors",
|
||||
"sfRequired":"Require 2FA",
|
||||
"sfRemovedNotification":"Warn if an expired SF is removed",
|
||||
"sfRequired":"Force 2FA registration at login",
|
||||
"sfRemovedNotification":"Warn if an expired 2FA is removed",
|
||||
"sfRemovedMsgRule":"تفعيل",
|
||||
"sfRemovedUseNotif":"Use Notifications plugin",
|
||||
"sfRemovedNotifMsg":"Notification message",
|
||||
"sfRemovedNotifRef":"Notification reference",
|
||||
"sfRemovedNotifTitle":"Notification title",
|
||||
"sfRemovedMsg":"Display a message if an expired SF is removed",
|
||||
"sfRemovedMsg":"Display a message if an expired 2FA is removed",
|
||||
"show":"عرض",
|
||||
"showHelp":"عرض المساعدة",
|
||||
"showLanguages":"Show languages choice",
|
||||
"singleIP":"عنوان آي بي واحد لكل مستخدم",
|
||||
"singleSession":"جلسة واحدة فقط من قبل المستخدم",
|
||||
"singleUserByIP":"مستخدم واحد لكل عنوان آي بي",
|
||||
"singleSessionUserByIP":"جلسة واحدة بواسطة عنوان الآي بي",
|
||||
"singleIP":"One IP address per user",
|
||||
"singleSession":"One session per user",
|
||||
"singleUserByIP":"One user per IP address",
|
||||
"skipRenewConfirmation":"Skip re-auth confirmation",
|
||||
"slaveAuthnLevel":"مستوى إثبات الهوية",
|
||||
"slaveDisplayLogo":"Display authentication logo",
|
||||
|
|
|
@ -319,6 +319,12 @@
|
|||
"forms":"Forms",
|
||||
"friendlyName":"Friendly name",
|
||||
"generalParameters":"General Parameters",
|
||||
"githubAuthnLevel":"Authentication level",
|
||||
"githubClientID":"Client ID",
|
||||
"githubClientSecret":"Client secret",
|
||||
"githubParams":"GitHub Parameters",
|
||||
"githubScope":"Scope",
|
||||
"githubUserField":"Field containing user identifier",
|
||||
"globalLogout":"Global logout",
|
||||
"globalLogoutCustomParam":"Custom parameter",
|
||||
"globalLogoutRule":"Activation",
|
||||
|
@ -430,6 +436,7 @@
|
|||
"ldapTimeout":"Timeout",
|
||||
"ldapUsePasswordResetAttribute":"Use reset attribute",
|
||||
"ldapVersion":"Version",
|
||||
"level":"Level",
|
||||
"linkedInAuthnLevel":"Authentication level",
|
||||
"linkedInClientID":"Client ID",
|
||||
"linkedInClientSecret":"Client secret",
|
||||
|
@ -559,7 +566,10 @@
|
|||
"oidcOPMetaDataJWKS":"JWKS data",
|
||||
"oidcOPMetaDataNode":"OpenID Connect Providers",
|
||||
"oidcOPMetaDataOptions":"Optionen",
|
||||
"oidcRPMetaDataOptionsAuthentication":"Authentication",
|
||||
"oidcRPMetaDataOptionsBasic":"Basic",
|
||||
"oidcRPMetaDataOptionsAdditionalAudiences":"Additional audiences",
|
||||
"oidcRPMetaDataOptionsAdvanced":"Advanced",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsAllowOffline":"Allow offline access",
|
||||
"oidcOPMetaDataOptionsCheckJWTSignature":"Check JWT signature",
|
||||
"oidcOPMetaDataOptionsClientID":"Client ID",
|
||||
|
@ -591,6 +601,7 @@
|
|||
"oidcRPMetaDataNode":"OpenID Connect Relying Parties",
|
||||
"oidcRPMetaDataOptions":"Options",
|
||||
"oidcRPMetaDataOptionsAccessTokenExpiration":"Access Token expiration",
|
||||
"oidcRPMetaDataOptionsAllowPasswordGrant":"Allow OAuth2.0 Password Grant",
|
||||
"oidcRPMetaDataOptionsAuthorizationCodeExpiration":"Authorization Code expiration",
|
||||
"oidcRPMetaDataOptionsBypassConsent":"Bypass consent",
|
||||
"oidcRPMetaDataOptionsClientID":"Client ID",
|
||||
|
@ -825,23 +836,22 @@
|
|||
"sessionStartedAt":"Session started on",
|
||||
"sessionStorage":"Sessions Storage",
|
||||
"sessionTitle":"Session content",
|
||||
"sfaTitle":"Second Factors Authentication",
|
||||
"sfaTitle":"Second factors authentication",
|
||||
"sfExtra":"Additional second factors",
|
||||
"sfRequired":"Require 2FA",
|
||||
"sfRemovedNotification":"Warn if an expired SF is removed",
|
||||
"sfRequired":"Force 2FA registration at login",
|
||||
"sfRemovedNotification":"Warn if an expired 2FA is removed",
|
||||
"sfRemovedMsgRule":"Activation",
|
||||
"sfRemovedUseNotif":"Use Notifications plugin",
|
||||
"sfRemovedNotifMsg":"Notification message",
|
||||
"sfRemovedNotifRef":"Notification reference",
|
||||
"sfRemovedNotifTitle":"Notification title",
|
||||
"sfRemovedMsg":"Display a message if an expired SF is removed",
|
||||
"sfRemovedMsg":"Display a message if an expired 2FA is removed",
|
||||
"show":"Show",
|
||||
"showHelp":"Show help",
|
||||
"showLanguages":"Show languages choice",
|
||||
"singleIP":"One IP only by user",
|
||||
"singleSession":"One session only by user",
|
||||
"singleUserByIP":"One user by IP address",
|
||||
"singleSessionUserByIP":"One session by IP address",
|
||||
"singleIP":"One IP address per user",
|
||||
"singleSession":"One session per user",
|
||||
"singleUserByIP":"One user per IP address",
|
||||
"skipRenewConfirmation":"Skip re-auth confirmation",
|
||||
"slaveAuthnLevel":"Authentication level",
|
||||
"slaveDisplayLogo":"Display authentication logo",
|
||||
|
|
|
@ -319,6 +319,12 @@
|
|||
"forms":"Forms",
|
||||
"friendlyName":"Friendly name",
|
||||
"generalParameters":"General Parameters",
|
||||
"githubAuthnLevel":"Authentication level",
|
||||
"githubClientID":"Client ID",
|
||||
"githubClientSecret":"Client secret",
|
||||
"githubParams":"GitHub Parameters",
|
||||
"githubScope":"Scope",
|
||||
"githubUserField":"Field containing user identifier",
|
||||
"globalLogout":"Global logout",
|
||||
"globalLogoutCustomParam":"Custom parameter",
|
||||
"globalLogoutRule":"Activation",
|
||||
|
@ -430,6 +436,7 @@
|
|||
"ldapTimeout":"Timeout",
|
||||
"ldapUsePasswordResetAttribute":"Use reset attribute",
|
||||
"ldapVersion":"Version",
|
||||
"level":"Level",
|
||||
"linkedInAuthnLevel":"Authentication level",
|
||||
"linkedInClientID":"Client ID",
|
||||
"linkedInClientSecret":"Client secret",
|
||||
|
@ -559,7 +566,10 @@
|
|||
"oidcOPMetaDataJWKS":"JWKS data",
|
||||
"oidcOPMetaDataNode":"OpenID Connect Providers",
|
||||
"oidcOPMetaDataOptions":"Options",
|
||||
"oidcRPMetaDataOptionsAuthentication":"Authentication",
|
||||
"oidcRPMetaDataOptionsBasic":"Basic",
|
||||
"oidcRPMetaDataOptionsAdditionalAudiences":"Additional audiences",
|
||||
"oidcRPMetaDataOptionsAdvanced":"Advanced",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsAllowOffline":"Allow offline access",
|
||||
"oidcOPMetaDataOptionsCheckJWTSignature":"Check JWT signature",
|
||||
"oidcOPMetaDataOptionsClientID":"Client ID",
|
||||
|
@ -591,6 +601,7 @@
|
|||
"oidcRPMetaDataNode":"OpenID Connect Relying Parties",
|
||||
"oidcRPMetaDataOptions":"Options",
|
||||
"oidcRPMetaDataOptionsAccessTokenExpiration":"Access Token expiration",
|
||||
"oidcRPMetaDataOptionsAllowPasswordGrant":"Allow OAuth2.0 Password Grant",
|
||||
"oidcRPMetaDataOptionsAuthorizationCodeExpiration":"Authorization Code expiration",
|
||||
"oidcRPMetaDataOptionsBypassConsent":"Bypass consent",
|
||||
"oidcRPMetaDataOptionsClientID":"Client ID",
|
||||
|
@ -825,23 +836,22 @@
|
|||
"sessionStartedAt":"Session started on",
|
||||
"sessionStorage":"Sessions Storage",
|
||||
"sessionTitle":"Session content",
|
||||
"sfaTitle":"Second Factors Authentication",
|
||||
"sfaTitle":"Second factors authentication",
|
||||
"sfExtra":"Additional second factors",
|
||||
"sfRequired":"Require 2FA",
|
||||
"sfRemovedNotification":"Warn if an expired SF is removed",
|
||||
"sfRequired":"Force 2FA registration at login",
|
||||
"sfRemovedNotification":"Warn if an expired 2FA is removed",
|
||||
"sfRemovedMsgRule":"Activation",
|
||||
"sfRemovedUseNotif":"Use Notifications plugin",
|
||||
"sfRemovedNotifMsg":"Notification message",
|
||||
"sfRemovedNotifRef":"Notification reference",
|
||||
"sfRemovedNotifTitle":"Notification title",
|
||||
"sfRemovedMsg":"Display a message if an expired SF is removed",
|
||||
"sfRemovedMsg":"Display a message if an expired 2FA is removed",
|
||||
"show":"Show",
|
||||
"showHelp":"Show help",
|
||||
"showLanguages":"Show languages choice",
|
||||
"singleIP":"One IP only by user",
|
||||
"singleSession":"One session only by user",
|
||||
"singleUserByIP":"One user by IP address",
|
||||
"singleSessionUserByIP":"One session by IP address",
|
||||
"singleIP":"One IP address per user",
|
||||
"singleSession":"One session per user",
|
||||
"singleUserByIP":"One user per IP address",
|
||||
"skipRenewConfirmation":"Skip re-auth confirmation",
|
||||
"slaveAuthnLevel":"Authentication level",
|
||||
"slaveDisplayLogo":"Display authentication logo",
|
||||
|
|
|
@ -319,6 +319,12 @@
|
|||
"forms":"Formulaires",
|
||||
"friendlyName":"Nom alternatif",
|
||||
"generalParameters":"Paramètres généraux",
|
||||
"githubAuthnLevel":"Niveau d'authentification",
|
||||
"githubClientID":"Identifiant",
|
||||
"githubClientSecret":"Mot de passe",
|
||||
"githubParams":"Paramètres GitHub",
|
||||
"githubScope":"Scope",
|
||||
"githubUserField":"Champ contenant l'identifiant de l'utilisateur",
|
||||
"globalLogout":"Déconnexion globale",
|
||||
"globalLogoutCustomParam":"Paramètre personnalisé",
|
||||
"globalLogoutRule":"Activation",
|
||||
|
@ -430,6 +436,7 @@
|
|||
"ldapTimeout":"Temps maximum d'inactivité",
|
||||
"ldapUsePasswordResetAttribute":"Utiliser l'attribut de réinitialisation",
|
||||
"ldapVersion":"Version",
|
||||
"level":"Niveau",
|
||||
"linkedInAuthnLevel":"Niveau d'authentification",
|
||||
"linkedInClientID":"Identifiant",
|
||||
"linkedInClientSecret":"Mot de passe",
|
||||
|
@ -559,7 +566,10 @@
|
|||
"oidcOPMetaDataJWKS":"Données JWKS",
|
||||
"oidcOPMetaDataNode":"Fournisseurs OpenID Connect",
|
||||
"oidcOPMetaDataOptions":"Options",
|
||||
"oidcRPMetaDataOptionsAuthentication":"Authentification",
|
||||
"oidcRPMetaDataOptionsBasic":"Basiques",
|
||||
"oidcRPMetaDataOptionsAdvanced":"Avancées",
|
||||
"oidcRPMetaDataOptionsAdditionalAudiences":"Audiences supplémentaires",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Expiration",
|
||||
"oidcRPMetaDataOptionsAllowOffline":"Autoriser l'accès hors ligne",
|
||||
"oidcOPMetaDataOptionsCheckJWTSignature":"Vérifier la signature des jetons",
|
||||
"oidcOPMetaDataOptionsClientID":"Identifiant",
|
||||
|
@ -591,6 +601,7 @@
|
|||
"oidcRPMetaDataNode":"Clients OpenID Connect",
|
||||
"oidcRPMetaDataOptions":"Options",
|
||||
"oidcRPMetaDataOptionsAccessTokenExpiration":"Expiration des jetons d'accès",
|
||||
"oidcRPMetaDataOptionsAllowPasswordGrant":"Autoriser le Password Grant OAuth2.0",
|
||||
"oidcRPMetaDataOptionsAuthorizationCodeExpiration":"Expiration des codes d'autorisation",
|
||||
"oidcRPMetaDataOptionsBypassConsent":"Contourner le consentement",
|
||||
"oidcRPMetaDataOptionsClientID":"Identifiant",
|
||||
|
@ -825,10 +836,10 @@
|
|||
"sessionStartedAt":"Session démarrée le ",
|
||||
"sessionStorage":"Stockage des sessions",
|
||||
"sessionTitle":"Contenu de la session",
|
||||
"sfaTitle":"Seconds Facteurs d'Authentification",
|
||||
"sfaTitle":"Seconds facteurs d'authentification",
|
||||
"sfExtra":"Seconds facteurs additionnels",
|
||||
"sfRequired":"Exiger 2FA",
|
||||
"sfRemovedNotification":"Avertir si un SF expiré a été supprimé",
|
||||
"sfRequired":"Exiger l'enrôlement d'un SF à l'authentification",
|
||||
"sfRemovedNotification":"Avertir si un SF expiré est supprimé",
|
||||
"sfRemovedMsgRule":"Activation",
|
||||
"sfRemovedUseNotif":"Utiliser les notifications",
|
||||
"sfRemovedNotifMsg":"Message de la notification",
|
||||
|
@ -838,10 +849,9 @@
|
|||
"show":"Montrer",
|
||||
"showHelp":"Montrer l'aide",
|
||||
"showLanguages":"Afficher le choix des langues",
|
||||
"singleIP":"Une seule session par couple utilisateur/IP",
|
||||
"singleIP":"Une seule adresse IP par utilisateur",
|
||||
"singleSession":"Une seule session par utilisateur",
|
||||
"singleUserByIP":"Un seul utilisateur par IP",
|
||||
"singleSessionUserByIP":"Une seule session par IP",
|
||||
"singleUserByIP":"Un seul utilisateur par adresse IP",
|
||||
"skipRenewConfirmation":"Éviter la confirmation de ré-authentification",
|
||||
"slaveAuthnLevel":"Niveau d'authentification",
|
||||
"slaveDisplayLogo":"Afficher le logo d'authentification",
|
||||
|
|
|
@ -319,6 +319,12 @@
|
|||
"forms":"Moduli",
|
||||
"friendlyName":"Nome amichevole",
|
||||
"generalParameters":"Parametri generali",
|
||||
"githubAuthnLevel":"Livello di autenticazione",
|
||||
"githubClientID":"Client ID",
|
||||
"githubClientSecret":"Client secret",
|
||||
"githubParams":"GitHub Parameters",
|
||||
"githubScope":"Scope",
|
||||
"githubUserField":"Field containing user identifier",
|
||||
"globalLogout":"Global logout",
|
||||
"globalLogoutCustomParam":"Custom parameter",
|
||||
"globalLogoutRule":"Attivazione",
|
||||
|
@ -430,6 +436,7 @@
|
|||
"ldapTimeout":"Timeout",
|
||||
"ldapUsePasswordResetAttribute":"Utilizza l'attributo di ripristino",
|
||||
"ldapVersion":"Versione",
|
||||
"level":"Livello",
|
||||
"linkedInAuthnLevel":"Livello di autenticazione",
|
||||
"linkedInClientID":"Client ID",
|
||||
"linkedInClientSecret":"Client segreto",
|
||||
|
@ -559,7 +566,10 @@
|
|||
"oidcOPMetaDataJWKS":"Dati di JWKS",
|
||||
"oidcOPMetaDataNode":"Provider di OpenID Connect",
|
||||
"oidcOPMetaDataOptions":"Opzioni",
|
||||
"oidcRPMetaDataOptionsAuthentication":"Autenticazione",
|
||||
"oidcRPMetaDataOptionsBasic":"Basic",
|
||||
"oidcRPMetaDataOptionsAdditionalAudiences":"Additional audiences",
|
||||
"oidcRPMetaDataOptionsAdvanced":"Advanced",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsAllowOffline":"Allow offline access",
|
||||
"oidcOPMetaDataOptionsCheckJWTSignature":"Controllare la firma JWT",
|
||||
"oidcOPMetaDataOptionsClientID":"ID Client",
|
||||
|
@ -591,6 +601,7 @@
|
|||
"oidcRPMetaDataNode":"Parti basate su OpenID Connect",
|
||||
"oidcRPMetaDataOptions":"Opzioni",
|
||||
"oidcRPMetaDataOptionsAccessTokenExpiration":"Scadenza accesso token",
|
||||
"oidcRPMetaDataOptionsAllowPasswordGrant":"Allow OAuth2.0 Password Grant",
|
||||
"oidcRPMetaDataOptionsAuthorizationCodeExpiration":"Authorization Code expiration",
|
||||
"oidcRPMetaDataOptionsBypassConsent":"Consenso di bypass",
|
||||
"oidcRPMetaDataOptionsClientID":"ID Client",
|
||||
|
@ -827,21 +838,20 @@
|
|||
"sessionTitle":"Contenuto della sessione",
|
||||
"sfaTitle":"Autenticazione a due fattori",
|
||||
"sfExtra":"Additional second factors",
|
||||
"sfRequired":"Richiedi 2FA",
|
||||
"sfRemovedNotification":"Warn if an expired SF is removed",
|
||||
"sfRequired":"Force 2FA registration at login",
|
||||
"sfRemovedNotification":"Warn if an expired 2FA is removed",
|
||||
"sfRemovedMsgRule":"Attivazione",
|
||||
"sfRemovedUseNotif":"Use Notifications plugin",
|
||||
"sfRemovedNotifMsg":"Notification message",
|
||||
"sfRemovedNotifRef":"Notification reference",
|
||||
"sfRemovedNotifTitle":"Notification title",
|
||||
"sfRemovedMsg":"Display a message if an expired SF is removed",
|
||||
"sfRemovedMsg":"Display a message if an expired 2FA is removed",
|
||||
"show":"Mostra",
|
||||
"showHelp":"Mostra aiuto",
|
||||
"showLanguages":"Mostra la scelta delle lingue",
|
||||
"singleIP":"Solo un IP per utente",
|
||||
"singleSession":"Una sola sessione per utente",
|
||||
"singleUserByIP":"Un utente per indirizzo IP",
|
||||
"singleSessionUserByIP":"Una sessione per indirizzo IP",
|
||||
"singleIP":"One IP address per user",
|
||||
"singleSession":"One session per user",
|
||||
"singleUserByIP":"One user per IP address",
|
||||
"skipRenewConfirmation":"Salta la conferma di re-auth",
|
||||
"slaveAuthnLevel":"Livello di autenticazione",
|
||||
"slaveDisplayLogo":"Display authentication logo",
|
||||
|
|
|
@ -319,6 +319,12 @@
|
|||
"forms":"Formlar",
|
||||
"friendlyName":"Kolay ad",
|
||||
"generalParameters":"Genel Parametreler",
|
||||
"githubAuthnLevel":"Doğrulama seviyesi",
|
||||
"githubClientID":"Client ID",
|
||||
"githubClientSecret":"Client secret",
|
||||
"githubParams":"GitHub Parameters",
|
||||
"githubScope":"Scope",
|
||||
"githubUserField":"Field containing user identifier",
|
||||
"globalLogout":"Global çıkış",
|
||||
"globalLogoutCustomParam":"Custom parameter",
|
||||
"globalLogoutRule":"Aktivasyon",
|
||||
|
@ -430,6 +436,7 @@
|
|||
"ldapTimeout":"Zaman aşımı",
|
||||
"ldapUsePasswordResetAttribute":"Sıfırlama niteliklerini kullan",
|
||||
"ldapVersion":"Sürüm",
|
||||
"level":"Seviyesi",
|
||||
"linkedInAuthnLevel":"Doğrulama seviyesi",
|
||||
"linkedInClientID":"İstemci ID",
|
||||
"linkedInClientSecret":"İstemci sırrı",
|
||||
|
@ -559,7 +566,10 @@
|
|||
"oidcOPMetaDataJWKS":"JWKS verisi",
|
||||
"oidcOPMetaDataNode":"OpenID Connect Sağlayıcıları",
|
||||
"oidcOPMetaDataOptions":"Seçenekler",
|
||||
"oidcRPMetaDataOptionsAuthentication":"Doğrulama",
|
||||
"oidcRPMetaDataOptionsBasic":"Basic",
|
||||
"oidcRPMetaDataOptionsAdditionalAudiences":"Additional audiences",
|
||||
"oidcRPMetaDataOptionsAdvanced":"Advanced",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsAllowOffline":"Çevrimdışı erişime izin ver",
|
||||
"oidcOPMetaDataOptionsCheckJWTSignature":"JWT imzasını kontrol et",
|
||||
"oidcOPMetaDataOptionsClientID":"İstemci ID",
|
||||
|
@ -591,6 +601,7 @@
|
|||
"oidcRPMetaDataNode":"OpenID Connect Relying Parties",
|
||||
"oidcRPMetaDataOptions":"Seçenekler",
|
||||
"oidcRPMetaDataOptionsAccessTokenExpiration":"Erişim jetonu sona erme",
|
||||
"oidcRPMetaDataOptionsAllowPasswordGrant":"Allow OAuth2.0 Password Grant",
|
||||
"oidcRPMetaDataOptionsAuthorizationCodeExpiration":"Yetkilendirme Kodu sona erme",
|
||||
"oidcRPMetaDataOptionsBypassConsent":"İzni es geç",
|
||||
"oidcRPMetaDataOptionsClientID":"İstemci ID",
|
||||
|
@ -827,21 +838,20 @@
|
|||
"sessionTitle":"Oturum içeriği",
|
||||
"sfaTitle":"İki Faktörlü Kimlik Doğrulaması",
|
||||
"sfExtra":"Ek ikinci faktörler",
|
||||
"sfRequired":"2FA gerektir",
|
||||
"sfRemovedNotification":"Warn if an expired SF is removed",
|
||||
"sfRequired":"Force 2FA registration at login",
|
||||
"sfRemovedNotification":"Warn if an expired 2FA is removed",
|
||||
"sfRemovedMsgRule":"Aktivasyon",
|
||||
"sfRemovedUseNotif":"Bildirimler eklentisini kullan",
|
||||
"sfRemovedNotifMsg":"Bildirim mesajı",
|
||||
"sfRemovedNotifRef":"Bildirim kaynağı",
|
||||
"sfRemovedNotifTitle":"Bildirim başlığı",
|
||||
"sfRemovedMsg":"Süresi dolan bir SF kaldırıldığında bir mesaj göster",
|
||||
"sfRemovedMsg":"Süresi dolan bir 2FA kaldırıldığında bir mesaj göster",
|
||||
"show":"Görüntüle",
|
||||
"showHelp":"Yardımı görüntüle",
|
||||
"showLanguages":"Dil tercihini görüntüle",
|
||||
"singleIP":"Kullanıcıdan yalnızca bir IP",
|
||||
"singleSession":"Kullanıcıdan yalnızca bir oturum",
|
||||
"singleUserByIP":"IP adresinden bir kullanıcı",
|
||||
"singleSessionUserByIP":"IP adresinden bir oturum",
|
||||
"singleIP":"One IP address per user",
|
||||
"singleSession":"One session per user",
|
||||
"singleUserByIP":"One user per IP address",
|
||||
"skipRenewConfirmation":"Yeniden yetkilendirme doğrulamasını geç",
|
||||
"slaveAuthnLevel":"Doğrulama seviyesi",
|
||||
"slaveDisplayLogo":"Doğrulama logosunu görüntüle",
|
||||
|
|
|
@ -319,6 +319,12 @@
|
|||
"forms":"Biểu mẫu",
|
||||
"friendlyName":"Tên thân thiện",
|
||||
"generalParameters":"Thông số chung",
|
||||
"githubAuthnLevel":"Mức xác thực",
|
||||
"githubClientID":"Client ID",
|
||||
"githubClientSecret":"Client secret",
|
||||
"githubParams":"GitHub Parameters",
|
||||
"githubScope":"Scope",
|
||||
"githubUserField":"Field containing user identifier",
|
||||
"globalLogout":"Global logout",
|
||||
"globalLogoutCustomParam":"Custom parameter",
|
||||
"globalLogoutRule":"Activation",
|
||||
|
@ -430,6 +436,7 @@
|
|||
"ldapTimeout":"Thời gian chờ",
|
||||
"ldapUsePasswordResetAttribute":"Sử dụng thuộc tính đặt lại",
|
||||
"ldapVersion":"Phiên bản",
|
||||
"level":"Mức",
|
||||
"linkedInAuthnLevel":"Mức xác thực",
|
||||
"linkedInClientID":"Client ID",
|
||||
"linkedInClientSecret":"Trình khách bí mật",
|
||||
|
@ -559,7 +566,10 @@
|
|||
"oidcOPMetaDataJWKS":"Dữ liệu JWKS",
|
||||
"oidcOPMetaDataNode":"Nhà cung cấp Kết nối OpenID",
|
||||
"oidcOPMetaDataOptions":"Tùy chọn",
|
||||
"oidcRPMetaDataOptionsAuthentication":"Xác thực",
|
||||
"oidcRPMetaDataOptionsBasic":"Basic",
|
||||
"oidcRPMetaDataOptionsAdditionalAudiences":"Additional audiences",
|
||||
"oidcRPMetaDataOptionsAdvanced":"Advanced",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsAllowOffline":"Allow offline access",
|
||||
"oidcOPMetaDataOptionsCheckJWTSignature":"Kiểm tra chữ ký JWT",
|
||||
"oidcOPMetaDataOptionsClientID":"Client ID",
|
||||
|
@ -591,6 +601,7 @@
|
|||
"oidcRPMetaDataNode":"OpenID Connect Relying Parties",
|
||||
"oidcRPMetaDataOptions":"Tùy chọn",
|
||||
"oidcRPMetaDataOptionsAccessTokenExpiration":"Access Token expiration",
|
||||
"oidcRPMetaDataOptionsAllowPasswordGrant":"Allow OAuth2.0 Password Grant",
|
||||
"oidcRPMetaDataOptionsAuthorizationCodeExpiration":"Authorization Code expiration",
|
||||
"oidcRPMetaDataOptionsBypassConsent":"Bỏ qua sự đồng ý",
|
||||
"oidcRPMetaDataOptionsClientID":"Client ID",
|
||||
|
@ -825,23 +836,22 @@
|
|||
"sessionStartedAt":"Phiên bắt đầu lúc",
|
||||
"sessionStorage":"Sessions lưu trữ",
|
||||
"sessionTitle":"Nội dung phiên",
|
||||
"sfaTitle":"Second Factors Authentication",
|
||||
"sfaTitle":"Second factors authentication",
|
||||
"sfExtra":"Additional second factors",
|
||||
"sfRequired":"Require 2FA",
|
||||
"sfRemovedNotification":"Warn if an expired SF is removed",
|
||||
"sfRequired":"Force 2FA registration at login",
|
||||
"sfRemovedNotification":"Warn if an expired 2FA is removed",
|
||||
"sfRemovedMsgRule":"Kích hoạt",
|
||||
"sfRemovedUseNotif":"Use Notifications plugin",
|
||||
"sfRemovedNotifMsg":"Notification message",
|
||||
"sfRemovedNotifRef":"Notification reference",
|
||||
"sfRemovedNotifTitle":"Notification title",
|
||||
"sfRemovedMsg":"Display a message if an expired SF is removed",
|
||||
"sfRemovedMsg":"Display a message if an expired 2FA is removed",
|
||||
"show":"Hiển thị",
|
||||
"showHelp":"Hiển thị trợ giúp",
|
||||
"showLanguages":"Show languages choice",
|
||||
"singleIP":"Chỉ một địa chỉ IP bởi người dùng",
|
||||
"singleSession":"Một phiên chỉ bởi người dùng",
|
||||
"singleUserByIP":"Một người dùng theo địa chỉ IP",
|
||||
"singleSessionUserByIP":"Một phiên theo địa chỉ IP",
|
||||
"singleIP":"One IP address per user",
|
||||
"singleSession":"One session per user",
|
||||
"singleUserByIP":"One user per IP address",
|
||||
"skipRenewConfirmation":"Skip re-auth confirmation",
|
||||
"slaveAuthnLevel":"Mức xác thực",
|
||||
"slaveDisplayLogo":"Display authentication logo",
|
||||
|
|
|
@ -319,6 +319,12 @@
|
|||
"forms":"Forms",
|
||||
"friendlyName":"Friendly name",
|
||||
"generalParameters":"通用参数",
|
||||
"githubAuthnLevel":"认证等级",
|
||||
"githubClientID":"Client ID",
|
||||
"githubClientSecret":"Client secret",
|
||||
"githubParams":"GitHub Parameters",
|
||||
"githubScope":"Scope",
|
||||
"githubUserField":"Field containing user identifier",
|
||||
"globalLogout":"Global logout",
|
||||
"globalLogoutCustomParam":"Custom parameter",
|
||||
"globalLogoutRule":"Activation",
|
||||
|
@ -430,6 +436,7 @@
|
|||
"ldapTimeout":"Timeout",
|
||||
"ldapUsePasswordResetAttribute":"Use reset attribute",
|
||||
"ldapVersion":"版本",
|
||||
"level":"Level",
|
||||
"linkedInAuthnLevel":"认证等级",
|
||||
"linkedInClientID":"Client ID",
|
||||
"linkedInClientSecret":"Client secret",
|
||||
|
@ -559,7 +566,10 @@
|
|||
"oidcOPMetaDataJWKS":"JWKS data",
|
||||
"oidcOPMetaDataNode":"OpenID Connect Providers",
|
||||
"oidcOPMetaDataOptions":"Options",
|
||||
"oidcRPMetaDataOptionsAuthentication":"Authentication",
|
||||
"oidcRPMetaDataOptionsBasic":"Basic",
|
||||
"oidcRPMetaDataOptionsAdditionalAudiences":"Additional audiences",
|
||||
"oidcRPMetaDataOptionsAdvanced":"Advanced",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsAllowOffline":"Allow offline access",
|
||||
"oidcOPMetaDataOptionsCheckJWTSignature":"Check JWT signature",
|
||||
"oidcOPMetaDataOptionsClientID":"Client ID",
|
||||
|
@ -591,6 +601,7 @@
|
|||
"oidcRPMetaDataNode":"OpenID Connect Relying Parties",
|
||||
"oidcRPMetaDataOptions":"Options",
|
||||
"oidcRPMetaDataOptionsAccessTokenExpiration":"Access Token expiration",
|
||||
"oidcRPMetaDataOptionsAllowPasswordGrant":"Allow OAuth2.0 Password Grant",
|
||||
"oidcRPMetaDataOptionsAuthorizationCodeExpiration":"Authorization Code expiration",
|
||||
"oidcRPMetaDataOptionsBypassConsent":"Bypass consent",
|
||||
"oidcRPMetaDataOptionsClientID":"Client ID",
|
||||
|
@ -746,7 +757,7 @@
|
|||
"radius2fSecret":"Shared secret",
|
||||
"radius2fUsernameSessionKey":"Session key containing login",
|
||||
"radius2fTimeout":"Authentication timeout",
|
||||
"radius2fAuthnLevel":"Authentication level",
|
||||
"radius2fAuthnLevel":"认证等级",
|
||||
"radius2fLogo":"Logo",
|
||||
"radius2fLabel":"Label",
|
||||
"radiusAuthnLevel":"认证等级",
|
||||
|
@ -825,23 +836,22 @@
|
|||
"sessionStartedAt":"Session started on",
|
||||
"sessionStorage":"Sessions Storage",
|
||||
"sessionTitle":"Session content",
|
||||
"sfaTitle":"Second Factors Authentication",
|
||||
"sfaTitle":"Second factors authentication",
|
||||
"sfExtra":"Additional second factors",
|
||||
"sfRequired":"Require 2FA",
|
||||
"sfRemovedNotification":"Warn if an expired SF is removed",
|
||||
"sfRequired":"Force 2FA registration at login",
|
||||
"sfRemovedNotification":"Warn if an expired 2FA is removed",
|
||||
"sfRemovedMsgRule":"激活",
|
||||
"sfRemovedUseNotif":"Use Notifications plugin",
|
||||
"sfRemovedNotifMsg":"Notification message",
|
||||
"sfRemovedNotifRef":"Notification reference",
|
||||
"sfRemovedNotifTitle":"Notification title",
|
||||
"sfRemovedMsg":"Display a message if an expired SF is removed",
|
||||
"sfRemovedMsg":"Display a message if an expired 2FA is removed",
|
||||
"show":"Show",
|
||||
"showHelp":"Show help",
|
||||
"showLanguages":"Show languages choice",
|
||||
"singleIP":"One IP only by user",
|
||||
"singleSession":"One session only by user",
|
||||
"singleUserByIP":"One user by IP address",
|
||||
"singleSessionUserByIP":"One session by IP address",
|
||||
"singleIP":"One IP address per user",
|
||||
"singleSession":"One session per user",
|
||||
"singleUserByIP":"One user per IP address",
|
||||
"skipRenewConfirmation":"Skip re-auth confirmation",
|
||||
"slaveAuthnLevel":"认证等级",
|
||||
"slaveDisplayLogo":"Display authentication logo",
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -54,9 +54,9 @@ sub check200 {
|
|||
checkJson( $test, $res );
|
||||
}
|
||||
|
||||
sub check405 {
|
||||
sub check400 {
|
||||
my ( $test, $res ) = splice @_;
|
||||
ok( $res->[0] == 405, "$test: Result code is 405" );
|
||||
ok( $res->[0] == 400, "$test: Result code is 400" );
|
||||
count(1);
|
||||
checkJson( $test, $res );
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ sub checkGetBadType {
|
|||
my ( $test, $res );
|
||||
$test = "Get for uid $uid and type \"$type\" should get rejected.";
|
||||
$res = get( $test, $uid, $type );
|
||||
check405( $test, $res );
|
||||
check400( $test, $res );
|
||||
}
|
||||
|
||||
sub checkGetOnIds {
|
||||
|
@ -211,7 +211,7 @@ sub checkDeleteBadType {
|
|||
my ( $test, $res );
|
||||
$test = "Delete for uid $uid and type \"$type\" should get rejected.";
|
||||
$res = del( $test, $uid, $type );
|
||||
check405( $test, $res );
|
||||
check400( $test, $res );
|
||||
}
|
||||
|
||||
my $sfaDevices = [];
|
||||
|
|
|
@ -8,28 +8,62 @@ require 't/test-lib.pm';
|
|||
|
||||
our $_json = JSON->new->allow_nonref;
|
||||
|
||||
sub check201 {
|
||||
my ( $test, $res ) = splice @_;
|
||||
|
||||
#diag Dumper($res);
|
||||
is( $res->[0], "201", "$test: Result code is 201" )
|
||||
or diag explain $res->[2];
|
||||
count(1);
|
||||
checkJson( $test, $res );
|
||||
}
|
||||
|
||||
sub check204 {
|
||||
my ( $test, $res ) = splice @_;
|
||||
|
||||
#diag Dumper($res);
|
||||
is( $res->[0], "204", "$test: Result code is 204" )
|
||||
or diag explain $res->[2];
|
||||
count(1);
|
||||
is( $res->[2]->[0], undef, "204 code returns no content" );
|
||||
}
|
||||
|
||||
sub check200 {
|
||||
my ( $test, $res ) = splice @_;
|
||||
|
||||
#diag Dumper($res);
|
||||
ok( $res->[0] == 200, "$test: Result code is 200" );
|
||||
is( $res->[0], "200", "$test: Result code is 200" )
|
||||
or diag explain $res->[2];
|
||||
count(1);
|
||||
checkJson( $test, $res );
|
||||
|
||||
}
|
||||
|
||||
sub check409 {
|
||||
my ( $test, $res ) = splice @_;
|
||||
|
||||
#diag Dumper($res);
|
||||
is( $res->[0], "409", "$test: Result code is 409" )
|
||||
or diag explain $res->[2];
|
||||
count(1);
|
||||
checkJson( $test, $res );
|
||||
}
|
||||
|
||||
sub check404 {
|
||||
my ( $test, $res ) = splice @_;
|
||||
|
||||
#diag Dumper($res);
|
||||
ok( $res->[0] == 404, "$test: Result code is 404" );
|
||||
is( $res->[0], "404", "$test: Result code is 404" )
|
||||
or diag explain $res->[2];
|
||||
count(1);
|
||||
checkJson( $test, $res );
|
||||
}
|
||||
|
||||
sub check405 {
|
||||
sub check400 {
|
||||
my ( $test, $res ) = splice @_;
|
||||
ok( $res->[0] == 405, "$test: Result code is 405" );
|
||||
is( $res->[0], "400", "$test: Result code is 400" )
|
||||
or diag explain $res->[2];
|
||||
count(1);
|
||||
count(1);
|
||||
checkJson( $test, $res );
|
||||
}
|
||||
|
@ -63,17 +97,17 @@ sub add {
|
|||
|
||||
sub checkAdd {
|
||||
my ( $test, $type, $add ) = splice @_;
|
||||
check200( $test, add( $test, $type, $add ) );
|
||||
check201( $test, add( $test, $type, $add ) );
|
||||
}
|
||||
|
||||
sub checkAddFailsIfExists {
|
||||
my ( $test, $type, $add ) = splice @_;
|
||||
check405( $test, add( $test, $type, $add ) );
|
||||
check409( $test, add( $test, $type, $add ) );
|
||||
}
|
||||
|
||||
sub checkAddWithUnknownAttributes {
|
||||
my ( $test, $type, $add ) = splice @_;
|
||||
check405( $test, add( $test, $type, $add ) );
|
||||
check400( $test, add( $test, $type, $add ) );
|
||||
}
|
||||
|
||||
sub get {
|
||||
|
@ -92,7 +126,12 @@ sub checkGet {
|
|||
my @path = split '/', $attrPath;
|
||||
my $key = from_json( $res->[2]->[0] );
|
||||
for (@path) {
|
||||
$key = $key->{$_};
|
||||
if ( ref($key) eq 'ARRAY' ) {
|
||||
$key = $key->[$_];
|
||||
}
|
||||
else {
|
||||
$key = $key->{$_};
|
||||
}
|
||||
}
|
||||
ok(
|
||||
$key eq $expectedValue,
|
||||
|
@ -126,7 +165,7 @@ sub update {
|
|||
|
||||
sub checkUpdate {
|
||||
my ( $test, $type, $confKey, $update ) = splice @_;
|
||||
check200( $test, update( $test, $type, $confKey, $update ) );
|
||||
check204( $test, update( $test, $type, $confKey, $update ) );
|
||||
}
|
||||
|
||||
sub checkUpdateNotFound {
|
||||
|
@ -136,12 +175,12 @@ sub checkUpdateNotFound {
|
|||
|
||||
sub checkUpdateFailsIfExists {
|
||||
my ( $test, $type, $confKey, $update ) = splice @_;
|
||||
check405( $test, update( $test, $type, $confKey, $update ) );
|
||||
check409( $test, update( $test, $type, $confKey, $update ) );
|
||||
}
|
||||
|
||||
sub checkUpdateWithUnknownAttributes {
|
||||
my ( $test, $type, $confKey, $update ) = splice @_;
|
||||
check405( $test, update( $test, $type, $confKey, $update ) );
|
||||
check400( $test, update( $test, $type, $confKey, $update ) );
|
||||
}
|
||||
|
||||
sub replace {
|
||||
|
@ -162,12 +201,12 @@ sub replace {
|
|||
|
||||
sub checkReplace {
|
||||
my ( $test, $type, $confKey, $replace ) = splice @_;
|
||||
check200( $test, replace( $test, $type, $confKey, $replace ) );
|
||||
check204( $test, replace( $test, $type, $confKey, $replace ) );
|
||||
}
|
||||
|
||||
sub checkReplaceAlreadyThere {
|
||||
my ( $test, $type, $confKey, $replace ) = splice @_;
|
||||
check405( $test, replace( $test, $type, $confKey, $replace ) );
|
||||
check400( $test, replace( $test, $type, $confKey, $replace ) );
|
||||
}
|
||||
|
||||
sub checkReplaceNotFound {
|
||||
|
@ -175,9 +214,9 @@ sub checkReplaceNotFound {
|
|||
check404( $test, replace( $test, $type, $confKey, $update ) );
|
||||
}
|
||||
|
||||
sub checkReplaceWithUnknownAttribute {
|
||||
sub checkReplaceWithInvalidAttribute {
|
||||
my ( $test, $type, $confKey, $replace ) = splice @_;
|
||||
check405( $test, replace( $test, $type, $confKey, $replace ) );
|
||||
check400( $test, replace( $test, $type, $confKey, $replace ) );
|
||||
}
|
||||
|
||||
sub findByConfKey {
|
||||
|
@ -194,21 +233,19 @@ sub findByConfKey {
|
|||
return $res;
|
||||
}
|
||||
|
||||
sub checkFindByConfKeyError {
|
||||
my ( $test, $type, $pattern ) = splice @_;
|
||||
my $res = findByConfKey( $test, $type, $pattern );
|
||||
check400( $test, $res );
|
||||
}
|
||||
|
||||
sub checkFindByConfKey {
|
||||
my ( $test, $type, $confKey, $expectedHits ) = splice @_;
|
||||
my $res = findByConfKey( $test, $type, $confKey );
|
||||
check200( $test, $res );
|
||||
my $hits = from_json( $res->[2]->[0] );
|
||||
my $hit;
|
||||
my $counter = 0;
|
||||
foreach $hit ( @{$hits} ) {
|
||||
$counter++;
|
||||
ok(
|
||||
$hit->{confKey} =~ $confKey,
|
||||
"$test: check if confKey value \"$hit->{confKey}\" matches pattern \"$confKey\""
|
||||
);
|
||||
count(1);
|
||||
}
|
||||
my $counter = @{$hits};
|
||||
ok(
|
||||
$counter eq $expectedHits,
|
||||
"$test: check if nb of hits returned ($counter) matches expectation ($expectedHits)"
|
||||
|
@ -271,7 +308,7 @@ sub deleteProvider {
|
|||
|
||||
sub checkDelete {
|
||||
my ( $test, $type, $confKey ) = splice @_;
|
||||
check200( $test, deleteProvider( $test, $type, $confKey ) );
|
||||
check204( $test, deleteProvider( $test, $type, $confKey ) );
|
||||
}
|
||||
|
||||
sub checkDeleteNotFound {
|
||||
|
@ -283,65 +320,74 @@ my $test;
|
|||
|
||||
my $oidcRp = {
|
||||
confKey => 'myOidcRp1',
|
||||
clientId => 'myOidcClient1',
|
||||
clientId => 'myOidcClient0',
|
||||
exportedVars => {
|
||||
'sub' => "uid",
|
||||
family_name => "sn",
|
||||
given_name => "givenName"
|
||||
},
|
||||
extraClaim => {
|
||||
macros => {
|
||||
given_name => '$cn',
|
||||
},
|
||||
redirectUris => [ "http://url/1", "http://url/2", ],
|
||||
extraClaims => {
|
||||
phone => 'telephoneNumber',
|
||||
email => 'mail'
|
||||
email => 'mail',
|
||||
},
|
||||
options => {
|
||||
oidcRPMetaDataOptionsClientSecret => 'secret',
|
||||
oidcRPMetaDataOptionsIcon => 'web.png'
|
||||
clientSecret => 'secret',
|
||||
icon => 'web.png'
|
||||
}
|
||||
};
|
||||
|
||||
$test = "OidcRp - Add should succeed";
|
||||
checkAdd( $test, 'oidc/rp', $oidcRp );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon',
|
||||
'web.png' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1',
|
||||
'options/oidcRPMetaDataOptionsClientSecret', 'secret' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/icon', 'web.png' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/clientId', 'myOidcClient0' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/redirectUris/0',
|
||||
'http://url/1' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/clientSecret', 'secret' );
|
||||
|
||||
$test = "OidcRp - Check attribute default value was set after add";
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1',
|
||||
'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/IDTokenSignAlg', 'HS512' );
|
||||
|
||||
$test = "OidcRp - Add Should fail on duplicate confKey";
|
||||
$test = "OidcRp - Add should fail on duplicate confKey";
|
||||
checkAddFailsIfExists( $test, 'oidc/rp', $oidcRp );
|
||||
|
||||
$test = "OidcRp - Update should succeed and keep existing values";
|
||||
$oidcRp->{options}->{oidcRPMetaDataOptionsClientSecret} = 'secret2';
|
||||
$oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg} = 'RS512';
|
||||
delete $oidcRp->{options}->{oidcRPMetaDataOptionsIcon};
|
||||
delete $oidcRp->{extraClaim};
|
||||
$oidcRp->{options}->{clientId} = 'myOidcClient1';
|
||||
$oidcRp->{options}->{clientSecret} = 'secret2';
|
||||
$oidcRp->{options}->{IDTokenSignAlg} = 'RS512';
|
||||
delete $oidcRp->{options}->{icon};
|
||||
delete $oidcRp->{extraClaims};
|
||||
delete $oidcRp->{exportedVars};
|
||||
$oidcRp->{exportedVars}->{cn} = 'cn';
|
||||
delete $oidcRp->{clientId};
|
||||
$oidcRp->{macros}->{given_name} = '$givenName';
|
||||
$oidcRp->{exportedVars}->{cn} = 'cn';
|
||||
checkUpdate( $test, 'oidc/rp', 'myOidcRp1', $oidcRp );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1',
|
||||
'options/oidcRPMetaDataOptionsClientSecret', 'secret2' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1',
|
||||
'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'RS512' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon',
|
||||
'web.png' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'exportedVars/cn', 'cn' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/clientSecret', 'secret2' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/clientId', 'myOidcClient1' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/IDTokenSignAlg', 'RS512' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/icon', 'web.png' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'exportedVars/cn', 'cn' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'exportedVars/family_name', 'sn' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'extraClaim/phone',
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'macros/given_name', '$givenName' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'extraClaims/phone',
|
||||
'telephoneNumber' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/redirectUris/1',
|
||||
'http://url/2' );
|
||||
|
||||
$test = "OidcRp - Update should fail on non existing options";
|
||||
$oidcRp->{options}->{playingPossum} = 'elephant';
|
||||
checkUpdateWithUnknownAttributes( $test, 'oidc/rp', 'myOidcRp1', $oidcRp );
|
||||
delete $oidcRp->{options}->{playingPossum};
|
||||
|
||||
$test = "OidcRp - Add Should fail on duplicate clientId";
|
||||
$oidcRp->{confKey} = 'myOidcRp2';
|
||||
$test = "OidcRp - Add should fail on duplicate clientId";
|
||||
$oidcRp->{clientId} = "myOidcClient1";
|
||||
$oidcRp->{confKey} = 'myOidcRp2';
|
||||
checkAddFailsIfExists( $test, 'oidc/rp', $oidcRp );
|
||||
|
||||
$test = "OidcRp - Add Should fail on non existing options";
|
||||
$test = "OidcRp - Add should fail on non existing options";
|
||||
$oidcRp->{confKey} = 'myOidcRp2';
|
||||
$oidcRp->{clientId} = 'myOidcClient2';
|
||||
$oidcRp->{options}->{playingPossum} = 'ElephantInTheRoom';
|
||||
|
@ -359,28 +405,35 @@ $test = "OidcRp - Update should fail if confKey not found";
|
|||
$oidcRp->{confKey} = 'myOidcRp3';
|
||||
checkUpdateNotFound( $test, 'oidc/rp', 'myOidcRp3', $oidcRp );
|
||||
|
||||
$test = "OidcRp - Replace should succeed";
|
||||
$oidcRp->{confKey} = 'myOidcRp2';
|
||||
$oidcRp->{clientId} = 'myOidcClient2';
|
||||
delete $oidcRp->{options}->{oidcRPMetaDataOptionsIcon};
|
||||
delete $oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg};
|
||||
$test = "OidcRp - Replace should succeed";
|
||||
$oidcRp->{confKey} = 'myOidcRp2';
|
||||
$oidcRp->{clientId} = 'myOidcClient2';
|
||||
$oidcRp->{redirectUris} = ["http://url/3"];
|
||||
delete $oidcRp->{options}->{icon};
|
||||
delete $oidcRp->{options}->{IDTokenSignAlg};
|
||||
checkReplace( $test, 'oidc/rp', 'myOidcRp2', $oidcRp );
|
||||
|
||||
$test = "OidcRp - Check attribute default value was set after replace";
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp2',
|
||||
'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp2', 'options/IDTokenSignAlg', 'HS512' );
|
||||
checkGet( $test, 'oidc/rp', 'myOidcRp2', 'options/redirectUris/0',
|
||||
'http://url/3' );
|
||||
|
||||
$test = "OidcRp - Replace should fail on non existing options";
|
||||
$test = "OidcRp - Replace should fail on non existing or invalid options";
|
||||
$oidcRp->{options}->{playingPossum} = 'elephant';
|
||||
checkReplaceWithUnknownAttribute( $test, 'oidc/rp', 'myOidcRp2', $oidcRp );
|
||||
checkReplaceWithInvalidAttribute( $test, 'oidc/rp', 'myOidcRp2', $oidcRp );
|
||||
delete $oidcRp->{options}->{playingPossum};
|
||||
$oidcRp->{options}->{IDTokenExpiration} = "XXX";
|
||||
checkReplaceWithInvalidAttribute( $test, 'oidc/rp', 'myOidcRp2', $oidcRp );
|
||||
|
||||
$test = "OidcRp - Replace should fail if confKey not found";
|
||||
$oidcRp->{confKey} = 'myOidcRp3';
|
||||
checkReplaceNotFound( $test, 'oidc/rp', 'myOidcRp3', $oidcRp );
|
||||
|
||||
$test = "OidcRp - FindByConfKey should find 2 hits";
|
||||
checkFindByConfKey( $test, 'oidc/rp', '^myOidcRp.$', 2 );
|
||||
checkFindByConfKey( $test, 'oidc/rp', '*', 2 );
|
||||
|
||||
$test = "OidcRp - FindByConfKey should find 2 hits";
|
||||
checkFindByConfKey( $test, 'oidc/rp', 'myOidcRp*', 2 );
|
||||
|
||||
$test = "OidcRp - FindByConfKey should find 1 hit";
|
||||
checkFindByConfKey( $test, 'oidc/rp', 'myOidcRp1', 1 );
|
||||
|
@ -388,6 +441,10 @@ checkFindByConfKey( $test, 'oidc/rp', 'myOidcRp1', 1 );
|
|||
$test = "OidcRp - FindByConfKey should find 0 hits";
|
||||
checkFindByConfKey( $test, 'oidc/rp', 'myOidcRp3', 0 );
|
||||
|
||||
$test = "OidcRp - FindByConfKey should err on invalid patterns";
|
||||
checkFindByConfKeyError( $test, 'oidc/rp', '' );
|
||||
checkFindByConfKeyError( $test, 'oidc/rp', '$' );
|
||||
|
||||
$test = "OidcRp - FindByClientId should find one entry";
|
||||
checkFindByProviderId( $test, 'oidc/rp', 'clientId', 'myOidcClient1' );
|
||||
|
||||
|
@ -440,40 +497,43 @@ my $samlSp = {
|
|||
name => "givenName"
|
||||
}
|
||||
},
|
||||
macros => {
|
||||
given_name => '$givenName',
|
||||
},
|
||||
options => {
|
||||
samlSPMetaDataOptionsCheckSLOMessageSignature => 0,
|
||||
samlSPMetaDataOptionsEncryptionMode => "assertion",
|
||||
samlSPMetaDataOptionsSessionNotOnOrAfterTimeout => 36000
|
||||
checkSLOMessageSignature => 0,
|
||||
encryptionMode => "assertion",
|
||||
sessionNotOnOrAfterTimeout => 36000
|
||||
}
|
||||
};
|
||||
|
||||
$test = "SamlSp - Add should succeed";
|
||||
checkAdd( $test, 'saml/sp', $samlSp );
|
||||
checkGet( $test, 'saml/sp', 'mySamlSp1',
|
||||
'options/samlSPMetaDataOptionsEncryptionMode', 'assertion' );
|
||||
'options/encryptionMode', 'assertion' );
|
||||
checkGet( $test, 'saml/sp', 'mySamlSp1',
|
||||
'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000 );
|
||||
'options/sessionNotOnOrAfterTimeout', 36000 );
|
||||
|
||||
$test = "SamlSp - Check attribute default value was set after add";
|
||||
checkGet( $test, 'saml/sp', 'mySamlSp1',
|
||||
'options/samlSPMetaDataOptionsNotOnOrAfterTimeout', 72000 );
|
||||
checkGet( $test, 'saml/sp', 'mySamlSp1', 'options/notOnOrAfterTimeout', 72000 );
|
||||
|
||||
$test = "SamlSp - Add Should fail on duplicate confKey";
|
||||
$test = "SamlSp - Add should fail on duplicate confKey";
|
||||
checkAddFailsIfExists( $test, 'saml/sp', $samlSp );
|
||||
|
||||
$test = "SamlSp - Update should succeed and keep existing values";
|
||||
$samlSp->{options}->{samlSPMetaDataOptionsCheckSLOMessageSignature} = 1;
|
||||
$samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode} = 'nameid';
|
||||
delete $samlSp->{options}->{samlSPMetaDataOptionsSessionNotOnOrAfterTimeout};
|
||||
$samlSp->{options}->{checkSLOMessageSignature} = 1;
|
||||
$samlSp->{options}->{encryptionMode} = 'nameid';
|
||||
delete $samlSp->{options}->{sessionNotOnOrAfterTimeout};
|
||||
delete $samlSp->{exportedAttributes};
|
||||
$samlSp->{exportedAttributes}->{cn}->{name} = "cn",
|
||||
$samlSp->{macros}->{family_name} = '$sn',
|
||||
$samlSp->{exportedAttributes}->{cn}->{name} = "cn",
|
||||
$samlSp->{exportedAttributes}->{cn}->{friendlyName} = "common_name",
|
||||
$samlSp->{exportedAttributes}->{cn}->{mandatory} = "false",
|
||||
checkUpdate( $test, 'saml/sp', 'mySamlSp1', $samlSp );
|
||||
checkGet( $test, 'saml/sp', 'mySamlSp1',
|
||||
'options/samlSPMetaDataOptionsCheckSLOMessageSignature', 1 );
|
||||
'options/checkSLOMessageSignature', 1 );
|
||||
checkGet( $test, 'saml/sp', 'mySamlSp1',
|
||||
'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000 );
|
||||
'options/sessionNotOnOrAfterTimeout', 36000 );
|
||||
checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/friendlyName',
|
||||
'common_name' );
|
||||
checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory',
|
||||
|
@ -483,17 +543,19 @@ checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory',
|
|||
checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/name', 'uid' );
|
||||
checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/given_name/name',
|
||||
'givenName' );
|
||||
checkGet( $test, 'saml/sp', 'mySamlSp1', 'macros/family_name', '$sn' );
|
||||
checkGet( $test, 'saml/sp', 'mySamlSp1', 'macros/given_name', '$givenName' );
|
||||
|
||||
$test = "SamlSp - Update should fail on non existing options";
|
||||
$samlSp->{options}->{playingPossum} = 'elephant';
|
||||
checkUpdateWithUnknownAttributes( $test, 'saml/sp', 'mySamlSp1', $samlSp );
|
||||
delete $samlSp->{options}->{playingPossum};
|
||||
|
||||
$test = "SamlSp - Add Should fail on duplicate entityId";
|
||||
$test = "SamlSp - Add should fail on duplicate entityId";
|
||||
$samlSp->{confKey} = 'mySamlSp2';
|
||||
checkAddFailsIfExists( $test, 'saml/sp', $samlSp );
|
||||
|
||||
$test = "SamlSp - Add Should fail on non existing options";
|
||||
$test = "SamlSp - Add should fail on non existing options";
|
||||
$samlSp->{confKey} = 'mySamlSp2';
|
||||
$samlSp->{metadata} = $metadata2;
|
||||
$samlSp->{options}->{playingPossum} = 'ElephantInTheRoom';
|
||||
|
@ -514,31 +576,42 @@ checkUpdateNotFound( $test, 'saml/sp', 'mySamlSp3', $samlSp );
|
|||
$test = "SamlSp - Replace should succeed";
|
||||
$samlSp->{confKey} = 'mySamlSp2';
|
||||
$samlSp->{metadata} = $metadata2;
|
||||
delete $samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode};
|
||||
delete $samlSp->{options}->{encryptionMode};
|
||||
checkReplace( $test, 'saml/sp', 'mySamlSp2', $samlSp );
|
||||
|
||||
$test = "SamlSp - Check attribute default value was set after replace";
|
||||
checkGet( $test, 'saml/sp', 'mySamlSp2',
|
||||
'options/samlSPMetaDataOptionsEncryptionMode', 'none' );
|
||||
checkGet( $test, 'saml/sp', 'mySamlSp2', 'options/encryptionMode', 'none' );
|
||||
|
||||
$test = "SamlSp - Replace should fail on non existing options";
|
||||
$samlSp->{options}->{playingPossum} = 'elephant';
|
||||
checkReplaceWithUnknownAttribute( $test, 'saml/sp', 'mySamlSp2', $samlSp );
|
||||
checkReplaceWithInvalidAttribute( $test, 'saml/sp', 'mySamlSp2', $samlSp );
|
||||
delete $samlSp->{options}->{playingPossum};
|
||||
$samlSp->{options}->{notOnOrAfterTimeout} = "XXX";
|
||||
checkReplaceWithInvalidAttribute( $test, 'saml/sp', 'mySamlSp2', $samlSp );
|
||||
|
||||
$test = "SamlSp - Replace should fail if confKey not found";
|
||||
$samlSp->{confKey} = 'mySamlSp3';
|
||||
checkReplaceNotFound( $test, 'saml/sp', 'mySamlSp3', $samlSp );
|
||||
|
||||
$test = "SamlSp - FindByConfKey should find 2 hits";
|
||||
checkFindByConfKey( $test, 'saml/sp', '^mySamlSp.$', 2 );
|
||||
checkFindByConfKey( $test, 'saml/sp', '*', 2 );
|
||||
|
||||
$test = "SamlSp - FindByConfKey should find 2 hits";
|
||||
checkFindByConfKey( $test, 'saml/sp', 'mySamlSp*', 2 );
|
||||
|
||||
$test = "SamlSp - FindByConfKey should find 1 hit";
|
||||
checkFindByConfKey( $test, 'saml/sp', 'mySamlSp1', 1 );
|
||||
|
||||
$test = "SamlSp - FindByConfKey should find 1 hit";
|
||||
checkFindByConfKey( $test, 'saml/sp', '*Sp1', 1 );
|
||||
|
||||
$test = "SamlSp - FindByConfKey should find 0 hits";
|
||||
checkFindByConfKey( $test, 'saml/sp', 'mySamlSp3', 0 );
|
||||
|
||||
$test = "SamlSp - FindByConfKey should err on invalid patterns";
|
||||
checkFindByConfKeyError( $test, 'saml/sp', '' );
|
||||
checkFindByConfKeyError( $test, 'saml/sp', '$' );
|
||||
|
||||
$test = "SamlSp - FindByEntityId should find one entry";
|
||||
checkFindByProviderId( $test, 'saml/sp', 'entityId',
|
||||
'https://myapp.domain.com/saml/metadata' );
|
||||
|
|
|
@ -1172,11 +1172,6 @@
|
|||
"id": "singleUserByIP",
|
||||
"title": "singleUserByIP",
|
||||
"type": "bool"
|
||||
}, {
|
||||
"default": 0,
|
||||
"id": "singleSessionUserByIP",
|
||||
"title": "singleSessionUserByIP",
|
||||
"type": "bool"
|
||||
}, {
|
||||
"default": 1,
|
||||
"id": "notifyDeleted",
|
||||
|
|
|
@ -1314,12 +1314,6 @@
|
|||
"title": "singleUserByIP",
|
||||
"type": "bool",
|
||||
"data": 0
|
||||
}, {
|
||||
"default": 0,
|
||||
"id": "singleSessionUserByIP",
|
||||
"title": "singleSessionUserByIP",
|
||||
"type": "bool",
|
||||
"data": 0
|
||||
}, {
|
||||
"default": 1,
|
||||
"id": "notifyDeleted",
|
||||
|
|
|
@ -1289,12 +1289,6 @@
|
|||
"title": "singleUserByIP",
|
||||
"type": "bool",
|
||||
"data": 0
|
||||
}, {
|
||||
"default": 0,
|
||||
"id": "singleSessionUserByIP",
|
||||
"title": "singleSessionUserByIP",
|
||||
"type": "bool",
|
||||
"data": 0
|
||||
}, {
|
||||
"default": 1,
|
||||
"id": "notifyDeleted",
|
||||
|
|
|
@ -1325,12 +1325,6 @@
|
|||
"title": "singleUserByIP",
|
||||
"type": "bool",
|
||||
"data": 0
|
||||
}, {
|
||||
"default": 0,
|
||||
"id": "singleSessionUserByIP",
|
||||
"title": "singleSessionUserByIP",
|
||||
"type": "bool",
|
||||
"data": 0
|
||||
}, {
|
||||
"default": 1,
|
||||
"id": "notifyDeleted",
|
||||
|
|
|
@ -1327,12 +1327,6 @@
|
|||
"title": "singleUserByIP",
|
||||
"type": "bool",
|
||||
"data": 0
|
||||
}, {
|
||||
"default": 0,
|
||||
"id": "singleSessionUserByIP",
|
||||
"title": "singleSessionUserByIP",
|
||||
"type": "bool",
|
||||
"data": 0
|
||||
}, {
|
||||
"default": 1,
|
||||
"id": "notifyDeleted",
|
||||
|
|
|
@ -1309,12 +1309,6 @@
|
|||
"title": "singleUserByIP",
|
||||
"type": "bool",
|
||||
"data": 0
|
||||
}, {
|
||||
"default": 0,
|
||||
"id": "singleSessionUserByIP",
|
||||
"title": "singleSessionUserByIP",
|
||||
"type": "bool",
|
||||
"data": 0
|
||||
}, {
|
||||
"default": 1,
|
||||
"id": "notifyDeleted",
|
||||
|
|
|
@ -1833,12 +1833,6 @@
|
|||
"title": "singleUserByIP",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"default": 0,
|
||||
"id": "singleSessionUserByIP",
|
||||
"title": "singleSessionUserByIP",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"default": 1,
|
||||
"id": "notifyDeleted",
|
||||
|
|
|
@ -2221,12 +2221,6 @@
|
|||
"title" : "singleUserByIP",
|
||||
"type" : "bool"
|
||||
},
|
||||
{
|
||||
"default" : 0,
|
||||
"id" : "singleSessionUserByIP",
|
||||
"title" : "singleSessionUserByIP",
|
||||
"type" : "bool"
|
||||
},
|
||||
{
|
||||
"default" : 1,
|
||||
"id" : "notifyDeleted",
|
||||
|
|
|
@ -1174,11 +1174,6 @@
|
|||
"id": "singleUserByIP",
|
||||
"title": "singleUserByIP",
|
||||
"type": "bool"
|
||||
}, {
|
||||
"default": 0,
|
||||
"id": "singleSessionUserByIP",
|
||||
"title": "singleSessionUserByIP",
|
||||
"type": "bool"
|
||||
}, {
|
||||
"default": 1,
|
||||
"id": "notifyDeleted",
|
||||
|
|
|
@ -23,7 +23,7 @@ useRedirectOnError = 0
|
|||
[manager]
|
||||
|
||||
skippedUnitTests = cookieExpiration
|
||||
skippedGlobalTests = cookieTTL
|
||||
skippedGlobalTests = cookieTTL oidcRPNeedRSAKey
|
||||
|
||||
protection = manager
|
||||
staticPrefix = app/
|
||||
|
|
|
@ -29,6 +29,7 @@ lib/Lemonldap/NG/Portal/Auth/Custom.pm
|
|||
lib/Lemonldap/NG/Portal/Auth/DBI.pm
|
||||
lib/Lemonldap/NG/Portal/Auth/Demo.pm
|
||||
lib/Lemonldap/NG/Portal/Auth/Facebook.pm
|
||||
lib/Lemonldap/NG/Portal/Auth/GitHub.pm
|
||||
lib/Lemonldap/NG/Portal/Auth/GPG.pm
|
||||
lib/Lemonldap/NG/Portal/Auth/Kerberos.pm
|
||||
lib/Lemonldap/NG/Portal/Auth/LDAP.pm
|
||||
|
@ -355,6 +356,7 @@ site/htdocs/static/common/modules/Apache.png
|
|||
site/htdocs/static/common/modules/CAS.png
|
||||
site/htdocs/static/common/modules/CustomAuth.png
|
||||
site/htdocs/static/common/modules/Facebook.png
|
||||
site/htdocs/static/common/modules/GitHub.png
|
||||
site/htdocs/static/common/modules/Google.png
|
||||
site/htdocs/static/common/modules/Kerberos.png
|
||||
site/htdocs/static/common/modules/LinkedIn.png
|
||||
|
@ -365,6 +367,7 @@ site/htdocs/static/common/modules/SSL.png
|
|||
site/htdocs/static/common/modules/Twitter.png
|
||||
site/htdocs/static/common/modules/WebID.png
|
||||
site/htdocs/static/common/nl.png
|
||||
site/htdocs/static/common/pl.png
|
||||
site/htdocs/static/common/pt.png
|
||||
site/htdocs/static/common/ro.png
|
||||
site/htdocs/static/common/tr.png
|
||||
|
@ -378,6 +381,7 @@ site/htdocs/static/languages/fi.json
|
|||
site/htdocs/static/languages/fr.json
|
||||
site/htdocs/static/languages/it.json
|
||||
site/htdocs/static/languages/nl.json
|
||||
site/htdocs/static/languages/pl.json
|
||||
site/htdocs/static/languages/pt.json
|
||||
site/htdocs/static/languages/ro.json
|
||||
site/htdocs/static/languages/tr.json
|
||||
|
@ -400,6 +404,7 @@ site/templates/bootstrap/customLoginFooter.tpl
|
|||
site/templates/bootstrap/customLoginHeader.tpl
|
||||
site/templates/bootstrap/decryptvalue.tpl
|
||||
site/templates/bootstrap/error.tpl
|
||||
site/templates/bootstrap/errormsg.tpl
|
||||
site/templates/bootstrap/ext2fcheck.tpl
|
||||
site/templates/bootstrap/footer.tpl
|
||||
site/templates/bootstrap/globallogout.tpl
|
||||
|
@ -612,6 +617,7 @@ t/44-CertificateResetByMail-LDAP.t
|
|||
t/50-IssuerGet.t
|
||||
t/57-GlobalLogout-without-Timer.t
|
||||
t/57-GlobalLogout.t
|
||||
t/57-LogoutForward.t
|
||||
t/58-DecryptValue-with-custom-function.t
|
||||
t/58-DecryptValue-with-internal-function.t
|
||||
t/59-Double-cookies-for-a-Single-session.t
|
||||
|
@ -677,8 +683,10 @@ t/76-2F-Ext-with-History.t
|
|||
t/77-2F-Extra.t
|
||||
t/77-2F-Mail-with-global-storage.t
|
||||
t/77-2F-Mail.t
|
||||
t/78-2F-Upgrade-Many.t
|
||||
t/78-2F-Upgrade.t
|
||||
t/90-Translations.t
|
||||
t/91-Memory-Leak.t
|
||||
t/99-Dont-load-Dumper.t
|
||||
t/99-pod.t
|
||||
t/gpghome/key.asc
|
||||
|
|
|
@ -113,11 +113,13 @@ sub init {
|
|||
my $rule = $self->conf->{sfExtra}->{$extraKey}->{rule} || 1;
|
||||
my $prefix = $m->prefix;
|
||||
|
||||
# Overwrite logo and label from user configuration
|
||||
# Overwrite logo, label, level from user configuration
|
||||
$m->logo( $self->conf->{sfExtra}->{$extraKey}->{logo} )
|
||||
if $self->conf->{sfExtra}->{$extraKey}->{logo};
|
||||
$m->label( $self->conf->{sfExtra}->{$extraKey}->{label} )
|
||||
if $self->conf->{sfExtra}->{$extraKey}->{label};
|
||||
$m->authnLevel( $self->conf->{sfExtra}->{$extraKey}->{level} )
|
||||
if $self->conf->{sfExtra}->{$extraKey}->{level};
|
||||
|
||||
# Compile rule
|
||||
$rule = $self->p->HANDLER->substitute($rule);
|
||||
|
@ -161,6 +163,8 @@ sub init {
|
|||
|
||||
# Enable REST request only if more than 1 2F module is enabled
|
||||
if ( @{ $self->{sfModules} } > 1 ) {
|
||||
$self->addAuthRoute( '2fchoice' => '_choice', ['POST'] );
|
||||
$self->addAuthRoute( '2fchoice' => '_redirect', ['GET'] );
|
||||
$self->addUnauthRoute( '2fchoice' => '_choice', ['POST'] );
|
||||
$self->addUnauthRoute( '2fchoice' => '_redirect', ['GET'] );
|
||||
}
|
||||
|
|
|
@ -13,6 +13,10 @@ extends 'Lemonldap::NG::Portal::Auth::_WebForm',
|
|||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
foreach (qw(dbiAuthTable dbiAuthLoginCol dbiAuthPasswordCol)) {
|
||||
$self->logger->warn( ref($self) . " seems not configured: missing $_" )
|
||||
unless $self->conf->{$_};
|
||||
}
|
||||
return ( $self->Lemonldap::NG::Portal::Auth::_WebForm::init
|
||||
and $self->Lemonldap::NG::Portal::Lib::DBI::init );
|
||||
}
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
package Lemonldap::NG::Portal::Auth::GitHub;
|
||||
|
||||
use strict;
|
||||
use JSON;
|
||||
use Mouse;
|
||||
use MIME::Base64 qw/encode_base64 decode_base64/;
|
||||
use Lemonldap::NG::Common::FormEncode;
|
||||
use Lemonldap::NG::Common::UserAgent;
|
||||
use Lemonldap::NG::Portal::Main::Constants qw(PE_OK PE_ERROR PE_REDIRECT);
|
||||
|
||||
our $VERSION = '2.0.8';
|
||||
|
||||
extends 'Lemonldap::NG::Portal::Main::Auth';
|
||||
|
||||
# INITIALIZATION
|
||||
|
||||
# return LWP::UserAgent object
|
||||
has ua => (
|
||||
is => 'rw',
|
||||
lazy => 1,
|
||||
builder => sub {
|
||||
|
||||
# TODO : LWP options to use a proxy for example
|
||||
my $ua = Lemonldap::NG::Common::UserAgent->new( $_[0]->{conf} );
|
||||
$ua->env_proxy();
|
||||
return $ua;
|
||||
}
|
||||
);
|
||||
|
||||
has githubAuthorizationEndpoint => (
|
||||
is => 'ro',
|
||||
lazy => 1,
|
||||
default => sub {
|
||||
$_[0]->conf->{githubAuthorizationEndpoint}
|
||||
|| 'https://github.com/login/oauth/authorize';
|
||||
}
|
||||
);
|
||||
|
||||
has githubTokenEndpoint => (
|
||||
is => 'ro',
|
||||
lazy => 1,
|
||||
default => sub {
|
||||
$_[0]->conf->{githubTokenEndpoint}
|
||||
|| 'https://github.com/login/oauth/access_token';
|
||||
}
|
||||
);
|
||||
|
||||
has githubUserEndpoint => (
|
||||
is => 'ro',
|
||||
lazy => 1,
|
||||
default => sub {
|
||||
$_[0]->conf->{githubUserEndpoint}
|
||||
|| 'https://api.github.com/user';
|
||||
}
|
||||
);
|
||||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
|
||||
my $ret = 1;
|
||||
foreach my $arg (qw(githubClientID githubClientSecret)) {
|
||||
unless ( $self->conf->{$arg} ) {
|
||||
$ret = 0;
|
||||
$self->error("Parameter $arg is required");
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
# RUNNING METHODS
|
||||
|
||||
sub extractFormInfo {
|
||||
my ( $self, $req ) = @_;
|
||||
my $nonce = time;
|
||||
|
||||
# Build redirect_uri
|
||||
my $callback_url = $self->conf->{portal};
|
||||
|
||||
# Check return values
|
||||
my $code = $req->param("code");
|
||||
my $state = $req->param("state");
|
||||
|
||||
# Code
|
||||
if ($code) {
|
||||
my %form;
|
||||
$form{"code"} = $code;
|
||||
$form{"state"} = $state if $state;
|
||||
$form{"client_id"} = $self->conf->{githubClientID};
|
||||
$form{"client_secret"} = $self->conf->{githubClientSecret};
|
||||
$form{"redirect_uri"} = $callback_url;
|
||||
|
||||
my $response = $self->ua->post(
|
||||
$self->githubTokenEndpoint,
|
||||
\%form,
|
||||
"Content-Type" => 'application/x-www-form-urlencoded',
|
||||
'Accept' => 'application/json'
|
||||
);
|
||||
|
||||
if ( $response->is_error ) {
|
||||
$self->logger->error(
|
||||
"Bad authorization response: " . $response->message );
|
||||
$self->logger->debug( $response->content );
|
||||
return PE_ERROR;
|
||||
}
|
||||
|
||||
my $content = $response->decoded_content;
|
||||
|
||||
my $json_hash;
|
||||
|
||||
eval { $json_hash = from_json( $content, { allow_nonref => 1 } ); };
|
||||
|
||||
if ($@) {
|
||||
$self->logger->error("Unable to decode JSON $content");
|
||||
return PE_ERROR;
|
||||
}
|
||||
|
||||
my $access_token = $json_hash->{access_token};
|
||||
|
||||
$self->logger->debug("Get access token $access_token from GitHub");
|
||||
|
||||
# Call People EndPoint URI
|
||||
$self->logger->debug(
|
||||
"Call GitHub People Endpoint " . $self->githubUserEndpoint );
|
||||
|
||||
my $people_response = $self->ua->get( $self->githubUserEndpoint,
|
||||
"Authorization" => "token $access_token" );
|
||||
|
||||
if ( $people_response->is_error ) {
|
||||
$self->logger->error(
|
||||
"Bad authorization response: " . $people_response->message );
|
||||
$self->logger->debug( $people_response->content );
|
||||
return PE_ERROR;
|
||||
}
|
||||
|
||||
my $user_content = $people_response->decoded_content;
|
||||
|
||||
$self->logger->debug("Response from GitHub People API: $user_content");
|
||||
|
||||
eval {
|
||||
$json_hash = from_json( $user_content, { allow_nonref => 1 } ); };
|
||||
if ($@) {
|
||||
$self->logger->error("Unable to decode JSON $user_content");
|
||||
return PE_ERROR;
|
||||
}
|
||||
|
||||
foreach ( keys %$json_hash ) {
|
||||
$req->data->{githubData}->{$_} = $json_hash->{$_};
|
||||
}
|
||||
|
||||
$req->user(
|
||||
$req->data->{githubData}->{ $self->conf->{githubUserField} } );
|
||||
|
||||
$self->logger->debug( "Good GitHub authentication for " . $req->user );
|
||||
|
||||
# Extract state
|
||||
if ($state) {
|
||||
my $stateSession = $self->p->getApacheSession( $state, 1 );
|
||||
|
||||
$req->urldc( $stateSession->data->{urldc} );
|
||||
$req->{checkLogins} = $stateSession->data->{checkLogins};
|
||||
|
||||
$stateSession->remove;
|
||||
}
|
||||
|
||||
return PE_OK;
|
||||
}
|
||||
|
||||
# No code, redirect to GitHub
|
||||
else {
|
||||
$self->logger->debug('Redirection to GitHub');
|
||||
|
||||
# Store state
|
||||
my $stateSession =
|
||||
$self->p->getApacheSession( undef, 1, 0, 'GitHubState' );
|
||||
|
||||
my $stateInfos = {};
|
||||
$stateInfos->{_utime} = time() + $self->conf->{timeout};
|
||||
$stateInfos->{urldc} = $req->urldc;
|
||||
$stateInfos->{checkLogins} = $req->{checkLogins};
|
||||
|
||||
$stateSession->update($stateInfos);
|
||||
|
||||
my $authn_uri = $self->githubAuthorizationEndpoint;
|
||||
my $client_id = $self->conf->{githubClientID};
|
||||
my $scope = $self->conf->{githubScope};
|
||||
$authn_uri .= '?'
|
||||
. build_urlencoded(
|
||||
response_type => 'code',
|
||||
client_id => $client_id,
|
||||
redirect_uri => $callback_url,
|
||||
scope => $scope,
|
||||
state => $stateSession->id,
|
||||
);
|
||||
|
||||
$req->urldc($authn_uri);
|
||||
|
||||
$self->logger->debug( "Redirect user to " . $req->urldc );
|
||||
|
||||
return PE_REDIRECT;
|
||||
}
|
||||
}
|
||||
|
||||
sub setAuthSessionInfo {
|
||||
my ( $self, $req ) = @_;
|
||||
|
||||
$req->{sessionInfo}->{authenticationLevel} =
|
||||
$self->conf->{githubAuthnLevel};
|
||||
|
||||
foreach ( keys %{ $req->data->{githubData} } ) {
|
||||
$req->{sessionInfo}->{ 'github_' . $_ } =
|
||||
$req->data->{githubData}->{$_};
|
||||
}
|
||||
|
||||
PE_OK;
|
||||
}
|
||||
|
||||
sub authenticate {
|
||||
PE_OK;
|
||||
}
|
||||
|
||||
sub authFinish {
|
||||
PE_OK;
|
||||
}
|
||||
|
||||
sub authLogout {
|
||||
PE_OK;
|
||||
}
|
||||
|
||||
sub authForce {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub getDisplayType {
|
||||
return "logo";
|
||||
}
|
||||
|
||||
1;
|
|
@ -26,6 +26,13 @@ sub init {
|
|||
# @return Lemonldap::NG::Portal constant
|
||||
sub extractFormInfo {
|
||||
my ( $self, $req ) = @_;
|
||||
|
||||
# If this is the ajax query, allow response to contain HTML code
|
||||
# to update the portal error message
|
||||
if ( $req->wantJSON ) {
|
||||
$req->wantErrorRender(1);
|
||||
}
|
||||
|
||||
my $field = $self->conf->{SSLVar};
|
||||
if ( $req->env->{SSL_CLIENT_I_DN} ) {
|
||||
$self->logger->debug(
|
||||
|
@ -50,6 +57,16 @@ sub extractFormInfo {
|
|||
return PE_BADCERTIFICATE;
|
||||
}
|
||||
elsif ( $self->conf->{sslByAjax} and not $req->param('nossl') ) {
|
||||
|
||||
# If this is the AJAX query
|
||||
if ( $req->wantJSON ) {
|
||||
return PE_CERTIFICATEREQUIRED;
|
||||
}
|
||||
|
||||
$self->logger->debug( 'Append ' . $self->{Name} . ' init/script' );
|
||||
$req->data->{customScript} .= $self->{AjaxInitScript};
|
||||
$self->logger->debug(
|
||||
"Send init/script -> " . $req->data->{customScript} );
|
||||
$req->data->{waitingMessage} = 1;
|
||||
return PE_FIRSTACCESS;
|
||||
}
|
||||
|
@ -58,6 +75,7 @@ sub extractFormInfo {
|
|||
$self->logger->debug( 'Append ' . $self->{Name} . ' init/script' );
|
||||
$self->logger->debug(
|
||||
"Send init/script -> " . $req->data->{customScript} );
|
||||
return PE_BADCERTIFICATE;
|
||||
}
|
||||
$self->userLogger->warn('No certificate found');
|
||||
return PE_CERTIFICATEREQUIRED;
|
||||
|
|
|
@ -99,7 +99,9 @@ sub extractFormInfo {
|
|||
}
|
||||
|
||||
# Security: check for captcha or token
|
||||
if ( $self->captcha or $self->ottRule->( $req, {} ) ) {
|
||||
if ( not $req->data->{'skipToken'}
|
||||
and ( $self->captcha or $self->ottRule->( $req, {} ) ) )
|
||||
{
|
||||
my $token;
|
||||
unless ( $token = $req->param('token') or $self->captcha ) {
|
||||
$self->userLogger->error('Authentication tried without token');
|
||||
|
|
|
@ -12,6 +12,7 @@ use Lemonldap::NG::Portal::Main::Constants qw(
|
|||
PE_LOGOUT_OK
|
||||
PE_REDIRECT
|
||||
PE_OK
|
||||
PE_PASSWORD_OK
|
||||
PE_UNAUTHORIZEDPARTNER
|
||||
PE_OIDC_SERVICE_NOT_ALLOWED
|
||||
);
|
||||
|
@ -77,7 +78,7 @@ sub init {
|
|||
$hd->substitute( $self->conf->{issuerDBOpenIDConnectRule} ) );
|
||||
unless ($rule) {
|
||||
my $error = $hd->tsv->{jail}->error || '???';
|
||||
$self->error( "Bad OIDC activation rule -> $error" );
|
||||
$self->error("Bad OIDC activation rule -> $error");
|
||||
return 0;
|
||||
}
|
||||
$self->{rule} = $rule;
|
||||
|
@ -346,14 +347,20 @@ sub run {
|
|||
}
|
||||
|
||||
# Check scope validity
|
||||
unless ( $oidc_request->{'scope'} =~ /^[a-zA-Z_\-\s]+$/ ) {
|
||||
$self->logger->error( "Submitted scope is not valid: "
|
||||
. $oidc_request->{'scope'} );
|
||||
# We use a slightly more relaxed version of
|
||||
# https://tools.ietf.org/html/rfc6749#appendix-A.4
|
||||
# To be tolerant of user error (trailing spaces, etc.)
|
||||
# Scope names are restricted to printable ASCII characters,
|
||||
# excluding double quote and backslash
|
||||
unless (
|
||||
$oidc_request->{'scope'} =~ /^[\x20\x21\x23-\x5B\x5D-\x7E]*$/ )
|
||||
{
|
||||
$self->logger->error("Submitted scope is not valid");
|
||||
return PE_ERROR;
|
||||
}
|
||||
|
||||
# Check openid scope
|
||||
unless ( $oidc_request->{'scope'} =~ /\bopenid\b/ ) {
|
||||
unless ( $self->_hasScope( 'openid', $oidc_request->{'scope'} ) ) {
|
||||
$self->logger->debug("No openid scope found");
|
||||
|
||||
#TODO manage standard OAuth request
|
||||
|
@ -467,7 +474,12 @@ sub run {
|
|||
foreach my $requested_scope (
|
||||
split( /\s+/, $oidc_request->{'scope'} ) )
|
||||
{
|
||||
if ( $consent_scope =~ /\b$requested_scope\b/ ) {
|
||||
if (
|
||||
$self->_hasScope(
|
||||
$requested_scope, $consent_scope
|
||||
)
|
||||
)
|
||||
{
|
||||
$self->logger->debug(
|
||||
"Scope $requested_scope already accepted");
|
||||
}
|
||||
|
@ -490,11 +502,17 @@ sub run {
|
|||
{
|
||||
$RPoidcConsent[0]{epoch} = time;
|
||||
$RPoidcConsent[0]{scope} = $oidc_request->{'scope'};
|
||||
push @{$_oidcConsents}, @RPoidcConsent;
|
||||
|
||||
# Build new consent list by removing all references
|
||||
# to the current RP from the old list and appending the
|
||||
# new consent
|
||||
my @newoidcConsents =
|
||||
grep { $_->{rp} ne $rp } @$_oidcConsents;
|
||||
push @newoidcConsents, $RPoidcConsent[0];
|
||||
$self->logger->debug(
|
||||
"Append Relying Party $rp Consent");
|
||||
$self->p->updatePersistentSession( $req,
|
||||
{ _oidcConsents => to_json($_oidcConsents) } );
|
||||
{ _oidcConsents => to_json( \@newoidcConsents ) } );
|
||||
|
||||
$self->logger->debug(
|
||||
"Consent given for Relying Party $rp");
|
||||
|
@ -537,7 +555,8 @@ sub run {
|
|||
my $display_name =
|
||||
$self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
->{oidcRPMetaDataOptionsDisplayName};
|
||||
my $icon = $self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
my $icon =
|
||||
$self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
->{oidcRPMetaDataOptionsIcon};
|
||||
my $imgSrc;
|
||||
|
||||
|
@ -558,7 +577,7 @@ sub run {
|
|||
};
|
||||
my @list;
|
||||
foreach my $requested_scope (
|
||||
split( /\s/, $oidc_request->{'scope'} ) )
|
||||
split( /\s+/, $oidc_request->{'scope'} ) )
|
||||
{
|
||||
if ( my $message =
|
||||
$scope_messages->{$requested_scope} )
|
||||
|
@ -614,7 +633,9 @@ sub run {
|
|||
|
||||
# WIP: Offline access
|
||||
my $offline = 0;
|
||||
if ( $oidc_request->{'scope'} =~ /\boffline_access\b/ ) {
|
||||
if (
|
||||
$self->_hasScope( 'offline_access', $oidc_request->{'scope'} ) )
|
||||
{
|
||||
$offline = 1;
|
||||
|
||||
# MUST ensure that the prompt parameter contains consent unless
|
||||
|
@ -649,8 +670,10 @@ sub run {
|
|||
}
|
||||
|
||||
# Strip offline_access from scopes from now on
|
||||
$oidc_request->{'scope'} = join " ", grep !/^offline_access$/,
|
||||
split /\s+/, $oidc_request->{'scope'};
|
||||
$oidc_request->{'scope'} = join " ",
|
||||
grep !/^offline_access$/,
|
||||
split /\s+/,
|
||||
$oidc_request->{'scope'};
|
||||
}
|
||||
|
||||
# Authorization Code Flow
|
||||
|
@ -724,7 +747,8 @@ sub run {
|
|||
"Generated access token: $access_token");
|
||||
|
||||
# Compute hash to store in at_hash
|
||||
my $alg = $self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
my $alg =
|
||||
$self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
->{oidcRPMetaDataOptionsIDTokenSignAlg};
|
||||
my ($hash_level) = ( $alg =~ /(?:\w{2})(\d{3})/ );
|
||||
$at_hash = $self->createHash( $access_token, $hash_level )
|
||||
|
@ -986,6 +1010,19 @@ sub token {
|
|||
return $self->_handleRefreshTokenGrant( $req, $rp );
|
||||
}
|
||||
|
||||
# Resource Owner Password Credenials
|
||||
elsif ( $grant_type eq 'password' ) {
|
||||
unless (
|
||||
$self->oidcRPList->{$rp}->{oidcRPMetaDataOptionsAllowPasswordGrant}
|
||||
)
|
||||
{
|
||||
$self->logger->warn(
|
||||
"Access to grant_type=password, is not allowed for RP $rp");
|
||||
return $self->p->sendError( $req, 'unauthorized_client', 400 );
|
||||
}
|
||||
return $self->_handlePasswordGrant( $req, $rp );
|
||||
}
|
||||
|
||||
# Unknown or unspecified grant type
|
||||
else {
|
||||
$self->userLogger->error(
|
||||
|
@ -998,6 +1035,137 @@ sub token {
|
|||
|
||||
}
|
||||
|
||||
sub _handlePasswordGrant {
|
||||
my ( $self, $req, $rp ) = @_;
|
||||
|
||||
my $client_id = $self->oidcRPList->{$rp}->{oidcRPMetaDataOptionsClientID};
|
||||
my $scope = $req->param('scope') || 'openid';
|
||||
|
||||
my $username = $req->param('username');
|
||||
my $password = $req->param('password');
|
||||
|
||||
unless ( $username and $password ) {
|
||||
$self->logger->error("Missing username or password");
|
||||
|
||||
# FIXME
|
||||
return $self->p->sendError( $req, 'invalid_request', 400 );
|
||||
}
|
||||
|
||||
####
|
||||
# Authenticate user by running through the regular login process
|
||||
# minus the buildCookie step
|
||||
$req->parameters->{user} = ($username);
|
||||
$req->parameters->{password} = $password;
|
||||
$req->data->{skipToken} = 1;
|
||||
|
||||
$req->steps( [
|
||||
@{ $self->p->beforeAuth },
|
||||
$self->p->authProcess,
|
||||
@{ $self->p->betweenAuthAndData },
|
||||
$self->p->sessionData,
|
||||
@{ $self->p->afterData },
|
||||
'storeHistory',
|
||||
@{ $self->p->endAuth },
|
||||
]
|
||||
);
|
||||
my $result = $self->p->process($req);
|
||||
|
||||
$self->logger->debug( "Credentials check returned "
|
||||
. $self->p->_formatProcessResult($result) )
|
||||
if $result;
|
||||
|
||||
## Make sure we returned successfuly from the process AND we were able to create a session
|
||||
unless ( $result == PE_OK and $req->id and $req->user ) {
|
||||
return $self->p->sendError( $req, 'invalid_grant', 400 );
|
||||
}
|
||||
|
||||
## Make sure the current user is allowed to use this RP
|
||||
if ( my $rule = $self->spRules->{$rp} ) {
|
||||
unless ( $rule->( $req, $req->sessionInfo ) ) {
|
||||
$self->userLogger->warn( 'User '
|
||||
. $req->sessionInfo->{ $self->conf->{whatToTrace} }
|
||||
. " is not authorized to access to $rp" );
|
||||
$self->p->deleteSession($req);
|
||||
return $self->p->sendError( $req, 'invalid_grant', 400 );
|
||||
}
|
||||
}
|
||||
|
||||
my $user_id_attribute =
|
||||
$self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
->{oidcRPMetaDataOptionsUserIDAttr}
|
||||
|| $self->conf->{whatToTrace};
|
||||
my $user_id = $req->sessionInfo->{$user_id_attribute};
|
||||
|
||||
$self->logger->debug("Found corresponding user: $user_id");
|
||||
|
||||
# Generate access_token
|
||||
my $accessTokenSession = $self->newAccessToken(
|
||||
$rp,
|
||||
{
|
||||
scope => $scope,
|
||||
rp => $rp,
|
||||
user_session_id => $req->id,
|
||||
}
|
||||
);
|
||||
|
||||
unless ($accessTokenSession) {
|
||||
$self->userLogger->error(
|
||||
"Unable to create OIDC session for access_token");
|
||||
|
||||
#FIXME: should be an error 500
|
||||
return $self->p->sendError( $req, 'invalid_request', 400 );
|
||||
}
|
||||
|
||||
my $access_token = $accessTokenSession->id;
|
||||
|
||||
$self->logger->debug("Generated access token: $access_token");
|
||||
|
||||
# Generate refresh_token
|
||||
my $refresh_token = undef;
|
||||
|
||||
if ( $self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
->{oidcRPMetaDataOptionsRefreshToken} )
|
||||
{
|
||||
my $refreshTokenSession = $self->newRefreshToken(
|
||||
$rp,
|
||||
{
|
||||
scope => $req->param('scope'),
|
||||
client_id => $client_id,
|
||||
user_session_id => $req->id,
|
||||
},
|
||||
0,
|
||||
);
|
||||
|
||||
unless ($refreshTokenSession) {
|
||||
$self->userLogger->error(
|
||||
"Unable to create OIDC session for refresh_token");
|
||||
return $self->p->sendError( $req,
|
||||
'Could not create refresh token session', 500 );
|
||||
}
|
||||
|
||||
$refresh_token = $refreshTokenSession->id;
|
||||
|
||||
$self->logger->debug("Generated refresh token: $refresh_token");
|
||||
}
|
||||
|
||||
# Send token response
|
||||
my $expires_in =
|
||||
$self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
->{oidcRPMetaDataOptionsAccessTokenExpiration}
|
||||
|| $self->conf->{oidcServiceAccessTokenExpiration};
|
||||
|
||||
my $token_response = {
|
||||
access_token => $access_token,
|
||||
token_type => 'Bearer',
|
||||
expires_in => $expires_in,
|
||||
( $refresh_token ? ( refresh_token => $refresh_token ) : () ),
|
||||
};
|
||||
|
||||
$self->logger->debug("Send token response");
|
||||
|
||||
return $self->p->sendJSONresponse( $req, $token_response );
|
||||
}
|
||||
|
||||
sub _handleAuthorizationCodeGrant {
|
||||
my ( $self, $req, $rp ) = @_;
|
||||
|
||||
|
@ -1174,7 +1342,7 @@ sub _handleAuthorizationCodeGrant {
|
|||
my $id_token_payload_hash = {
|
||||
iss => $self->iss, # Issuer Identifier
|
||||
sub => $user_id, # Subject Identifier
|
||||
aud => [$client_id], # Audience
|
||||
aud => $self->getAudiences($rp), # Audience
|
||||
exp => $id_token_exp, # expiration
|
||||
iat => time, # Issued time
|
||||
auth_time => $apacheSession->data->{_lastAuthnUTime}
|
||||
|
@ -1400,11 +1568,11 @@ sub _handleRefreshTokenGrant {
|
|||
my $id_token_acr = "loa-0";
|
||||
|
||||
my $id_token_payload_hash = {
|
||||
iss => $self->iss, # Issuer Identifier
|
||||
sub => $user_id, # Subject Identifier
|
||||
aud => [$client_id], # Audience
|
||||
exp => $id_token_exp, # expiration
|
||||
iat => time, # Issued time
|
||||
iss => $self->iss, # Issuer Identifier
|
||||
sub => $user_id, # Subject Identifier
|
||||
aud => $self->getAudiences($rp), # Audience
|
||||
exp => $id_token_exp, # expiration
|
||||
iat => time, # Issued time
|
||||
# TODO: is this the right value when using refresh tokens??
|
||||
auth_time => $auth_time, # Authentication time
|
||||
acr => $id_token_acr, # Authentication Context Class Reference
|
||||
|
@ -1494,42 +1662,18 @@ sub userInfo {
|
|||
my $rp = $accessTokenSession->data->{rp};
|
||||
my $user_session_id = $accessTokenSession->data->{user_session_id};
|
||||
|
||||
my $session;
|
||||
|
||||
# If using a refreshed access token
|
||||
if ($user_session_id) {
|
||||
|
||||
# Get user identifier
|
||||
$session = $self->p->getApacheSession($user_session_id);
|
||||
|
||||
unless ($session) {
|
||||
$self->logger->error("Unable to find user session");
|
||||
return $self->returnBearerError( 'invalid_request',
|
||||
'Invalid request', 401 );
|
||||
}
|
||||
}
|
||||
else {
|
||||
my $offline_session_id =
|
||||
$accessTokenSession->data->{offline_session_id};
|
||||
unless ($offline_session_id) {
|
||||
return $self->returnBearerError( 'invalid_request',
|
||||
'Invalid request', 401 );
|
||||
}
|
||||
|
||||
$session = $self->getRefreshToken($offline_session_id);
|
||||
|
||||
unless ($session) {
|
||||
$self->logger->error("Unable to find refresh session");
|
||||
return $self->returnBearerError( 'invalid_request',
|
||||
'Invalid request', 401 );
|
||||
}
|
||||
my $session =
|
||||
$self->_getSessionFromAccessTokenData( $accessTokenSession->data );
|
||||
unless ($session) {
|
||||
return $self->returnBearerError( 'invalid_request',
|
||||
'Invalid request', 401 );
|
||||
}
|
||||
|
||||
my $userinfo_response =
|
||||
$self->buildUserInfoResponse( $req, $scope, $rp, $session );
|
||||
unless ($userinfo_response) {
|
||||
return $self->returnBearerError( 'invalid_request', 'Invalid request',
|
||||
401 );
|
||||
return $self->returnBearerError( 'invalid_request',
|
||||
'Invalid request', 401 );
|
||||
}
|
||||
|
||||
my $userinfo_sign_alg = $self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
|
@ -1553,6 +1697,32 @@ sub userInfo {
|
|||
}
|
||||
}
|
||||
|
||||
sub _getSessionFromAccessTokenData {
|
||||
my ( $self, $tokenData ) = @_;
|
||||
my $session;
|
||||
|
||||
# If using a refreshed access token
|
||||
if ( $tokenData->{user_session_id} ) {
|
||||
|
||||
# Get user identifier
|
||||
$session = $self->p->getApacheSession( $tokenData->{user_session_id} );
|
||||
|
||||
unless ($session) {
|
||||
$self->logger->error("Unable to find user session");
|
||||
}
|
||||
}
|
||||
else {
|
||||
my $offline_session_id = $tokenData->{offline_session_id};
|
||||
if ($offline_session_id) {
|
||||
$session = $self->getRefreshToken($offline_session_id);
|
||||
unless ($session) {
|
||||
$self->logger->error("Unable to find refresh session");
|
||||
}
|
||||
}
|
||||
}
|
||||
return $session;
|
||||
}
|
||||
|
||||
sub introspection {
|
||||
my ( $self, $req ) = @_;
|
||||
$self->logger->debug("URL detected as an OpenID Connect INTROSPECTION URL");
|
||||
|
@ -1580,35 +1750,31 @@ sub introspection {
|
|||
my $response = { active => JSON::false };
|
||||
my $oidcSession = $self->getOpenIDConnectSession($token);
|
||||
if ($oidcSession) {
|
||||
if ( my $user_session_id = $oidcSession->{data}->{user_session_id} ) {
|
||||
my $apacheSession =
|
||||
$self->_getSessionFromAccessTokenData( $oidcSession->{data} );
|
||||
if ($apacheSession) {
|
||||
|
||||
# Get user identifier
|
||||
my $apacheSession = $self->p->getApacheSession($user_session_id);
|
||||
if ($apacheSession) {
|
||||
|
||||
$response->{active} = JSON::true;
|
||||
$response->{active} = JSON::true;
|
||||
|
||||
# The ID attribute we choose is the one of the calling webservice,
|
||||
# which might be different from the OIDC client the token was issued to.
|
||||
my $user_id_attribute =
|
||||
$self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
->{oidcRPMetaDataOptionsUserIDAttr}
|
||||
|| $self->conf->{whatToTrace};
|
||||
$response->{sub} = $apacheSession->data->{$user_id_attribute};
|
||||
$response->{scope} = $oidcSession->{data}->{scope}
|
||||
if $oidcSession->{data}->{scope};
|
||||
$response->{client_id} =
|
||||
$self->oidcRPList->{ $oidcSession->{data}->{rp} }
|
||||
->{oidcRPMetaDataOptionsClientID}
|
||||
if $oidcSession->{data}->{rp};
|
||||
$response->{iss} = $self->iss;
|
||||
$response->{exp} =
|
||||
$oidcSession->{data}->{_utime} + $self->conf->{timeout};
|
||||
}
|
||||
my $user_id_attribute =
|
||||
$self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
->{oidcRPMetaDataOptionsUserIDAttr}
|
||||
|| $self->conf->{whatToTrace};
|
||||
$response->{sub} = $apacheSession->data->{$user_id_attribute};
|
||||
$response->{scope} = $oidcSession->{data}->{scope}
|
||||
if $oidcSession->{data}->{scope};
|
||||
$response->{client_id} =
|
||||
$self->oidcRPList->{ $oidcSession->{data}->{rp} }
|
||||
->{oidcRPMetaDataOptionsClientID}
|
||||
if $oidcSession->{data}->{rp};
|
||||
$response->{iss} = $self->iss;
|
||||
$response->{exp} =
|
||||
$oidcSession->{data}->{_utime} + $self->conf->{timeout};
|
||||
}
|
||||
else {
|
||||
$self->logger->error(
|
||||
"Could not find user session ID in access token object");
|
||||
$self->logger->error("Count not find session tied to Access Token");
|
||||
}
|
||||
}
|
||||
return $self->p->sendJSONresponse( $req, $response );
|
||||
|
@ -1688,7 +1854,7 @@ sub registration {
|
|||
my $redirect_uris = $client_metadata->{redirect_uris};
|
||||
|
||||
# Register RP in global configuration
|
||||
my $conf = $self->confAcc->getConf( { raw => 1 } );
|
||||
my $conf = $self->confAcc->getConf( { raw => 1, noCache => 1 } );
|
||||
|
||||
$conf->{cfgAuthor} = "OpenID Connect Registration ($client_name)";
|
||||
$conf->{cfgAuthorIP} = $source_ip;
|
||||
|
@ -1857,7 +2023,7 @@ sub logout {
|
|||
. '</iframe>' );
|
||||
}
|
||||
else {
|
||||
# TODO
|
||||
# TODO #1194
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1897,6 +2063,17 @@ sub metadata {
|
|||
push( @$response_types, "id_token", "id_token token" );
|
||||
push( @$grant_types, "implicit" );
|
||||
}
|
||||
|
||||
# If one of the RPs has password grant enabled
|
||||
if (
|
||||
grep {
|
||||
$self->oidcRPList->{$_}->{oidcRPMetaDataOptionsAllowPasswordGrant}
|
||||
} keys %{ $self->oidcRPList }
|
||||
)
|
||||
{
|
||||
push( @$grant_types, "password" );
|
||||
}
|
||||
|
||||
if ( $self->conf->{oidcServiceAllowHybridFlow} ) {
|
||||
push( @$response_types,
|
||||
"code id_token",
|
||||
|
@ -1920,8 +2097,8 @@ sub metadata {
|
|||
introspection_endpoint => $baseUrl . $introspection_uri,
|
||||
|
||||
# Logout capabilities
|
||||
backchannel_logout_supported => JSON::true,
|
||||
backchannel_logout_session_supported => JSON::true,
|
||||
backchannel_logout_supported => JSON::false,
|
||||
backchannel_logout_session_supported => JSON::false,
|
||||
frontchannel_logout_supported => JSON::true,
|
||||
frontchannel_logout_session_supported => JSON::true,
|
||||
(
|
||||
|
@ -2016,6 +2193,11 @@ sub exportRequestParameters {
|
|||
return PE_OK;
|
||||
}
|
||||
|
||||
sub _hasScope {
|
||||
my ( $self, $scope, $scopelist ) = @_;
|
||||
return scalar grep { $_ eq $scope } ( split /\s+/, $scopelist );
|
||||
}
|
||||
|
||||
sub _convertOldFormatConsents {
|
||||
my ( $self, $req ) = @_;
|
||||
my @oidcConsents = ();
|
||||
|
@ -2073,11 +2255,10 @@ sub _convertOldFormatConsents {
|
|||
}
|
||||
|
||||
sub _generateIDToken {
|
||||
my ( $self, $req, $oidc_request, $rp, $extra_claims )
|
||||
= @_;
|
||||
my ( $self, $req, $oidc_request, $rp, $extra_claims ) = @_;
|
||||
|
||||
my $response_type = $oidc_request->{'response_type'};
|
||||
my $client_id = $oidc_request->{'client_id'};
|
||||
my $client_id = $oidc_request->{'client_id'};
|
||||
|
||||
my $id_token_exp =
|
||||
$self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
|
@ -2106,7 +2287,7 @@ sub _generateIDToken {
|
|||
my $id_token_payload_hash = {
|
||||
iss => $self->iss, # Issuer Identifier
|
||||
sub => $user_id, # Subject Identifier
|
||||
aud => [$client_id], # Audience
|
||||
aud => $self->getAudiences($rp), # Audience
|
||||
exp => $id_token_exp, # expiration
|
||||
iat => time, # Issued time
|
||||
auth_time => $req->{sessionInfo}->{_lastAuthnUTime}
|
||||
|
|
|
@ -75,6 +75,11 @@ sub init {
|
|||
my ($self) = @_;
|
||||
$self->_dbh
|
||||
or $self->logger->error("DBI connection has failed, but let's continue");
|
||||
unless ( $self->table ) {
|
||||
$self->logger->error(
|
||||
"SQL Table name is not set, can't load " . ref($self) );
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -384,8 +384,7 @@ sub buildHybridAuthnResponse {
|
|||
: ()
|
||||
),
|
||||
(
|
||||
$id_token
|
||||
? ( id_token => $id_token )
|
||||
$id_token ? ( id_token => $id_token )
|
||||
: ()
|
||||
),
|
||||
( $expires_in ? ( expires_in => $expires_in ) : () ),
|
||||
|
@ -1717,6 +1716,26 @@ sub force_id_claims {
|
|||
->{oidcRPMetaDataOptionsIDTokenForceClaims};
|
||||
}
|
||||
|
||||
# https://openid.net/specs/openid-connect-core-1_0.html#IDToken
|
||||
# Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0
|
||||
# client_id of the Relying Party as an audience value. It MAY also contain
|
||||
# identifiers for other audiences. In the general case, the aud value is an
|
||||
# array of case sensitive strings. In the common special case when there is one
|
||||
# audience, the aud value MAY be a single case sensitive string.
|
||||
sub getAudiences {
|
||||
my ( $self, $rp ) = @_;
|
||||
|
||||
my $client_id = $self->oidcRPList->{$rp}->{oidcRPMetaDataOptionsClientID};
|
||||
my @addAudiences = split /\s+/,
|
||||
( $self->oidcRPList->{$rp}->{oidcRPMetaDataOptionsAdditionalAudiences}
|
||||
|| '' );
|
||||
|
||||
my $result = [$client_id];
|
||||
push @{$result}, @addAudiences;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
|
|
@ -1743,6 +1743,8 @@ sub replayProtection {
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$self->logger->warn( "No assertion session found for request ID ".$samlID);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -8,6 +8,7 @@ package Lemonldap::NG::Portal::Main;
|
|||
use strict;
|
||||
use Mouse;
|
||||
use JSON;
|
||||
use URI;
|
||||
|
||||
use constant CommonPrms => {
|
||||
MAIN_LOGO => 'portalMainLogo',
|
||||
|
@ -147,13 +148,13 @@ sub display {
|
|||
AUTH_ERROR_TYPE => $req->error_type,
|
||||
MSG => $info,
|
||||
URL => $req->{urldc},
|
||||
HIDDEN_INPUTS => $self->buildHiddenForm($req),
|
||||
HIDDEN_INPUTS => $self->buildOutgoingHiddenForm( $req, $method ),
|
||||
ACTIVE_TIMER => $req->data->{activeTimer},
|
||||
CHOICE_PARAM => $self->conf->{authChoiceParam},
|
||||
CHOICE_VALUE => $req->data->{_authChoice},
|
||||
FORM_METHOD => $method,
|
||||
(
|
||||
$method ne 'get' ? ( SEND_PARAMS => 1 )
|
||||
(not $req->{urldc}) ? ( SEND_PARAMS => 1 )
|
||||
: ()
|
||||
),
|
||||
(
|
||||
|
@ -191,13 +192,14 @@ sub display {
|
|||
|
||||
# 2.1 Redirection
|
||||
elsif ( $req->{error} == PE_REDIRECT ) {
|
||||
my $method = $req->data->{redirectFormMethod} || 'get';
|
||||
$skinfile = "redirect";
|
||||
%templateParams = (
|
||||
MAIN_LOGO => $self->conf->{portalMainLogo},
|
||||
LANGS => $self->conf->{showLanguages},
|
||||
URL => $req->{urldc},
|
||||
HIDDEN_INPUTS => $self->buildHiddenForm($req),
|
||||
FORM_METHOD => $req->data->{redirectFormMethod} || 'get',
|
||||
HIDDEN_INPUTS => $self->buildOutgoingHiddenForm( $req, $method ),
|
||||
FORM_METHOD => $method,
|
||||
(
|
||||
$req->data->{customScript}
|
||||
? ( CUSTOM_SCRIPT => $req->data->{customScript} )
|
||||
|
@ -410,6 +412,29 @@ sub staticFile {
|
|||
];
|
||||
}
|
||||
|
||||
sub buildOutgoingHiddenForm {
|
||||
my ( $self, $req, $method ) = @_;
|
||||
my @keys = keys %{ $req->{portalHiddenFormValues} };
|
||||
|
||||
# Redirection URL contains query string. Before displaying a form,
|
||||
# we must set the query string parameters as form fields so they can
|
||||
# be preserved #2085
|
||||
|
||||
my $uri = URI->new( $req->{urldc} );
|
||||
my %query_params = $uri->query_form;
|
||||
if (%query_params) {
|
||||
$self->logger->debug(
|
||||
"urldc contains query parameters, setting them as hidden form values"
|
||||
);
|
||||
$self->clearHiddenFormValue($req);
|
||||
foreach ( keys %query_params ) {
|
||||
$self->setHiddenFormValue( $req, $_, $query_params{$_}, "", 0 );
|
||||
}
|
||||
}
|
||||
|
||||
return $self->buildHiddenForm($req);
|
||||
}
|
||||
|
||||
sub buildHiddenForm {
|
||||
my ( $self, $req ) = @_;
|
||||
my @keys = keys %{ $req->{portalHiddenFormValues} };
|
||||
|
@ -507,6 +532,7 @@ sub mkSessionArray {
|
|||
ip => $session->{ipAddr},
|
||||
values => [ map { { v => $session->{$_} } } @fields ],
|
||||
error => $session->{error},
|
||||
displayUser => $displayUser,
|
||||
displayError => $displayError,
|
||||
}
|
||||
} @$sessions
|
||||
|
|
|
@ -135,7 +135,18 @@ sub init {
|
|||
return 0;
|
||||
}
|
||||
|
||||
# Handle requests (other path may be declared in enabled plugins)
|
||||
# Default routes must point to routines declared above
|
||||
$self->defaultAuthRoute('');
|
||||
$self->defaultUnauthRoute('');
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub setPortalRoutes {
|
||||
my ($self) = @_;
|
||||
$self->authRoutes(
|
||||
{ GET => {}, POST => {}, PUT => {}, DELETE => {}, OPTIONS => {} } );
|
||||
$self->unAuthRoutes(
|
||||
{ GET => {}, POST => {}, PUT => {}, DELETE => {}, OPTIONS => {} } );
|
||||
$self
|
||||
|
||||
# "/" or undeclared paths
|
||||
|
@ -173,11 +184,15 @@ sub init {
|
|||
$self->defaultAuthRoute('');
|
||||
$self->defaultUnauthRoute('');
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
sub reloadConf {
|
||||
my ( $self, $conf ) = @_;
|
||||
|
||||
# Handle requests (other path may be declared in enabled plugins)
|
||||
$self->setPortalRoutes;
|
||||
|
||||
# Reinitialize $self->conf
|
||||
%{ $self->{conf} } = %{ $self->localConfig };
|
||||
|
||||
|
@ -518,4 +533,25 @@ sub displayError {
|
|||
'Portal error, contact your administrator', 500 );
|
||||
}
|
||||
|
||||
# This helper method builds a rule from a string expression
|
||||
# - $rule: rule text
|
||||
# - $ruleDesc optional hint of what the rule is for, to display in error message
|
||||
# returns undef if the rule syntax was invalid
|
||||
sub buildRule {
|
||||
my ( $self, $rule, $ruleDesc ) = @_;
|
||||
if ($ruleDesc) {
|
||||
$ruleDesc = " $ruleDesc ";
|
||||
}
|
||||
else {
|
||||
$ruleDesc = " ";
|
||||
}
|
||||
my $compiledRule =
|
||||
$self->HANDLER->buildSub( $self->HANDLER->substitute($rule) );
|
||||
unless ($compiledRule) {
|
||||
my $error = $self->HANDLER->tsv->{jail}->error || '???';
|
||||
$self->logger->error( "Bad" . $ruleDesc . "rule: " . $error );
|
||||
}
|
||||
return $compiledRule,;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -39,15 +39,19 @@ sub process {
|
|||
}
|
||||
}
|
||||
}
|
||||
$self->logger->debug( "Returned "
|
||||
. ( $err > 0 ? "error" : "status" )
|
||||
. ": $err ("
|
||||
. portalConsts->{$err}
|
||||
. ")" )
|
||||
$self->logger->debug( "Returned " . $self->_formatProcessResult($err) )
|
||||
if ($err);
|
||||
return $err;
|
||||
}
|
||||
|
||||
sub _formatProcessResult {
|
||||
my ( $self, $err ) = @_;
|
||||
return ( ( $err > 0 ? "error" : "status" )
|
||||
. ": $err ("
|
||||
. portalConsts->{$err}
|
||||
. ")" );
|
||||
}
|
||||
|
||||
# First process block: check args
|
||||
# -------------------------------
|
||||
|
||||
|
@ -198,7 +202,20 @@ sub deleteSession {
|
|||
}
|
||||
}
|
||||
|
||||
# TODO
|
||||
# Merge logoutServices from user context (for example CAS logoutServices
|
||||
# url) and from global configuration
|
||||
if ( $self->conf->{logoutServices}
|
||||
and %{ $self->conf->{logoutServices} } )
|
||||
{
|
||||
|
||||
# Initialize logoutServices (if not already done)
|
||||
$req->data->{logoutServices} ||= {};
|
||||
$req->data->{logoutServices} = {
|
||||
%{ $req->data->{logoutServices} },
|
||||
%{ $self->conf->{logoutServices} }
|
||||
};
|
||||
}
|
||||
|
||||
# Collect logout services and build hidden iFrames
|
||||
if ( $req->data->{logoutServices} and %{ $req->data->{logoutServices} } ) {
|
||||
|
||||
|
@ -212,15 +229,12 @@ sub deleteSession {
|
|||
);
|
||||
|
||||
foreach ( keys %{ $req->data->{logoutServices} } ) {
|
||||
my $logoutServiceName = $_;
|
||||
my $logoutServiceUrl =
|
||||
$req->data->{logoutServices}->{$logoutServiceName};
|
||||
my $logoutServiceUrl = $req->data->{logoutServices}->{$_};
|
||||
|
||||
$self->logger->debug(
|
||||
"Find logout service $logoutServiceName ($logoutServiceUrl)");
|
||||
$self->logger->debug("Find logout service $_ ($logoutServiceUrl)");
|
||||
|
||||
my $iframe =
|
||||
qq'<iframe src="$logoutServiceUrl" alt="$logoutServiceName"'
|
||||
qq'<iframe src="$logoutServiceUrl" alt="$_"'
|
||||
. ' marginwidth="0" marginheight="0" scrolling="no"'
|
||||
. ' class="hiddenFrame" width="0" height="0"'
|
||||
. ' frameborder="0"></iframe>';
|
||||
|
|
|
@ -80,6 +80,10 @@ has captcha => ( is => 'rw' );
|
|||
# Token
|
||||
has token => ( is => 'rw' );
|
||||
|
||||
# Whether or not to include a HTML render of the error message
|
||||
# in error responses
|
||||
has wantErrorRender => ( is => 'rw' );
|
||||
|
||||
# Error type
|
||||
sub error_type {
|
||||
my $req = shift;
|
||||
|
|
|
@ -260,16 +260,25 @@ sub do {
|
|||
if ( !$self->conf->{noAjaxHook} and $req->wantJSON ) {
|
||||
$self->logger->debug('Processing to JSON response');
|
||||
if ( ( $err > 0 and !$req->id ) or $err eq PE_SESSIONNOTGRANTED ) {
|
||||
my $s = qq'{"result":0,"error":$err}';
|
||||
return [
|
||||
401,
|
||||
[
|
||||
my $json = { result => 0, error => $err };
|
||||
if ( $req->wantErrorRender ) {
|
||||
$json->{html} = $self->loadTemplate(
|
||||
$req,
|
||||
'errormsg',
|
||||
params => {
|
||||
AUTH_ERROR => $err,
|
||||
AUTH_ERROR_TYPE => $req->error_type,
|
||||
}
|
||||
);
|
||||
}
|
||||
return $self->sendJSONresponse(
|
||||
$req, $json,
|
||||
code => 401,
|
||||
headers => [
|
||||
'WWW-Authenticate' => "SSO " . $self->conf->{portal},
|
||||
'Content-Type' => 'application/json',
|
||||
'Content-Length' => length($s)
|
||||
"Content-Type" => "application/javascript"
|
||||
],
|
||||
[$s]
|
||||
];
|
||||
);
|
||||
}
|
||||
elsif ( $err > 0 ) {
|
||||
return $self->sendJSONresponse(
|
||||
|
@ -364,7 +373,10 @@ sub autoRedirect {
|
|||
$req->data->{redirectFormMethod} = "get";
|
||||
}
|
||||
else {
|
||||
if ( $req->{pdata}->{_url} eq encode_base64( $req->{urldc}, '' ) ) {
|
||||
if ( $req->{pdata}->{_url}
|
||||
and $req->{pdata}->{_url} eq encode_base64( $req->{urldc}, '' )
|
||||
)
|
||||
{
|
||||
$self->logger->info("Force cleaning pdata");
|
||||
$req->pdata( {} );
|
||||
}
|
||||
|
@ -1099,7 +1111,19 @@ sub corsPreflight {
|
|||
sub sendJSONresponse {
|
||||
my ( $self, $req, $j, %args ) = @_;
|
||||
my $res = Lemonldap::NG::Common::PSGI::sendJSONresponse(@_);
|
||||
if ( $self->conf->{corsEnabled} ) {
|
||||
|
||||
# If this is a cross-domain request from the portal itself
|
||||
# (Ajax SSL to a different VHost)
|
||||
# we allow CORS
|
||||
if ( $req->origin and index( $self->conf->{portal}, $req->origin ) == 0 ) {
|
||||
$self->logger->debug('AJAX request from portal, allowing CORS');
|
||||
push @{ $res->[1] },
|
||||
"Access-Control-Allow-Origin" => $req->origin,
|
||||
"Access-Control-Allow-Methods" => "*",
|
||||
"Access-Control-Allow-Credentials" => "true";
|
||||
|
||||
}
|
||||
elsif ( $self->conf->{corsEnabled} ) {
|
||||
my @cors = split /;/, $self->cors;
|
||||
push @{ $res->[1] }, @cors;
|
||||
$self->logger->debug('Apply following CORS policy :');
|
||||
|
|
|
@ -34,6 +34,13 @@ has prefix => ( is => 'rw' );
|
|||
has logo => ( is => 'rw', default => '2f.png' );
|
||||
has label => ( is => 'rw' );
|
||||
has noRoute => ( is => 'ro' );
|
||||
has authnLevel => (
|
||||
is => 'rw',
|
||||
lazy => 1,
|
||||
default => sub {
|
||||
return $_[0]->conf->{ $_[0]->prefix . '2fAuthnLevel' };
|
||||
}
|
||||
);
|
||||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
|
@ -122,7 +129,7 @@ sub _verify {
|
|||
. '2F verification for '
|
||||
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
|
||||
|
||||
if ( my $l = $self->conf->{ $self->prefix . '2fAuthnLevel' } ) {
|
||||
if ( my $l = $self->authnLevel ) {
|
||||
$self->logger->debug(
|
||||
"Update sessionInfo with new authenticationLevel: $l");
|
||||
$req->sessionInfo->{authenticationLevel} = $l;
|
||||
|
|
|
@ -28,9 +28,12 @@ has ott => (
|
|||
return $ott;
|
||||
}
|
||||
);
|
||||
has idRule => ( is => 'rw', default => sub { 1 } );
|
||||
has sorted => ( is => 'rw', default => sub { 0 } );
|
||||
has merged => ( is => 'rw', default => '' );
|
||||
has idRule => ( is => 'rw', default => sub { 1 } );
|
||||
has displayEmptyValuesRule => ( is => 'rw', default => sub { 0 } );
|
||||
has displayEmptyHeadersRule => ( is => 'rw', default => sub { 0 } );
|
||||
has displayPersistentInfoRule => ( is => 'rw', default => sub { 0 } );
|
||||
has sorted => ( is => 'rw', default => sub { 0 } );
|
||||
has merged => ( is => 'rw', default => '' );
|
||||
|
||||
sub hAttr {
|
||||
$_[0]->{conf}->{checkUserHiddenAttributes} . ' '
|
||||
|
@ -44,25 +47,44 @@ sub persistentAttrs {
|
|||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
my $hd = $self->p->HANDLER;
|
||||
$self->addAuthRoute( checkuser => 'check', ['POST'] );
|
||||
$self->addAuthRouteWithRedirect( checkuser => 'display', ['GET'] );
|
||||
|
||||
# Parse identity rule
|
||||
$self->logger->debug(
|
||||
"checkUser identities rule -> " . $self->conf->{checkUserIdRule} );
|
||||
my $rule =
|
||||
$hd->buildSub( $hd->substitute( $self->conf->{checkUserIdRule} ) );
|
||||
unless ($rule) {
|
||||
my $error = $hd->tsv->{jail}->error || '???';
|
||||
$self->error("Bad checkUser identities rule -> $error");
|
||||
return 0;
|
||||
}
|
||||
$self->idRule($rule);
|
||||
# Parse checkUser rules
|
||||
$self->idRule(
|
||||
$self->p->buildRule( $self->conf->{checkUserIdRule}, 'checkUserId' ) );
|
||||
return 0 unless $self->idRule;
|
||||
|
||||
$self->displayEmptyValuesRule(
|
||||
$self->p->buildRule(
|
||||
$self->conf->{checkUserDisplayEmptyValues},
|
||||
'checkUserDisplayEmptyValues'
|
||||
)
|
||||
);
|
||||
return 0 unless $self->displayEmptyValuesRule;
|
||||
|
||||
$self->displayEmptyHeadersRule(
|
||||
$self->p->buildRule(
|
||||
$self->conf->{checkUserDisplayEmptyHeaders},
|
||||
'checkUserDisplayEmptyHeaders'
|
||||
)
|
||||
);
|
||||
return 0 unless $self->displayEmptyHeadersRule;
|
||||
|
||||
$self->displayPersistentInfoRule(
|
||||
$self->p->buildRule(
|
||||
$self->conf->{checkUserDisplayPersistentInfo},
|
||||
'checkUserDisplayPersistentInfo'
|
||||
)
|
||||
);
|
||||
return 0 unless $self->displayPersistentInfoRule;
|
||||
|
||||
# Init. other options
|
||||
$self->sorted( $self->conf->{impersonationRule}
|
||||
|| $self->conf->{contextSwitchingRule} );
|
||||
$self->merged( $self->conf->{impersonationMergeSSOgroups}
|
||||
&& $self->conf->{impersonationRule} ? 'Merged' : '' );
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -77,11 +99,11 @@ sub display {
|
|||
$attrs = $req->userData;
|
||||
|
||||
$attrs = $self->_removePersistentAttributes($attrs)
|
||||
unless $self->conf->{checkUserDisplayPersistentInfo};
|
||||
unless $self->displayPersistentInfoRule->( $req, $req->userData );
|
||||
|
||||
# Create an array of hashes for template loop
|
||||
$self->logger->debug("Delete hidden or empty attributes");
|
||||
if ( $self->conf->{checkUserDisplayEmptyValues} ) {
|
||||
if ( $self->displayEmptyValuesRule->( $req, $req->userData ) ) {
|
||||
foreach my $k ( sort keys %$attrs ) {
|
||||
|
||||
# Ignore hidden attributes
|
||||
|
@ -246,7 +268,7 @@ sub check {
|
|||
else {
|
||||
$msg = 'checkUser' . $self->merged;
|
||||
$attrs = $self->_removePersistentAttributes($attrs)
|
||||
unless $self->conf->{checkUserDisplayPersistentInfo};
|
||||
unless $self->displayPersistentInfoRule->( $req, $req->userData );
|
||||
|
||||
if ($compute) {
|
||||
$msg = 'checkUserComputeSession';
|
||||
|
@ -273,7 +295,7 @@ sub check {
|
|||
|
||||
# Create an array of hashes for template loop
|
||||
$self->logger->debug("Delete hidden or empty attributes");
|
||||
if ( $self->conf->{checkUserDisplayEmptyValues} ) {
|
||||
if ( $self->displayEmptyValuesRule->( $req, $req->userData ) ) {
|
||||
foreach my $k ( sort keys %$attrs ) {
|
||||
|
||||
# Ignore hidden attributes
|
||||
|
@ -453,7 +475,7 @@ sub _headers {
|
|||
$self->logger->debug(
|
||||
"Return \"$attrs->{ $self->{conf}->{whatToTrace} }\" headers");
|
||||
return $self->p->HANDLER->checkHeaders( $req, $attrs )
|
||||
if ( $self->conf->{checkUserDisplayEmptyHeaders} );
|
||||
if ( $self->displayEmptyHeadersRule->( $req, $req->userData ) );
|
||||
|
||||
$self->logger->debug("Remove empty headers");
|
||||
my @headers = grep $_->{value} =~ /.+/,
|
||||
|
|
|
@ -39,33 +39,25 @@ has idRule => ( is => 'rw', default => sub { 1 } );
|
|||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
my $hd = $self->p->HANDLER;
|
||||
$self->addAuthRoute( switchcontext => 'run', ['POST'] )
|
||||
->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) {
|
||||
my $error = $hd->tsv->{jail}->error || '???';
|
||||
$self->error("Bad contextSwitching rule -> $error");
|
||||
return 0;
|
||||
}
|
||||
$self->rule($rule);
|
||||
# Parse ContextSwitching rules
|
||||
$self->rule(
|
||||
$self->p->buildRule(
|
||||
$self->conf->{contextSwitchingRule},
|
||||
'contextSwitching'
|
||||
)
|
||||
);
|
||||
return 0 unless $self->rule;
|
||||
|
||||
# Parse identity rule
|
||||
$self->logger->debug( "ContextSwitching identities rule -> "
|
||||
. $self->conf->{contextSwitchingIdRule} );
|
||||
$rule =
|
||||
$hd->buildSub( $hd->substitute( $self->conf->{contextSwitchingIdRule} ) );
|
||||
unless ($rule) {
|
||||
my $error = $hd->tsv->{jail}->error || '???';
|
||||
$self->error("Bad contextSwitching identities rule -> $error");
|
||||
return 0;
|
||||
}
|
||||
$self->idRule($rule);
|
||||
$self->idRule(
|
||||
$self->p->buildRule(
|
||||
$self->conf->{contextSwitchingIdRule},
|
||||
'contextSwitchingId'
|
||||
)
|
||||
);
|
||||
return 0 unless $self->idRule;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -74,13 +66,15 @@ sub init {
|
|||
|
||||
sub display {
|
||||
my ( $self, $req ) = @_;
|
||||
my $realSessionId =
|
||||
$req->userData->{"$self->{conf}->{contextSwitchingPrefix}_session_id"};
|
||||
my $realSession;
|
||||
unless ( $realSession = $self->p->getApacheSession($realSessionId) ) {
|
||||
$self->userLogger->info(
|
||||
"ContextSwitching: session $realSessionId expired");
|
||||
return $self->p->do( $req, [ sub { PE_SESSIONEXPIRED } ] );
|
||||
my ( $realSession, $realSessionId );
|
||||
if ( $realSessionId =
|
||||
$req->userData->{"$self->{conf}->{contextSwitchingPrefix}_session_id"} )
|
||||
{
|
||||
unless ( $realSession = $self->p->getApacheSession($realSessionId) ) {
|
||||
$self->userLogger->info(
|
||||
"ContextSwitching: session $realSessionId expired");
|
||||
return $self->p->do( $req, [ sub { PE_SESSIONEXPIRED } ] );
|
||||
}
|
||||
}
|
||||
|
||||
# Check access rules
|
||||
|
@ -241,7 +235,8 @@ sub _switchContext {
|
|||
delete $req->sessionInfo->{groups};
|
||||
|
||||
# Compute groups & macros again with real authenticationLevel
|
||||
$req->steps( [ $self->p->groupsAndMacros, 'setLocalGroups' ] );
|
||||
$req->steps(
|
||||
[ 'setSessionInfo', $self->p->groupsAndMacros, 'setLocalGroups' ] );
|
||||
if ( my $error = $self->p->process($req) ) {
|
||||
$self->logger->debug(
|
||||
"ContextSwitching: Process returned error: $error");
|
||||
|
|
|
@ -30,21 +30,14 @@ has ott => (
|
|||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
my $hd = $self->p->HANDLER;
|
||||
$self->addAuthRoute( decryptvalue => 'run', ['POST'] )
|
||||
->addAuthRouteWithRedirect( decryptvalue => 'display', ['GET'] );
|
||||
|
||||
# Parse activation rule
|
||||
$self->logger->debug(
|
||||
'DecryptValue rule -> ' . $self->conf->{decryptValueRule} );
|
||||
my $rule =
|
||||
$hd->buildSub( $hd->substitute( $self->conf->{decryptValueRule} ) );
|
||||
unless ($rule) {
|
||||
my $error = $hd->tsv->{jail}->error || '???';
|
||||
$self->error("Bad decryptValue rule -> $error");
|
||||
return 0;
|
||||
}
|
||||
$self->rule($rule);
|
||||
$self->rule(
|
||||
$self->p->buildRule( $self->conf->{decryptValueRule}, 'decryptValue' )
|
||||
);
|
||||
return 0 unless $self->rule;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package Lemonldap::NG::Portal::Plugins::GlobalLogout;
|
|||
use strict;
|
||||
use Mouse;
|
||||
use JSON qw(from_json to_json);
|
||||
use Time::Local;
|
||||
use Lemonldap::NG::Portal::Main::Constants qw(
|
||||
PE_OK
|
||||
PE_ERROR
|
||||
|
@ -39,17 +40,10 @@ sub init {
|
|||
$self->addAuthRoute( globallogout => 'globalLogout', [ 'POST', 'GET' ] );
|
||||
|
||||
# Parse activation rule
|
||||
my $hd = $self->p->HANDLER;
|
||||
$self->logger->debug(
|
||||
"GlobalLogout rule -> " . $self->conf->{globalLogoutRule} );
|
||||
my $rule =
|
||||
$hd->buildSub( $hd->substitute( $self->conf->{globalLogoutRule} ) );
|
||||
unless ($rule) {
|
||||
my $error = $hd->tsv->{jail}->error || '???';
|
||||
$self->error("Bad globalLogout rule -> $error");
|
||||
return 0;
|
||||
}
|
||||
$self->rule($rule);
|
||||
$self->rule(
|
||||
$self->p->buildRule( $self->conf->{globalLogoutRule}, 'globalLogout' )
|
||||
);
|
||||
return 0 unless $self->rule;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -112,6 +106,7 @@ sub run {
|
|||
|
||||
sub globalLogout {
|
||||
my ( $self, $req ) = @_;
|
||||
my $res = PE_OK;
|
||||
my $count = 0;
|
||||
|
||||
if ( $req->param('all') ) {
|
||||
|
@ -122,7 +117,7 @@ sub globalLogout {
|
|||
my $sessions = eval { from_json( $token->{sessions} ) };
|
||||
if ($@) {
|
||||
$self->logger->error("Bad encoding in OTT: $@");
|
||||
return PE_ERROR;
|
||||
$res = PE_ERROR;
|
||||
}
|
||||
my $as;
|
||||
foreach (@$sessions) {
|
||||
|
@ -145,23 +140,24 @@ sub globalLogout {
|
|||
else {
|
||||
$self->userLogger->warn(
|
||||
"GlobalLogout called with an unvalid token");
|
||||
return PE_TOKENEXPIRED;
|
||||
$res = PE_TOKENEXPIRED;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$self->userLogger->error(
|
||||
"GlobalLogout called with an expired token");
|
||||
return PE_TOKENEXPIRED;
|
||||
$res = PE_TOKENEXPIRED;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$self->userLogger->error('GlobalLogout called without token');
|
||||
return PE_NOTOKEN;
|
||||
$res = PE_NOTOKEN;
|
||||
}
|
||||
}
|
||||
$self->userLogger->info("$count remaining session(s) removed");
|
||||
|
||||
return $self->p->do( $req, [ sub { $res } ] ) if $res;
|
||||
$self->userLogger->info("$count remaining session(s) removed");
|
||||
return $self->p->do( $req, [ 'authLogout', 'deleteSession' ] );
|
||||
}
|
||||
|
||||
|
@ -169,7 +165,6 @@ sub activeSessions {
|
|||
my ( $self, $req ) = @_;
|
||||
my $activeSessions = [];
|
||||
my $sessions = {};
|
||||
my $regex = '^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$';
|
||||
my $user = $req->{userData}->{ $self->conf->{whatToTrace} };
|
||||
my $customParam = $self->conf->{globalLogoutCustomParam} || '';
|
||||
|
||||
|
@ -183,21 +178,29 @@ sub activeSessions {
|
|||
$user );
|
||||
|
||||
$self->logger->debug("Building array ref with sessions info...");
|
||||
@$activeSessions = map { {
|
||||
@$activeSessions =
|
||||
map {
|
||||
my $epoch;
|
||||
my $regex = '^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$';
|
||||
$_->{startTime} =~ /$regex/;
|
||||
$epoch = timelocal( $6, $5, $4, $3, $2 - 1, $1 );
|
||||
$_->{startTime} = $epoch;
|
||||
if ( $_->{updateTime} ) {
|
||||
$_->{updateTime} =~ /$regex/;
|
||||
$epoch = timelocal( $6, $5, $4, $3, $2 - 1, $1 );
|
||||
$_->{updateTime} = $epoch;
|
||||
}
|
||||
$_;
|
||||
}
|
||||
sort { $b->{startTime} cmp $a->{startTime} } map { {
|
||||
id => $_,
|
||||
customParam => $sessions->{$_}->{$customParam},
|
||||
ipAddr => $sessions->{$_}->{'ipAddr'},
|
||||
authLevel => $sessions->{$_}->{'authenticationLevel'},
|
||||
startTime => $sessions->{$_}->{'_startTime'} =~
|
||||
s/$regex/$1-$2-$3 $4:$5:$6/ro,
|
||||
updateTime => (
|
||||
$sessions->{$_}->{'_updateTime'}
|
||||
? $sessions->{$_}->{'_updateTime'} =~
|
||||
s/$regex/$1-$2-$3 $4:$5:$6/ro
|
||||
: ''
|
||||
),
|
||||
ipAddr => $sessions->{$_}->{ipAddr},
|
||||
authLevel => $sessions->{$_}->{authenticationLevel},
|
||||
startTime => $sessions->{$_}->{_startTime},
|
||||
updateTime => $sessions->{$_}->{_updateTime}
|
||||
};
|
||||
} keys %$sessions;
|
||||
} keys %$sessions;
|
||||
|
||||
return $activeSessions;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ sub init { 1 }
|
|||
|
||||
sub run {
|
||||
my ( $self, $req ) = @_;
|
||||
|
||||
if ( $req->param('checkLogins') ) {
|
||||
$self->logger->debug('History asked');
|
||||
$req->info( (
|
||||
|
|
|
@ -23,31 +23,23 @@ sub hAttr {
|
|||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
my $hd = $self->p->HANDLER;
|
||||
|
||||
# Parse activation rule
|
||||
$self->logger->debug(
|
||||
"Impersonation rule -> " . $self->conf->{impersonationRule} );
|
||||
my $rule =
|
||||
$hd->buildSub( $hd->substitute( $self->conf->{impersonationRule} ) );
|
||||
unless ($rule) {
|
||||
my $error = $hd->tsv->{jail}->error || '???';
|
||||
$self->error("Bad impersonation rule -> $error");
|
||||
return 0;
|
||||
}
|
||||
$self->rule($rule);
|
||||
# Parse Impersonation rules
|
||||
$self->rule(
|
||||
$self->p->buildRule(
|
||||
$self->conf->{impersonationRule}, 'impersonation'
|
||||
)
|
||||
);
|
||||
return 0 unless $self->rule;
|
||||
|
||||
$self->idRule(
|
||||
$self->p->buildRule(
|
||||
$self->conf->{impersonationIdRule},
|
||||
'impersonationId'
|
||||
)
|
||||
);
|
||||
return 0 unless $self->idRule;
|
||||
|
||||
# Parse identity rule
|
||||
$self->logger->debug( "Impersonation identities rule -> "
|
||||
. $self->conf->{impersonationIdRule} );
|
||||
$rule =
|
||||
$hd->buildSub( $hd->substitute( $self->conf->{impersonationIdRule} ) );
|
||||
unless ($rule) {
|
||||
my $error = $hd->tsv->{jail}->error || '???';
|
||||
$self->error("Bad impersonation identities rule -> $error");
|
||||
return 0;
|
||||
}
|
||||
$self->idRule($rule);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -235,7 +227,8 @@ sub _userData {
|
|||
$req->sessionInfo->{authenticationLevel} =
|
||||
$realSession->{real_authenticationLevel};
|
||||
delete $req->sessionInfo->{groups};
|
||||
$req->steps( [ $self->p->groupsAndMacros, 'setLocalGroups' ] );
|
||||
$req->steps(
|
||||
[ 'setSessionInfo', $self->p->groupsAndMacros, 'setLocalGroups' ] );
|
||||
if ( my $error = $self->p->process($req) ) {
|
||||
$self->logger->debug("Impersonation: Process returned error: $error");
|
||||
$req->error($error);
|
||||
|
|
|
@ -321,12 +321,16 @@ sub updateSession {
|
|||
my $force = 0;
|
||||
if ( my $s = delete $infos->{__secret} ) {
|
||||
my $t;
|
||||
if ( $t =
|
||||
$self->conf->{cipher}->decrypt($s)
|
||||
and $t <= time + $self->conf->{restClockTolerance}
|
||||
and $t > time - $self->conf->{restClockTolerance} )
|
||||
{
|
||||
$force = 1;
|
||||
if ( $t = $self->conf->{cipher}->decrypt($s) ) {
|
||||
if ( $t <= time + $self->conf->{restClockTolerance}
|
||||
and $t > time - $self->conf->{restClockTolerance} )
|
||||
{
|
||||
$force = 1;
|
||||
}
|
||||
else {
|
||||
$self->userLogger->error( 'Clock drift between servers is'
|
||||
. ' beyond tolerance, force denied.' );
|
||||
}
|
||||
}
|
||||
else {
|
||||
$self->userLogger->error('Bad key, force denied');
|
||||
|
|
|
@ -2,7 +2,14 @@ package Lemonldap::NG::Portal::Plugins::SingleSession;
|
|||
|
||||
use strict;
|
||||
use Mouse;
|
||||
use Lemonldap::NG::Portal::Main::Constants qw(PE_OK);
|
||||
use MIME::Base64;
|
||||
use JSON qw(from_json to_json);
|
||||
use Lemonldap::NG::Portal::Main::Constants qw(
|
||||
PE_OK
|
||||
PE_ERROR
|
||||
PE_TOKENEXPIRED
|
||||
PE_NOTOKEN
|
||||
);
|
||||
|
||||
our $VERSION = '2.1.0';
|
||||
|
||||
|
@ -11,14 +18,49 @@ extends 'Lemonldap::NG::Portal::Main::Plugin',
|
|||
|
||||
use constant endAuth => 'run';
|
||||
|
||||
sub init { 1 }
|
||||
has singleIPRule => ( is => 'rw' );
|
||||
has singleSessionRule => ( is => 'rw' );
|
||||
has singleUserByIPRule => ( is => 'rw' );
|
||||
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;
|
||||
}
|
||||
);
|
||||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
$self->addAuthRoute( removeOther => 'removeOther', ['GET'] );
|
||||
|
||||
# Build triggering rules from configuration
|
||||
$self->singleIPRule(
|
||||
$self->p->buildRule( $self->conf->{singleIP}, 'singleIP' ) );
|
||||
return 0 unless $self->singleIPRule;
|
||||
|
||||
$self->singleSessionRule(
|
||||
$self->p->buildRule( $self->conf->{singleSession}, 'singleSession' ) );
|
||||
return 0 unless $self->singleSessionRule;
|
||||
|
||||
$self->singleUserByIPRule(
|
||||
$self->p->buildRule( $self->conf->{singleUserByIP}, 'singleUserByIP' )
|
||||
);
|
||||
return 0 unless $self->singleUserByIPRule;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub run {
|
||||
my ( $self, $req ) = @_;
|
||||
my ( $linkedSessionId, $token, $html ) = ( '', '', '' );
|
||||
my $deleted = [];
|
||||
my $otherSessions = [];
|
||||
my $linkedSessionId = '';
|
||||
my $moduleOptions = $self->conf->{globalStorageOptions} || {};
|
||||
my @otherSessionsId = ();
|
||||
|
||||
my $moduleOptions = $self->conf->{globalStorageOptions} || {};
|
||||
$moduleOptions->{backend} = $self->conf->{globalStorage};
|
||||
|
||||
my $sessions = $self->module->searchOn(
|
||||
|
@ -42,8 +84,8 @@ sub run {
|
|||
next if ( $linkedSessionId and $id eq $linkedSessionId );
|
||||
my $session = $self->p->getApacheSession($id) or next;
|
||||
if (
|
||||
$self->conf->{singleSession}
|
||||
or ( $self->conf->{singleIP}
|
||||
$self->singleSessionRule->( $req, $req->sessionInfo )
|
||||
or ( $self->singleIPRule->( $req, $req->sessionInfo )
|
||||
and $req->{sessionInfo}->{ipAddr} ne $session->data->{ipAddr} )
|
||||
)
|
||||
{
|
||||
|
@ -51,13 +93,21 @@ sub run {
|
|||
$self->p->_deleteSession( $req, $session, 1 );
|
||||
}
|
||||
else {
|
||||
push @$otherSessions, $self->p->_sumUpSession( $session->data );
|
||||
push @$otherSessions, $self->p->_sumUpSession( $session->data );
|
||||
push @otherSessionsId, $id;
|
||||
}
|
||||
}
|
||||
if ( $self->conf->{singleUserByIP} ) {
|
||||
|
||||
$token = $self->ott->createToken( {
|
||||
user => $req->{sessionInfo}->{ $self->conf->{whatToTrace} },
|
||||
sessions => to_json( \@otherSessionsId )
|
||||
}
|
||||
) if @otherSessionsId;
|
||||
|
||||
if ( $self->singleUserByIPRule->( $req, $req->sessionInfo ) ) {
|
||||
my $sessions =
|
||||
$self->module->searchOn( $moduleOptions, 'ipAddr',
|
||||
$req->sessionInfo->ipAddr );
|
||||
$req->sessionInfo->{ipAddr} );
|
||||
foreach my $id ( keys %$sessions ) {
|
||||
next if ( $req->id eq $id );
|
||||
my $session = $self->p->getApacheSession($id) or next;
|
||||
|
@ -69,29 +119,79 @@ sub run {
|
|||
}
|
||||
}
|
||||
}
|
||||
$req->info(
|
||||
$self->p->mkSessionArray( $req, $deleted, 'sessionsDeleted', 1 ) )
|
||||
|
||||
$html = $self->p->mkSessionArray( $req, $deleted, 'sessionsDeleted', 1 )
|
||||
if ( $self->conf->{notifyDeleted} and @$deleted );
|
||||
$req->info(
|
||||
$self->p->mkSessionArray( $req, $otherSessions, 'otherSessions', 1 )
|
||||
. $self->_mkRemoveOtherLink($req) )
|
||||
$html .=
|
||||
$self->p->mkSessionArray( $req, $otherSessions, 'otherSessions', 1 )
|
||||
. $self->_mkRemoveOtherLink( $req, $token )
|
||||
if ( $self->conf->{notifyOther} and @$otherSessions );
|
||||
|
||||
$req->info($html);
|
||||
return PE_OK;
|
||||
}
|
||||
|
||||
sub removeOther {
|
||||
my ( $self, $req ) = @_;
|
||||
my $res = PE_OK;
|
||||
$req->{urldc} = decode_base64( $req->param('url') );
|
||||
if ( my $token = $req->param('token') ) {
|
||||
if ( $token = $self->ott->getToken($token) ) {
|
||||
|
||||
# Read sessions from token
|
||||
my $sessions = eval { from_json( $token->{sessions} ) };
|
||||
if ($@) {
|
||||
$self->logger->error("Bad encoding in OTT: $@");
|
||||
$res = PE_ERROR;
|
||||
}
|
||||
my $as;
|
||||
foreach my $id (@$sessions) {
|
||||
unless ( $as = $self->p->getApacheSession($id) ) {
|
||||
$self->userLogger->info(
|
||||
"SingleSession: session $id expired");
|
||||
next;
|
||||
}
|
||||
my $user = $token->{user};
|
||||
if ( $req->{userData}->{ $self->{conf}->{whatToTrace} } eq
|
||||
$user )
|
||||
{
|
||||
$self->userLogger->info("Remove \"$user\" session: $id");
|
||||
$self->p->_deleteSession( $req, $as, 1 );
|
||||
}
|
||||
else {
|
||||
$self->userLogger->warn(
|
||||
"SingleSession called with an unvalid token");
|
||||
$res = PE_TOKENEXPIRED;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$self->userLogger->error(
|
||||
"SingleSession called with an expired token");
|
||||
$res = PE_TOKENEXPIRED;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$self->userLogger->error('SingleSession called without token');
|
||||
$res = PE_NOTOKEN;
|
||||
}
|
||||
|
||||
return $self->p->do( $req, [ sub { $res } ] ) if $res;
|
||||
$req->mustRedirect(1);
|
||||
return $self->p->autoRedirect($req);
|
||||
}
|
||||
|
||||
# Build the removeOther link
|
||||
# Last part of URL is built trough javascript
|
||||
# @return removeOther link in HTML code
|
||||
sub _mkRemoveOtherLink {
|
||||
my ( $self, $req ) = @_;
|
||||
my ( $self, $req, $token ) = @_;
|
||||
|
||||
# TODO: remove this
|
||||
return $self->loadTemplate(
|
||||
$req,
|
||||
'removeOther',
|
||||
params => {
|
||||
link => $self->conf->{portal} . "?removeOther=1"
|
||||
link => $self->conf->{portal} . "removeOther?token=$token"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,5 +13,8 @@ timer = () ->
|
|||
window.setTimeout timer, 1000
|
||||
|
||||
$(document).ready ->
|
||||
$(".data-epoch").each ->
|
||||
myDate = new Date($(this).text() * 1000)
|
||||
$(this).text(myDate.toLocaleString())
|
||||
window.setTimeout go, 30000
|
||||
window.setTimeout timer, 1000
|
||||
|
|
|
@ -227,6 +227,11 @@ datas = {}
|
|||
$(window).on 'load', () ->
|
||||
# Get application/init variables
|
||||
datas = getValues()
|
||||
|
||||
# Keep the currently selected tab
|
||||
if "datas" of window && "choicetab" of window.datas
|
||||
datas.choicetab = window.datas.choicetab;
|
||||
|
||||
# Export datas for other scripts
|
||||
window.datas = datas
|
||||
|
||||
|
@ -280,26 +285,48 @@ $(window).on 'load', () ->
|
|||
|
||||
# Complete removeOther link
|
||||
if $("p.removeOther").length
|
||||
action = $("form.login").attr "action"
|
||||
method = $("form.login").attr "method"
|
||||
action = $("#form").attr "action"
|
||||
method = $("#form").attr "method"
|
||||
console.log 'method=', method
|
||||
|
||||
hiddenParams = ""
|
||||
if $("#form input[type=hidden]")
|
||||
console.log 'Parse hidden values'
|
||||
$("#form input[type=hidden]").each (index) ->
|
||||
console.log ' ->', $(this).attr("name"), $(this).val()
|
||||
hiddenParams += "&" + $(this).attr("name") + "=" + $(this).val()
|
||||
|
||||
back_url = ""
|
||||
if action.indexOf("?") != -1
|
||||
action.substring(0, action.indexOf("?")) + "?"
|
||||
else
|
||||
back_url = action + "?"
|
||||
if action
|
||||
console.log 'action=', action
|
||||
if action.indexOf("?") != -1
|
||||
action.substring(0, action.indexOf("?")) + "?"
|
||||
else
|
||||
back_url = action + "?"
|
||||
back_url += hiddenParams
|
||||
hiddenParams = ""
|
||||
|
||||
$("form.login input[type=hidden]").each (index) ->
|
||||
back_url += "&" + $(this).attr("name") + "=" + $(this).val()
|
||||
|
||||
link = $("p.removeOther a").attr("href") + "&method=" + method + "&url=" + btoa(back_url)
|
||||
link = $("p.removeOther a").attr("href") + "&method=" + method + hiddenParams
|
||||
link += "&url=" + btoa(back_url) if back_url
|
||||
$("p.removeOther a").attr "href", link
|
||||
|
||||
# Language detection. Priority order:
|
||||
# 0 - llnglanguage parameter
|
||||
# 1 - cookie value
|
||||
# 2 - first navigator.languages item that exists in window.availableLanguages
|
||||
# 3 - first value of window.availableLanguages
|
||||
lang = getCookie 'llnglanguage'
|
||||
queryString = window.location.search
|
||||
if queryString
|
||||
console.log 'Parsed queryString:', queryString
|
||||
urlParams = new URLSearchParams queryString
|
||||
if urlParams
|
||||
queryLang = urlParams.get('llnglanguage')
|
||||
console.log 'Get lang from parameter' if queryLang
|
||||
setCookieLang = urlParams.get('setCookieLang')
|
||||
console.log 'Set lang cookie' if setCookieLang == 1
|
||||
if !lang
|
||||
lang = getCookie 'llnglanguage'
|
||||
console.log 'Get lang from cookie' if lang && !queryLang
|
||||
if !lang
|
||||
if navigator
|
||||
langs = []
|
||||
|
@ -320,13 +347,26 @@ $(window).on 'load', () ->
|
|||
else if al.substring(0, 1) == nl.substring(0, 1)
|
||||
langs2.push al
|
||||
lang = if langs[0] then langs[0] else if langs2[0] then langs2[0] else window.availableLanguages[0]
|
||||
console.log 'Get lang from navigator' if lang && !queryLang
|
||||
else
|
||||
lang = window.availableLanguages[0]
|
||||
console.log 'Get lang from window' if lang && !queryLang
|
||||
else if lang not in window.availableLanguages
|
||||
lang = window.availableLanguages[0]
|
||||
console.log 'Selected lang ->', lang
|
||||
setCookie 'llnglanguage', lang
|
||||
translatePage(lang)
|
||||
console.log 'Lang not available -> Get default lang' if !queryLang
|
||||
if queryLang
|
||||
if queryLang not in window.availableLanguages
|
||||
console.log 'Lang not available -> Get default lang'
|
||||
queryLang = window.availableLanguages[0]
|
||||
console.log 'Selected lang ->', queryLang
|
||||
if setCookieLang
|
||||
console.log 'Set cookie lang ->', queryLang
|
||||
setCookie 'llnglanguage', queryLang
|
||||
translatePage(queryLang)
|
||||
else
|
||||
console.log 'Selected lang ->', lang
|
||||
setCookie 'llnglanguage', lang
|
||||
translatePage(lang)
|
||||
|
||||
# Build language icons
|
||||
langdiv = ''
|
||||
|
|
|
@ -5,12 +5,9 @@ tryssl = () ->
|
|||
console.log 'path -> ', path
|
||||
console.log 'Call URL -> ', window.datas.sslHost
|
||||
$.ajax window.datas.sslHost,
|
||||
dataType: 'jsonp'
|
||||
# PE_BADCREDENTIALS
|
||||
statusCode:
|
||||
401: () ->
|
||||
$('#lform').submit()
|
||||
console.log 'Error code 401'
|
||||
dataType: 'json',
|
||||
xhrFields:
|
||||
withCredentials: true
|
||||
# If request succeed, cookie is set, posting form to get redirection
|
||||
# or menu
|
||||
success: (data) ->
|
||||
|
@ -18,9 +15,23 @@ tryssl = () ->
|
|||
console.log 'Success -> ', data
|
||||
# Case else, will display PE_BADCREDENTIALS or fallback to next auth
|
||||
# backend
|
||||
error: () ->
|
||||
sendUrl path
|
||||
console.log 'Error'
|
||||
error: (result) ->
|
||||
# If the AJAX query didn't fire at all, it's probably
|
||||
# a bad certificate
|
||||
if result.status == 0
|
||||
# We couldn't send the request.
|
||||
# if client verification is optional, this means
|
||||
# the certificate was rejected (or some network error)
|
||||
sendUrl path
|
||||
# For compatibility with earlier configs, handle PE9 by posting form
|
||||
if result.responseJSON && 'error' of result.responseJSON && result.responseJSON.error == "9"
|
||||
sendUrl path
|
||||
|
||||
# If the server sent a html error description, display it
|
||||
if result.responseJSON && 'html' of result.responseJSON
|
||||
$('#errormsg').html(result.responseJSON.html);
|
||||
$(window).trigger('load');
|
||||
console.log 'Error during AJAX SSL authentication', result
|
||||
false
|
||||
|
||||
sendUrl = (path) ->
|
||||
|
@ -34,4 +45,4 @@ sendUrl = (path) ->
|
|||
$('#lform').submit()
|
||||
|
||||
$(document).ready ->
|
||||
$('.sslclick').on 'click', tryssl
|
||||
$('.sslclick').on 'click', tryssl
|
||||
|
|
|
@ -5,12 +5,9 @@ tryssl = () ->
|
|||
console.log 'path -> ', path
|
||||
console.log 'Call URL -> ', window.datas.sslHost
|
||||
$.ajax window.datas.sslHost,
|
||||
dataType: 'jsonp'
|
||||
# PE_BADCREDENTIALS
|
||||
statusCode:
|
||||
401: () ->
|
||||
$('#lformSSL').submit()
|
||||
console.log 'Error code 401'
|
||||
dataType: 'json',
|
||||
xhrFields:
|
||||
withCredentials: true
|
||||
# If request succeed, cookie is set, posting form to get redirection
|
||||
# or menu
|
||||
success: (data) ->
|
||||
|
@ -18,9 +15,23 @@ tryssl = () ->
|
|||
console.log 'Success -> ', data
|
||||
# Case else, will display PE_BADCREDENTIALS or fallback to next auth
|
||||
# backend
|
||||
error: () ->
|
||||
sendUrl path
|
||||
console.log 'Error'
|
||||
error: (result) ->
|
||||
# If the AJAX query didn't fire at all, it's probably
|
||||
# a bad certificate
|
||||
if result.status == 0
|
||||
# We couldn't send the request.
|
||||
# if client verification is optional, this means
|
||||
# the certificate was rejected (or some network error)
|
||||
sendUrl path
|
||||
# For compatibility with earlier configs, handle PE9 by posting form
|
||||
if result.responseJSON && 'error' of result.responseJSON && result.responseJSON.error == "9"
|
||||
sendUrl path
|
||||
|
||||
# If the server sent a html error description, display it
|
||||
if result.responseJSON && 'html' of result.responseJSON
|
||||
$('#errormsg').html(result.responseJSON.html);
|
||||
$(window).trigger('load');
|
||||
console.log 'Error during AJAX SSL authentication', result
|
||||
false
|
||||
|
||||
sendUrl = (path) ->
|
||||
|
@ -34,4 +45,4 @@ sendUrl = (path) ->
|
|||
$('#lformSSL').submit()
|
||||
|
||||
$(document).ready ->
|
||||
$('.sslclick').on 'click', tryssl
|
||||
$('.sslclick').on 'click', tryssl
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
$(document).ready(function() {
|
||||
$(window).on("load", function() {
|
||||
|
||||
// Adapt some class to fit Bootstrap theme
|
||||
$("div.message-positive").addClass("alert-success");
|
||||
|
@ -16,4 +16,9 @@ $(document).ready(function() {
|
|||
}
|
||||
});
|
||||
|
||||
// Remember selected tab
|
||||
$('#authMenu .nav-link').on('click', function (e) {
|
||||
window.datas.choicetab = e.target.hash.substr(1)
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -1 +1 @@
|
|||
$(document).ready(function(){$("div.message-positive").addClass("alert-success"),$("div.message-warning").addClass("alert-warning"),$("div.message-negative").addClass("alert-danger"),$("table.info").addClass("table"),$(".notifCheck").addClass("checkbox"),$('.collapse li[class!="dropdown"]').on("click",function(){$(".navbar-toggler").hasClass("collapsed")||$(".navbar-toggler").trigger("click")})});
|
||||
$(window).on("load",function(){$("div.message-positive").addClass("alert-success"),$("div.message-warning").addClass("alert-warning"),$("div.message-negative").addClass("alert-danger"),$("table.info").addClass("table"),$(".notifCheck").addClass("checkbox"),$('.collapse li[class!="dropdown"]').on("click",function(){$(".navbar-toggler").hasClass("collapsed")||$(".navbar-toggler").trigger("click")}),$("#authMenu .nav-link").on("click",function(a){window.datas.choicetab=a.target.hash.substr(1)})});
|
|
@ -1 +1 @@
|
|||
{"version":3,"sources":["lemonldap-ng-portal/site/htdocs/static/bootstrap/js/skin.js"],"names":["$","document","ready","addClass","on","hasClass","trigger"],"mappings":"AAAAA,EAAEC,UAAUC,MAAM,WAGhBF,EAAE,wBAAwBG,SAAS,iBACnCH,EAAE,uBAAuBG,SAAS,iBAClCH,EAAE,wBAAwBG,SAAS,gBAEnCH,EAAE,cAAcG,SAAS,SAEzBH,EAAE,eAAeG,SAAS,YAG1BH,EAAE,mCAAmCI,GAAG,QAAS,WAC1CJ,EAAE,mBAAmBK,SAAS,cACjCL,EAAE,mBAAmBM,QAAQ"}
|
||||
{"version":3,"sources":["lemonldap-ng-portal/site/htdocs/static/bootstrap/js/skin.js"],"names":["$","window","on","addClass","hasClass","trigger","e","datas","choicetab","target","hash","substr"],"mappings":"AAAAA,EAAEC,QAAQC,GAAG,OAAQ,WAGnBF,EAAE,wBAAwBG,SAAS,iBACnCH,EAAE,uBAAuBG,SAAS,iBAClCH,EAAE,wBAAwBG,SAAS,gBAEnCH,EAAE,cAAcG,SAAS,SAEzBH,EAAE,eAAeG,SAAS,YAG1BH,EAAE,mCAAmCE,GAAG,QAAS,WAC1CF,EAAE,mBAAmBI,SAAS,cACjCJ,EAAE,mBAAmBK,QAAQ,WAKjCL,EAAE,uBAAuBE,GAAG,QAAS,SAAUI,GAC3CL,OAAOM,MAAMC,UAAYF,EAAEG,OAAOC,KAAKC,OAAO"}
|
|
@ -20,6 +20,11 @@
|
|||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
$(".data-epoch").each(function() {
|
||||
var myDate;
|
||||
myDate = new Date($(this).text() * 1000);
|
||||
return $(this).text(myDate.toLocaleString());
|
||||
});
|
||||
window.setTimeout(go, 30000);
|
||||
return window.setTimeout(timer, 1000);
|
||||
});
|
||||
|
|
|
@ -1 +1 @@
|
|||
(function(){var t,e,n;e=30,t=function(){return $("#globallogout").submit()},n=function(){var t;return t=$("#timer").html(),0<e&&e--,t=t.replace(/\d+/,e),$("#timer").html(t),window.setTimeout(n,1e3)},$(document).ready(function(){return window.setTimeout(t,3e4),window.setTimeout(n,1e3)})}).call(this);
|
||||
(function(){var t,e,n;e=30,t=function(){return $("#globallogout").submit()},n=function(){var t;return t=$("#timer").html(),0<e&&e--,t=t.replace(/\d+/,e),$("#timer").html(t),window.setTimeout(n,1e3)},$(document).ready(function(){return $(".data-epoch").each(function(){var t;return t=new Date(1e3*$(this).text()),$(this).text(t.toLocaleString())}),window.setTimeout(t,3e4),window.setTimeout(n,1e3)})}).call(this);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue