Merge branch 'v2.0'
This commit is contained in:
commit
051a8e4331
|
@ -64,7 +64,7 @@ build_centos_7:
|
|||
script:
|
||||
- rm -f /etc/yum.repos.d/CentOS-Sources.repo
|
||||
- yum -y install epel-release
|
||||
- make rpm-dist
|
||||
- make dist
|
||||
- ci-build-pkg
|
||||
|
||||
build_centos_8:
|
||||
|
@ -74,7 +74,7 @@ build_centos_8:
|
|||
- yum-config-manager --enable PowerTools
|
||||
- yum-config-manager --enable AppStream
|
||||
- yum -y install epel-release
|
||||
- make rpm-dist
|
||||
- make dist
|
||||
- ci-build-pkg
|
||||
|
||||
sign:
|
||||
|
@ -91,7 +91,7 @@ sign:
|
|||
- build_buster
|
||||
- build_bionic
|
||||
- build_centos_7
|
||||
# - build_centos_8
|
||||
- build_centos_8
|
||||
artifacts:
|
||||
expire_in: 1 day
|
||||
paths:
|
||||
|
|
12
Makefile
12
Makefile
|
@ -1085,23 +1085,13 @@ manager_uninstall: manager
|
|||
|
||||
dist: clean
|
||||
@mkdir -p lemonldap-ng-$(VERSION)
|
||||
@cp -pRH $$(find * -maxdepth 0|grep -v -e "\(lemonldap-ng-$(VERSION)\|debian\|rpm\)") lemonldap-ng-$(VERSION)
|
||||
@cp -pRH $$(find * -maxdepth 0|grep -v -e "lemonldap-ng-$(VERSION)") lemonldap-ng-$(VERSION)
|
||||
@find $$dir -name '*.bak' -delete
|
||||
@rm -rf lemonldap-ng-$(VERSION)/lemonldap-ng-$(VERSION)
|
||||
@rm -rf lemonldap-ng-$(VERSION)/node_modules
|
||||
@$(COMPRESS) lemonldap-ng-$(VERSION).$(COMPRESSSUFFIX) lemonldap-ng-$(VERSION)
|
||||
@rm -rf lemonldap-ng-$(VERSION)
|
||||
|
||||
rpm-dist: clean
|
||||
@mkdir -p lemonldap-ng-$(VERSION)
|
||||
@cp -pRH $$(find * -maxdepth 0|grep -v -e "\(lemonldap-ng-$(VERSION)\|debian\)") lemonldap-ng-$(VERSION)
|
||||
@find $$dir -name '*.bak' -delete
|
||||
@rm -rf lemonldap-ng-$(VERSION)/lemonldap-ng-$(VERSION)
|
||||
@rm -rf lemonldap-ng-$(VERSION)/node_modules
|
||||
@$(COMPRESS) lemonldap-ng-$(VERSION).$(COMPRESSSUFFIX) lemonldap-ng-$(VERSION)
|
||||
@rm -rf lemonldap-ng-$(VERSION)
|
||||
|
||||
|
||||
debian-dist: clean
|
||||
@mkdir -p lemonldap-ng-$(VERSION)
|
||||
@cp -pRH $$(find * -maxdepth 0|grep -v -e "\(lemonldap-ng-$(VERSION)\|rpm\)") lemonldap-ng-$(VERSION)
|
||||
|
|
7
RELEASE
7
RELEASE
|
@ -60,10 +60,7 @@ $ make clean && make dist
|
|||
|
||||
- RedHat packaging:
|
||||
|
||||
Create the RPM specific tarball:
|
||||
$ make clean && make rpm-dist
|
||||
|
||||
Next steps: see rpm/README
|
||||
See rpm/README
|
||||
|
||||
- Debian packaging:
|
||||
|
||||
|
@ -87,7 +84,7 @@ Upload modules tarballs (generated by make cpan)
|
|||
- OW2 Release:
|
||||
Upload dist and bundles on sftp://release-up.ow2.org/projects/lemonldap
|
||||
|
||||
- RPM: see rpm/REDAME
|
||||
- RPM: see rpm/README
|
||||
|
||||
- DEB:
|
||||
The DEB repository is hosted on https://lemonldap-ng.org/deb
|
||||
|
|
|
@ -895,6 +895,11 @@
|
|||
"default" : "HS512",
|
||||
"enum" : [ "none", "RS256", "RS384", "RS512" ]
|
||||
},
|
||||
"userInfoSignAlg" : {
|
||||
"type" : "string",
|
||||
"default" : "",
|
||||
"enum" : [ "", "none", "HS256", "HS384", "HS512", "RS256", "RS384", "RS512" ]
|
||||
},
|
||||
"accessTokenJWT" : {
|
||||
"type" : "bool"
|
||||
},
|
||||
|
|
|
@ -34,6 +34,7 @@ Applications
|
|||
applications/nextcloud
|
||||
applications/obm
|
||||
applications/office365
|
||||
applications/publik
|
||||
applications/phpldapadmin
|
||||
applications/roundcube
|
||||
applications/salesforce
|
||||
|
@ -113,6 +114,7 @@ Application Configuration
|
|||
.. image:: applications/nextcloud-logo.png :doc:`NextCloud<applications/nextcloud>` ✔
|
||||
.. image:: applications/obm_logo.png :doc:`OBM<applications/obm>` ✔
|
||||
.. image:: applications/logo_office_365.png :doc:`Office 365<applications/office365>` ✔
|
||||
.. image:: applications/logo-publik.png :doc:`Publik<applications/publik>` ✔
|
||||
.. image:: applications/phpldapadmin_logo.png :doc:`phpLDAPAdmin<applications/phpldapadmin>` ✔
|
||||
.. image:: applications/roundcube_logo.png :doc:`Roundcube<applications/roundcube>` ✔
|
||||
.. image:: applications/salesforce-logo.jpg :doc:`SalesForce<applications/salesforce>` ✔
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
|
@ -0,0 +1,53 @@
|
|||
Publik
|
||||
=======
|
||||
|
||||
|image0|
|
||||
|
||||
Presentation
|
||||
------------
|
||||
|
||||
Publik is an open-source citizen relationship management tool.
|
||||
|
||||
See `the official Publik website <https://publik.entrouvert.com/>`__ for a
|
||||
complete presentation.
|
||||
|
||||
It feature an OpenID Connect login that work with LemonLDAP::NG.
|
||||
|
||||
Configuring Publik
|
||||
-------------------
|
||||
|
||||
Connect to your publik instance authentic2 webui with an Admin user, in the admin panel, go to "Authentic2_Auth_Oidc" › "Oidc providers".
|
||||
|
||||
Click on "Add Oidc Provider".
|
||||
|
||||
* Name : LemonLDAP SSO
|
||||
* Short id : lemonldap
|
||||
* Provider : https://auth.example.com/
|
||||
* Client id : clientid
|
||||
* Client secret : secret
|
||||
* Authorization endpoint : https://auth.example.com/oauth2/authorize
|
||||
* Token endpoint : https://auth.example.com/oauth2/token
|
||||
* Userinfo endpoint : https://auth.example.com/oauth2/userinfo
|
||||
* End session endpont : https://auth.example.com/oauth2/logout
|
||||
* WebKey JSON : Copy/Paste the content of https://auth.example.com/oauth2/jwks
|
||||
* Claims Enabled : yes
|
||||
* Show on connection page : yes
|
||||
|
||||
Strategy and Collectivity can be configured based to your needs.
|
||||
|
||||
OIDC Claim mappings can be configured based on your needs.
|
||||
|
||||
Configuring LemonLDAP
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We now have to configure LemonLDAP::NG to recognize publik as a valid OIDC relying party.
|
||||
|
||||
Add a :doc:`new OpenID Connect relying party<..//idpopenidconnect>`
|
||||
with the following parameters (Options -> Basic) :
|
||||
|
||||
* **Client ID**: the same you set in Publik configuration.
|
||||
* **Client Secret**: the same you set in Publik configuration.
|
||||
* **Allowed redirection addresses for login**: The "Callback URL" for authentic2 : https://authentic2-instance/accounts/oidc/callback/
|
||||
|
||||
.. |image0| image:: /applications/logo-publik.png
|
||||
:class: align-center
|
|
@ -11,6 +11,13 @@ repeatedly trying to guess the password of an user. If disabled,
|
|||
automated tools may submit thousands of password attempts in a matter of
|
||||
seconds.
|
||||
|
||||
.. attention::
|
||||
This plugin relies on the Login History, stored in users' persistent sessions.
|
||||
This means that the authentication and persistent session backends will be
|
||||
accessed for every login attempt, even fraudulent ones. This plugin is not
|
||||
meant to protect against denial of service attacks.
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
|
|
|
@ -314,6 +314,11 @@ Options
|
|||
(RSXXX) or HMAC (HSXXX) based signature algorithms
|
||||
- **Access Token signature algorithm**: Select one of the available public
|
||||
key signature algorithms
|
||||
- **Userinfo signature algorithm** (since version ``2.0.12``): Select one
|
||||
of the available signature algorithms to release user information as a JWT
|
||||
on the ``/userinfo`` endpoint. If this option is left empty, user
|
||||
information will be released as a plain JSON object. The ``None`` value
|
||||
will release user information as an unsigned JWT.
|
||||
- **Require PKCE** (since version ``2.0.4``): a code challenge is
|
||||
required at token endpoint (see
|
||||
`RFC7636 <https://tools.ietf.org/html/rfc7636>`__)
|
||||
|
|
|
@ -51,17 +51,19 @@ protected from being impersonated.
|
|||
|
||||
|
||||
|
||||
|
||||
.. attention::
|
||||
|
||||
Both spoofed and real session attributes can be used to
|
||||
set access rules, groups or macros.
|
||||
|
||||
By example : ``$real_uid eq 'dwho'`` or ``$real_groups =~ /\bsu\b/``
|
||||
By example : ``$real_uid && $real_uid eq 'dwho'`` or ``$real_groups && $real_groups =~ /\bsu\b/``
|
||||
|
||||
Keep in mind that real session is computed first. Afterward, if access
|
||||
is granted, impersonated session is computed with real and spoofed
|
||||
session attributes if Impersonation is allowed.
|
||||
So, ``real_`` attributes are computed by second authentication process.
|
||||
To avoid Perl warnings, you have to prefix regex with ``$real_var &&``.
|
||||
|
||||
|
||||
|
||||
.. attention::
|
||||
|
|
|
@ -89,7 +89,7 @@ configuration.
|
|||
|image0|
|
||||
|
||||
To set your own background, copy your file in
|
||||
``/usr/share/lemonldap-ng/portal/htdocs/skins/common/backgrounds/`` and
|
||||
``/usr/share/lemonldap-ng/portal/htdocs/static/common/backgrounds/`` and
|
||||
register it in ``/etc/lemonldap-ng/lemonldap-ng.ini``:
|
||||
|
||||
.. code-block:: ini
|
||||
|
|
|
@ -354,7 +354,7 @@ Go in Manager, ``General parameters`` » ``Advanced parameters`` »
|
|||
to disable CSRF token by setting a special rule based on callers IP
|
||||
address like this :
|
||||
|
||||
requireToken => $env->{REMOTE_ADDR} !~ /^127\.0\.[1-3]\.1$/
|
||||
requireToken => $env->{REMOTE_ADDR} && $env->{REMOTE_ADDR} !~ /^127\.0\.[1-3]\.1$/
|
||||
|
||||
.. danger::
|
||||
|
||||
|
|
|
@ -54,6 +54,15 @@ You can then remove them with ::
|
|||
|
||||
lemonldap-ng-sessions delete <session_id> <session_id> <etc.>
|
||||
|
||||
Brute-force protection plugin may cause duplicate persistent sessions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Because of `bug #2482 <https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/issues/2482>`__ , some users may notice that the persistent session database is filling with duplicate sessions. Some examples include:
|
||||
|
||||
* An uppercase version of the regular persistent session (dwho vs DWHO)
|
||||
* An unqualified version (dwho vs dwho@idp.com)
|
||||
|
||||
This bug was fixed in 2.0.12, but administrators are advised to clean up their persistent session database to remove any duplicate persistent sessions remaining after the upgrade.
|
||||
|
||||
2.0.11
|
||||
------
|
||||
|
|
|
@ -1213,6 +1213,18 @@ components:
|
|||
- RS384
|
||||
- RS512
|
||||
default: HS512
|
||||
userInfoSignAlg:
|
||||
type: string
|
||||
enum:
|
||||
- ""
|
||||
- none
|
||||
- HS256
|
||||
- HS384
|
||||
- HS512
|
||||
- RS256
|
||||
- RS384
|
||||
- RS512
|
||||
default: ""
|
||||
accessTokenJWT:
|
||||
type: bool
|
||||
accessTokenClaims:
|
||||
|
|
|
@ -27,7 +27,7 @@ our $specialNodeKeys = '(?:(?:(?:saml(?:ID|S)|oidc[OR])P|cas(?:App|Srv))MetaData
|
|||
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(?:llow(?:(?:ClientCredentials|Password)Grant|Offline)|ccessToken(?:Expiration|SignAlg|Claims|JWT)|uth(?:orizationCodeExpiration|nLevel)|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|ScopeRule|Macro)s)';
|
||||
our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:A(?:llow(?:(?:ClientCredentials|Password)Grant|Offline)|ccessToken(?:Expiration|SignAlg|Claims|JWT)|uth(?:orizationCodeExpiration|nLevel)|dditionalAudiences)|I(?:DToken(?:ForceClaims|Expiration|SignAlg)|con)|R(?:e(?:directUris|freshToken|quirePKCE)|ule)|Logout(?:SessionRequired|Type|Url)|P(?:ostLogoutRedirectUris|ublic)|UserI(?:nfoSignAlg|DAttr)|OfflineSessionExpiration|Client(?:Secret|ID)|BypassConsent|DisplayName|ExtraClaims)|(?:ExportedVar|ScopeRule|Macro)s)';
|
||||
our $samlIDPMetaDataNodeKeys = 'samlIDPMetaData(?:Options(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|EncryptionMod|UserAttribut|DisplayNam)e|S(?:ign(?:S[LS]OMessage|atureMethod)|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(?:S(?:ign(?:S[LS]OMessage|atureMethod)|essionNotOnOrAfterTimeout)|N(?:ameID(?:SessionKey|Format)|otOnOrAfterTimeout)|(?:CheckS[LS]OMessageSignatur|OneTimeUs|Rul)e|En(?:ableIDPInitiatedURL|cryptionMode)|AuthnLevel|ForceUTF8)|(?:ExportedAttribute|Macro)s|XML)';
|
||||
our $virtualHostKeys = '(?:vhost(?:A(?:ccessToTrace|uthnLevel|liases)|(?:Maintenanc|Typ)e|ServiceTokenTTL|Https|Port)|(?:exportedHeader|locationRule)s|post)';
|
||||
|
|
|
@ -122,7 +122,7 @@ sub encrypt {
|
|||
}
|
||||
|
||||
sub token {
|
||||
return encrypt( join( ':', time, @_ ) );
|
||||
return $_[0] ? encrypt( join( ':', time, @_ ) ) : encrypt(time);
|
||||
}
|
||||
|
||||
## @method reval
|
||||
|
|
|
@ -139,7 +139,9 @@ sub run {
|
|||
}
|
||||
|
||||
# Try to recover cookie and user session
|
||||
if ( $id = $class->fetchId($req)
|
||||
$id = $class->fetchId($req);
|
||||
$class->data( {} ) unless($id);
|
||||
if ( $id
|
||||
and $session = $class->retrieveSession( $req, $id ) )
|
||||
{
|
||||
|
||||
|
|
|
@ -2453,6 +2453,43 @@ m[^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
|
|||
'oidcRPMetaDataOptionsUserIDAttr' => {
|
||||
'type' => 'text'
|
||||
},
|
||||
'oidcRPMetaDataOptionsUserInfoSignAlg' => {
|
||||
'default' => '',
|
||||
'select' => [ {
|
||||
'k' => '',
|
||||
'v' => ''
|
||||
},
|
||||
{
|
||||
'k' => 'none',
|
||||
'v' => 'None'
|
||||
},
|
||||
{
|
||||
'k' => 'HS256',
|
||||
'v' => 'HS256'
|
||||
},
|
||||
{
|
||||
'k' => 'HS384',
|
||||
'v' => 'HS384'
|
||||
},
|
||||
{
|
||||
'k' => 'HS512',
|
||||
'v' => 'HS512'
|
||||
},
|
||||
{
|
||||
'k' => 'RS256',
|
||||
'v' => 'RS256'
|
||||
},
|
||||
{
|
||||
'k' => 'RS384',
|
||||
'v' => 'RS384'
|
||||
},
|
||||
{
|
||||
'k' => 'RS512',
|
||||
'v' => 'RS512'
|
||||
}
|
||||
],
|
||||
'type' => 'select'
|
||||
},
|
||||
'oidcRPMetaDataScopeRules' => {
|
||||
'default' => {},
|
||||
'test' => {
|
||||
|
|
|
@ -4262,6 +4262,20 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
|
|||
],
|
||||
default => 'RS256',
|
||||
},
|
||||
oidcRPMetaDataOptionsUserInfoSignAlg => {
|
||||
type => 'select',
|
||||
select => [
|
||||
{ k => '', v => '' },
|
||||
{ k => 'none', v => 'None' },
|
||||
{ k => 'HS256', v => 'HS256' },
|
||||
{ k => 'HS384', v => 'HS384' },
|
||||
{ k => 'HS512', v => 'HS512' },
|
||||
{ k => 'RS256', v => 'RS256' },
|
||||
{ k => 'RS384', v => 'RS384' },
|
||||
{ k => 'RS512', v => 'RS512' },
|
||||
],
|
||||
default => '',
|
||||
},
|
||||
oidcRPMetaDataOptionsAccessTokenJWT => { type => 'bool', default => 0 },
|
||||
oidcRPMetaDataOptionsAccessTokenClaims =>
|
||||
{ type => 'bool', default => 0 },
|
||||
|
|
|
@ -224,6 +224,7 @@ sub cTrees {
|
|||
nodes => [
|
||||
'oidcRPMetaDataOptionsIDTokenSignAlg',
|
||||
'oidcRPMetaDataOptionsAccessTokenSignAlg',
|
||||
'oidcRPMetaDataOptionsUserInfoSignAlg',
|
||||
'oidcRPMetaDataOptionsRequirePKCE',
|
||||
'oidcRPMetaDataOptionsAllowOffline',
|
||||
'oidcRPMetaDataOptionsAllowPasswordGrant',
|
||||
|
|
|
@ -567,6 +567,47 @@ function templates(tpl,key) {
|
|||
"title" : "oidcRPMetaDataOptionsAccessTokenSignAlg",
|
||||
"type" : "select"
|
||||
},
|
||||
{
|
||||
"default" : "",
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsUserInfoSignAlg",
|
||||
"id" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsUserInfoSignAlg",
|
||||
"select" : [
|
||||
{
|
||||
"k" : "",
|
||||
"v" : ""
|
||||
},
|
||||
{
|
||||
"k" : "none",
|
||||
"v" : "None"
|
||||
},
|
||||
{
|
||||
"k" : "HS256",
|
||||
"v" : "HS256"
|
||||
},
|
||||
{
|
||||
"k" : "HS384",
|
||||
"v" : "HS384"
|
||||
},
|
||||
{
|
||||
"k" : "HS512",
|
||||
"v" : "HS512"
|
||||
},
|
||||
{
|
||||
"k" : "RS256",
|
||||
"v" : "RS256"
|
||||
},
|
||||
{
|
||||
"k" : "RS384",
|
||||
"v" : "RS384"
|
||||
},
|
||||
{
|
||||
"k" : "RS512",
|
||||
"v" : "RS512"
|
||||
}
|
||||
],
|
||||
"title" : "oidcRPMetaDataOptionsUserInfoSignAlg",
|
||||
"type" : "select"
|
||||
},
|
||||
{
|
||||
"default" : 0,
|
||||
"get" : tpl+"s/"+key+"/"+"oidcRPMetaDataOptionsRequirePKCE",
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -689,6 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"قاعدة الدخول",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"خاصّيّة المستخدم",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"اسم أوبين أيدي كونيكت RP",
|
||||
"oidcRPStateTimeout":"حالة مهلة الجلسة",
|
||||
|
@ -1210,4 +1211,4 @@
|
|||
"yubikey2fUrl":"خدمة أل يو أر ل",
|
||||
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
|
||||
"zeroConfExplanations":"لا يحتوي الخادم على إعدادات. استخدام قالب لحفظ الأول"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -689,6 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Access rule",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"User attribute",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"OpenID Connect RP Name",
|
||||
"oidcRPStateTimeout":"State session timeout",
|
||||
|
@ -1210,4 +1211,4 @@
|
|||
"yubikey2fUrl":"Service URL",
|
||||
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
|
||||
"zeroConfExplanations":"Server has no configuration. Use template to save the first."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -689,6 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Access rule",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"User attribute",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"OpenID Connect RP Name",
|
||||
"oidcRPStateTimeout":"State session timeout",
|
||||
|
|
|
@ -689,6 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Regla de acceso",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"Atributo de usuario",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"OpenID Connect RP Name",
|
||||
"oidcRPStateTimeout":"Caducidad de estado de sesión",
|
||||
|
@ -1210,4 +1211,4 @@
|
|||
"yubikey2fUrl":"Service URL",
|
||||
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
|
||||
"zeroConfExplanations":"Server has no configuration. Use template to save the first."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -689,6 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Règle d'accès",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Expiration",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"Attribut de l'utilisateur",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Algorithme de signature des informations utilisateur",
|
||||
"oidcRPMetaDataScopeRules":"Règles de scope",
|
||||
"oidcRPName":"Nom du client OpenID Connect",
|
||||
"oidcRPStateTimeout":"Durée d'une session state",
|
||||
|
|
|
@ -689,6 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Regola di accesso",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"Attributo utente",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"Nome di OpenID Connect RP",
|
||||
"oidcRPStateTimeout":"Durata della sessione stato",
|
||||
|
@ -1210,4 +1211,4 @@
|
|||
"yubikey2fUrl":"URL del servizio",
|
||||
"yubikey2fUserCanRemoveKey":"Autorizza l'utente a rimuovere la Yubikey",
|
||||
"zeroConfExplanations":"Il server non ha alcuna configurazione. Utilizza il modello per salvare il primo."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -689,6 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Reguła dostępu",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Limit czasu",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"Atrybut użytkownika",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataScopeRules":"Zasady dotyczące zakresu",
|
||||
"oidcRPName":"Nazwa RP OpenID Connect",
|
||||
"oidcRPStateTimeout":"Limit czasu sesji stanowej",
|
||||
|
@ -1210,4 +1211,4 @@
|
|||
"yubikey2fUrl":"URL usługi",
|
||||
"yubikey2fUserCanRemoveKey":"Pozwól użytkownikowi usunąć Yubikey",
|
||||
"zeroConfExplanations":"Serwer nie ma konfiguracji. Użyj szablonu, aby zapisać pierwszy."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -689,6 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Erişim kuralı",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Zaman aşımları",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"Kullanıcı niteliği",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataScopeRules":"Kapsam kuralları",
|
||||
"oidcRPName":"OpenID Connect RP Adı",
|
||||
"oidcRPStateTimeout":"Oturum zaman aşımını belirle",
|
||||
|
@ -1210,4 +1211,4 @@
|
|||
"yubikey2fUrl":"Servis URL'si",
|
||||
"yubikey2fUserCanRemoveKey":"Yubikey'i kaldırmak için kullanıcıya izin ver",
|
||||
"zeroConfExplanations":"Sunucunun yapılandırması yok. Şimdi bir tane kaydetmek için şablonu kullanın."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -689,6 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Quy tắc truy cập",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"thuộc tính người dùng",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"OpenID Connect RP Name",
|
||||
"oidcRPStateTimeout":"Thời gian chờ của trạng thái phiên làm việc",
|
||||
|
@ -1210,4 +1211,4 @@
|
|||
"yubikey2fUrl":"Dịch vụ URL",
|
||||
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
|
||||
"zeroConfExplanations":"Máy chủ không có cấu hình. Sử dụng mẫu để lưu đầu tiên. "
|
||||
}
|
||||
}
|
||||
|
|
|
@ -689,6 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Access rule",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"User attribute",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"OpenID Connect RP Name",
|
||||
"oidcRPStateTimeout":"State session timeout",
|
||||
|
@ -1210,4 +1211,4 @@
|
|||
"yubikey2fUrl":"Service URL",
|
||||
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
|
||||
"zeroConfExplanations":"Server has no configuration. Use template to save the first."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -689,6 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"存取規則",
|
||||
"oidcRPMetaDataOptionsTimeouts":"逾時",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"使用者屬性",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"OpenID 連線 RP 名稱",
|
||||
"oidcRPStateTimeout":"狀態工作階段逾時",
|
||||
|
@ -1210,4 +1211,4 @@
|
|||
"yubikey2fUrl":"服務 URL",
|
||||
"yubikey2fUserCanRemoveKey":"允許使用者移除 Yubikey",
|
||||
"zeroConfExplanations":"伺服器未設定。使用飯本來儲存第一個。"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -485,6 +485,7 @@ t/01-Handler-redirection-and-URL-check-by-portal.t
|
|||
t/01-pdata.t
|
||||
t/01-Reject-Hashes-in-URL.t
|
||||
t/01-Unauth-Logout.t
|
||||
t/02-Password-Demo-Hook.t
|
||||
t/02-Password-Demo-Local-noPpolicy.t
|
||||
t/02-Password-Demo-Local-Ppolicy.t
|
||||
t/02-Password-Demo-Local-SpeChars-Ppolicy.t
|
||||
|
@ -552,6 +553,7 @@ t/31-Auth-and-issuer-CAS-proxied.t
|
|||
t/31-Auth-and-issuer-CAS-with-choice-and-cancel.t
|
||||
t/31-Auth-and-issuer-CAS-with-choice.t
|
||||
t/31-Auth-and-issuer-CAS-XSS-on-logout.t
|
||||
t/32-Auth-and-issuer-OIDC-authorization_code-jwt-userinfo.t
|
||||
t/32-Auth-and-issuer-OIDC-authorization_code-OP-logout.t
|
||||
t/32-Auth-and-issuer-OIDC-authorization_code-public_client.t
|
||||
t/32-Auth-and-issuer-OIDC-authorization_code-with-authchoice.t
|
||||
|
@ -563,6 +565,7 @@ t/32-Auth-and-issuer-OIDC-implicit-no-token.t
|
|||
t/32-Auth-and-issuer-OIDC-implicit.t
|
||||
t/32-Auth-and-issuer-OIDC-sorted.t
|
||||
t/32-CAS-10.t
|
||||
t/32-CAS-Hooks.t
|
||||
t/32-CAS-Macros.t
|
||||
t/32-CAS-Prefix.t
|
||||
t/32-OIDC-ClaimTypes.t
|
||||
|
@ -631,6 +634,7 @@ t/43-MailPasswordReset-Choice.t
|
|||
t/43-MailPasswordReset-Combination-LDAP.t
|
||||
t/43-MailPasswordReset-Combination.t
|
||||
t/43-MailPasswordReset-DBI.t
|
||||
t/43-MailPasswordReset-Hook.t
|
||||
t/43-MailPasswordReset-LDAP.t
|
||||
t/43-MailPasswordReset-with-captcha.t
|
||||
t/43-MailPasswordReset-with-token.t
|
||||
|
@ -748,9 +752,11 @@ t/78-2F-UpgradeOnly.t
|
|||
t/79-2F-Yubikey-from-Session.t
|
||||
t/79-2F-Yubikey.t
|
||||
t/90-Translations.t
|
||||
t/91-handler-cache-cleaned.t
|
||||
t/91-Memory-Leak.t
|
||||
t/99-Dont-load-Dumper.t
|
||||
t/99-pod.t
|
||||
t/CasHookPlugin.pm
|
||||
t/gpghome/key.asc
|
||||
t/gpghome/openpgp-revocs.d/9482CEFB055809CBAFE6D71AAB2D5542891D1677.rev
|
||||
t/gpghome/private-keys-v1.d/A076B0E7DB141A919271EE8B581CDFA8DA42F333.key
|
||||
|
@ -767,6 +773,7 @@ t/lib/Lemonldap/NG/Portal/Custom.pm
|
|||
t/lmConf-1.json
|
||||
t/oidc-lib.pm
|
||||
t/OidcHookPlugin.pm
|
||||
t/PasswordHookPlugin.pm
|
||||
t/pdata.pm
|
||||
t/README.md
|
||||
t/saml-lib.pm
|
||||
|
|
|
@ -368,6 +368,7 @@ sub run {
|
|||
->{$idp_initiated_spConfKey}
|
||||
->{samlSPMetaDataOptionsNameIDFormat} || "email";
|
||||
eval {
|
||||
|
||||
$login->request()->NameIDPolicy()
|
||||
->Format( $self->getNameIDFormat($nameIDFormatKey) );
|
||||
};
|
||||
|
@ -458,7 +459,9 @@ sub run {
|
|||
}
|
||||
|
||||
unless ($result) {
|
||||
$self->logger->error("Could not verify signature of incoming SSO request from $spConfKey");
|
||||
$self->logger->error(
|
||||
"Could not verify signature of incoming SSO request from $spConfKey"
|
||||
);
|
||||
return PE_SAML_SIGNATURE_ERROR;
|
||||
}
|
||||
else {
|
||||
|
@ -469,11 +472,35 @@ sub run {
|
|||
$self->logger->debug("Message signature will not be checked");
|
||||
}
|
||||
|
||||
my $nameIDFormat;
|
||||
|
||||
# Check NameID Policy in request
|
||||
if ( $login->request()->NameIDPolicy ) {
|
||||
$nameIDFormat = $login->request()->NameIDPolicy->Format();
|
||||
$self->logger->debug(
|
||||
"Get NameID format $nameIDFormat from request");
|
||||
}
|
||||
|
||||
# Get default NameID Format from configuration
|
||||
# Set to "email" if no value in configuration
|
||||
my $nameIDFormatKey =
|
||||
$self->conf->{samlSPMetaDataOptions}->{$spConfKey}
|
||||
->{samlSPMetaDataOptionsNameIDFormat} || "email";
|
||||
|
||||
# NameID unspecified is forced to default NameID format
|
||||
if ( !$nameIDFormat
|
||||
or $nameIDFormat eq $self->getNameIDFormat("unspecified") )
|
||||
{
|
||||
$nameIDFormat = $self->getNameIDFormat($nameIDFormatKey);
|
||||
eval {
|
||||
$login->request()->NameIDPolicy()->Format($nameIDFormat);
|
||||
};
|
||||
}
|
||||
|
||||
# Force AllowCreate to TRUE for transient/persistent NameIDPolicy
|
||||
if ( $login->request()->NameIDPolicy ) {
|
||||
my $nif = $login->request()->NameIDPolicy->Format();
|
||||
if ( $nif eq $self->getNameIDFormat("transient")
|
||||
or $nif eq $self->getNameIDFormat("persistent") )
|
||||
if ( $nameIDFormat eq $self->getNameIDFormat("transient")
|
||||
or $nameIDFormat eq $self->getNameIDFormat("persistent") )
|
||||
{
|
||||
$self->logger->debug(
|
||||
"Force AllowCreate flag in NameIDPolicy");
|
||||
|
@ -566,27 +593,6 @@ sub run {
|
|||
|
||||
$self->logger->debug("SSO: assertion is built");
|
||||
|
||||
# Get default NameID Format from configuration
|
||||
# Set to "email" if no value in configuration
|
||||
my $nameIDFormatKey =
|
||||
$self->conf->{samlSPMetaDataOptions}->{$spConfKey}
|
||||
->{samlSPMetaDataOptionsNameIDFormat} || "email";
|
||||
my $nameIDFormat;
|
||||
|
||||
# Check NameID Policy in request
|
||||
if ( $login->request()->NameIDPolicy ) {
|
||||
$nameIDFormat = $login->request()->NameIDPolicy->Format();
|
||||
$self->logger->debug(
|
||||
"Get NameID format $nameIDFormat from request");
|
||||
}
|
||||
|
||||
# NameID unspecified is forced to default NameID format
|
||||
if ( !$nameIDFormat
|
||||
or $nameIDFormat eq $self->getNameIDFormat("unspecified") )
|
||||
{
|
||||
$nameIDFormat = $self->getNameIDFormat($nameIDFormatKey);
|
||||
}
|
||||
|
||||
# Get session key associated with NameIDFormat
|
||||
# Not for unspecified, transient, persistent, entity, encrypted
|
||||
my $nameIDFormatConfiguration = {
|
||||
|
@ -599,7 +605,9 @@ sub run {
|
|||
};
|
||||
|
||||
my $nameIDSessionKey =
|
||||
$self->conf->{ $nameIDFormatConfiguration->{$nameIDFormat} };
|
||||
$nameIDFormatConfiguration->{$nameIDFormat}
|
||||
? $self->conf->{ $nameIDFormatConfiguration->{$nameIDFormat} }
|
||||
: '';
|
||||
|
||||
# Override default NameID Mapping
|
||||
if ( $self->conf->{samlSPMetaDataOptions}->{$spConfKey}
|
||||
|
@ -611,12 +619,16 @@ sub run {
|
|||
}
|
||||
|
||||
my $nameIDContent;
|
||||
if ( $self->spMacros->{$sp}->{$nameIDSessionKey} ) {
|
||||
if ( $nameIDSessionKey
|
||||
and $self->spMacros->{$sp}->{$nameIDSessionKey} )
|
||||
{
|
||||
$nameIDContent =
|
||||
$self->spMacros->{$sp}->{$nameIDSessionKey}
|
||||
->( $req, $req->{sessionInfo} );
|
||||
}
|
||||
elsif ( defined $req->{sessionInfo}->{$nameIDSessionKey} ) {
|
||||
elsif ( $nameIDSessionKey
|
||||
and ( defined $req->{sessionInfo}->{$nameIDSessionKey} ) )
|
||||
{
|
||||
$nameIDContent =
|
||||
$self->p->getFirstValue(
|
||||
$req->{sessionInfo}->{$nameIDSessionKey} );
|
||||
|
@ -650,8 +662,8 @@ sub run {
|
|||
|
||||
$self->logger->debug(
|
||||
"NameID Format is " . $login->nameIdentifier->Format );
|
||||
$self->logger->debug(
|
||||
"NameID Content is " . $login->nameIdentifier->content );
|
||||
$self->logger->debug( "NameID Content is "
|
||||
. ( $login->nameIdentifier->content || "" ) );
|
||||
|
||||
# Push attributes
|
||||
my @attributes;
|
||||
|
@ -700,7 +712,8 @@ sub run {
|
|||
}
|
||||
|
||||
$self->logger->debug(
|
||||
"SAML2 attribute $name will be set with $_ session key ($sp)");
|
||||
"SAML2 attribute $name will be set with $_ session key ($sp)"
|
||||
);
|
||||
|
||||
# SAML2 attribute
|
||||
my $attribute =
|
||||
|
@ -1317,8 +1330,7 @@ sub soapSloServer {
|
|||
"SLO response signature according to metadata");
|
||||
}
|
||||
|
||||
$h =
|
||||
$self->p->processHook( $req, 'samlBuildLogoutResponse', $logout );
|
||||
$h = $self->p->processHook( $req, 'samlBuildLogoutResponse', $logout );
|
||||
if ( $h != PE_OK ) {
|
||||
return $self->p->sendError( $req,
|
||||
"SLO: samlBuildLogoutResponse hook returned error", 400 );
|
||||
|
@ -1976,7 +1988,9 @@ sub sloServer {
|
|||
|
||||
if ($checkSLOMessageSignature) {
|
||||
unless ( $self->checkSignatureStatus($logout) ) {
|
||||
$self->logger->error("Could not verify signature of incoming SLO request from $spConfKey");
|
||||
$self->logger->error(
|
||||
"Could not verify signature of incoming SLO request from $spConfKey"
|
||||
);
|
||||
$self->imgnok($req);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -434,6 +434,7 @@ sub display {
|
|||
),
|
||||
( $req->token ? ( TOKEN => $req->token ) : () ),
|
||||
( $req->data->{waitingMessage} ? ( WAITING_MESSAGE => 1 ) : () ),
|
||||
ENABLE_PASSWORD_DISPLAY => $self->conf->{portalEnablePasswordDisplay},
|
||||
);
|
||||
|
||||
# Disable all forms on:
|
||||
|
|
|
@ -396,7 +396,7 @@ sub authenticate {
|
|||
$req->steps( [
|
||||
'setSessionInfo', 'setMacros',
|
||||
'setPersistentSessionInfo', 'storeHistory',
|
||||
@{ $self->afterData }, sub { PE_BADCREDENTIALS }
|
||||
@{ $self->afterData }, sub { PE_BADCREDENTIALS }
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -475,13 +475,12 @@ sub setGroups {
|
|||
}
|
||||
|
||||
sub setPersistentSessionInfo {
|
||||
|
||||
# $user passed by BruteForceProtection plugin
|
||||
my ( $self, $req, $user ) = @_;
|
||||
my ( $self, $req ) = @_;
|
||||
|
||||
# Do not restore infos if session already opened
|
||||
unless ( $req->id ) {
|
||||
my $key = $req->{sessionInfo}->{ $self->conf->{whatToTrace} } || $user;
|
||||
my $key = $req->{sessionInfo}->{ $self->conf->{whatToTrace} };
|
||||
|
||||
return PE_OK unless ( $key and length($key) );
|
||||
|
||||
my $persistentSession = $self->getPersistentSession($key);
|
||||
|
@ -620,9 +619,9 @@ sub secondFactor {
|
|||
}
|
||||
|
||||
sub storeHistory {
|
||||
my ( $self, $req, $uid ) = @_; # $uid passed by BruteForceProtection plugin
|
||||
my ( $self, $req ) = @_;
|
||||
if ( $self->conf->{loginHistoryEnabled} ) {
|
||||
$self->registerLogin( $req, $uid );
|
||||
$self->registerLogin($req);
|
||||
}
|
||||
PE_OK;
|
||||
}
|
||||
|
|
|
@ -1047,9 +1047,7 @@ sub tplParams {
|
|||
}
|
||||
|
||||
sub registerLogin {
|
||||
|
||||
# $user passed by BruteForceProtection plugin
|
||||
my ( $self, $req, $uid ) = @_;
|
||||
my ( $self, $req ) = @_;
|
||||
return
|
||||
unless ( $self->conf->{loginHistoryEnabled}
|
||||
and defined $req->authResult );
|
||||
|
@ -1079,8 +1077,7 @@ sub registerLogin {
|
|||
}
|
||||
}
|
||||
}
|
||||
$self->updatePersistentSession( $req, { 'loginHistory' => undef },
|
||||
$uid );
|
||||
$self->updatePersistentSession( $req, { 'loginHistory' => undef } );
|
||||
delete $req->sessionInfo->{loginHistory};
|
||||
}
|
||||
|
||||
|
@ -1105,7 +1102,7 @@ sub registerLogin {
|
|||
if ( scalar @{ $history->{$type} } > $self->conf->{ $type . "Number" } );
|
||||
|
||||
# Save into persistent session
|
||||
$self->updatePersistentSession( $req, { _loginHistory => $history }, $uid );
|
||||
$self->updatePersistentSession( $req, { _loginHistory => $history, } );
|
||||
|
||||
PE_OK;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ our $VERSION = '2.1.0';
|
|||
extends 'Lemonldap::NG::Portal::Main::Plugin';
|
||||
|
||||
# INITIALIZATION
|
||||
use constant aroundSub => { authenticate => 'check' };
|
||||
use constant afterSub => { setPersistentSessionInfo => 'run' };
|
||||
|
||||
has lockTimes => (
|
||||
is => 'rw',
|
||||
|
@ -61,7 +61,9 @@ sub init {
|
|||
sort { $a <=> $b }
|
||||
map {
|
||||
$_ =~ s/\D//;
|
||||
abs $_ < $self->conf->{bruteForceProtectionMaxLockTime} ? abs $_ : ()
|
||||
abs $_ < $self->conf->{bruteForceProtectionMaxLockTime}
|
||||
? abs $_
|
||||
: ()
|
||||
}
|
||||
grep { /\d+/ }
|
||||
split /\s*,\s*/, $self->conf->{bruteForceProtectionLockTimes};
|
||||
|
@ -99,13 +101,9 @@ sub init {
|
|||
}
|
||||
|
||||
# RUNNING METHOD
|
||||
sub check {
|
||||
my ( $self, $sub, $req ) = @_;
|
||||
my $now = time;
|
||||
$self->p->setSessionInfo($req);
|
||||
$self->logger->debug("Retrieve $req->{user} logins history");
|
||||
$self->p->setPersistentSessionInfo( $req, $req->{user} );
|
||||
|
||||
sub run {
|
||||
my ( $self, $req ) = @_;
|
||||
my $now = time;
|
||||
my $countFailed = my @failedLogins =
|
||||
map { ( $now - $_->{_utime} ) <= $self->maxAge ? $_ : () }
|
||||
@{ $req->sessionInfo->{_loginHistory}->{failedLogin} };
|
||||
|
@ -115,7 +113,7 @@ sub check {
|
|||
my $lastFailedLoginEpoch = $failedLogins[0]->{_utime} || undef;
|
||||
|
||||
if ( $self->conf->{bruteForceProtectionIncrementalTempo} ) {
|
||||
return $sub->($req) unless $lastFailedLoginEpoch;
|
||||
return PE_OK unless $lastFailedLoginEpoch;
|
||||
|
||||
# Delta between current attempt and last failed login
|
||||
my $delta = $now - $lastFailedLoginEpoch;
|
||||
|
@ -148,10 +146,10 @@ sub check {
|
|||
$req->lockTime( $waitingTime - $delta );
|
||||
return PE_WAIT;
|
||||
}
|
||||
return $sub->($req);
|
||||
return PE_OK;
|
||||
}
|
||||
|
||||
return $sub->($req)
|
||||
return PE_OK
|
||||
if ( $countFailed < $self->maxFailed );
|
||||
|
||||
# Delta between current attempt and last failed login
|
||||
|
@ -159,7 +157,7 @@ sub check {
|
|||
$self->logger->debug(" -> Delta = $delta");
|
||||
|
||||
# Delta < Tempo => wait
|
||||
return $sub->($req)
|
||||
return PE_OK
|
||||
unless ( $delta < $self->conf->{bruteForceProtectionTempo}
|
||||
&& $countFailed );
|
||||
|
||||
|
|
|
@ -172,50 +172,52 @@ sub activeSessions {
|
|||
my $customParam = $self->conf->{globalLogoutCustomParam} || '';
|
||||
|
||||
# Try to retrieve sessions from sessions DB
|
||||
$self->logger->debug('Try to retrieve sessions from DB');
|
||||
my $moduleOptions = $self->conf->{globalStorageOptions} || {};
|
||||
$moduleOptions->{backend} = $self->conf->{globalStorage};
|
||||
$self->logger->debug("Looking for \"$user\" sessions...");
|
||||
$sessions =
|
||||
$self->module->searchOn( $moduleOptions, $self->conf->{whatToTrace},
|
||||
$user );
|
||||
if ($user) {
|
||||
$self->logger->debug('Try to retrieve sessions from DB');
|
||||
my $moduleOptions = $self->conf->{globalStorageOptions} || {};
|
||||
$moduleOptions->{backend} = $self->conf->{globalStorage};
|
||||
$self->logger->debug("Looking for \"$user\" sessions...");
|
||||
$sessions =
|
||||
$self->module->searchOn( $moduleOptions, $self->conf->{whatToTrace},
|
||||
$user );
|
||||
|
||||
$self->logger->debug('Remove non-SSO session(s)...');
|
||||
my $other = 0;
|
||||
foreach ( keys %$sessions ) {
|
||||
unless ( $sessions->{$_}->{_session_kind} eq 'SSO' ) {
|
||||
delete $sessions->{$_};
|
||||
$other++;
|
||||
$self->logger->debug('Skip non-SSO session(s)...');
|
||||
my $other = 0;
|
||||
foreach ( keys %$sessions ) {
|
||||
unless ( $sessions->{$_}->{_session_kind} eq 'SSO' ) {
|
||||
delete $sessions->{$_};
|
||||
$other++;
|
||||
}
|
||||
}
|
||||
}
|
||||
$self->logger->info("$other non-SSO session(s) removed")
|
||||
if $other;
|
||||
$self->logger->info("$other non-SSO session(s) skipped")
|
||||
if $other;
|
||||
|
||||
$self->logger->debug('Build an array ref with sessions info...');
|
||||
@$activeSessions =
|
||||
map {
|
||||
my $epoch;
|
||||
my $regex = '^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$';
|
||||
$_->{startTime} =~ /$regex/;
|
||||
$epoch = timelocal( $6, $5, $4, $3, $2 - 1, $1 );
|
||||
$_->{startTime} = $epoch;
|
||||
if ( $_->{updateTime} ) {
|
||||
$_->{updateTime} =~ /$regex/;
|
||||
$self->logger->debug('Build an array ref with sessions info...');
|
||||
@$activeSessions =
|
||||
map {
|
||||
my $epoch;
|
||||
my $regex = '^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$';
|
||||
$_->{startTime} =~ /$regex/;
|
||||
$epoch = timelocal( $6, $5, $4, $3, $2 - 1, $1 );
|
||||
$_->{updateTime} = $epoch;
|
||||
}
|
||||
$_;
|
||||
}
|
||||
sort { $b->{startTime} cmp $a->{startTime} } map { {
|
||||
id => $_,
|
||||
customParam => $sessions->{$_}->{$customParam},
|
||||
ipAddr => $sessions->{$_}->{ipAddr},
|
||||
authLevel => $sessions->{$_}->{authenticationLevel},
|
||||
startTime => $sessions->{$_}->{_startTime},
|
||||
updateTime => $sessions->{$_}->{_updateTime}
|
||||
};
|
||||
} keys %$sessions;
|
||||
|
||||
$_->{startTime} = $epoch;
|
||||
if ( $_->{updateTime} ) {
|
||||
$_->{updateTime} =~ /$regex/;
|
||||
$epoch = timelocal( $6, $5, $4, $3, $2 - 1, $1 );
|
||||
$_->{updateTime} = $epoch;
|
||||
}
|
||||
$_;
|
||||
}
|
||||
sort { $b->{startTime} cmp $a->{startTime} } map { {
|
||||
id => $_,
|
||||
customParam => $sessions->{$_}->{$customParam},
|
||||
ipAddr => $sessions->{$_}->{ipAddr},
|
||||
authLevel => $sessions->{$_}->{authenticationLevel},
|
||||
startTime => $sessions->{$_}->{_startTime},
|
||||
updateTime => $sessions->{$_}->{_updateTime}
|
||||
};
|
||||
} keys %$sessions;
|
||||
}
|
||||
|
||||
return $activeSessions;
|
||||
}
|
||||
|
||||
|
|
|
@ -515,6 +515,14 @@ $(window).on 'load', () ->
|
|||
if window.datas.ppolicy? and $('#newpassword').length
|
||||
$('#reset').change togglecheckpassword
|
||||
|
||||
if datas['enablePasswordDisplay']
|
||||
$(".toggle-password").mousedown (e) ->
|
||||
$(this).toggleClass("fa-eye fa-eye-slash");
|
||||
$("input[name=password]").attr("type", "text");
|
||||
$(".toggle-password").mouseup (e) ->
|
||||
$(this).toggleClass("fa-eye fa-eye-slash");
|
||||
$("input[name=password]").attr("type", "password");
|
||||
|
||||
# Ping if asked
|
||||
if datas['pingInterval'] and datas['pingInterval'] > 0
|
||||
window.setTimeout ping, datas['pingInterval']
|
||||
|
@ -577,4 +585,4 @@ $(window).on 'load', () ->
|
|||
console.log 'Error', err if err
|
||||
res = JSON.parse j.responseText if j
|
||||
if res and res.error
|
||||
console.log 'Returned error', res
|
||||
console.log 'Returned error', res
|
||||
|
|
|
@ -561,6 +561,16 @@ LemonLDAP::NG Portal jQuery scripts
|
|||
if ((window.datas.ppolicy != null) && $('#newpassword').length) {
|
||||
$('#reset').change(togglecheckpassword);
|
||||
}
|
||||
if (datas['enablePasswordDisplay']) {
|
||||
$(".toggle-password").mousedown(function(e) {
|
||||
$(this).toggleClass("fa-eye fa-eye-slash");
|
||||
return $("input[name=password]").attr("type", "text");
|
||||
});
|
||||
$(".toggle-password").mouseup(function(e) {
|
||||
$(this).toggleClass("fa-eye fa-eye-slash");
|
||||
return $("input[name=password]").attr("type", "password");
|
||||
});
|
||||
}
|
||||
if (datas['pingInterval'] && datas['pingInterval'] > 0) {
|
||||
window.setTimeout(ping, datas['pingInterval']);
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
!function(){var o=function(e,r){return $("#msg").html(window.translate(e)),$("#color").removeClass("message-positive message-warning message-danger alert-success alert-warning alert-danger"),$("#color").addClass("message-"+r),"positive"===r&&(r="success"),$("#color").addClass("alert-"+r)},r=function(e,r,t){if(console.log("Error",t),(e=JSON.parse(e.responseText))&&e.error)return e=e.error.replace(/.* /,""),console.log("Returned error",e),o(e,"warning")},t="",e=function(e){return o("yourTotpKey","warning"),$.ajax({type:"POST",url:portal+"/2fregisters/totp/getkey",dataType:"json",data:{newkey:e},error:r,success:function(e){var r;return e.error?(e.error.match(/totpExistingKey/)&&$("#divToHide").hide(),o(e.error,"warning")):e.portal&&e.user&&e.secret?($("#divToHide").show(),r="otpauth://totp/"+escape(e.portal)+":"+escape(e.user)+"?secret="+e.secret+"&issuer="+escape(e.portal),6!==e.digits&&(r+="&digits="+e.digits),30!==e.interval&&(r+="&period="+e.interval),new QRious({element:document.getElementById("qr"),value:r,size:150}),$("#serialized").text(r),e.newkey?o("yourNewTotpKey","warning"):o("yourTotpKey","success"),t=e.token):o("PE24","danger")}})},n=function(){var e=$("#code").val();return e?$.ajax({type:"POST",url:portal+"/2fregisters/totp/verify",dataType:"json",data:{token:t,code:e,TOTPName:$("#TOTPName").val()},error:r,success:function(e){return e.error?e.error.match(/bad(Code|Name)/)?o(e.error,"warning"):o(e.error,"danger"):o("yourKeyIsRegistered","success")}}):o("fillTheForm","warning")};$(document).ready(function(){return e(0),$("#changekey").on("click",function(){return e(1)}),$("#verify").on("click",n)})}.call(this);
|
||||
(function(){var r,e,n,t,o;n=function(e,r){return $("#msg").html(window.translate(e)),$("#color").removeClass("message-positive message-warning message-danger alert-success alert-warning alert-danger"),$("#color").addClass("message-"+r),"positive"===r&&(r="success"),$("#color").addClass("alert-"+r)},r=function(e,r,t){var o;if(console.log("Error",t),(o=JSON.parse(e.responseText))&&o.error)return o=o.error.replace(/.* /,""),console.log("Returned error",o),n(o,"warning")},t="",e=function(e){return n("yourTotpKey","warning"),$.ajax({type:"POST",url:portal+"/2fregisters/totp/getkey",dataType:"json",data:{newkey:e},error:r,success:function(e){var r;return e.error?(e.error.match(/totpExistingKey/)&&$("#divToHide").hide(),n(e.error,"warning")):e.portal&&e.user&&e.secret?($("#divToHide").show(),r="otpauth://totp/"+escape(e.portal)+":"+escape(e.user)+"?secret="+e.secret+"&issuer="+escape(e.portal),6!==e.digits&&(r+="&digits="+e.digits),30!==e.interval&&(r+="&period="+e.interval),new QRious({element:document.getElementById("qr"),value:r,size:150}),$("#serialized").text(r),e.newkey?n("yourNewTotpKey","warning"):n("yourTotpKey","success"),t=e.token):n("PE24","danger")}})},o=function(){var e;return(e=$("#code").val())?$.ajax({type:"POST",url:portal+"/2fregisters/totp/verify",dataType:"json",data:{token:t,code:e,TOTPName:$("#TOTPName").val()},error:r,success:function(e){return e.error?e.error.match(/bad(Code|Name)/)?n(e.error,"warning"):n(e.error,"danger"):n("yourKeyIsRegistered","success")}}):n("fillTheForm","warning")},$(document).ready(function(){return e(0),$("#changekey").on("click",function(){return e(1)}),$("#verify").on("click",function(){return o()})})}).call(this);
|
|
@ -1 +1 @@
|
|||
{"version":3,"sources":["totpregistration.js"],"names":["setMsg","msg","level","$","html","window","translate","removeClass","addClass","displayError","j","status","err","console","log","res","JSON","parse","responseText","error","replace","token","getKey","reset","ajax","type","url","portal","dataType","data","newkey","success","s","match","hide","user","secret","show","escape","digits","interval","QRious","element","document","getElementById","value","size","text","verify","val","code","TOTPName","ready","on","call","this"],"mappings":"CAMA,WACE,IAEAA,EAAS,SAASC,EAAKC,GAOrB,OANAC,EAAE,QAAQC,KAAKC,OAAOC,UAAUL,IAChCE,EAAE,UAAUI,YAAY,4FACxBJ,EAAE,UAAUK,SAAS,WAAaN,GACpB,aAAVA,IACFA,EAAQ,WAEHC,EAAE,UAAUK,SAAS,SAAWN,IAGzCO,EAAe,SAASC,EAAGC,EAAQC,GAIjC,GAFAC,QAAQC,IAAI,QAASF,IACrBG,EAAMC,KAAKC,MAAMP,EAAEQ,gBACRH,EAAII,MAGb,OAFAJ,EAAMA,EAAII,MAAMC,QAAQ,MAAO,IAC/BP,QAAQC,IAAI,iBAAkBC,GACvBf,EAAOe,EAAK,YAIvBM,EAAQ,GAERC,EAAS,SAASC,GAEhB,OADAvB,EAAO,cAAe,WACfG,EAAEqB,KAAK,CACZC,KAAM,OACNC,IAAKC,OAAS,2BACdC,SAAU,OACVC,KAAM,CACJC,OAAQP,GAEVJ,MAAOV,EACPsB,QAAS,SAASF,GAChB,IAAQG,EACR,OAAIH,EAAKV,OACHU,EAAKV,MAAMc,MAAM,oBACnB9B,EAAE,cAAc+B,OAEXlC,EAAO6B,EAAKV,MAAO,YAEtBU,EAAKF,QAAUE,EAAKM,MAAQN,EAAKO,QAGvCjC,EAAE,cAAckC,OAChBL,EAAI,kBAAqBM,OAAOT,EAAKF,QAAW,IAAOW,OAAOT,EAAKM,MAAS,WAAaN,EAAKO,OAAS,WAAcE,OAAOT,EAAKF,QAC7G,IAAhBE,EAAKU,SACPP,GAAK,WAAaH,EAAKU,QAEH,KAAlBV,EAAKW,WACPR,GAAK,WAAaH,EAAKW,UAEpB,IAAIC,OAAO,CACdC,QAASC,SAASC,eAAe,MACjCC,MAAOb,EACPc,KAAM,MAER3C,EAAE,eAAe4C,KAAKf,GAClBH,EAAKC,OACP9B,EAAO,iBAAkB,WAEzBA,EAAO,cAAe,WAEjBqB,EAAQQ,EAAKR,OArBXrB,EAAO,OAAQ,cA0B9BgD,EAAS,WACP,IACAC,EAAM9C,EAAE,SAAS8C,MACjB,OAAKA,EAGI9C,EAAEqB,KAAK,CACZC,KAAM,OACNC,IAAKC,OAAS,2BACdC,SAAU,OACVC,KAAM,CACJR,MAAOA,EACP6B,KAAMD,EACNE,SAAUhD,EAAE,aAAa8C,OAE3B9B,MAAOV,EACPsB,QAAS,SAASF,GAChB,OAAIA,EAAKV,MACHU,EAAKV,MAAMc,MAAM,kBACZjC,EAAO6B,EAAKV,MAAO,WAEnBnB,EAAO6B,EAAKV,MAAO,UAGrBnB,EAAO,sBAAuB,cApBpCA,EAAO,cAAe,YA2BjCG,EAAEwC,UAAUS,MAAM,WAKhB,OAJA9B,EAAO,GACPnB,EAAE,cAAckD,GAAG,QAAS,WAC1B,OAAO/B,EAAO,KAETnB,EAAE,WAAWkD,GAAG,QACdL,MAIVM,KAAKC"}
|
||||
{"version":3,"sources":["totpregistration.js"],"names":["displayError","getKey","setMsg","token","verify","msg","level","$","html","window","translate","removeClass","addClass","j","status","err","res","console","log","JSON","parse","responseText","error","replace","reset","ajax","type","url","portal","dataType","data","newkey","success","s","match","hide","user","secret","show","escape","digits","interval","QRious","element","document","getElementById","value","size","text","val","code","TOTPName","ready","on","call","this"],"mappings":"CAMA,WACE,IAAIA,EAAcC,EAAQC,EAAQC,EAAOC,EAEzCF,EAAS,SAASG,EAAKC,GAOrB,OANAC,EAAE,QAAQC,KAAKC,OAAOC,UAAUL,IAChCE,EAAE,UAAUI,YAAY,4FACxBJ,EAAE,UAAUK,SAAS,WAAaN,GACpB,aAAVA,IACFA,EAAQ,WAEHC,EAAE,UAAUK,SAAS,SAAWN,IAGzCN,EAAe,SAASa,EAAGC,EAAQC,GACjC,IAAIC,EAGJ,GAFAC,QAAQC,IAAI,QAASH,IACrBC,EAAMG,KAAKC,MAAMP,EAAEQ,gBACRL,EAAIM,MAGb,OAFAN,EAAMA,EAAIM,MAAMC,QAAQ,MAAO,IAC/BN,QAAQC,IAAI,iBAAkBF,GACvBd,EAAOc,EAAK,YAIvBb,EAAQ,GAERF,EAAS,SAASuB,GAEhB,OADAtB,EAAO,cAAe,WACfK,EAAEkB,KAAK,CACZC,KAAM,OACNC,IAAKC,OAAS,2BACdC,SAAU,OACVC,KAAM,CACJC,OAAQP,GAEVF,MAAOtB,EACPgC,QAAS,SAASF,GAChB,IAAQG,EACR,OAAIH,EAAKR,OACHQ,EAAKR,MAAMY,MAAM,oBACnB3B,EAAE,cAAc4B,OAEXjC,EAAO4B,EAAKR,MAAO,YAEtBQ,EAAKF,QAAUE,EAAKM,MAAQN,EAAKO,QAGvC9B,EAAE,cAAc+B,OAChBL,EAAI,kBAAqBM,OAAOT,EAAKF,QAAW,IAAOW,OAAOT,EAAKM,MAAS,WAAaN,EAAKO,OAAS,WAAcE,OAAOT,EAAKF,QAC7G,IAAhBE,EAAKU,SACPP,GAAK,WAAaH,EAAKU,QAEH,KAAlBV,EAAKW,WACPR,GAAK,WAAaH,EAAKW,UAEpB,IAAIC,OAAO,CACdC,QAASC,SAASC,eAAe,MACjCC,MAAOb,EACPc,KAAM,MAERxC,EAAE,eAAeyC,KAAKf,GAClBH,EAAKC,OACP7B,EAAO,iBAAkB,WAEzBA,EAAO,cAAe,WAEjBC,EAAQ2B,EAAK3B,OArBXD,EAAO,OAAQ,cA0B9BE,EAAS,WACP,IAAI6C,EAEJ,OADAA,EAAM1C,EAAE,SAAS0C,OAIR1C,EAAEkB,KAAK,CACZC,KAAM,OACNC,IAAKC,OAAS,2BACdC,SAAU,OACVC,KAAM,CACJ3B,MAAOA,EACP+C,KAAMD,EACNE,SAAU5C,EAAE,aAAa0C,OAE3B3B,MAAOtB,EACPgC,QAAS,SAASF,GAChB,OAAIA,EAAKR,MACHQ,EAAKR,MAAMY,MAAM,kBACZhC,EAAO4B,EAAKR,MAAO,WAEnBpB,EAAO4B,EAAKR,MAAO,UAGrBpB,EAAO,sBAAuB,cApBpCA,EAAO,cAAe,YA2BjCK,EAAEqC,UAAUQ,MAAM,WAKhB,OAJAnD,EAAO,GACPM,EAAE,cAAc8C,GAAG,QAAS,WAC1B,OAAOpD,EAAO,KAETM,EAAE,WAAW8C,GAAG,QAAS,WAC9B,OAAOjD,UAIVkD,KAAKC"}
|
|
@ -20,6 +20,11 @@
|
|||
<input id="passwordfield" name="password" type="text" class="form-control key" autocomplete="off" required aria-required="true" aria-hidden="true"/>
|
||||
<TMPL_ELSE>
|
||||
<input id="passwordfield" name="password" type="password" class="form-control" trplaceholder="password" required aria-required="true"/>
|
||||
<TMPL_IF NAME="ENABLE_PASSWORD_DISPLAY">
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text"><i class="fa fa-eye-slash toggle-password"></i></span>
|
||||
</div>
|
||||
</TMPL_IF>
|
||||
</TMPL_IF>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
"scriptname":"<TMPL_VAR NAME="SCRIPT_NAME">",
|
||||
"activeTimer":<TMPL_VAR NAME="ACTIVE_TIMER" DEFAULT="0">,
|
||||
"pingInterval":<TMPL_VAR NAME="PING" DEFAULT="0">,
|
||||
"trOver":<TMPL_VAR NAME="TROVER" DEFAULT="[]"><TMPL_IF NAME="DISPLAY_PPOLICY">,
|
||||
"trOver":<TMPL_VAR NAME="TROVER" DEFAULT="[]">,<TMPL_IF NAME="DISPLAY_PPOLICY">
|
||||
"ppolicy": {
|
||||
"display": "<TMPL_VAR NAME="DISPLAY_PPOLICY" DEFAULT="0">",
|
||||
"minsize": "<TMPL_VAR NAME="PPOLICY_MINSIZE" DEFAULT="0">",
|
||||
|
@ -36,7 +36,8 @@
|
|||
"nopolicy": "<TMPL_VAR NAME="PPOLICY_NOPOLICY" DEFAULT="0">",
|
||||
"allowedspechar": "<TMPL_VAR NAME="PPOLICY_ALLOWEDSPECHAR" ESCAPE="js" DEFAULT="">",
|
||||
"minspechar": "<TMPL_VAR NAME="PPOLICY_MINSPECHAR" DEFAULT="0">"
|
||||
}</TMPL_IF>
|
||||
},</TMPL_IF>
|
||||
"enablePasswordDisplay":<TMPL_VAR NAME="ENABLE_PASSWORD_DISPLAY" DEFAULT="0">
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -0,0 +1,345 @@
|
|||
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';
|
||||
}
|
||||
|
||||
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, [], [] ];
|
||||
}
|
||||
);
|
||||
|
||||
sub runTest {
|
||||
my ( $req_nif, $res_nif, $force_attr, $expect_req_nif, $expect_res_nif,
|
||||
$expect_nameid )
|
||||
= @_;
|
||||
|
||||
# Initialization
|
||||
$issuer = register( 'issuer', sub { issuer( $res_nif, $force_attr ) } );
|
||||
$sp = register( 'sp', sub { sp($req_nif) } );
|
||||
|
||||
# Simple SP access
|
||||
my $res;
|
||||
ok(
|
||||
$res = $sp->_get(
|
||||
'/', accept => 'text/html',
|
||||
),
|
||||
'Unauth SP request'
|
||||
);
|
||||
count(1);
|
||||
|
||||
expectOK($res);
|
||||
my ( $host, $url, $s ) =
|
||||
expectAutoPost( $res, 'auth.idp.com', '/saml/singleSignOn',
|
||||
'SAMLRequest' );
|
||||
|
||||
my $sr = expectSamlRequest($s);
|
||||
expectXPath(
|
||||
$sr, '/samlp:AuthnRequest/samlp:NameIDPolicy/@Format',
|
||||
$expect_req_nif, 'Found expected NameID Format in request',
|
||||
);
|
||||
|
||||
# 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'
|
||||
);
|
||||
count(1);
|
||||
expectOK($res);
|
||||
my $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
|
||||
|
||||
# Try to authenticate with an authorized user to IdP
|
||||
$s = "user=french&password=french&$s";
|
||||
ok(
|
||||
$res = $issuer->_post(
|
||||
$url,
|
||||
IO::String->new($s),
|
||||
accept => 'text/html',
|
||||
cookie => $pdata,
|
||||
length => length($s),
|
||||
),
|
||||
'Post authentication'
|
||||
);
|
||||
count(1);
|
||||
my $idpId = expectCookie($res);
|
||||
|
||||
# Expect pdata to be cleared
|
||||
$pdata = expectCookie( $res, 'lemonldappdata' );
|
||||
ok( $pdata !~ 'issuerRequestsaml', 'SAML request cleared from pdata' )
|
||||
or explain( $pdata, 'not issuerRequestsaml' );
|
||||
count(1);
|
||||
|
||||
( $host, $url, $s ) =
|
||||
expectAutoPost( $res, 'auth.sp.com', '/saml/proxySingleSignOnPost',
|
||||
'SAMLResponse' );
|
||||
|
||||
my ($sr) = expectSamlResponse($s);
|
||||
expectXPath(
|
||||
$sr, '/samlp:Response/saml:Assertion/saml:Subject/saml:NameID/@Format',
|
||||
$expect_res_nif, 'Found expected NameID Format in response',
|
||||
);
|
||||
|
||||
if ($expect_nameid) {
|
||||
my $nameidvalue = expectXPath( $sr,
|
||||
'/samlp:Response/saml:Assertion/saml:Subject/saml:NameID/text()' );
|
||||
if ( ref($expect_nameid) eq "Regexp" ) {
|
||||
like( $nameidvalue, $expect_nameid, "NameID matches" );
|
||||
}
|
||||
else {
|
||||
is( $nameidvalue, $expect_nameid, "NameID matches" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SKIP: {
|
||||
eval "use Lasso";
|
||||
if ($@) {
|
||||
skip 'Lasso not found';
|
||||
}
|
||||
|
||||
# Default settings use the email NIF
|
||||
runTest(
|
||||
# requested NameIDFormat (sp side)
|
||||
undef,
|
||||
|
||||
# returned NameIDFormat (idp side)
|
||||
undef,
|
||||
|
||||
# Name ID session key
|
||||
undef,
|
||||
|
||||
# Expected NameIDFormat in SAMLRequest
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
||||
|
||||
# Expected NameIDFormat in SAMLResponse
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
||||
|
||||
# Expected NameID value
|
||||
'fa@badwolf.org'
|
||||
);
|
||||
|
||||
# Override session key
|
||||
runTest(
|
||||
undef,
|
||||
undef,
|
||||
"uid",
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
||||
'french'
|
||||
);
|
||||
|
||||
# Using email explicitely return the email
|
||||
runTest(
|
||||
"email",
|
||||
"email",
|
||||
undef,
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
||||
'fa@badwolf.org'
|
||||
);
|
||||
|
||||
# Changing the format on the IDP side has no effect if the client
|
||||
# specifies a NIF
|
||||
runTest(
|
||||
"email",
|
||||
"kerberos",
|
||||
undef,
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
||||
'fa@badwolf.org'
|
||||
);
|
||||
|
||||
# Using unspecified on the requesting side causes IDP settings to be honored
|
||||
# specifies a NIF
|
||||
runTest(
|
||||
"unspecified",
|
||||
"kerberos",
|
||||
undef,
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
|
||||
"urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos",
|
||||
'french'
|
||||
);
|
||||
|
||||
# Unspecified both ways + no forced key returns no value
|
||||
runTest(
|
||||
"unspecified",
|
||||
"unspecified",
|
||||
undef,
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
|
||||
undef,
|
||||
);
|
||||
|
||||
# Unspecified both ways returns forced key in unspecified format
|
||||
runTest(
|
||||
"unspecified",
|
||||
"unspecified",
|
||||
'mail',
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
|
||||
'fa@badwolf.org',
|
||||
);
|
||||
|
||||
# persistent asked by SP returns a value
|
||||
runTest(
|
||||
"persistent",
|
||||
"email",
|
||||
undef,
|
||||
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
|
||||
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
|
||||
qr/./,
|
||||
);
|
||||
|
||||
# persistent chosen by IDP returns a value
|
||||
runTest(
|
||||
"unspecified",
|
||||
"persistent",
|
||||
undef,
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
|
||||
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
|
||||
qr/./,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
clean_sessions();
|
||||
done_testing();
|
||||
|
||||
sub issuer {
|
||||
my ( $res_nif, $force_attr ) = @_;
|
||||
return LLNG::Manager::Test->new( {
|
||||
ini => {
|
||||
logLevel => $debug,
|
||||
domain => 'idp.com',
|
||||
portal => 'http://auth.idp.com',
|
||||
authentication => 'Demo',
|
||||
userDB => 'Same',
|
||||
issuerDBSAMLActivation => 1,
|
||||
issuerDBSAMLRule => '$uid eq "french"',
|
||||
samlSPMetaDataOptions => {
|
||||
'sp.com' => {
|
||||
samlSPMetaDataOptionsEncryptionMode => 'none',
|
||||
samlSPMetaDataOptionsSignSSOMessage => 1,
|
||||
samlSPMetaDataOptionsSignSLOMessage => 1,
|
||||
samlSPMetaDataOptionsCheckSSOMessageSignature => 0,
|
||||
samlSPMetaDataOptionsCheckSLOMessageSignature => 0,
|
||||
(
|
||||
$res_nif
|
||||
? ( samlSPMetaDataOptionsNameIDFormat => $res_nif, )
|
||||
: ()
|
||||
),
|
||||
(
|
||||
$force_attr
|
||||
? ( samlSPMetaDataOptionsNameIDSessionKey =>
|
||||
$force_attr, )
|
||||
: ()
|
||||
),
|
||||
}
|
||||
},
|
||||
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 {
|
||||
my ($req_nif) = @_;
|
||||
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,
|
||||
samlIDPMetaDataOptionsSignSSOMessage => 0,
|
||||
(
|
||||
$req_nif
|
||||
? ( samlIDPMetaDataOptionsNameIDFormat => $req_nif,
|
||||
)
|
||||
: ()
|
||||
),
|
||||
}
|
||||
},
|
||||
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,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -11,7 +11,7 @@ BEGIN {
|
|||
require 't/saml-lib.pm';
|
||||
}
|
||||
|
||||
my $maintests = 21;
|
||||
my $maintests = 22;
|
||||
my $debug = 'error';
|
||||
my ( $issuer, $sp, $res );
|
||||
|
||||
|
@ -35,8 +35,22 @@ SKIP: {
|
|||
$issuer = register( 'issuer', \&issuer );
|
||||
$sp = register( 'sp', \&sp );
|
||||
|
||||
# Simple SP access
|
||||
# Try to authenticate
|
||||
# -------------------
|
||||
switch ('issuer');
|
||||
my $res;
|
||||
ok(
|
||||
$res = $issuer->_post(
|
||||
'/', IO::String->new('user=french&password=french'),
|
||||
length => 27
|
||||
),
|
||||
'Auth query'
|
||||
);
|
||||
expectOK($res);
|
||||
my $id = expectCookie($res);
|
||||
|
||||
# Simple SP access
|
||||
switch ('sp');
|
||||
ok(
|
||||
$res = $sp->_get(
|
||||
'/', accept => 'text/html',
|
||||
|
@ -140,7 +154,7 @@ SKIP: {
|
|||
|
||||
# Verify authentication on SP
|
||||
expectRedirection( $res, 'http://auth.sp.com' );
|
||||
my $spId = expectCookie($res);
|
||||
my $spId = expectCookie($res);
|
||||
$rawCookie = getHeader( $res, 'Set-Cookie' );
|
||||
ok( $rawCookie =~ /;\s*SameSite=None/, 'Found SameSite=None' );
|
||||
|
||||
|
@ -236,6 +250,8 @@ sub issuer {
|
|||
portal => 'http://auth.idp.com',
|
||||
authentication => 'Demo',
|
||||
userDB => 'Same',
|
||||
globalLogoutRule => 1,
|
||||
globalLogoutTimer => 0,
|
||||
issuerDBSAMLActivation => 1,
|
||||
issuerDBSAMLRule => '$uid eq "french"',
|
||||
samlSPMetaDataOptions => {
|
||||
|
|
|
@ -439,6 +439,7 @@ JjTJecOOS+88fK8qL1TrYv5rapIdqUI7aQ==
|
|||
),
|
||||
'Push U2F signature'
|
||||
);
|
||||
$id = expectCookie($res);
|
||||
ok(
|
||||
$res = $client->_get(
|
||||
'/2fregisters',
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
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 $client = LLNG::Manager::Test->new( {
|
||||
ini => {
|
||||
logLevel => 'debug',
|
||||
mail2fActivation => '$uid eq "msmith"',
|
||||
mail2fAuthnLevel => 3,
|
||||
mail2fCodeRegex => '\d{4}',
|
||||
authentication => 'Demo',
|
||||
userDB => 'Same',
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
# Login as dwho
|
||||
ok(
|
||||
my $res = $client->_post(
|
||||
'/',
|
||||
IO::String->new('user=dwho&password=dwho'),
|
||||
length => 23,
|
||||
accept => 'text/html',
|
||||
),
|
||||
'Auth query'
|
||||
);
|
||||
count(1);
|
||||
my $dwho_id = expectCookie($res);
|
||||
|
||||
ok(
|
||||
$res = $client->_get(
|
||||
'/', cookie => "lemonldap=$dwho_id",
|
||||
),
|
||||
'Get portal'
|
||||
);
|
||||
expectAuthenticatedAs($res, 'dwho');
|
||||
count(1);
|
||||
|
||||
# Start logging in as msmith
|
||||
my $s = buildForm({
|
||||
user => 'msmith',
|
||||
password => 'msmith',
|
||||
});
|
||||
ok(
|
||||
$res = $client->_post(
|
||||
'/',
|
||||
IO::String->new($s),
|
||||
length => length($s),
|
||||
accept => 'text/html',
|
||||
),
|
||||
'Auth query'
|
||||
);
|
||||
count(1);
|
||||
|
||||
my ( $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() );
|
||||
|
||||
my $code = $1;
|
||||
count(1);
|
||||
|
||||
# Fill the handler cache with dwho session info
|
||||
ok(
|
||||
$res = $client->_get(
|
||||
'/', cookie => "lemonldap=$dwho_id",
|
||||
),
|
||||
'Get portal'
|
||||
);
|
||||
count(1);
|
||||
|
||||
# Finish logging in as msmith,
|
||||
# this corrupts the dwho cache with msmith macros
|
||||
$query =~ s/code=/code=${code}/;
|
||||
ok(
|
||||
$res = $client->_post(
|
||||
'/mail2fcheck',
|
||||
IO::String->new($query),
|
||||
length => length($query),
|
||||
accept => 'text/html',
|
||||
),
|
||||
'Post code'
|
||||
);
|
||||
count(1);
|
||||
diag Dumper($res);
|
||||
|
||||
# Reuse the corrupted cache
|
||||
ok(
|
||||
$res = $client->_get(
|
||||
'/', cookie => "lemonldap=$dwho_id",
|
||||
),
|
||||
'Get portal'
|
||||
);
|
||||
count(1);
|
||||
|
||||
|
||||
TODO: {
|
||||
local $TODO = "Fix 2539";
|
||||
expectAuthenticatedAs($res, 'dwho');
|
||||
}
|
||||
|
||||
clean_sessions();
|
||||
|
||||
done_testing( count() );
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
use XML::LibXML;
|
||||
use URI::Escape;
|
||||
use MIME::Base64;
|
||||
|
||||
sub saml_key_proxy_private_enc {
|
||||
"-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA2vzoUiQ4GsM5qLjoxslEDKj+RrPh/A743JCWe1Hbadjd5yD4
|
||||
|
@ -800,4 +804,60 @@ EOF
|
|||
;
|
||||
}
|
||||
|
||||
=head4 expectXPath($xml_string, $xpath, $namespaces, $value, $message)
|
||||
|
||||
Match a XPath expression against the provided string, and verify that the correct value is
|
||||
|
||||
=cut
|
||||
|
||||
sub expectXPath {
|
||||
my ( $xml_string, $xpath, $value, $message ) = @_;
|
||||
my $dom = XML::LibXML->load_xml( string => $xml_string );
|
||||
return unless ok( $dom, 'XML successfully parsed' );
|
||||
|
||||
my $xpc = XML::LibXML::XPathContext->new($dom);
|
||||
my $namespaces = {
|
||||
samlp => 'urn:oasis:names:tc:SAML:2.0:protocol',
|
||||
saml => 'urn:oasis:names:tc:SAML:2.0:assertion',
|
||||
};
|
||||
if ( ref($namespaces) eq "HASH" ) {
|
||||
for my $key ( keys %{$namespaces} ) {
|
||||
$xpc->registerNs( $key, $namespaces->{$key} );
|
||||
}
|
||||
}
|
||||
|
||||
my ($match1) = $xpc->findnodes($xpath);
|
||||
return unless ok( $match1, 'Found a match for XPath Expression ' . $xpath );
|
||||
|
||||
if ( ref($match1) eq 'XML::LibXML::Attr' ) {
|
||||
if ($value) {
|
||||
is( $match1->value, $value, $message );
|
||||
}
|
||||
return $match1->value;
|
||||
}
|
||||
elsif ( ref($match1) eq 'XML::LibXML::Text' ) {
|
||||
if ($value) {
|
||||
is( $match1->data, $value, $message );
|
||||
}
|
||||
return $match1->data;
|
||||
}
|
||||
else {
|
||||
fail( "Unexpected XPath result: " . ref($match1) );
|
||||
}
|
||||
}
|
||||
|
||||
sub expectSamlRequest {
|
||||
my ($string) = @_;
|
||||
my ($sr) = $string =~ m/SAMLRequest=([^&]*)/;
|
||||
ok( $sr, "Found SAMLRequest" );
|
||||
return decode_base64( uri_unescape($sr) );
|
||||
}
|
||||
|
||||
sub expectSamlResponse {
|
||||
my ($string) = @_;
|
||||
my ($sr) = $string =~ m/SAMLResponse=([^&]*)/;
|
||||
ok( $sr, "Found SAMLResponse" );
|
||||
return decode_base64( uri_unescape($sr) );
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -337,9 +337,7 @@ Verify that result has a C<Lm-Remote-User> header and value is $user
|
|||
|
||||
sub expectAuthenticatedAs {
|
||||
my ( $res, $user ) = @_;
|
||||
ok( getHeader( $res, 'Lm-Remote-User' ) eq $user,
|
||||
" Authenticated as $user" )
|
||||
or explain( $res->[1], "Lm-Remote-User => $user" );
|
||||
is( getHeader( $res, 'Lm-Remote-User' ), $user, "Authenticated as $user" );
|
||||
count(1);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue