Merge branch 'feature-delayed-2fa-2124' into 'v2.0'

Delay 2FA until required by an application

See merge request lemonldap-ng/lemonldap-ng!147
This commit is contained in:
Maxime Besson 2020-09-04 17:40:46 +02:00
commit 66c68f6056
62 changed files with 1521 additions and 140 deletions

View File

@ -868,6 +868,9 @@
"allowOffline" : {
"type" : "boolean"
},
"authnLevel" : {
"type" : "integer"
},
"rule" : {
"type" : "string"
},
@ -1057,6 +1060,9 @@
"type" : "integer",
"default" : 72000
},
"authnLevel" : {
"type" : "integer"
},
"rule" : {
"type" : "string"
},

View File

@ -109,6 +109,8 @@ Options
application.
- **User attribute** : session field that will be used as main
identifier.
- **Authentication Level** : required authentication level to access this
application
- **Rule** : The access control rule to enforce on this application. If
left blank, access will be allowed for everyone.

View File

@ -268,7 +268,8 @@ Options
https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
for details. These offline sessions can be administered through
the Session Browser.
- **Allow OAuth2.0 Password Grant** (since version ``2.0.8``) Allow the use of the Resource Owner Password Credentials Grant on by this client. This feature only works if you have configured a form-based authentication module.
- **Allow OAuth2.0 Password Grant** (since version ``2.0.8``): Allow the use of the Resource Owner Password Credentials Grant on by this client. This feature only works if you have configured a form-based authentication module.
- **Authentication Level**: required authentication level to access this application
- **Access Rule**: lets you specify a :doc:`Perl rule<rules_examples>` to restrict access to this client
- **Logout**

View File

@ -162,10 +162,12 @@ These options override service signature options (see
Security
''''''''
- **Encryption mode**: set the encryption mode for this IDP (None,
- **Encryption mode**: set the encryption mode for this SP (None,
NameID or Assertion).
- **Enable use of IDP initiated URL**: set to ``On`` to enable IDP
Initiated URL on this SP.
- **Authentication Level**: required authentication level to access this SP
- **Access Rule**: lets you specify a :doc:`Perl rule<rules_examples>` to restrict access to this SP
.. tip::

View File

@ -29,22 +29,44 @@ The E-Mail, External and REST 2F modules
parameters.
.. tip::
Registration on first use
-------------------------
If you want to force a 2F registration on first login, you can
use 'Require 2FA'. You can also use a rule to force 2FA registration
only for some users.
.. tip::
If you want to force a 2F registration on first login, you can use the *Force
2FA registration at login* option.
You can display a message if an
expired second factor has been removed by enabling 'Display a message if
an expired SF is removed' option or setting a rule.
You can use a `rule<writingrulesand_headers>` to enable this behavior only for
some users.
.. tip::
Second factor expiration
------------------------
Link to second factor Manager is automatically display if at least a
SFA module is enabled. You can set a rule to display or not the
link.
You can display a message if an expired second factor has been removed by
enabling *Display a message if an expired SF is removed* option or setting a
rule.
Self-care on Portal
-------------------
User may register second facrots themselves on the Portal by using the 2FA Manager.
The link will be displayed if at least a SFA module is enabled. You can set a
rule to display or not the link.
Session upgrade through 2FA
---------------------------
|beta|
If you enable the *Use 2FA for session upgrade* option, second factor will only
be asked on login if the target application requires an authentication level
that is strictly higher than the one obtained by the Authentication backend
(first factor).
The session upgrade mechanism will only require the second factor step, instead
of doing a complete reauthentication.
.. |beta| image:: /documentation/beta.png
Providing tokens from an external source
----------------------------------------
@ -100,4 +122,3 @@ To enable manager Second Factor Administration Module, set
[portal]
enabledModules = conf, sessions, notifications, 2ndFA

View File

@ -1098,6 +1098,8 @@ components:
notOnOrAfterTimeout:
type: integer
default: 72000
authnLevel:
type: integer
rule:
type: string
forceUTF8:
@ -1181,6 +1183,8 @@ components:
type: string
allowOffline:
type: boolean
authnLevel:
type: integer
rule:
type: string
IDTokenSignAlg:

View File

@ -30,7 +30,7 @@ use constant DEFAULTCONFBACKENDOPTIONS => (
dirName => '/usr/local/lemonldap-ng/data/conf',
);
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)|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(?:freshMyRights|setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|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)|re(?:st(?:(?:Password|Session|Config|Auth)Server|ExportSecretKeys)|freshSessions)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|(?: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)|f(?:RemovedUseNotif|OnlyUpgrade)|kip(?:Upgrade|Renew)Confirmation|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|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(?:freshMyRights|setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|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)|re(?:st(?:(?:Password|Session|Config|Auth)Server|ExportSecretKeys)|freshSessions)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|(?: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' );

View File

@ -24,12 +24,12 @@ our $specialNodeHash = {
our $doubleHashKeys = 'issuerDBGetParameters';
our $simpleHashKeys = '(?:(?:l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|c(?:as(?:StorageOption|Attribute)|ustom(?:Plugins|Add)Param|ombModule)|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|(?:(?:d(?:emo|bi)|facebook|webID)E|e)xportedVar|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|p(?:ersistentStorageOption|ortalSkinRule)|macro)s|o(?:idcS(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|penIdExportedVars)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember|fExtra)|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|S(?:MTPTLSOpts|SLVarIf))';
our $specialNodeKeys = '(?:(?:(?:saml(?:ID|S)|oidc[OR])P|cas(?:App|Srv))MetaDataNode|virtualHost)s';
our $casAppMetaDataNodeKeys = 'casAppMetaData(?:Options(?:UserAttribut|Servic|Rul)e|(?:ExportedVar|Macro)s)';
our $casAppMetaDataNodeKeys = 'casAppMetaData(?:Options(?:(?:UserAttribut|Servic|Rul)e|AuthnLevel)|(?: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|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 $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:A(?:uth(?:orizationCodeExpiration|nLevel)|llow(?:PasswordGrant|Offline)|ccessTokenExpiration|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 $samlSPMetaDataNodeKeys = 'samlSPMetaData(?:Options(?:N(?:ameID(?:SessionKey|Format)|otOnOrAfterTimeout)|S(?:essionNotOnOrAfterTimeout|ignS[LS]OMessage)|(?:CheckS[LS]OMessageSignatur|OneTimeUs|Rul)e|En(?:ableIDPInitiatedURL|cryptionMode)|AuthnLevel|ForceUTF8)|(?:ExportedAttribute|Macro)s|XML)';
our $virtualHostKeys = '(?:vhost(?:A(?:uthnLevel|liases)|(?:Maintenanc|Typ)e|ServiceTokenTTL|Https|Port)|(?:exportedHeader|locationRule)s|post)';
our $authParameters = {

View File

@ -123,12 +123,6 @@ sub BUILD {
$data = $self->_tie_session;
}
# If session is created
# Then set session kind in session
if ( $creation and $self->kind ) {
$data->{_session_kind} = $self->kind;
}
if ( $self->{info} ) {
foreach ( keys %{ $self->{info} } ) {
next if ( $_ eq "_session_id" and $data->{_session_id} );
@ -143,6 +137,12 @@ sub BUILD {
delete $self->{info};
}
# If session is created
# Then set session kind in session
if ( $creation and $self->kind ) {
$data->{_session_kind} = $self->kind;
}
# Load session data into object
if ($data) {
if ( $self->kind and $data->{_session_kind} ) {

View File

@ -18,6 +18,8 @@ sub portalConsts {
'10' => 'PE_BADCERTIFICATE',
'100' => 'PE_PP_NOT_ALLOWED_CHARACTER',
'101' => 'PE_PP_NOT_ALLOWED_CHARACTERS',
'102' => 'PE_UPGRADESESSION',
'103' => 'PE_NO_SECOND_FACTORS',
'2' => 'PE_FORMEMPTY',
'21' => 'PE_PP_ACCOUNT_LOCKED',
'22' => 'PE_PP_PASSWORD_EXPIRED',

View File

@ -265,17 +265,14 @@ sub checkMaintenanceMode {
return 0;
}
## @rmethod boolean grant(string uri, string cond)
# Grant or refuse client using compiled regexp and functions
## @rmethod int getLevel(string uri, string $vhost)
# Return required authentication level for this URI
# default to vhost authentication level
# @param $uri URI
# @param $cond optional Function granting access
# @return True if the user is granted to access to the current URL
sub grant {
my ( $class, $req, $session, $uri, $cond, $vhost ) = @_;
# @param $vhost vhost name, default to current request
sub getLevel {
my ( $class, $req, $uri, $vhost ) = @_;
my $level;
return $cond->( $req, $session ) if ($cond);
$vhost ||= $class->resolveAlias($req);
# Using URL authentification level if exists
@ -290,13 +287,33 @@ sub grant {
last;
}
}
$level
? $class->logger->debug(
'Found AuthnLevel=' . $level . ' for "' . "$vhost$uri" . '"' )
: $class->logger->debug("No URL authentication level found...");
if ($level) {
$class->logger->debug(
'Found AuthnLevel=' . $level . ' for "' . "$vhost$uri" . '"' );
return $level;
}
else {
$class->logger->debug("No URL authentication level found...");
return $class->tsv->{authnLevel}->{$vhost};
}
}
## @rmethod boolean grant(string uri, string cond)
# Grant or refuse client using compiled regexp and functions
# @param $uri URI
# @param $cond optional Function granting access
# @return True if the user is granted to access to the current URL
sub grant {
my ( $class, $req, $session, $uri, $cond, $vhost ) = @_;
return $cond->( $req, $session ) if ($cond);
$vhost ||= $class->resolveAlias($req);
my $level = $class->getLevel( $req, $uri );
# Using VH authentification level if exists
if ( $level ||= $class->tsv->{authnLevel}->{$vhost} ) {
if ($level) {
if ( $session->{authenticationLevel} < $level ) {
$class->logger->debug(
"User authentication level = $session->{authenticationLevel}");

View File

@ -713,6 +713,9 @@ sub attributes {
'casAppMetaDataOptions' => {
'type' => 'subContainer'
},
'casAppMetaDataOptionsAuthnLevel' => {
'type' => 'int'
},
'casAppMetaDataOptionsRule' => {
'test' => sub {
return perlExpr(@_);
@ -2152,6 +2155,9 @@ m[^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
'default' => 0,
'type' => 'bool'
},
'oidcRPMetaDataOptionsAuthnLevel' => {
'type' => 'int'
},
'oidcRPMetaDataOptionsAuthorizationCodeExpiration' => {
'type' => 'int'
},
@ -3392,6 +3398,9 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'keyTest' => qr/^[a-zA-Z](?:[a-zA-Z0-9_\-\.]*\w)?$/,
'type' => 'keyTextContainer'
},
'samlSPMetaDataOptionsAuthnLevel' => {
'type' => 'int'
},
'samlSPMetaDataOptionsCheckSLOMessageSignature' => {
'default' => 1,
'type' => 'bool'
@ -3629,6 +3638,9 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'default' => 1,
'type' => 'boolOrExpr'
},
'sfOnlyUpgrade' => {
'type' => 'bool'
},
'sfRemovedMsgRule' => {
'default' => 0,
'type' => 'boolOrExpr'
@ -3674,6 +3686,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'default' => 0,
'type' => 'bool'
},
'skipUpgradeConfirmation' => {
'default' => 0,
'type' => 'bool'
},
'slaveAuthnLevel' => {
'default' => 2,
'type' => 'int'

View File

@ -601,6 +601,12 @@ sub attributes {
documentation =>
'Avoid asking confirmation when an Issuer asks to renew auth',
},
skipUpgradeConfirmation => {
type => 'bool',
default => 0,
documentation =>
'Avoid asking confirmation during a session upgrade',
},
refreshSessions => {
type => 'bool',
documentation => 'Refresh sessions plugin',
@ -2304,6 +2310,10 @@ sub attributes {
type => 'text',
documentation => 'CAS User attribute',
},
casAppMetaDataOptionsAuthnLevel => {
type => 'int',
documentation => 'Authentication level requires to access to this CAS application',
},
casAppMetaDataOptionsRule => {
type => 'text',
test => sub { return perlExpr(@_) },
@ -2915,6 +2925,10 @@ sub attributes {
type => 'bool',
default => 1,
},
samlSPMetaDataOptionsAuthnLevel => {
type => 'int',
documentation => 'Authentication level requires to access to this SP',
},
samlSPMetaDataOptionsRule => {
type => 'text',
test => sub { return perlExpr(@_) },
@ -3010,6 +3024,11 @@ sub attributes {
help => 'secondfactor.html',
documentation => 'Second factor required',
},
sfOnlyUpgrade => {
type => 'bool',
help => 'secondfactor.html',
documentation => 'Only trigger second factor on session upgrade',
},
sfManagerRule => {
type => 'boolOrExpr',
default => 1,
@ -4065,6 +4084,10 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
default => 0,
documentation => 'Issue refresh tokens',
},
oidcRPMetaDataOptionsAuthnLevel => {
type => 'int',
documentation => 'Authentication level requires to access to this RP',
},
oidcRPMetaDataOptionsRule => {
type => 'text',
test => sub { return perlExpr(@_) },

View File

@ -134,6 +134,7 @@ sub cTrees {
nodes => [
"samlSPMetaDataOptionsEncryptionMode",
"samlSPMetaDataOptionsEnableIDPInitiatedURL",
"samlSPMetaDataOptionsAuthnLevel",
"samlSPMetaDataOptionsRule",
]
}
@ -221,6 +222,7 @@ sub cTrees {
'oidcRPMetaDataOptionsRequirePKCE',
'oidcRPMetaDataOptionsAllowOffline',
'oidcRPMetaDataOptionsAllowPasswordGrant',
'oidcRPMetaDataOptionsAuthnLevel',
'oidcRPMetaDataOptionsRule',
]
},
@ -286,6 +288,7 @@ sub cTrees {
nodes => [
'casAppMetaDataOptionsService',
'casAppMetaDataOptionsUserAttribute',
'casAppMetaDataOptionsAuthnLevel',
'casAppMetaDataOptionsRule'
]
},

View File

@ -108,7 +108,9 @@ sub portalConstants {
PE_RESETCERTIFICATE_FORMEMPTY => 98,
PE_RESETCERTIFICATE_FIRSTACCESS => 99,
PE_PP_NOT_ALLOWED_CHARACTER => 100,
PE_PP_NOT_ALLOWED_CHARACTERS => 101
PE_PP_NOT_ALLOWED_CHARACTERS => 101,
PE_UPGRADESESSION => 102,
PE_NO_SECOND_FACTORS => 103
};
}

View File

@ -907,6 +907,7 @@ sub tree {
'sfRemovedNotifMsg',
],
},
'sfOnlyUpgrade',
'sfManagerRule',
'sfRequired',
]
@ -1015,6 +1016,7 @@ sub tree {
nodes => [
'jsRedirect', 'noAjaxHook',
'skipRenewConfirmation',
'skipUpgradeConfirmation',
]
},
'nginxCustomHandlers',

View File

@ -48,6 +48,12 @@ function templates(tpl,key) {
"id" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsUserAttribute",
"title" : "casAppMetaDataOptionsUserAttribute"
},
{
"get" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsAuthnLevel",
"id" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsAuthnLevel",
"title" : "casAppMetaDataOptionsAuthnLevel",
"type" : "int"
},
{
"get" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsRule",
"id" : tpl+"s/"+key+"/"+"casAppMetaDataOptionsRule",
@ -535,6 +541,12 @@ function templates(tpl,key) {
"title" : "oidcRPMetaDataOptionsAllowPasswordGrant",
"type" : "bool"
},
{
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAuthnLevel",
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsAuthnLevel",
"title" : "oidcRPMetaDataOptionsAuthnLevel",
"type" : "int"
},
{
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRule",
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRule",
@ -1153,6 +1165,12 @@ function templates(tpl,key) {
"title" : "samlSPMetaDataOptionsEnableIDPInitiatedURL",
"type" : "bool"
},
{
"get" : tpl+"s/"+key+"/"+"samlSPMetaDataOptionsAuthnLevel",
"id" : tpl+"s/"+key+"/"+"samlSPMetaDataOptionsAuthnLevel",
"title" : "samlSPMetaDataOptionsAuthnLevel",
"type" : "int"
},
{
"get" : tpl+"s/"+key+"/"+"samlSPMetaDataOptionsRule",
"id" : tpl+"s/"+key+"/"+"samlSPMetaDataOptionsRule",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -122,6 +122,7 @@
"casAppMetaDataNodes":"تطبيق كاس",
"casAppMetaDataOptions":"خيارات",
"casAppMetaDataOptionsService":"خدمة أل يو أر ل",
"casAppMetaDataOptionsAuthnLevel":"مستوى إثبات الهوية",
"casAppMetaDataOptionsRule":"القاعدة",
"casAppMetaDataMacros":"ماكرو",
"casAppMetaDataOptionsUserAttribute":"خاصّيّة المستخدم",
@ -593,6 +594,7 @@
"oidcOPMetaDataOptionsProtocol":"بروتوكول",
"oidcRPMetaDataOptionsPublic":"Public client",
"oidcRPMetaDataOptionsRequirePKCE":"Require PKCE",
"oidcRPMetaDataOptionsAuthnLevel":"مستوى إثبات الهوية",
"oidcRPMetaDataOptionsRule":"قاعدة الدخول",
"oidcRPMetaDataMacros":"ماكرو",
"oidcOPMetaDataOptionsScope":"نطاق",
@ -847,6 +849,7 @@
"sfaTitle":"Second factors authentication",
"sfExtra":"Additional second factors",
"sfManagerRule":"Display Manager link",
"sfOnlyUpgrade": "Use 2FA for session upgrade",
"sfRequired":"Force 2FA registration at login",
"sfRemovedNotification":"Warn if an expired 2FA is removed",
"sfRemovedMsgRule":"تفعيل",
@ -862,6 +865,7 @@
"singleSession":"One session per user",
"singleUserByIP":"مستخدم واحد لكل عنوان آي بي",
"skipRenewConfirmation":"Skip re-auth confirmation",
"skipUpgradeConfirmation":"Skip upgrade confirmation",
"slaveAuthnLevel":"مستوى إثبات الهوية",
"slaveDisplayLogo":"Display authentication logo",
"slaveExportedVars":"المتغيرات المصدرة",
@ -1074,6 +1078,7 @@
"samlSPMetaDataOptionsSessionNotOnOrAfterTimeout":"جلسة ليست مع أو بعد المدة",
"samlSPMetaDataOptionsNotOnOrAfterTimeout":"ليس على أو بعد المدة",
"samlSPMetaDataOptionsForceUTF8":"فرضUTF-8 ",
"samlSPMetaDataOptionsAuthnLevel":"مستوى إثبات الهوية",
"samlSPMetaDataOptionsRule":"قاعدة الدخول",
"samlSPMetaDataMacros":"ماكرو",
"samlIDPName":"اسم SAML IDP",
@ -1141,4 +1146,4 @@
"samlRelayStateTimeout":"تناوب حالة مهلة الجلسة ",
"samlUseQueryStringSpecific":"استخدام أسلوب query_string المعين",
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
}
}

View File

@ -122,6 +122,7 @@
"casAppMetaDataNodes":"CAS Applikationen",
"casAppMetaDataOptions":"Optionen",
"casAppMetaDataOptionsService":"Service URL",
"casAppMetaDataOptionsAuthnLevel":"Authentication level",
"casAppMetaDataOptionsRule":"Regel",
"casAppMetaDataMacros":"Macros",
"casAppMetaDataOptionsUserAttribute":"User attribute",
@ -593,6 +594,7 @@
"oidcOPMetaDataOptionsProtocol":"Protocol",
"oidcRPMetaDataOptionsPublic":"Public client",
"oidcRPMetaDataOptionsRequirePKCE":"Require PKCE",
"oidcRPMetaDataOptionsAuthnLevel":"Authentication level",
"oidcRPMetaDataOptionsRule":"Access rule",
"oidcRPMetaDataMacros":"Macros",
"oidcOPMetaDataOptionsScope":"Scope",
@ -847,6 +849,7 @@
"sfaTitle":"Second factors authentication",
"sfExtra":"Additional second factors",
"sfManagerRule":"Display Manager link",
"sfOnlyUpgrade": "Use 2FA for session upgrade",
"sfRequired":"Force 2FA registration at login",
"sfRemovedNotification":"Warn if an expired 2FA is removed",
"sfRemovedMsgRule":"Activation",
@ -862,6 +865,7 @@
"singleSession":"One session per user",
"singleUserByIP":"One user per IP address",
"skipRenewConfirmation":"Skip re-auth confirmation",
"skipUpgradeConfirmation":"Skip upgrade confirmation",
"slaveAuthnLevel":"Authentication level",
"slaveDisplayLogo":"Display authentication logo",
"slaveExportedVars":"Exported variables",
@ -1074,6 +1078,7 @@
"samlSPMetaDataOptionsSessionNotOnOrAfterTimeout":"sessionNotOnOrAfter duration",
"samlSPMetaDataOptionsNotOnOrAfterTimeout":"notOnOrAfter duration",
"samlSPMetaDataOptionsForceUTF8":"Force UTF-8",
"samlSPMetaDataOptionsAuthnLevel":"Authentication level",
"samlSPMetaDataOptionsRule":"Access rule",
"samlSPMetaDataMacros":"Macros",
"samlIDPName":"SAML IDP Name",
@ -1141,4 +1146,4 @@
"samlRelayStateTimeout":"RelayState session timeout",
"samlUseQueryStringSpecific":"Use specific query_string method",
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
}
}

View File

@ -122,6 +122,7 @@
"casAppMetaDataNodes":"CAS Applications",
"casAppMetaDataOptions":"Options",
"casAppMetaDataOptionsService":"Service URL",
"casAppMetaDataOptionsAuthnLevel":"Authentication level",
"casAppMetaDataOptionsRule":"Rule",
"casAppMetaDataMacros":"Macros",
"casAppMetaDataOptionsUserAttribute":"User attribute",
@ -593,6 +594,7 @@
"oidcOPMetaDataOptionsProtocol":"Protocol",
"oidcRPMetaDataOptionsPublic":"Public client",
"oidcRPMetaDataOptionsRequirePKCE":"Require PKCE",
"oidcRPMetaDataOptionsAuthnLevel":"Authentication level",
"oidcRPMetaDataOptionsRule":"Access rule",
"oidcRPMetaDataMacros":"Macros",
"oidcOPMetaDataOptionsScope":"Scope",
@ -847,6 +849,7 @@
"sfaTitle":"Second factors authentication",
"sfExtra":"Additional second factors",
"sfManagerRule":"Display Manager link",
"sfOnlyUpgrade": "Use 2FA for session upgrade",
"sfRequired":"Force 2FA registration at login",
"sfRemovedNotification":"Warn if an expired 2FA is removed",
"sfRemovedMsgRule":"Activation",
@ -862,6 +865,7 @@
"singleSession":"One session per user",
"singleUserByIP":"One user per IP address",
"skipRenewConfirmation":"Skip re-auth confirmation",
"skipUpgradeConfirmation":"Skip upgrade confirmation",
"slaveAuthnLevel":"Authentication level",
"slaveDisplayLogo":"Display authentication logo",
"slaveExportedVars":"Exported variables",
@ -1074,6 +1078,7 @@
"samlSPMetaDataOptionsSessionNotOnOrAfterTimeout":"sessionNotOnOrAfter duration",
"samlSPMetaDataOptionsNotOnOrAfterTimeout":"notOnOrAfter duration",
"samlSPMetaDataOptionsForceUTF8":"Force UTF-8",
"samlSPMetaDataOptionsAuthnLevel":"Authentication level",
"samlSPMetaDataOptionsRule":"Access rule",
"samlSPMetaDataMacros":"Macros",
"samlIDPName":"SAML IDP Name",

View File

@ -122,6 +122,7 @@
"casAppMetaDataNodes":"Applications CAS",
"casAppMetaDataOptions":"Options",
"casAppMetaDataOptionsService":"URL du service",
"casAppMetaDataOptionsAuthnLevel":"Niveau d'authentification",
"casAppMetaDataOptionsRule":"Règle",
"casAppMetaDataMacros":"Macros",
"casAppMetaDataOptionsUserAttribute":"Attribut de l'utilisateur",
@ -593,6 +594,7 @@
"oidcOPMetaDataOptionsProtocol":"Protocole",
"oidcRPMetaDataOptionsPublic":"Client public",
"oidcRPMetaDataOptionsRequirePKCE":"PKCE requis",
"oidcRPMetaDataOptionsAuthnLevel":"Niveau d'authentification",
"oidcRPMetaDataOptionsRule":"Règle d'accès",
"oidcRPMetaDataMacros":"Macros",
"oidcOPMetaDataOptionsScope":"Étendue",
@ -847,6 +849,7 @@
"sfaTitle":"Seconds facteurs d'authentification",
"sfExtra":"Seconds facteurs additionnels",
"sfManagerRule":"Afficher le lien du Gestionnaire",
"sfOnlyUpgrade": "Utiliser le second facteur pour augmenter le niveau d'authentification",
"sfRequired":"Exiger l'enrôlement d'un SF à l'authentification",
"sfRemovedNotification":"Avertir si un SF expiré est supprimé",
"sfRemovedMsgRule":"Activation",
@ -862,6 +865,7 @@
"singleSession":"Une seule session par utilisateur",
"singleUserByIP":"Un seul utilisateur par adresse IP",
"skipRenewConfirmation":"Éviter la confirmation de ré-authentification",
"skipUpgradeConfirmation":"Éviter la confirmation d'élévation du niveau d'authentification",
"slaveAuthnLevel":"Niveau d'authentification",
"slaveDisplayLogo":"Afficher le logo d'authentification",
"slaveExportedVars":"Variables exportées",
@ -1074,6 +1078,7 @@
"samlSPMetaDataOptionsSessionNotOnOrAfterTimeout":"Durée sessionNotOnOrAfter",
"samlSPMetaDataOptionsNotOnOrAfterTimeout":"Durée notOnOrAfter",
"samlSPMetaDataOptionsForceUTF8":"Forcer l'UTF-8",
"samlSPMetaDataOptionsAuthnLevel":"Niveau d'authentification",
"samlSPMetaDataOptionsRule":"Règle d'accès",
"samlSPMetaDataMacros":"Macros",
"samlIDPName":"Nom du fournisseur d'identité SAML",

View File

@ -122,6 +122,7 @@
"casAppMetaDataNodes":"Applicazioni CAS",
"casAppMetaDataOptions":"Opzioni",
"casAppMetaDataOptionsService":"URL del servizio",
"casAppMetaDataOptionsAuthnLevel":"Livello di autenticazione",
"casAppMetaDataOptionsRule":"Regola",
"casAppMetaDataMacros":"Macro",
"casAppMetaDataOptionsUserAttribute":"Attributo utente",
@ -593,6 +594,7 @@
"oidcOPMetaDataOptionsProtocol":"Protocollo",
"oidcRPMetaDataOptionsPublic":"Cliente pubblico",
"oidcRPMetaDataOptionsRequirePKCE":"Richiedi PKCE",
"oidcRPMetaDataOptionsAuthnLevel":"Livello di autenticazione",
"oidcRPMetaDataOptionsRule":"Regola di accesso",
"oidcRPMetaDataMacros":"Macro",
"oidcOPMetaDataOptionsScope":"Scopo",
@ -847,6 +849,7 @@
"sfaTitle":"Autenticazione a due fattori",
"sfExtra":"Additional second factors",
"sfManagerRule":"Display Manager link",
"sfOnlyUpgrade": "Use 2FA for session upgrade",
"sfRequired":"Force 2FA registration at login",
"sfRemovedNotification":"Warn if an expired 2FA is removed",
"sfRemovedMsgRule":"Attivazione",
@ -862,6 +865,7 @@
"singleSession":"One session per user",
"singleUserByIP":"One user per IP address",
"skipRenewConfirmation":"Salta la conferma di re-auth",
"skipUpgradeConfirmation":"Skip upgrade confirmation",
"slaveAuthnLevel":"Livello di autenticazione",
"slaveDisplayLogo":"Display authentication logo",
"slaveExportedVars":"Variabili esportate",
@ -1074,6 +1078,7 @@
"samlSPMetaDataOptionsSessionNotOnOrAfterTimeout":"Durata sessionNotOnOrAfter ",
"samlSPMetaDataOptionsNotOnOrAfterTimeout":"Durata di notOnOrAfter ",
"samlSPMetaDataOptionsForceUTF8":"Forza UTF-8",
"samlSPMetaDataOptionsAuthnLevel":"Livello di autenticazione",
"samlSPMetaDataOptionsRule":"Regola di accesso",
"samlSPMetaDataMacros":"Macro",
"samlIDPName":"Nome di SAML IDP ",
@ -1141,4 +1146,4 @@
"samlRelayStateTimeout":"Timeout di sessione di RelayState",
"samlUseQueryStringSpecific":"Utilizza il metodo specifico query_string",
"samlOverrideIDPEntityID":"Sostituisci l'ID entità quando agisce come IDP"
}
}

View File

@ -122,6 +122,7 @@
"casAppMetaDataNodes":"Aplikacje CAS",
"casAppMetaDataOptions":"Opcje",
"casAppMetaDataOptionsService":"URL usługi",
"casAppMetaDataOptionsAuthnLevel":"Poziom uwierzytelnienia",
"casAppMetaDataOptionsRule":"Reguła",
"casAppMetaDataMacros":"Makra",
"casAppMetaDataOptionsUserAttribute":"Atrybut użytkownika",
@ -593,6 +594,7 @@
"oidcOPMetaDataOptionsProtocol":"Protokół",
"oidcRPMetaDataOptionsPublic":"Klient publiczny",
"oidcRPMetaDataOptionsRequirePKCE":"Wymagaj PKCE",
"oidcRPMetaDataOptionsAuthnLevel":"Poziom uwierzytelnienia",
"oidcRPMetaDataOptionsRule":"Reguła dostępu",
"oidcRPMetaDataMacros":"Makra",
"oidcOPMetaDataOptionsScope":"Zakres",
@ -847,6 +849,7 @@
"sfaTitle":"Drugi czynnik uwierzytelniania",
"sfExtra":"Dodatkowe drugie czynniki",
"sfManagerRule":"Link do Menedżera wyświetlania",
"sfOnlyUpgrade": "Use 2FA for session upgrade",
"sfRequired":"Wymuś rejestrację 2FA przy logowaniu",
"sfRemovedNotification":"Ostrzeż, gdy przeterminowany 2FA został usunięty",
"sfRemovedMsgRule":"Aktywacja",
@ -862,6 +865,7 @@
"singleSession":"Jedna sesja na użytkownika",
"singleUserByIP":"Jeden użytkownik na adres IP",
"skipRenewConfirmation":"Pomiń potwierdzenie ponownego uwierzytelnienia",
"skipUpgradeConfirmation":"Skip upgrade confirmation",
"slaveAuthnLevel":"Poziom uwierzytelnienia",
"slaveDisplayLogo":"Wyświetl logo uwierzytelniające",
"slaveExportedVars":"Wyeksportowane zmienne",
@ -1074,6 +1078,7 @@
"samlSPMetaDataOptionsSessionNotOnOrAfterTimeout":"czas trwania sessionNotOnOrAfter",
"samlSPMetaDataOptionsNotOnOrAfterTimeout":"czas trwania notOnOrAfter",
"samlSPMetaDataOptionsForceUTF8":"Wymuś UTF-8",
"samlSPMetaDataOptionsAuthnLevel":"Poziom uwierzytelnienia",
"samlSPMetaDataOptionsRule":"Reguła dostępu",
"samlSPMetaDataMacros":"Makra",
"samlIDPName":"Nazwa IDP SAML",
@ -1141,4 +1146,4 @@
"samlRelayStateTimeout":"Limit czasu sesji RelayState",
"samlUseQueryStringSpecific":"Użyj określonej metody query_string",
"samlOverrideIDPEntityID":"Zastąp identyfikator jednostki podczas działania jako IDP"
}
}

View File

@ -122,6 +122,7 @@
"casAppMetaDataNodes":"CAS Uygulamaları",
"casAppMetaDataOptions":"Seçenekler",
"casAppMetaDataOptionsService":"Servis URL'si",
"casAppMetaDataOptionsAuthnLevel":"Doğrulama seviyesi",
"casAppMetaDataOptionsRule":"Kural",
"casAppMetaDataMacros":"Makrolar",
"casAppMetaDataOptionsUserAttribute":"Kullanıcı niteliği",
@ -593,6 +594,7 @@
"oidcOPMetaDataOptionsProtocol":"Protokol",
"oidcRPMetaDataOptionsPublic":"Açık istemci",
"oidcRPMetaDataOptionsRequirePKCE":"PKCE gerektir",
"oidcRPMetaDataOptionsAuthnLevel":"Doğrulama seviyesi",
"oidcRPMetaDataOptionsRule":"Erişim kuralı",
"oidcRPMetaDataMacros":"Makrolar",
"oidcOPMetaDataOptionsScope":"Kapsam",
@ -847,6 +849,7 @@
"sfaTitle":"İki faktörlü kimlik doğrulaması",
"sfExtra":"Ek ikinci faktörler",
"sfManagerRule":"Yönetici bağlantısını görüntüle",
"sfOnlyUpgrade": "Use 2FA for session upgrade",
"sfRequired":"Girişte 2FA kayıtlanmaya zorla",
"sfRemovedNotification":"Süresi dolan 2FA kaldırıldığında uyar",
"sfRemovedMsgRule":"Aktivasyon",
@ -862,6 +865,7 @@
"singleSession":"Her kullanıcı için bir oturum",
"singleUserByIP":"Her IP adresi için bir kullanıcı",
"skipRenewConfirmation":"Yeniden yetkilendirme doğrulamasını geç",
"skipUpgradeConfirmation":"Skip upgrade confirmation",
"slaveAuthnLevel":"Doğrulama seviyesi",
"slaveDisplayLogo":"Doğrulama logosunu görüntüle",
"slaveExportedVars":"Dışa aktarılan değişkenler",
@ -1074,6 +1078,7 @@
"samlSPMetaDataOptionsSessionNotOnOrAfterTimeout":"sessionNotOnOrAfter süresi",
"samlSPMetaDataOptionsNotOnOrAfterTimeout":"notOnOrAfter süresi",
"samlSPMetaDataOptionsForceUTF8":"UTF-8'e zorla",
"samlSPMetaDataOptionsAuthnLevel":"Doğrulama seviyesi",
"samlSPMetaDataOptionsRule":"Erişim kuralı",
"samlSPMetaDataMacros":"Makrolar",
"samlIDPName":"SAML IDP Adı",
@ -1141,4 +1146,4 @@
"samlRelayStateTimeout":"RelayState oturum zaman aşımı",
"samlUseQueryStringSpecific":"Spesifik query_string metodu kullan",
"samlOverrideIDPEntityID":"IDP olarak davrandığında Varlık ID'yi geçersiz kıl"
}
}

View File

@ -122,6 +122,7 @@
"casAppMetaDataNodes":"Ứng dụng CAS",
"casAppMetaDataOptions":"Tùy chọn",
"casAppMetaDataOptionsService":"Dịch vụ URL",
"casAppMetaDataOptionsAuthnLevel":"Mức xác thực",
"casAppMetaDataOptionsRule":"Quy tắc",
"casAppMetaDataMacros":"Macros",
"casAppMetaDataOptionsUserAttribute":"thuộc tính người dùng",
@ -593,6 +594,7 @@
"oidcOPMetaDataOptionsProtocol":"Giao thức",
"oidcRPMetaDataOptionsPublic":"Public client",
"oidcRPMetaDataOptionsRequirePKCE":"Require PKCE",
"oidcRPMetaDataOptionsAuthnLevel":"Mức xác thực",
"oidcRPMetaDataOptionsRule":"Quy tắc truy cập",
"oidcRPMetaDataMacros":"Macros",
"oidcOPMetaDataOptionsScope":"Phạm vi",
@ -847,6 +849,7 @@
"sfaTitle":"Second factors authentication",
"sfExtra":"Additional second factors",
"sfManagerRule":"Display Manager link",
"sfOnlyUpgrade": "Use 2FA for session upgrade",
"sfRequired":"Force 2FA registration at login",
"sfRemovedNotification":"Warn if an expired 2FA is removed",
"sfRemovedMsgRule":"Kích hoạt",
@ -862,6 +865,7 @@
"singleSession":"One session per user",
"singleUserByIP":"Một người dùng theo địa chỉ IP",
"skipRenewConfirmation":"Skip re-auth confirmation",
"skipUpgradeConfirmation":"Skip upgrade confirmation",
"slaveAuthnLevel":"Mức xác thực",
"slaveDisplayLogo":"Display authentication logo",
"slaveExportedVars":"Biến đã được xuất",
@ -1074,6 +1078,7 @@
"samlSPMetaDataOptionsSessionNotOnOrAfterTimeout":"thời gian sessionNotOnOrAfter ",
"samlSPMetaDataOptionsNotOnOrAfterTimeout":"Thời gian notOnOrAfter ",
"samlSPMetaDataOptionsForceUTF8":"Bắt buộc UTF-8",
"samlSPMetaDataOptionsAuthnLevel":"Mức xác thực",
"samlSPMetaDataOptionsRule":"Quy tắc truy cập",
"samlSPMetaDataMacros":"Macros",
"samlIDPName":"Tên SAML IDP ",
@ -1141,4 +1146,4 @@
"samlRelayStateTimeout":"Thời gian hết hạn phiên RelayState ",
"samlUseQueryStringSpecific":"Sử dụng phương pháp query_string cụ thể",
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
}
}

View File

@ -122,6 +122,7 @@
"casAppMetaDataNodes":"CAS 系列应用",
"casAppMetaDataOptions":"选项",
"casAppMetaDataOptionsService":"服务 URL",
"casAppMetaDataOptionsAuthnLevel":"认证等级",
"casAppMetaDataOptionsRule":"规则",
"casAppMetaDataMacros":"Macros",
"casAppMetaDataOptionsUserAttribute":"User attribute",
@ -593,6 +594,7 @@
"oidcOPMetaDataOptionsProtocol":"Protocol",
"oidcRPMetaDataOptionsPublic":"Public client",
"oidcRPMetaDataOptionsRequirePKCE":"Require PKCE",
"oidcRPMetaDataOptionsAuthnLevel":"认证等级",
"oidcRPMetaDataOptionsRule":"Access rule",
"oidcRPMetaDataMacros":"Macros",
"oidcOPMetaDataOptionsScope":"Scope",
@ -847,6 +849,7 @@
"sfaTitle":"Second factors authentication",
"sfExtra":"Additional second factors",
"sfManagerRule":"Display Manager link",
"sfOnlyUpgrade": "Use 2FA for session upgrade",
"sfRequired":"Force 2FA registration at login",
"sfRemovedNotification":"Warn if an expired 2FA is removed",
"sfRemovedMsgRule":"激活",
@ -862,6 +865,7 @@
"singleSession":"One session per user",
"singleUserByIP":"One user per IP address",
"skipRenewConfirmation":"Skip re-auth confirmation",
"skipUpgradeConfirmation":"Skip upgrade confirmation",
"slaveAuthnLevel":"认证等级",
"slaveDisplayLogo":"Display authentication logo",
"slaveExportedVars":"Exported variables",
@ -1074,6 +1078,7 @@
"samlSPMetaDataOptionsSessionNotOnOrAfterTimeout":"sessionNotOnOrAfter duration",
"samlSPMetaDataOptionsNotOnOrAfterTimeout":"notOnOrAfter duration",
"samlSPMetaDataOptionsForceUTF8":"Force UTF-8",
"samlSPMetaDataOptionsAuthnLevel":"认证等级",
"samlSPMetaDataOptionsRule":"Access rule",
"samlSPMetaDataMacros":"Macros",
"samlIDPName":"SAML IDP Name",
@ -1141,4 +1146,4 @@
"samlRelayStateTimeout":"RelayState session timeout",
"samlUseQueryStringSpecific":"Use specific query_string method",
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -19,6 +19,7 @@ use Lemonldap::NG::Portal::Main::Constants qw(
PE_OK
PE_SENDRESPONSE
PE_TOKENEXPIRED
PE_NO_SECOND_FACTORS
);
our $VERSION = '2.0.8';
@ -198,7 +199,30 @@ sub run {
$self->logger->debug("2F checkLogins set") if ($checkLogins);
# Skip 2F unless a module has been registered
return PE_OK unless ( @{ $self->sfModules } );
unless ( @{ $self->sfModules } ) {
if ( $self->conf->{sfOnlyUpgrade} and $req->data->{doingSfUpgrade} ) {
$self->logger->error(
"Trying to perform 2FA session upgrade but no "
. "second factor modules are configured" );
return PE_ERROR;
}
else {
return PE_OK;
}
}
# Skip 2F if authnLevel is already high enough
if (
$self->conf->{sfOnlyUpgrade}
and ( ( $req->pdata->{targetAuthnLevel} || 0 ) <=
( $req->sessionInfo->{authenticationLevel} || 0 ) )
)
{
$self->logger->debug(
"Current authentication level satisfied target service,"
. " skipping 2FA" );
return PE_OK;
}
# Remove expired 2F devices
my $session = $req->sessionInfo;
@ -296,7 +320,16 @@ sub run {
return PE_SENDRESPONSE;
}
else {
return PE_OK;
if ( $self->conf->{sfOnlyUpgrade} and $req->data->{doingSfUpgrade} )
{
# cancel redirection to issuer/vhost
delete $req->pdata->{_url};
return PE_NO_SECOND_FACTORS;
}
else {
return PE_OK;
}
}
}

View File

@ -100,6 +100,13 @@ sub storeEnvAndCheckGateway {
if ($app) {
$req->env->{llng_cas_app} = $app;
# Store target authentication level in pdata
my $targetAuthnLevel = $self->conf->{casAppMetaDataOptions}->{$app}
->{casAppMetaDataOptionsAuthnLevel};
$req->pdata->{targetAuthnLevel} = $targetAuthnLevel
if $targetAuthnLevel;
}
}
@ -150,20 +157,6 @@ sub run {
|| $req->param('gateway');
my $casServiceTicket;
# Renew
if ( $renew
and $renew eq 'true'
and time - $req->sessionInfo->{_utime} >
$self->conf->{portalForceAuthnInterval} )
{
# Authentication must be replayed
$self->logger->debug("Authentication renew requested");
$self->{updateSession} = 1;
$req->env->{QUERY_STRING} =~ s/renew=true/renew=false/;
return $self->reAuth($req);
}
# If no service defined, exit
unless ( defined $service ) {
$self->logger->debug("No service defined in CAS URL");
@ -177,6 +170,26 @@ sub run {
my ( $host, $uri ) = ( $1, $2 );
my $app = $self->casAppList->{$host};
my $spAuthnLevel =
$self->conf->{casAppMetaDataOptions}->{$app}
->{casAppMetaDataOptionsAuthnLevel} || 0;
# Renew
if ( $renew
and $renew eq 'true'
and time - $req->sessionInfo->{_utime} >
$self->conf->{portalForceAuthnInterval} )
{
# Authentication must be replayed
$self->logger->debug("Authentication renew requested");
$self->{updateSession} = 1;
$req->env->{QUERY_STRING} =~ s/renew=true/renew=false/;
$req->pdata->{targetAuthnLevel} = $spAuthnLevel;
return $self->reAuth($req);
}
# Check access on the service
my $casAccessControlPolicy = $self->conf->{casAccessControlPolicy};
@ -188,6 +201,21 @@ sub run {
$self->userLogger->error('CAS service not configured');
return PE_CAS_SERVICE_NOT_ALLOWED;
}
# Check if we have sufficient auth level
my $authenticationLevel =
$req->{sessionInfo}->{authenticationLevel} || 0;
if ( $authenticationLevel < $spAuthnLevel ) {
$self->logger->debug(
"Insufficient authentication level for service $app"
. " (has: $authenticationLevel, want: $spAuthnLevel)" );
# Reauth with sp auth level as target
$req->pdata->{targetAuthnLevel} = $spAuthnLevel;
return $self->upgradeAuth($req);
}
# Check access rule
if ( my $rule = $self->spRules->{$app} ) {
if ( $rule->( $req, $req->sessionInfo ) ) {
$self->logger->debug("CAS service $service access allowed");

View File

@ -321,6 +321,9 @@ sub run {
);
}
my $spAuthnLevel = $self->conf->{oidcRPMetaDataOptions}->{$rp}
->{oidcRPMetaDataOptionsAuthnLevel} || 0;
# Check if user needs to be reauthenticated
my $prompt = $oidc_request->{'prompt'};
if (
@ -334,6 +337,7 @@ sub run {
$self->logger->debug(
"Reauthentication required by Relying Party in prompt parameter"
);
$req->pdata->{targetAuthnLevel} = $spAuthnLevel;
return $self->reAuth($req);
}
@ -343,9 +347,23 @@ sub run {
$self->logger->debug(
"Reauthentication forced because authentication time ($_lastAuthnUTime) is too old (>$max_age s)"
);
$req->pdata->{targetAuthnLevel} = $spAuthnLevel;
return $self->reAuth($req);
}
# Check if we have sufficient auth level
my $authenticationLevel =
$req->{sessionInfo}->{authenticationLevel} || 0;
if ( $authenticationLevel < $spAuthnLevel ) {
$self->logger->debug(
"Insufficient authentication level for service $rp"
. " (has: $authenticationLevel, want: $spAuthnLevel)" );
# Reauth with sp auth level as target
$req->pdata->{targetAuthnLevel} = $spAuthnLevel;
return $self->upgradeAuth($req);
}
# Check scope validity
# We use a slightly more relaxed version of
# https://tools.ietf.org/html/rfc6749#appendix-A.4
@ -2162,6 +2180,12 @@ sub exportRequestParameters {
if ( $req->param('client_id') ) {
my $rp = $self->getRP( $req->param('client_id') );
$req->env->{"llng_oidc_rp"} = $rp if $rp;
# Store target authentication level in pdata
my $targetAuthnLevel = $self->conf->{oidcRPMetaDataOptions}->{$rp}
->{oidcRPMetaDataOptionsAuthnLevel};
$req->pdata->{targetAuthnLevel} = $targetAuthnLevel
if $targetAuthnLevel;
}
return PE_OK;

View File

@ -185,6 +185,13 @@ sub storeEnv {
$req->env->{llng_saml_sp} = $sp;
if ( my $spConfKey = $self->spList->{$sp}->{confKey} ) {
$req->env->{llng_saml_spconfkey} = $spConfKey;
# Store target authentication level in pdata
my $targetAuthnLevel =
$self->conf->{samlSPMetaDataOptions}->{$spConfKey}
->{samlSPMetaDataOptionsAuthnLevel};
$req->pdata->{targetAuthnLevel} = $targetAuthnLevel
if $targetAuthnLevel;
}
}
return PE_OK;
@ -389,6 +396,7 @@ sub run {
$self->logger->debug("$sp match $spConfKey SP in configuration");
$req->env->{llng_saml_spconfkey} = $spConfKey;
# Check access rule
if ( my $rule = $self->spRules->{$spConfKey} ) {
unless ( $rule->( $req, $req->sessionInfo ) ) {
$self->userLogger->warn( 'User '
@ -450,6 +458,10 @@ sub run {
$self->logger->debug("SSO: authentication request is valid");
my $spAuthnLevel =
$self->conf->{samlSPMetaDataOptions}->{$spConfKey}
->{samlSPMetaDataOptionsAuthnLevel} || 0;
# Get ForceAuthn flag
my $force_authn;
@ -477,6 +489,7 @@ sub run {
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
# Replay authentication process
$req->pdata->{targetAuthnLevel} = $spAuthnLevel;
return $self->reAuth($req);
}
@ -486,9 +499,18 @@ sub run {
unless ( $self->checkDestination( $login->request, $url ) );
}
# Map authenticationLevel with SAML2 authentication context
# Check if we have sufficient auth level
my $authenticationLevel =
$req->{sessionInfo}->{authenticationLevel};
$req->{sessionInfo}->{authenticationLevel} || 0;
if ( $authenticationLevel < $spAuthnLevel ) {
$self->logger->debug(
"Insufficient authentication level for service $spConfKey"
. " (has: $authenticationLevel, want: $spAuthnLevel)" );
# Reauth with sp auth level as target
$req->pdata->{targetAuthnLevel} = $spAuthnLevel;
return $self->upgradeAuth($req);
}
$authn_context =
$self->authnLevel2authnContext($authenticationLevel);

View File

@ -105,6 +105,8 @@ use constant {
PE_RESETCERTIFICATE_FIRSTACCESS => 99,
PE_PP_NOT_ALLOWED_CHARACTER => 100,
PE_PP_NOT_ALLOWED_CHARACTERS => 101,
PE_UPGRADESESSION => 102,
PE_NO_SECOND_FACTORS => 103,
};
sub portalConsts {
@ -119,6 +121,8 @@ sub portalConsts {
'10' => 'PE_BADCERTIFICATE',
'100' => 'PE_PP_NOT_ALLOWED_CHARACTER',
'101' => 'PE_PP_NOT_ALLOWED_CHARACTERS',
'102' => 'PE_UPGRADESESSION',
'103' => 'PE_NO_SECOND_FACTORS',
'2' => 'PE_FORMEMPTY',
'21' => 'PE_PP_ACCOUNT_LOCKED',
'22' => 'PE_PP_PASSWORD_EXPIRED',
@ -310,7 +314,9 @@ our @EXPORT_OK = (
'PE_RESETCERTIFICATE_FORMEMPTY',
'PE_RESETCERTIFICATE_FIRSTACCESS',
'PE_PP_NOT_ALLOWED_CHARACTER',
'PE_PP_NOT_ALLOWED_CHARACTERS'
'PE_PP_NOT_ALLOWED_CHARACTERS',
'PE_UPGRADESESSION',
'PE_NO_SECOND_FACTORS'
);
our %EXPORT_TAGS = ( 'all' => [ @EXPORT_OK, 'import' ], );

View File

@ -261,15 +261,20 @@ sub display {
);
}
elsif ( $req->error == PE_RENEWSESSION ) {
# when upgrading session, the administrator can configure LLNG
# to ask only for 2FA
elsif ( $req->error == PE_UPGRADESESSION ) {
$skinfile = 'upgradesession';
%templateParams = (
MAIN_LOGO => $self->conf->{portalMainLogo},
LANGS => $self->conf->{showLanguages},
MSG => 'askToRenew',
CONFIRMKEY => $self->stamp,
PORTAL => $self->conf->{portal},
URL => $req->data->{_url},
MAIN_LOGO => $self->conf->{portalMainLogo},
LANGS => $self->conf->{showLanguages},
FORMACTION => '/upgradesession',
MSG => 'askToUpgrade',
PORTALBUTTON => 1,
BUTTON => 'upgradeSession',
CONFIRMKEY => $self->stamp,
PORTAL => $self->conf->{portal},
URL => $req->data->{_url},
(
$req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} )
@ -278,13 +283,37 @@ sub display {
);
}
# renew uses the same plugin as upgrade, but first factor is mandatory
elsif ( $req->error == PE_RENEWSESSION ) {
$skinfile = 'upgradesession';
%templateParams = (
MAIN_LOGO => $self->conf->{portalMainLogo},
LANGS => $self->conf->{showLanguages},
FORMACTION => '/renewsession',
MSG => 'askToRenew',
CONFIRMKEY => $self->stamp,
PORTAL => $self->conf->{portal},
PORTALBUTTON => 1,
BUTTON => 'renewSession',
URL => $req->data->{_url},
(
$req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} )
: ()
),
);
}
# Looks a lot like upgradesession, but no portal logo
elsif ( $req->error == PE_MUSTAUTHN ) {
$skinfile = 'updatesession';
$skinfile = 'upgradesession';
%templateParams = (
MAIN_LOGO => $self->conf->{portalMainLogo},
LANGS => $self->conf->{showLanguages},
FORMACTION => '/renewsession',
MSG => 'PE87',
CONFIRMKEY => $self->stamp,
BUTTON => 'renewSession',
PORTAL => $self->conf->{portal},
URL => $req->data->{_url},
(

View File

@ -17,6 +17,7 @@ use Lemonldap::NG::Common::FormEncode;
use Lemonldap::NG::Portal::Main::Constants qw(
PE_OK
PE_RENEWSESSION
PE_UPGRADESESSION
);
extends 'Lemonldap::NG::Portal::Main::Plugin';
@ -250,6 +251,19 @@ qq'<script type="text/javascript" src="$self->{p}->{staticPrefix}/common/js/auto
return PE_RENEWSESSION;
}
sub upgradeAuth {
my ( $self, $req ) = @_;
$req->data->{customScript} =
qq'<script type="text/javascript" src="$self->{p}->{staticPrefix}/common/js/autoRenew.min.js"></script>'
if ( $self->conf->{skipUpgradeConfirmation} );
$req->data->{_url} =
encode_base64( $self->conf->{portal} . $req->path_info, '' );
$req->pdata->{ $self->ipath } = $self->storeRequest($req);
push @{ $req->pdata->{keepPdata} }, $self->ipath, $self->ipath . 'Path';
$req->pdata->{issuerTs} = time;
return PE_UPGRADESESSION;
}
1;
__END__

View File

@ -138,11 +138,18 @@ sub controlUrl {
# Unprotected hosts
my ( $vhost, $appuri ) = $tmp =~ m#^https?://([^/]*)(.*)#;
$vhost =~ s/:\d+$//;
$vhost = 'http://' . $self->HANDLER->resolveAlias($vhost);
# try to resolve alias
my $originalVhost = $self->HANDLER->resolveAlias($vhost);
$vhost = 'http://' . $originalVhost;
$self->logger->debug( "Required URL (param: "
. ( $req->param('logout') ? 'HTTP Referer' : 'urldc' )
. " | value: $tmp | alias: $vhost)" );
# If the target URL has an authLevel set in config, remember it.
my $level = $self->HANDLER->getLevel( $req, $appuri, $originalVhost );
$req->pdata->{targetAuthnLevel} = $level if $level;
if ( $tmp
and !$self->isTrustedUrl($tmp)
and !$self->isTrustedUrl($vhost) )

View File

@ -381,7 +381,7 @@ sub autoRedirect {
)
{
$self->logger->info("Force cleaning pdata");
$req->pdata( {} );
delete $req->{pdata}->{_url};
}
return [ 302, [ Location => $req->{urldc}, $req->spliceHdrs ], [] ];
}

View File

@ -32,14 +32,38 @@ sub init {
"-> Upgrade tokens will be stored into global storage");
$self->ott->cache(undef);
}
$self->addAuthRoute( upgradesession => 'ask', ['GET'] );
$self->addAuthRoute( upgradesession => 'confirm', ['POST'] );
$self->addAuthRoute( upgradesession => 'askUpgrade', ['GET'] );
$self->addAuthRoute( upgradesession => 'confirmUpgrade', ['POST'] );
$self->addAuthRoute( renewsession => 'askRenew', ['GET'] );
$self->addAuthRoute( renewsession => 'confirmRenew', ['POST'] );
}
sub askUpgrade {
my ( $self, $req ) = @_;
$self->ask( $req, '/upgradesession', 'askToUpgrade', 'upgradeSession' );
}
sub askRenew {
my ( $self, $req ) = @_;
$self->ask( $req, '/renewsession', 'askToRenew', 'renewSession' );
}
sub confirmUpgrade {
my ( $self, $req ) = @_;
# sfOnlyUpgrade feature can only be used during session renew
return $self->confirm( $req, $self->conf->{sfOnlyUpgrade} );
}
sub confirmRenew {
my ( $self, $req ) = @_;
return $self->confirm($req);
}
# RUNNING METHOD
sub ask {
my ( $self, $req ) = @_;
my ( $self, $req, $url, $message, $buttonlabel ) = @_;
# Check if auth is already running
if ( $req->param('upgrading') or $req->param('kerberos') ) {
@ -53,19 +77,21 @@ sub ask {
$req,
'upgradesession',
params => {
MAIN_LOGO => $self->conf->{portalMainLogo},
LANGS => $self->conf->{showLanguages},
MSG => 'askToUpgrade',
CONFIRMKEY => $self->p->stamp,
PORTAL => $self->conf->{portal},
URL => $req->param('url'),
MAIN_LOGO => $self->conf->{portalMainLogo},
LANGS => $self->conf->{showLanguages},
FORMACTION => $url,
PORTALBUTTON => 1,
MSG => $message,
BUTTON => $buttonlabel,
CONFIRMKEY => $self->p->stamp,
PORTAL => $self->conf->{portal},
URL => $req->param('url'),
}
);
}
sub confirm {
my ( $self, $req ) = @_;
my ( $self, $req, $sfOnly ) = @_;
my $upg;
if ( $req->param('kerberos') ) {
@ -86,12 +112,31 @@ sub confirm {
return $self->p->do( $req, [ sub { $res } ] ) if ($res);
if ( $upg or $req->param('confirm') == 1 ) {
$req->data->{noerror} = 1;
$self->p->setHiddenFormValue(
$req,
upgrading => $self->ott->createToken,
'', 0
); # Insert token
return $self->p->login($req);
if ($sfOnly) {
$req->data->{doingSfUpgrade} = 1;
# Short circuit the first part of login, only do a 2FA step
return $self->p->do(
$req,
[
'importHandlerData', 'secondFactor',
@{ $self->p->afterData }, $self->p->validSession,
@{ $self->p->endAuth },
]
);
}
else {
$self->p->setHiddenFormValue(
$req,
upgrading => $self->ott->createToken,
'', 0
); # Insert token
# Do a regular login
# Do a regular login
return $self->p->login($req);
}
}
else {
# Go to portal

View File

@ -91,6 +91,8 @@
"PE99":"Please select your new certificate",
"PE100":"Password contains not allowed character",
"PE101":"Password contains not allowed characters",
"PE102":"Session must be upgraded",
"PE103":"No second factors available for your account",
"2fRegRequired":"This service requires a double factor authentication. Register a device now, then go back to the portal.",
"accept":"قبول",
"accessDenied":"ليس لديك إذن بالدخول لهذا التطبيق",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"تم إصدار طلب تسجيل لهذا الحساب من قبل",
"rememberChoice":"تذكر اختياري",
"removeOtherSessions":"إزالة الجلسات الأخرى",
"renewSession":"Renew session",
"resendConfirmMail":"هل تريد إعادة إرسال رسالة التأكيد؟",
"resentConfirm":"هل تريد إعادة إرسال رسالة التأكيد؟",
"resetCertificateOK":"Your certificate has been successfully reset!",
@ -315,4 +318,4 @@
"yourProfile":"Know your profile",
"yourTotpKey":"Your TOTP key",
"yubikey2f":"Yubikey"
}
}

View File

@ -91,6 +91,8 @@
"PE99":"Please select your new certificate",
"PE100":"Password contains not allowed character",
"PE101":"Password contains not allowed characters",
"PE102":"Session must be upgraded",
"PE103":"No second factors available for your account",
"2fRegRequired":"Dieser Dienst benötigt Zwei-Faktor-Authentifizierung. Bitte legen Sie ein Gerät an und gehen dann zum Portal zurück.",
"accept":"Akzeptieren",
"accessDenied":"Sie haben keine Zugriffsberechtigung für diese Anwendung",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"Eine Registrierungsanforderung für dieses Konto wurde bereits gestellt am",
"rememberChoice":"Meine Auswahl merken",
"removeOtherSessions":"Andere Sitzungen löschen",
"renewSession":"Renew session",
"resendConfirmMail":"Bestätigungsmail erneuert senden ?",
"resentConfirm":"Möchtest du, dass die Bestätigungsmail erneut gesendet wird ?",
"resetCertificateOK":"Your certificate has been successfully reset!",
@ -315,4 +318,4 @@
"yourProfile":"Know your profile",
"yourTotpKey":"Your TOTP key",
"yubikey2f":"Yubikey"
}
}

View File

@ -91,6 +91,8 @@
"PE99":"Please select your new certificate",
"PE100":"Password contains not allowed character",
"PE101":"Password contains not allowed characters",
"PE102":"Session must be upgraded",
"PE103":"No second factors available for your account",
"2fRegRequired":"This service requires a double factor authentication. Register a device now, then go back to the portal.",
"accept":"Accept",
"accessDenied":"You have no access authorization for this application",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"A register request for this account was already issued on ",
"rememberChoice":"Remember my choice",
"removeOtherSessions":"Remove other sessions",
"renewSession":"Renew session",
"resendConfirmMail":"Resend confirmation mail?",
"resentConfirm":"Do you want the confirmation mail to be resent?",
"resetCertificateOK":"Your certificate has been successfully reset!",

View File

@ -91,6 +91,8 @@
"PE99":"Por favor, seleccione su nuevo certificado",
"PE100":"Password contains not allowed character",
"PE101":"Password contains not allowed characters",
"PE102":"Session must be upgraded",
"PE103":"No second factors available for your account",
"2fRegRequired":"Este servicio necesita la autenticación de dos factores. Registre un dispositivo ahora, luego reingrese al portal.",
"accept":"Aceptar",
"accessDenied":"No está autorizado a acceder a esta aplicación",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"Ya fue expedida una solicitud de registro para esta cuenta",
"rememberChoice":"Recordar mi elección",
"removeOtherSessions":"Suprimir las otras sesiones",
"renewSession":"Renew session",
"resendConfirmMail":"¿Reenviar e-mail de confirmación?",
"resentConfirm":"¿Desea que el e-mail de confirmación sea reenviado?",
"resetCertificateOK":"Su certificado ha sido reiniciado con éxito",
@ -315,4 +318,4 @@
"yourProfile":"Conozca su perfil",
"yourTotpKey":"Su llave TOTP",
"yubikey2f":"Yubikey"
}
}

View File

@ -91,6 +91,8 @@
"PE99":"Please select your new certificate",
"PE100":"Password contains not allowed character",
"PE101":"Password contains not allowed characters",
"PE102":"Session must be upgraded",
"PE103":"No second factors available for your account",
"2fRegRequired":"This service requires a double factor authentication. Register a device now, then go back to the portal.",
"accept":"Hyväksy",
"accessDenied":"Sinulla ei ole käyttöoikeutta tähän sovellukseen",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"A register request for this account was already issued on ",
"rememberChoice":"Muista valintani",
"removeOtherSessions":"Remove other sessions",
"renewSession":"Renew session",
"resendConfirmMail":"Uudelleen lähetä vahvistus sähköposti?",
"resentConfirm":"Do you want the confirmation mail to be resent?",
"resetCertificateOK":"Your certificate has been successfully reset!",
@ -315,4 +318,4 @@
"yourProfile":"Know your profile",
"yourTotpKey":"Your TOTP key",
"yubikey2f":"Yubikey"
}
}

View File

@ -91,6 +91,8 @@
"PE99":"Veuillez sélectionner votre nouveau certificat",
"PE100":"Le mot de passe contient un caractère interdit",
"PE101":"Le mot de passe contient des caractères interdits",
"PE102":"Mise à niveau de la session",
"PE103":"Aucun second facteur disponible pour votre compte",
"2fRegRequired":"Ce service requiert une authentification à deux facteurs. Enregistrez un équipement ici et retournez au portail.",
"accept":"Accepter",
"accessDenied":"Vous n'avez pas les droits d'accès à cette application",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"Une demande de création pour ce compte a déjà été faite le ",
"rememberChoice":"Se souvenir de mon choix",
"removeOtherSessions":"Fermer les autres sessions",
"renewSession":"Renouveller la session",
"resendConfirmMail":"Renvoyer le mail de confirmation ?",
"resentConfirm":"Voulez-vous que le message de confirmation soit renvoyé ?",
"resetCertificateOK":"Votre certificat a bien été réinitialisé!",

View File

@ -91,6 +91,8 @@
"PE99":"Please select your new certificate",
"PE100":"Password contains not allowed character",
"PE101":"Password contains not allowed characters",
"PE102":"Session must be upgraded",
"PE103":"No second factors available for your account",
"2fRegRequired":"Questo servizio richiede un'autenticazione a doppio fattore. Registrare un dispositivo ora, quindi tornare al portale.",
"accept":"Accetta",
"accessDenied":"Non hai un'autorizzazione di accesso per questa applicazione",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"Una richiesta di registrazione per questo conto é già stata rilasciata il",
"rememberChoice":"Ricordarsi della mia scelta",
"removeOtherSessions":"Rimuovere altre sessioni",
"renewSession":"Renew session",
"resendConfirmMail":"Inviare nuovamente mail di conferma?",
"resentConfirm":"Vuoi inviare di nuovo la mail di conferma?",
"resetCertificateOK":"Your certificate has been successfully reset!",
@ -315,4 +318,4 @@
"yourProfile":"Know your profile",
"yourTotpKey":"La tua chiave TOTP",
"yubikey2f":"Yubikey"
}
}

View File

@ -91,6 +91,8 @@
"PE99":"Please select your new certificate",
"PE100":"Password contains not allowed character",
"PE101":"Password contains not allowed characters",
"PE102":"Session must be upgraded",
"PE103":"No second factors available for your account",
"2fRegRequired":"This service requires a double factor authentication. Register a device now, then go back to the portal.",
"accept":"Accept",
"accessDenied":"You have no access authorization for this application",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"A register request for this account was already issued on ",
"rememberChoice":"Remember my choice",
"removeOtherSessions":"Remove other sessions",
"renewSession":"Renew session",
"resendConfirmMail":"Resend confirmation mail?",
"resentConfirm":"Do you want the confirmation mail to be resent?",
"resetCertificateOK":"Your certificate has been successfully reset!",
@ -315,4 +318,4 @@
"yourProfile":"Know your profile",
"yourTotpKey":"Your TOTP key",
"yubikey2f":"Yubikey"
}
}

View File

@ -91,6 +91,8 @@
"PE99":"Wybierz nowy certyfikat",
"PE100":"Hasło zawiera niedozwolony znak",
"PE101":"Hasło zawiera niedozwolone znaki",
"PE102":"Session must be upgraded",
"PE103":"No second factors available for your account",
"2fRegRequired":"Ta usługa wymaga podwójnego uwierzytelnienia. Zarejestruj urządzenie 2ndFA teraz, a następnie wróć do portalu.",
"accept":"Akceptuj",
"accessDenied":"Nie masz dostępu do tej aplikacji",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"Wniosek o rejestrację tego konta został już złożony w dniu ",
"rememberChoice":"Zapamiętaj mój wybór",
"removeOtherSessions":"Usuń inne sesje",
"renewSession":"Renew session",
"resendConfirmMail":"Czy wysłać ponownie wiadomość z potwierdzeniem?",
"resentConfirm":"Czy chcesz, aby wiadomość z potwierdzeniem została ponownie wysłana?",
"resetCertificateOK":"Twój certyfikat został pomyślnie zresetowany!",
@ -315,4 +318,4 @@
"yourProfile":"Twój profil",
"yourTotpKey":"Twój klucz TOTP",
"yubikey2f":"Yubikey"
}
}

View File

@ -91,6 +91,8 @@
"PE99":"Please select your new certificate",
"PE100":"Password contains not allowed character",
"PE101":"Password contains not allowed characters",
"PE102":"Session must be upgraded",
"PE103":"No second factors available for your account",
"2fRegRequired":"This service requires a double factor authentication. Register a device now, then go back to the portal.",
"accept":"Accept",
"accessDenied":"You have no access authorization for this application",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"A register request for this account was already issued on ",
"rememberChoice":"Remember my choice",
"removeOtherSessions":"Remove other sessions",
"renewSession":"Renew session",
"resendConfirmMail":"Resend confirmation mail?",
"resentConfirm":"Do you want the confirmation mail to be resent?",
"resetCertificateOK":"Your certificate has been successfully reset!",
@ -315,4 +318,4 @@
"yourProfile":"Know your profile",
"yourTotpKey":"Your TOTP key",
"yubikey2f":"Yubikey"
}
}

View File

@ -91,6 +91,8 @@
"PE99":"Please select your new certificate",
"PE100":"Password contains not allowed character",
"PE101":"Password contains not allowed characters",
"PE102":"Session must be upgraded",
"PE103":"No second factors available for your account",
"2fRegRequired":"This service requires a double factor authentication. Register a device now, then go back to the portal.",
"accept":"Accept",
"accessDenied":"You have no access authorization for this application",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"A register request for this account was already issued on ",
"rememberChoice":"Remember my choice",
"removeOtherSessions":"Remove other sessions",
"renewSession":"Renew session",
"resendConfirmMail":"Resend confirmation mail?",
"resentConfirm":"Do you want the confirmation mail to be resent?",
"resetCertificateOK":"Your certificate has been successfully reset!",
@ -315,4 +318,4 @@
"yourProfile":"Know your profile",
"yourTotpKey":"Your TOTP key",
"yubikey2f":"Yubikey"
}
}

View File

@ -91,6 +91,8 @@
"PE99":"Lütfen yeni sertifikanızı seçin",
"PE100":"Parola izin verilmeyen karakter içeriyor",
"PE101":"Parola izin verilmeyen karakterler içeriyor",
"PE102":"Session must be upgraded",
"PE103":"No second factors available for your account",
"2fRegRequired":"Bu servis iki adımlı kimlik doğrulama gerektiriyor. Şimdi bir cihaz ekleyin ve ardından portala geri dönün",
"accept":"Kabul Et",
"accessDenied":"Bu uygulamaya erişim yetkiniz yok",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"Bu hesap için kayıt olma isteği zaten şu tarihte alındı:",
"rememberChoice":"Seçimimi hatırla",
"removeOtherSessions":"Diğer oturumları sil",
"renewSession":"Renew session",
"resendConfirmMail":"Doğrulama e-postasını tekrar gönder?",
"resentConfirm":"Onay e-postasının tekrar gönderilmesini ister misiniz?",
"resetCertificateOK":"Sertifikanız başarıyla sıfırlandı!",
@ -315,4 +318,4 @@
"yourProfile":"Profilini bil",
"yourTotpKey":"TOTP anahtarınız",
"yubikey2f":"Yubikey"
}
}

View File

@ -91,6 +91,8 @@
"PE99":"Please select your new certificate",
"PE100":"Password contains not allowed character",
"PE101":"Password contains not allowed characters",
"PE102":"Session must be upgraded",
"PE103":"No second factors available for your account",
"2fRegRequired":"This service requires a double factor authentication. Register a device now, then go back to the portal.",
"accept":"Chấp nhận",
"accessDenied":"Bạn không có quyền truy cập vào ứng dụng này",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"Yêu cầu đăng ký cho tài khoản này đã được cấp phát",
"rememberChoice":"Hãy nhớ sự lựa chọn của tôi",
"removeOtherSessions":"Xóa các phiên khác",
"renewSession":"Renew session",
"resendConfirmMail":"Gửi lại thư xác nhận?",
"resentConfirm":"Bạn có muốn gửi lại thư xác nhận không?",
"resetCertificateOK":"Your certificate has been successfully reset!",
@ -315,4 +318,4 @@
"yourProfile":"Know your profile",
"yourTotpKey":"Your TOTP key",
"yubikey2f":"Yubikey"
}
}

View File

@ -91,6 +91,8 @@
"PE99":"Please select your new certificate",
"PE100":"Password contains not allowed character",
"PE101":"Password contains not allowed characters",
"PE102":"Session must be upgraded",
"PE103":"No second factors available for your account",
"2fRegRequired":"This service requires a double factor authentication. Register a device now, then go back to the portal.",
"accept":"Accept 方法",
"accessDenied":"您无权访问此应用",
@ -249,6 +251,7 @@
"registerRequestAlreadyIssued":"此账户已存在一个注册请求",
"rememberChoice":"记住我的选择",
"removeOtherSessions":"移除其他会话",
"renewSession":"Renew session",
"resendConfirmMail":"重新发送确认邮件?",
"resentConfirm":"您想确认邮件被重新发送吗?",
"resetCertificateOK":"Your certificate has been successfully reset!",
@ -315,4 +318,4 @@
"yourProfile":"Know your profile",
"yourTotpKey":"Your TOTP key",
"yubikey2f":"Yubikey"
}
}

View File

@ -1,20 +0,0 @@
<TMPL_INCLUDE NAME="header.tpl">
<div id="errorcontent" class="container">
<div class="message message-positive alert"><span trspan="<TMPL_VAR NAME="MSG">"></span></div>
<form id="upgrd" action="/upgradesession" method="post" class="password" role="form">
<input type="hidden" name="confirm" value="<TMPL_VAR NAME="CONFIRMKEY">">
<input type="hidden" name="url" value="<TMPL_VAR NAME="URL">">
<div class="buttons">
<button type="submit" class="btn btn-success">
<span class="fa fa-sign-in"></span>
<span trspan="upgradeSession">Upgrade session</span>
</button>
</div>
</form>
</div>
<TMPL_INCLUDE NAME="footer.tpl">

View File

@ -4,18 +4,20 @@
<div class="message message-positive alert"><span trspan="<TMPL_VAR NAME="MSG">"></span></div>
<form id="upgrd" action="/upgradesession" method="post" class="password" role="form">
<form id="upgrd" action="<TMPL_VAR NAME="FORMACTION">" method="post" class="password" role="form">
<input type="hidden" name="confirm" value="<TMPL_VAR NAME="CONFIRMKEY">">
<input type="hidden" name="url" value="<TMPL_VAR NAME="URL">">
<div class="buttons">
<button type="submit" class="btn btn-success">
<span class="fa fa-sign-in"></span>
<span trspan="upgradeSession">Upgrade session</span>
<span trspan="<TMPL_VAR NAME="BUTTON">">Upgrade session</span>
</button>
<TMPL_IF NAME="PORTALBUTTON">
<a href="<TMPL_VAR NAME="PORTAL_URL">" class="btn btn-primary" role="button">
<span class="fa fa-home"></span>
<span trspan="goToPortal">Go to portal</span>
</a>
</TMPL_IF>
</div>
</form>

View File

@ -0,0 +1,381 @@
use lib 'inc';
use Test::More;
use strict;
use IO::String;
use LWP::UserAgent;
use LWP::Protocol::PSGI;
use MIME::Base64;
BEGIN {
require 't/test-lib.pm';
require 't/saml-lib.pm';
require 't/smtp.pm';
}
my $maintests = 20;
my $debug = 'error';
my ( $issuer, $sp, $res );
# Redefine LWP methods for tests
LWP::Protocol::PSGI->register(
sub {
my $req = Plack::Request->new(@_);
fail('POST should not launch SOAP requests');
count(1);
return [ 500, [], [] ];
}
);
SKIP: {
eval "use Lasso";
if ($@) {
skip 'Lasso not found', $maintests;
}
# Initialization
$issuer = register( 'issuer', \&issuer );
$sp = register( 'sp', \&sp );
## FIRST CASE##
# * Login directly to SP, 2FA is asked
# Simple SP access
ok(
$res = $sp->_get(
'/', accept => 'text/html',
),
'Unauth SP request'
);
expectOK($res);
my ( $host, $url, $s ) =
expectAutoPost( $res, 'auth.idp.com', '/saml/singleSignOn',
'SAMLRequest' );
# Push SAML request to IdP
ok(
$res = $issuer->_post(
$url,
IO::String->new($s),
accept => 'text/html',
length => length($s)
),
'Post SAML request to IdP'
);
expectOK($res);
my $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
# Try to authenticate with an authorized user to IdP
$s = "user=dwho&password=dwho&$s";
ok(
$res = $issuer->_post(
$url,
IO::String->new($s),
accept => 'text/html',
cookie => $pdata,
length => length($s),
),
'Post authentication'
);
( $host, $url, $s ) =
expectForm( $res, undef, '/mail2fcheck?skin=bootstrap', 'token', 'code' );
ok(
$res->[2]->[0] =~
qr%<input name="code" value="" type="text" class="form-control" id="extcode" trplaceholder="code" autocomplete="off" />%,
'Found EXTCODE input'
) or print STDERR Dumper( $res->[2]->[0] );
ok( mail() =~ m%<b>(\d{4})</b>%, 'Found 2F code in mail' )
or print STDERR Dumper( mail() );
my $code = $1;
$s =~ s/code=/code=${code}/;
ok(
$res = $issuer->_post(
'/mail2fcheck',
IO::String->new($s),
length => length($s),
cookie => $pdata,
accept => 'text/html',
),
'Post code'
);
my $idpId = expectCookie($res);
$pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
expectRedirection( $res, 'http://auth.idp.com/saml' );
ok(
$res = $issuer->_get(
'/saml',
cookie => "lemonldap=$idpId; $pdata",
accept => 'text/html',
),
'Follow redirection'
);
# Expect pdata to be cleared
$pdata = expectCookie( $res, 'lemonldappdata' );
ok( $pdata !~ 'issuerRequestsaml', 'SAML request cleared from pdata' );
( $host, $url, $s ) =
expectAutoPost( $res, 'auth.sp.com', '/saml/proxySingleSignOnPost',
'SAMLResponse' );
# Post SAML response to SP
switch ('sp');
ok(
$res = $sp->_post(
$url, IO::String->new($s),
accept => 'text/html',
length => length($s),
),
'Post SAML response to SP'
);
# Verify authentication on SP
expectRedirection( $res, 'http://auth.sp.com' );
my $spId = expectCookie($res);
ok( $res = $sp->_get( '/', cookie => "lemonldap=$spId" ), 'Get / on SP' );
expectOK($res);
expectAuthenticatedAs( $res, 'dwho@badwolf.org@idp' );
## SECOND CASE##
# * Login to IDP without 2FA
# * Login to SP, 2FA is asked to upgrade session
# Login to IDP
$s = "user=dwho&password=dwho";
ok(
$res = $issuer->_post(
"/",
IO::String->new($s),
accept => 'text/html',
length => length($s),
),
'Post authentication'
);
# No 2FA asked
$idpId = expectCookie($res);
# Simple SP access
ok(
$res = $sp->_get(
'/', accept => 'text/html',
),
'Unauth SP request'
);
expectOK($res);
my ( $host, $url, $s ) =
expectAutoPost( $res, 'auth.idp.com', '/saml/singleSignOn',
'SAMLRequest' );
# Push SAML request to IdP
ok(
$res = $issuer->_post(
$url,
IO::String->new($s),
accept => 'text/html',
cookie => "lemonldap=$idpId",
length => length($s)
),
'Post SAML request to IdP'
);
expectOK($res);
$pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
# IDP should offer to upgrade the session
( $host, $url, $s ) =
expectForm( $res, undef, '/upgradesession', 'confirm', 'url' );
ok(
$res = $issuer->_post(
'/upgradesession',
IO::String->new($s),
length => length($s),
accept => 'text/html',
cookie => "lemonldap=$idpId; $pdata",
),
'Post code'
);
count(1);
$pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
( $host, $url, $s ) =
expectForm( $res, undef, '/mail2fcheck?skin=bootstrap', 'token', 'code' );
ok(
$res->[2]->[0] =~
qr%<input name="code" value="" type="text" class="form-control" id="extcode" trplaceholder="code" autocomplete="off" />%,
'Found EXTCODE input'
) or print STDERR Dumper( $res->[2]->[0] );
ok( mail() =~ m%<b>(\d{4})</b>%, 'Found 2F code in mail' )
or print STDERR Dumper( mail() );
my $code = $1;
$s =~ s/code=/code=${code}/;
ok(
$res = $issuer->_post(
'/mail2fcheck',
IO::String->new($s),
length => length($s),
cookie => "lemonldap=$idpId; $pdata",
accept => 'text/html',
),
'Post code'
);
$pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
expectRedirection( $res, 'http://auth.idp.com/saml/singleSignOn' );
ok(
$res = $issuer->_get(
'/saml',
cookie => "lemonldap=$idpId; $pdata",
accept => 'text/html',
),
'Follow redirection'
);
# Expect pdata to be cleared
$pdata = expectCookie( $res, 'lemonldappdata' );
ok( $pdata !~ 'issuerRequestsaml', 'SAML request cleared from pdata' );
( $host, $url, $s ) =
expectAutoPost( $res, 'auth.sp.com', '/saml/proxySingleSignOnPost',
'SAMLResponse' );
# Post SAML response to SP
switch ('sp');
ok(
$res = $sp->_post(
$url, IO::String->new($s),
accept => 'text/html',
length => length($s),
),
'Post SAML response to SP'
);
# Verify authentication on SP
expectRedirection( $res, 'http://auth.sp.com' );
my $spId = expectCookie($res);
ok( $res = $sp->_get( '/', cookie => "lemonldap=$spId" ), 'Get / on SP' );
expectOK($res);
expectAuthenticatedAs( $res, 'dwho@badwolf.org@idp' );
}
count($maintests);
clean_sessions();
done_testing( count() );
sub issuer {
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'idp.com',
portal => 'http://auth.idp.com',
authentication => 'Demo',
userDB => 'Same',
sfOnlyUpgrade => 1,
issuerDBSAMLActivation => 1,
mail2fActivation => 1,
mail2fCodeRegex => '\d{4}',
mail2fAuthnLevel => 5,
samlSPMetaDataOptions => {
'sp.com' => {
samlSPMetaDataOptionsEncryptionMode => 'none',
samlSPMetaDataOptionsSignSSOMessage => 1,
samlSPMetaDataOptionsSignSLOMessage => 1,
samlSPMetaDataOptionsCheckSSOMessageSignature => 1,
samlSPMetaDataOptionsCheckSLOMessageSignature => 1,
samlSPMetaDataOptionsAuthnLevel => 4,
}
},
samlSPMetaDataExportedAttributes => {
'sp.com' => {
cn =>
'1;cn;urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
uid =>
'1;uid;urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
}
},
samlOrganizationDisplayName => "IDP",
samlOrganizationName => "IDP",
samlOrganizationURL => "http://www.idp.com/",
samlServicePrivateKeyEnc => saml_key_idp_private_enc,
samlServicePrivateKeySig => saml_key_idp_private_sig,
samlServicePublicKeyEnc => saml_key_idp_public_enc,
samlServicePublicKeySig => saml_key_idp_public_sig,
samlSPMetaDataXML => {
"sp.com" => {
samlSPMetaDataXML =>
samlSPMetaDataXML( 'sp', 'HTTP-POST' )
},
},
}
}
);
}
sub sp {
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'sp.com',
portal => 'http://auth.sp.com',
authentication => 'SAML',
userDB => 'Same',
issuerDBSAMLActivation => 0,
restSessionServer => 1,
samlIDPMetaDataExportedAttributes => {
idp => {
mail => "0;mail;;",
uid => "1;uid",
cn => "0;cn"
}
},
samlIDPMetaDataOptions => {
idp => {
samlIDPMetaDataOptionsEncryptionMode => 'none',
samlIDPMetaDataOptionsSSOBinding => 'post',
samlIDPMetaDataOptionsSLOBinding => 'post',
samlIDPMetaDataOptionsSignSSOMessage => 1,
samlIDPMetaDataOptionsSignSLOMessage => 1,
samlIDPMetaDataOptionsCheckSSOMessageSignature => 1,
samlIDPMetaDataOptionsCheckSLOMessageSignature => 1,
samlIDPMetaDataOptionsForceUTF8 => 1,
}
},
samlIDPMetaDataExportedAttributes => {
idp => {
"uid" => "0;uid;;",
"cn" => "1;cn;;",
},
},
samlIDPMetaDataXML => {
idp => {
samlIDPMetaDataXML =>
samlIDPMetaDataXML( 'idp', 'HTTP-POST' )
}
},
samlOrganizationDisplayName => "SP",
samlOrganizationName => "SP",
samlOrganizationURL => "http://www.sp.com",
samlServicePublicKeySig => saml_key_sp_public_sig,
samlServicePrivateKeyEnc => saml_key_sp_private_enc,
samlServicePrivateKeySig => saml_key_sp_private_sig,
samlServicePublicKeyEnc => saml_key_sp_public_enc,
samlSPSSODescriptorAuthnRequestsSigned => 1,
},
}
);
}

View File

@ -88,11 +88,11 @@ SKIP: {
my $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
my $tmp;
( $host, $tmp, $query ) =
expectForm( $res, undef, '/upgradesession', 'confirm' );
expectForm( $res, undef, '/renewsession', 'confirm' );
ok( $res->[2]->[0] =~ /trspan="askToRenew"/, 'Propose to renew session' );
ok(
$res = $issuer->_post(
'/upgradesession',
'/renewsession',
IO::String->new($query),
accept => 'text/html',
length => length($query),
@ -110,7 +110,7 @@ SKIP: {
$query .= '&user=dwho&password=dwho';
ok(
$res = $issuer->_post(
'/upgradesession',
'/renewsession',
IO::String->new($query),
accept => 'text/html',
length => length($query),

View File

@ -77,11 +77,11 @@ SKIP: {
my $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
my $tmp;
( $host, $tmp, $query ) =
expectForm( $res, undef, '/upgradesession', 'confirm' );
expectForm( $res, undef, '/renewsession', 'confirm' );
ok( $res->[2]->[0] =~ /trspan="askToRenew"/, 'Propose to renew session' );
ok(
$res = $issuer->_post(
'/upgradesession',
'/renewsession',
IO::String->new($query),
accept => 'text/html',
length => length($query),
@ -99,7 +99,7 @@ SKIP: {
$query .= '&user=russian&password=russian';
ok(
$res = $issuer->_post(
'/upgradesession',
'/renewsession',
IO::String->new($query),
accept => 'text/html',
length => length($query),

View File

@ -185,13 +185,13 @@ SKIP: {
# Verify that confirmation is asked
my ( $host, $url );
( $host, $url, $query ) =
expectForm( $res, undef, '/upgradesession', 'confirm', 'url' );
expectForm( $res, undef, '/renewsession', 'confirm', 'url' );
# Verify that autopost is required (skipRenewConfirmation is set to 1)
ok( $res->[2]->[0] =~ /autoRenew\.(?:min\.)js/m, ' Get autorenew.js' );
ok(
$res = $issuer->_post(
'/upgradesession', IO::String->new($query),
'/renewsession', IO::String->new($query),
length => length($query),
cookie => "lemonldap=$idpId; $pdata",
accept => 'text/html'
@ -206,7 +206,7 @@ SKIP: {
$query .= '&password=dwho';
ok(
$res = $issuer->_post(
'/upgradesession', IO::String->new($query),
'/renewsession', IO::String->new($query),
length => length($query),
cookie => "lemonldap=$idpId; $pdata",
accept => 'text/html'

View File

@ -0,0 +1,427 @@
use lib 'inc';
use Test::More;
use strict;
use IO::String;
use LWP::UserAgent;
use LWP::Protocol::PSGI;
use MIME::Base64;
BEGIN {
require 't/test-lib.pm';
require 't/oidc-lib.pm';
}
my $debug = 'error';
my ( $op, $rp, $res );
my $access_token;
LWP::Protocol::PSGI->register(
sub {
my $req = Plack::Request->new(@_);
ok( $req->uri =~ m#http://auth.((?:o|r)p).com(.*)#, ' REST request' );
my $host = $1;
my $url = $2;
my ( $res, $client );
count(1);
if ( $host eq 'op' ) {
pass(" Request from RP to OP, endpoint $url");
$client = $op;
}
elsif ( $host eq 'rp' ) {
pass(' Request from OP to RP');
$client = $rp;
}
else {
fail(' Aborting REST request (external)');
return [ 500, [], [] ];
}
if ( $req->method =~ /^post$/i ) {
my $s = $req->content;
ok(
$res = $client->_post(
$url, IO::String->new($s),
length => length($s),
type => $req->header('Content-Type'),
),
' Execute request'
);
}
else {
ok(
$res = $client->_get(
$url,
custom => {
HTTP_AUTHORIZATION => $req->header('Authorization'),
}
),
' Execute request'
);
}
ok( $res->[0] == 200, ' Response is 200' );
ok( getHeader( $res, 'Content-Type' ) =~ m#^application/json#,
' Content is JSON' )
or explain( $res->[1], 'Content-Type => application/json' );
count(4);
if ( $res->[2]->[0] =~ /"access_token":"(.*?)"/ ) {
$access_token = $1;
pass "Found access_token $access_token";
count(1);
}
return $res;
}
);
# Initialization
ok( $op = op(), 'OP portal' );
ok( $res = $op->_get('/oauth2/jwks'), 'Get JWKS, endpoint /oauth2/jwks' );
expectOK($res);
my $jwks = $res->[2]->[0];
ok(
$res = $op->_get('/.well-known/openid-configuration'),
'Get metadata, endpoint /.well-known/openid-configuration'
);
expectOK($res);
my $metadata = $res->[2]->[0];
count(3);
###### FIRST TEST #####################
# Authenticate to OP at low level, then
# upgrade with 2FA
# Authenticate to OP, 2FA not required
my $query = "user=french&password=french";
ok(
$res = $op->_post(
'/',
IO::String->new($query),
accept => 'text/html',
length => length($query),
),
"Post authentication with no target auth level",
);
count(1);
my $idpId = expectCookie($res);
switch ('rp');
&Lemonldap::NG::Handler::Main::cfgNum( 0, 0 );
ok( $rp = rp( $jwks, $metadata ), 'RP portal' );
count(1);
# Query RP for auth
ok( $res = $rp->_get( '/', accept => 'text/html' ), 'Unauth SP request' );
count(1);
( my $url, $query ) =
expectRedirection( $res, qr#http://auth.op.com(/oauth2/authorize)\?(.*)$# );
# Push request to OP
switch ('op');
ok(
$res = $op->_get(
$url,
query => $query,
accept => 'text/html',
cookie => "lemonldap=$idpId",
),
"Push request to OP, endpoint $url"
);
count(1);
expectOK($res);
my $pdata = expectCookie( $res, 'lemonldappdata' );
# OP should offer to upgrade the session
( my $host, $url, $query ) =
expectForm( $res, undef, '/upgradesession', 'confirm', 'url' );
ok(
$res = $op->_post(
'/upgradesession',
IO::String->new($query),
length => length($query),
accept => 'text/html',
cookie => "lemonldap=$idpId;lemonldappdata=$pdata",
),
'Post code'
);
count(1);
$pdata = expectCookie( $res, 'lemonldappdata' );
# Prompt for 2FA
( $host, $url, $query ) =
expectForm( $res, undef, '/ext2fcheck?skin=bootstrap', 'token', 'code',
'checkLogins' );
ok(
$res->[2]->[0] =~
qr%<input name="code" value="" type="text" class="form-control" id="extcode" trplaceholder="code" autocomplete="off" />%,
'Found EXTCODE input'
) or print STDERR Dumper( $res->[2]->[0] );
count(1);
$query =~ s/code=/code=123456/;
ok(
$res = $op->_post(
'/ext2fcheck',
IO::String->new($query),
length => length($query),
accept => 'text/html',
cookie => "lemonldap=$idpId;lemonldappdata=$pdata",
),
'Post code'
);
count(1);
$pdata = expectCookie( $res, 'lemonldappdata' );
($url) = expectRedirection( $res, qr#http://auth.op.com(/?/oauth2.*)# );
ok(
$res = $op->_get(
"$url",
accept => 'text/html',
cookie => "lemonldap=$idpId; lemonldappdata=$pdata",
),
"Follow redirection to Oauth2 issuer"
);
count(1);
$pdata = expectCookie( $res, 'lemonldappdata' );
is( $pdata, '', "Pdata was cleared" );
count(1);
( $host, my $tmp );
( $host, $url, $query ) =
expectForm( $res, undef, qr#/oauth2/authorize.*#, 'confirm' );
ok(
$res = $op->_post(
'/oauth2/authorize',
IO::String->new($query),
accept => 'text/html',
cookie => "lemonldap=$idpId",
length => length($query),
),
"Post confirmation, endpoint $url"
);
count(1);
($query) = expectRedirection( $res, qr#^http://auth.rp.com/?\?(.*)$# );
# Push OP response to RP
switch ('rp');
ok( $res = $rp->_get( '/', query => $query, accept => 'text/html' ),
'Call openidconnectcallback on RP' );
count(1);
my $spId = expectCookie($res);
ok( $res = $rp->_get( '/', cookie => "lemonldap=$spId" ), 'Get / on SP' );
count(1);
expectOK($res);
expectAuthenticatedAs( $res, 'french' );
###### SECOND TEST #####################
# Authenticate to secure RP immediately
# Query RP for auth
ok( $res = $rp->_get( '/', accept => 'text/html' ), 'Unauth SP request' );
count(1);
( $url, $query ) =
expectRedirection( $res, qr#http://auth.op.com(/oauth2/authorize)\?(.*)$# );
# Push request to OP
switch ('op');
ok( $res = $op->_get( $url, query => $query, accept => 'text/html' ),
"Push request to OP, endpoint $url" );
count(1);
expectOK($res);
$pdata = expectCookie( $res, 'lemonldappdata' );
# Try to authenticate to OP
$query = "user=french&password=french&$query";
ok(
$res = $op->_post(
$url,
IO::String->new($query),
accept => 'text/html',
length => length($query),
cookie => "lemonldappdata=$pdata",
),
"Post authentication, endpoint $url"
);
count(1);
$pdata = expectCookie( $res, 'lemonldappdata' );
( $host, $url, $query ) =
expectForm( $res, undef, '/ext2fcheck?skin=bootstrap', 'token', 'code',
'checkLogins' );
ok(
$res->[2]->[0] =~
qr%<input name="code" value="" type="text" class="form-control" id="extcode" trplaceholder="code" autocomplete="off" />%,
'Found EXTCODE input'
) or print STDERR Dumper( $res->[2]->[0] );
count(1);
$query =~ s/code=/code=123456/;
ok(
$res = $op->_post(
'/ext2fcheck',
IO::String->new($query),
length => length($query),
accept => 'text/html',
cookie => "lemonldappdata=$pdata",
),
'Post code'
);
count(1);
$idpId = expectCookie($res);
$pdata = expectCookie( $res, 'lemonldappdata' );
($url) = expectRedirection( $res, qr#http://auth.op.com(/?/oauth2)# );
ok(
$res = $op->_get(
"$url",
accept => 'text/html',
cookie => "lemonldap=$idpId; lemonldappdata=$pdata",
),
"Follow redirection to Oauth2 issuer"
);
count(1);
$pdata = expectCookie( $res, 'lemonldappdata' );
is( $pdata, '', "Pdata was cleared" );
count(1);
($query) = expectRedirection( $res, qr#^http://auth.rp.com/?\?(.*)$# );
# Push OP response to RP
switch ('rp');
ok( $res = $rp->_get( '/', query => $query, accept => 'text/html' ),
'Call openidconnectcallback on RP' );
count(1);
$spId = expectCookie($res);
ok( $res = $rp->_get( '/', cookie => "lemonldap=$spId" ), 'Get / on SP' );
count(1);
expectOK($res);
expectAuthenticatedAs( $res, 'french' );
clean_sessions();
done_testing( count() );
sub op {
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'idp.com',
portal => 'http://auth.op.com/',
authentication => 'Demo',
userDB => 'Same',
issuerDBOpenIDConnectActivation => 1,
sfOnlyUpgrade => 1,
ext2fActivation => 1,
ext2fAuthnLevel => 5,
ext2fCodeActivation => '123456',
ext2FSendCommand => 't/sendOTP.pl -uid dwho',
ext2FValidateCommand => 't/vrfyOTP.pl -uid dwho -code $code',
restSessionServer => 1,
oidcRPMetaDataExportedVars => {
rp => {
email => "mail",
family_name => "cn",
name => "cn"
}
},
oidcServiceMetaDataAuthorizeURI => "authorize",
oidcServiceMetaDataCheckSessionURI => "checksession.html",
oidcServiceMetaDataJWKSURI => "jwks",
oidcServiceMetaDataEndSessionURI => "logout",
oidcServiceMetaDataRegistrationURI => "register",
oidcServiceMetaDataTokenURI => "token",
oidcServiceMetaDataUserInfoURI => "userinfo",
oidcServiceAllowHybridFlow => 1,
oidcServiceAllowImplicitFlow => 1,
oidcServiceAllowDynamicRegistration => 1,
oidcServiceAllowAuthorizationCodeFlow => 1,
oidcRPMetaDataOptions => {
rp => {
oidcRPMetaDataOptionsDisplayName => "RP",
oidcRPMetaDataOptionsIDTokenExpiration => 3600,
oidcRPMetaDataOptionsClientID => "rpid",
oidcRPMetaDataOptionsIDTokenSignAlg => "HS512",
oidcRPMetaDataOptionsBypassConsent => 0,
oidcRPMetaDataOptionsClientSecret => "rpsecret",
oidcRPMetaDataOptionsUserIDAttr => "",
oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
oidcRPMetaDataOptionsAuthnLevel => 5,
oidcRPMetaDataOptionsPostLogoutRedirectUris =>
"http://auth.rp.com/?logout=1"
}
},
oidcOPMetaDataOptions => {},
oidcOPMetaDataJSON => {},
oidcOPMetaDataJWKS => {},
oidcServiceMetaDataAuthnContext => {
'loa-4' => 4,
'loa-1' => 1,
'loa-5' => 5,
'loa-2' => 2,
'loa-3' => 3
},
oidcServicePrivateKeySig => oidc_key_op_private_sig,
oidcServicePublicKeySig => oidc_key_op_public_sig,
}
}
);
}
sub rp {
my ( $jwks, $metadata ) = @_;
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'rp.com',
portal => 'http://auth.rp.com/',
authentication => 'OpenIDConnect',
userDB => 'Same',
restSessionServer => 1,
oidcOPMetaDataExportedVars => {
op => {
cn => "name",
uid => "sub",
sn => "family_name",
mail => "email"
}
},
oidcOPMetaDataOptions => {
op => {
oidcOPMetaDataOptionsCheckJWTSignature => 1,
oidcOPMetaDataOptionsJWKSTimeout => 0,
oidcOPMetaDataOptionsClientSecret => "rpsecret",
oidcOPMetaDataOptionsScope => "openid profile",
oidcOPMetaDataOptionsStoreIDToken => 0,
oidcOPMetaDataOptionsMaxAge => 30,
oidcOPMetaDataOptionsDisplay => "",
oidcOPMetaDataOptionsClientID => "rpid",
oidcOPMetaDataOptionsConfigurationURI =>
"https://auth.op.com/.well-known/openid-configuration"
}
},
oidcOPMetaDataJWKS => {
op => $jwks,
},
oidcOPMetaDataJSON => {
op => $metadata,
}
}
}
);
}

View File

@ -0,0 +1,177 @@
use Test::More;
use strict;
use IO::String;
use Data::Dumper;
require 't/test-lib.pm';
require 't/smtp.pm';
use_ok('Lemonldap::NG::Common::FormEncode');
count(1);
my $res;
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
sfOnlyUpgrade => 1,
mail2fActivation => '$uid eq "dwho"',
mail2fCodeRegex => '\d{4}',
mail2fAuthnLevel => 5,
authentication => 'Demo',
userDB => 'Same',
'vhostOptions' => {
'test1.example.com' => {
'vhostAuthnLevel' => 3
},
},
}
}
);
# CASE 1: no 2F available
# -----------------------
my $query = 'user=rtyler&password=rtyler';
ok(
$res = $client->_post(
'/',
IO::String->new($query),
length => length($query),
accept => 'text/html',
),
'Auth query'
);
count(1);
my $id = expectCookie($res);
# After attempting to access test1,
# the handler sends up back to /upgradesession
# --------------------------------------------
ok(
$res = $client->_get(
'/upgradesession',
query => 'url=aHR0cDovL3Rlc3QxLmV4YW1wbGUuY29t',
accept => 'text/html',
cookie => "lemonldap=$id",
),
'Upgrade session query'
);
count(1);
( my $host, my $url, $query ) =
expectForm( $res, undef, '/upgradesession', 'confirm', 'url' );
# Accept session upgrade
# ----------------------
ok(
$res = $client->_post(
'/upgradesession',
IO::String->new($query),
length => length($query),
accept => 'text/html',
cookie => "lemonldap=$id",
),
'Accept session upgrade query'
);
count(1);
my $pdata = expectCookie( $res, 'lemonldappdata' );
# A message warns the user that they do not have any 2FA available
expectPortalError( $res, 103 );
# CASE 2: has 2F available
# -------------------
$query = 'user=dwho&password=dwho';
ok(
$res = $client->_post(
'/',
IO::String->new($query),
length => length($query),
accept => 'text/html',
),
'Auth query'
);
count(1);
$id = expectCookie($res);
# After attempting to access test1,
# the handler sends up back to /upgradesession
# --------------------------------------------
ok(
$res = $client->_get(
'/upgradesession',
query => 'url=aHR0cDovL3Rlc3QxLmV4YW1wbGUuY29t',
accept => 'text/html',
cookie => "lemonldap=$id",
),
'Upgrade session query'
);
count(1);
( my $host, my $url, $query ) =
expectForm( $res, undef, '/upgradesession', 'confirm', 'url' );
# Accept session upgrade
# ----------------------
ok(
$res = $client->_post(
'/upgradesession',
IO::String->new($query),
length => length($query),
accept => 'text/html',
cookie => "lemonldap=$id",
),
'Accept session upgrade query'
);
count(1);
my $pdata = expectCookie( $res, 'lemonldappdata' );
( $host, $url, $query ) =
expectForm( $res, undef, '/mail2fcheck?skin=bootstrap', 'token', 'code' );
ok(
$res->[2]->[0] =~
qr%<input name="code" value="" type="text" class="form-control" id="extcode" trplaceholder="code" autocomplete="off" />%,
'Found EXTCODE input'
) or print STDERR Dumper( $res->[2]->[0] );
count(1);
ok( mail() =~ m%<b>(\d{4})</b>%, 'Found 2F code in mail' )
or print STDERR Dumper( mail() );
count(1);
my $code = $1;
# Post 2F code
# ------------
$query =~ s/code=/code=${code}/;
ok(
$res = $client->_post(
'/mail2fcheck',
IO::String->new($query),
length => length($query),
accept => 'text/html',
cookie => "lemonldap=$id;lemonldappdata=$pdata",
),
'Post code'
);
count(1);
expectRedirection( $res, 'http://test1.example.com' );
$id = expectCookie($res);
my $cookies = getCookies($res);
ok( !$cookies->{lemonldappdata}, " Make sure no pdata is returned" );
count(1);
clean_sessions();
done_testing( count() );