Merge branch 'v2.0' into master

This commit is contained in:
Xavier Guimard 2020-10-07 17:11:27 +02:00
commit 4583f3a9e6
70 changed files with 1557 additions and 273 deletions

View File

@ -32,7 +32,7 @@ server {
#real_ip_header X-Forwarded-For;
location = /reload {
allow 127.0.0.1/8;
allow 127.0.0.0/8;
allow ::1/128;
deny all;

View File

@ -0,0 +1,46 @@
Adaptative Authentication Level
===============================
Presentation
------------
A user obtain an authentication level depending on which authentication
module was used, and eventually which second factor module.
This plugin allows to adapt this authentication level depending on
other conditions, like network, device, etc.
Sample use case: a strategic application is configured to require an
authentication level of 5. Users obtain level 2 with their login/password
and level 5 using a TOTP second factor. You can trust users form internal
network by incrementing their authentication level based on their IP address,
they would then not be forced to use 2FA to access the strategic application.
.. tip::
This use case works if you enable the *Use 2FA for session upgrade* option.
Configuration
-------------
This plugin is enabled when at least one rule is defind.
To configure rules, go in ``General Parameters`` > ``Plugins`` >
``Adapative Authentication Level``.
You can then create rules with these fields:
- **Rule**: The condition that will be evaluated. If this condition
does not return true, then the level is not changed.
- **Value**: How change the authentication level. First character is
``+``, ``-`` or ``=``, the second part is the number to add, remove
or set.
.. tip::
By example, to add 3 to authentication level for users from 192.168.0.0/24 network:
- Rule: ``$env->{REMOTE_ADDR} =~ /^192\.168\./``
- Value: ``+3``

View File

@ -95,6 +95,20 @@ configuration file:
proxy_pass http://127.0.0.1:8888/login;
}
.. warning::
Thoses 2 blocks should be append BEFORE the following block::
#Anything that didn't match above, and isn't a real file,
#assume it's a room name and redirect to /
location ~ ^/([^/?&:'"]+)/(.*)$ {
set $subdomain "$1.";
set $subdir "$1/";
rewrite ^/([^/?&:'"]+)/(.*)$ /$2;
}
Jitsi Meet Virtual host in Manager
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -175,6 +175,7 @@ Signature
These options override service signature options (see
:ref:`SAML service configuration<samlservice-general-options>`).
- **Signature method**: signature method for requests sent to this provider
- **Sign SSO message**: sign SSO message
- **Check SSO message signature**: check SSO message signature
- **Sign SLO message**: sign SLO message

View File

@ -6,6 +6,9 @@ Your network infrastructure might require that LemonLDAP::NG components
|image0|
Transmitting the correct IP address to the portal
-------------------------------------------------
In this case, LemonLDAP::NG components will store the ip address of the
connection between the reverse proxy and the webserver in the session,
and in logs. This prevents features such as session restrictions and
@ -19,7 +22,7 @@ to forward the original IP address all the way to LemonLDAP::NG.
In order to do this you have several options.
HTTP Header
-----------
~~~~~~~~~~~
This generic method is the most likely to work in your particular
environment.
@ -68,7 +71,7 @@ uncomment the relevant parts of the configuration file.
module documentation carefully.
PROXY Protocol
--------------
~~~~~~~~~~~~~~
Alternatively, if your proxy supports the PROXY protocol (Nginx,
HAProxy, Amazon ELB), you may use it to carry over the information
@ -86,6 +89,41 @@ Portal/Manager/Handler:
# or
# listen 443 ssl proxy_protocol;
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
.. |image0| image:: /documentation/reverseproxy.png
:class: align-center
Fixing handler redirections
---------------------------
If your handler server runs behind a reverse proxy, it may have trouble figuring
out the right URL to redirect you to after logging in.
In this case, you can force a particular port and scheme in the Virtual Host's
Options.
But is instead you want this scheme to be auto-detected by LemonLDAP (in order to have a
same VHost domain available over multiple schemes), you can also use the following
declarations in the handler's virtual host to force LemonLDAP to use the correct port
and scheme
Nginx
~~~~~
::
fastcgi_param SERVER_PORT 443
fastcgi_param HTTPS On
Apache
~~~~~~
.. versionadded: 2.0.10
::
PerlSetEnv SERVER_PORT 443
PerlSetEnv HTTPS On

View File

@ -27,7 +27,7 @@ Red Hat/CentOS
::
yum install lemonldap-ng-fastcgi-server
yum install lemonldap-ng-nginx lemonldap-ng-fastcgi-server
Enable and start the service :
@ -40,25 +40,25 @@ Files
-----
With tarball installation, Nginx configuration files will be installed
in ``/usr/local/lemonldap-ng/etc/``, else they are in
``/etc/lemonldap-ng``.
You have to include them in Nginx main configuration.
in ``/usr/local/lemonldap-ng/etc/``, else they are directly in web server
configuration.
.. _debianubuntu-1:
Debian/Ubuntu
~~~~~~~~~~~~~
Link files into ``sites-available`` directory (should already have been
done if you used packages):
- Install log format *(automatically loaded when linked in this place)*
::
ln -s /etc/lemonldap-ng/handler-nginx.conf /etc/nginx/sites-available/
ln -s /etc/lemonldap-ng/manager-nginx.conf /etc/nginx/sites-available/
ln -s /etc/lemonldap-ng/portal-nginx.conf /etc/nginx/sites-available/
ln -s /etc/lemonldap-ng/test-nginx.conf /etc/nginx/sites-available/
ln -s /etc/lemonldap-ng/nginx-lmlog.conf /etc/nginx/conf.d/llng-lmlog.conf
- Install snippet for vhost configuration files:
::
ln -s /etc/lemonldap-ng/nginx-lua-headers.conf /etc/nginx/snippets/llng-lua-headers.conf
- Enable sites:
@ -68,18 +68,3 @@ done if you used packages):
ln -s /etc/nginx/sites-available/manager-nginx.conf /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/portal-nginx.conf /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/test-nginx.conf /etc/nginx/sites-enabled/
.. _red-hatcentos-1:
Red Hat/CentOS
~~~~~~~~~~~~~~
Link files directly in ``conf.d`` directory:
::
ln -s /etc/lemonldap-ng/handler-nginx.conf /etc/nginx/conf.d/
ln -s /etc/lemonldap-ng/manager-nginx.conf /etc/nginx/conf.d/
ln -s /etc/lemonldap-ng/portal-nginx.conf /etc/nginx/conf.d/
ln -s /etc/lemonldap-ng/test-nginx.conf /etc/nginx/conf.d/

View File

@ -261,7 +261,7 @@ Example of a protected virtual host for a local application:
Reverse proxy
~~~~~~~~~~~~~
\* Example of a protected reverse-proxy:
- Example of a protected reverse-proxy:
.. code-block:: nginx
@ -300,7 +300,7 @@ Reverse proxy
# PASSING HEADERS TO APPLICATION #
##################################
# IF LUA IS SUPPORTED
#include /path/to/nginx-lua-headers.conf
#include /path/to/nginx-lua-headers.conf;
# ELSE
# Set manually your headers
@ -309,8 +309,16 @@ Reverse proxy
}
}
\* Example of a Nginx Virtual Host using uWSGI with many URIs protected
by different types of handler :
If /etc/nginx/proxy_params file does not exist, you can create it with this content:
.. code-block:: nginx
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
- Example of a Nginx Virtual Host using uWSGI with many URIs protected by different types of handler:
.. code-block:: nginx

View File

@ -15,7 +15,8 @@ can be forbidden to assume.
- **Parameters**:
- **Use rule**: Select which users may use this plugin
- **Use rule**: Rule to enable or define which users may use this plugin
(By example: $uid eq 'dwho' && $authenticationLevel > 2).
- **Identities use rule**: Rule to define which identities can be
assumed. Useful to prevent impersonation of certain sensitive
identities like CEO, administrators or anonymous/protected users.
@ -30,7 +31,7 @@ can be forbidden to assume.
During context switching authentication process, all
plugins are disabled. In other words, all entry points like afterData,
endAuth and so on are skipped. Therefore, second factors or
notifications by example will not be prompted!
notifications by example will not be prompted and login history is not updated!
.. attention::
@ -39,6 +40,11 @@ can be forbidden to assume.
backend. You can not switch context with federated authentication.
.. attention::
Used identity, start and end of switching context process are logged!
contextSwitchingPrefix is used to store real user's session Id. You can
set this prefix ('switching' by default) by editing ``lemonldap-ng.ini``
in [portal] section:

View File

@ -154,6 +154,7 @@ Signature
These options override service signature options (see
:ref:`SAML service configuration<samlservice-general-options>`).
- **Signature method**: signature method for messages sent to this service
- **Sign SSO message**: sign SSO message
- **Check SSO message signature**: check SSO message signature
- **Sign SLO message**: sign SLO message

View File

@ -4,6 +4,7 @@ Plugins
.. toctree::
:maxdepth: 1
adaptativeauthenticationlevel
autosignin
bruteforceprotection
cda

View File

@ -6,8 +6,8 @@ Presentation
Main settings:
- **REMOTE_USER** : session attribute used for logging user access.
- **REMOTE_CUSTOM** : can be used for logging a second user attribute
- **REMOTE_USER** : session attribute used for logging user access
- **REMOTE_CUSTOM** : can be used for logging an another user attribute or a macro
(optional)
- **Hidden attributes** : session attributes never displayed or sent

View File

@ -0,0 +1,27 @@
InWebo Second Factor
====================
`InWebo <https://www.inwebo.com/>`_ is a proprietary MFA solution.
You can use is as second factor trough :doc:`Radius 2FA module<radius2f>`.
Configuration
~~~~~~~~~~~~~
On InWebo side :
- Create a connector of type ``Radius Push``.
- Fill in the “IP Address” field with the IP of the public interface of your LL::NG server.
- Enter a secret, that you will also configure on LL::NG side.
See `InWebo Radius documentation <https://inwebo.atlassian.net/wiki/spaces/DOCS/pages/2216886275/RADIUS+integration+and+redundancy>`_ for more details.
On LL::NG side, go in "General Parameters » Second factors »
Radius second factor".
- **Activation**: Set to ``On`` to activate this module, or use a
specific rule to select which users may use this type of second
factor
- **Server hostname**: The hostname of InWebo Radius server (for example `radius2.myinwebo.com`)
- **Shared secret**: The secret key declared on InWebo side
See :doc:`Radius 2FA module<radius2f>` for more details.

View File

@ -63,3 +63,13 @@ Mail second factor".
- **Logo** (Optional): logo file *(in static/<skin> directory)*
- **Label** (Optional): label that should be displayed to the user on
the choice screen
Vendor specific
~~~~~~~~~~~~~~~
Some configuration examples for specific vendors:
.. toctree::
:maxdepth: 1
radius2f-inwebo

View File

@ -121,8 +121,12 @@ encryption.
To define keys, you can:
- import your own private and public keys (``Replace by file`` input)
- generate new public and private keys (``New keys`` button)
- generate new public and private keys (``New certificate`` button)
.. versionchanged:: 2.0.10
A X.509 certificate is now generated instead of a plain public key. It has
20 years of validity, and is self signed with the 2048bit RSA key.
.. tip::
@ -132,34 +136,35 @@ To define keys, you can:
|image1|
You can import a certificate containing the public key instead the raw
public key. However, certificate will not be really validated by other
SAML components (expiration date, common name, etc.), but will just be a
public key wrapper.
.. tip::
You can easily generate a certificate to replace your public
key by saving the private key in a file, and use ``openssl`` commands to
issue a self-signed certificate:
::
$ openssl req -new -key private.key -out cert.pem -x509 -days 3650
- **Use certificate in response**: Certificate will be sent inside SAML
responses.
- **Signature method**: set the signature algorithm
.. versionchanged:: 2.0.10
The signature method can now be overriden for a SP or IDP. This will only work
if you are using a certificate for signature instead of a public key.
.. attention::
Default value is RSA SHA1 for compatibility purpose but
we recommend to use RSA SHA256. This requires to test all partners to
check their compatibility.
If you are running a version under 2.0.10, the choice of a signature
algorithm will affect all SP and IDP.
Converting a RSA public key to a certificate
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If your application complains about the lack of certificate in SAML Metadatas, and you generated a public RSA key instead of a certificate in a previous version of LemonLDAP::NG, you can convert the public key into a certificate without changing the private key.
Save the private key in a file, and use the ``openssl`` commands to
issue a self-signed certificate:
::
$ openssl req -new -key private.key -out cert.pem -x509 -days 3650
NameID formats
~~~~~~~~~~~~~~

View File

@ -1,6 +1,6 @@
# Plack app to use with Nginx
#
# This app can remplace FastCGI server using Starman, Twiggy, uWSGI-Plugin-PSGI,
# This app can replace FastCGI server using Starman, Twiggy, uWSGI-Plugin-PSGI,
# Feersum,...
my %builder = (

View File

@ -29,8 +29,8 @@ use constant DEFAULTCONFBACKEND => "File";
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)|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 $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)|a(?:(?:daptativeAuthenticationLevelR|ut(?:hChoiceMod|oSigninR))ules|pplicationList)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|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)|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)|c(?:o(?:ntextSwitching(?:Allowed2fModifications|StopWithLogout)|mpactConf|rsEnabled)|a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:State|User|XSS)|da)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|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

@ -312,7 +312,7 @@ sub defaultValues {
'samlOrganizationURL' => 'http://www.example.com',
'samlOverrideIDPEntityID' => '',
'samlRelayStateTimeout' => 600,
'samlServiceSignatureMethod' => 'RSA_SHA1',
'samlServiceSignatureMethod' => 'RSA_SHA256',
'samlSPSSODescriptorArtifactResolutionServiceArtifact' =>
'1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;#PORTAL#/saml/artifact',
'samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact' =>

View File

@ -22,14 +22,14 @@ 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 $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)|a(?:(?:daptativeAuthenticationLevelR|ut(?:hChoiceMod|oSigninR))ules|pplicationList)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember|fExtra)|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|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(?: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)|AuthnLevel|ForceUTF8)|(?:ExportedAttribute|Macro)s|XML)';
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(?:uthnLevel|liases)|(?:Maintenanc|Typ)e|ServiceTokenTTL|Https|Port)|(?:exportedHeader|locationRule)s|post)';
our $authParameters = {

View File

@ -25,7 +25,7 @@ sub new {
QUERY_STRING => $args,
REQUEST_URI => $uri_full,
PATH_INFO => '',
SERVER_PORT => $r->get_server_port,
SERVER_PORT => $ENV{SERVER_PORT} || $r->get_server_port,
REQUEST_METHOD => $r->method,
'psgi.version' => [ 1, 1 ],
'psgi.url_scheme' => ( $ENV{HTTPS} || 'off' ) =~ /^(?:on|1)$/i

View File

@ -614,7 +614,7 @@ sub _getPort {
return $class->tsv->{port}->{_};
}
else {
return $req->{env}->{SERVER_PORT};
return $req->port;
}
}
}
@ -638,7 +638,7 @@ sub _isHttps {
return $class->tsv->{https}->{_};
}
else {
return ( uc( $req->{env}->{HTTPS} || "OFF" ) eq "ON" );
return $req->secure;
}
}
}

View File

@ -139,7 +139,7 @@ site/htdocs/static/forms/post.html
site/htdocs/static/forms/postContainer.html
site/htdocs/static/forms/README.md
site/htdocs/static/forms/restore.html
site/htdocs/static/forms/RSAKey.html
site/htdocs/static/forms/RSACertKey.html
site/htdocs/static/forms/RSAKeyNoPassword.html
site/htdocs/static/forms/rule.html
site/htdocs/static/forms/ruleContainer.html

View File

@ -171,7 +171,7 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
'RSAPrivateKey' => {
'test' => sub {
return $_[0] =~
m[^(?:(?:\-+\s*BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$]s
m[^(?:(?:\-+\s*BEGIN\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$]s
? 1
: ( 1, '__badPemEncoding__' );
}
@ -263,6 +263,18 @@ sub attributes {
'default' => 1,
'type' => 'bool'
},
'adaptativeAuthenticationLevelRules' => {
'keyMsgFail' => '__badRegexp__',
'keyTest' => sub {
eval {
do {
qr/$_[0]/;
}
};
return $@ ? 0 : 1;
},
'type' => 'keyTextContainer'
},
'ADPwdExpireWarning' => {
'default' => 0,
'type' => 'int'
@ -1026,6 +1038,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
],
'type' => 'select'
},
'contextSwitchingAllowed2fModifications' => {
'default' => 0,
'type' => 'bool'
},
'contextSwitchingIdRule' => {
'default' => 1,
'test' => sub {
@ -3203,6 +3219,31 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'default' => '',
'type' => 'longtext'
},
'samlIDPMetaDataOptionsSignatureMethod' => {
'default' => '',
'select' => [ {
'k' => '',
'v' => 'default'
},
{
'k' => 'RSA_SHA1',
'v' => 'RSA SHA1'
},
{
'k' => 'RSA_SHA256',
'v' => 'RSA SHA256'
},
{
'k' => 'RSA_SHA384',
'v' => 'RSA SHA384'
},
{
'k' => 'RSA_SHA512',
'v' => 'RSA SHA512'
}
],
'type' => 'select'
},
'samlIDPMetaDataOptionsSignSLOMessage' => {
'default' => -1,
'type' => 'trool'
@ -3394,7 +3435,7 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'type' => 'RSAPublicKeyOrCertificate'
},
'samlServiceSignatureMethod' => {
'default' => 'RSA_SHA1',
'default' => 'RSA_SHA256',
'select' => [ {
'k' => 'RSA_SHA1',
'v' => 'RSA SHA1'
@ -3402,6 +3443,14 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
{
'k' => 'RSA_SHA256',
'v' => 'RSA SHA256'
},
{
'k' => 'RSA_SHA384',
'v' => 'RSA SHA384'
},
{
'k' => 'RSA_SHA512',
'v' => 'RSA SHA512'
}
],
'type' => 'select'
@ -3539,6 +3588,31 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'default' => 72000,
'type' => 'int'
},
'samlSPMetaDataOptionsSignatureMethod' => {
'default' => '',
'select' => [ {
'k' => '',
'v' => 'default'
},
{
'k' => 'RSA_SHA1',
'v' => 'RSA SHA1'
},
{
'k' => 'RSA_SHA256',
'v' => 'RSA SHA256'
},
{
'k' => 'RSA_SHA384',
'v' => 'RSA SHA384'
},
{
'k' => 'RSA_SHA512',
'v' => 'RSA SHA512'
}
],
'type' => 'select'
},
'samlSPMetaDataOptionsSignSLOMessage' => {
'default' => -1,
'type' => 'trool'

View File

@ -154,7 +154,7 @@ sub types {
test => sub {
return (
$_[0] =~
/^(?:(?:\-+\s*BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$/s
/^(?:(?:\-+\s*BEGIN\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$/s
? (1)
: ( 1, '__badPemEncoding__' )
);
@ -594,6 +594,12 @@ sub attributes {
documentation => 'Stop context switching by logout',
flags => 'p',
},
contextSwitchingAllowed2fModifications => {
type => 'bool',
default => 0,
documentation => 'Allowed SFA modifications',
flags => 'p',
},
contextSwitchingPrefix => {
type => 'text',
default => 'switching',
@ -2119,6 +2125,18 @@ sub attributes {
documentation => 'List of auto signin rules',
},
# Adaptative Authentication Level plugin
adaptativeAuthenticationLevelRules => {
type => 'keyTextContainer',
keyTest => sub {
eval { qr/$_[0]/ };
return $@ ? 0 : 1;
},
keyMsgFail => '__badRegexp__',
documentation => 'Adaptative authentication level rules',
flags => 'p',
},
## Virtualhosts
# Fake attribute: used by manager REST API to agglomerate all other
@ -2490,8 +2508,10 @@ sub attributes {
select => [
{ k => 'RSA_SHA1', v => 'RSA SHA1' },
{ k => 'RSA_SHA256', v => 'RSA SHA256' },
{ k => 'RSA_SHA384', v => 'RSA SHA384' },
{ k => 'RSA_SHA512', v => 'RSA SHA512' },
],
default => 'RSA_SHA1',
default => 'RSA_SHA256',
},
samlServiceUseCertificateInResponse => {
type => 'bool',
@ -2764,6 +2784,17 @@ sub attributes {
type => 'trool',
default => -1,
},
samlIDPMetaDataOptionsSignatureMethod => {
type => 'select',
select => [
{ k => '', v => 'default' },
{ k => 'RSA_SHA1', v => 'RSA SHA1' },
{ k => 'RSA_SHA256', v => 'RSA SHA256' },
{ k => 'RSA_SHA384', v => 'RSA SHA384' },
{ k => 'RSA_SHA512', v => 'RSA SHA512' },
],
default => '',
},
samlIDPMetaDataOptionsCheckSLOMessageSignature => {
type => 'bool',
default => 1,
@ -2920,6 +2951,17 @@ sub attributes {
type => 'trool',
default => -1,
},
samlSPMetaDataOptionsSignatureMethod => {
type => 'select',
select => [
{ k => '', v => 'default' },
{ k => 'RSA_SHA1', v => 'RSA SHA1' },
{ k => 'RSA_SHA256', v => 'RSA SHA256' },
{ k => 'RSA_SHA384', v => 'RSA SHA384' },
{ k => 'RSA_SHA512', v => 'RSA SHA512' },
],
default => '',
},
samlSPMetaDataOptionsCheckSSOMessageSignature => {
type => 'bool',
default => 1,

View File

@ -52,6 +52,7 @@ sub cTrees {
title => "samlIDPMetaDataOptionsSignature",
form => 'simpleInputContainer',
nodes => [
"samlIDPMetaDataOptionsSignatureMethod",
"samlIDPMetaDataOptionsSignSSOMessage",
"samlIDPMetaDataOptionsCheckSSOMessageSignature",
"samlIDPMetaDataOptionsSignSLOMessage",
@ -122,10 +123,11 @@ sub cTrees {
title => "samlSPMetaDataOptionsSignature",
form => 'simpleInputContainer',
nodes => [
"samlSPMetaDataOptionsSignatureMethod",
"samlSPMetaDataOptionsSignSSOMessage",
"samlSPMetaDataOptionsCheckSSOMessageSignature",
"samlSPMetaDataOptionsSignSLOMessage",
"samlSPMetaDataOptionsCheckSLOMessageSignature"
"samlSPMetaDataOptionsCheckSLOMessageSignature",
]
},
{

View File

@ -615,6 +615,7 @@ sub tree {
nodes => [
'stayConnected',
'portalStatus',
'adaptativeAuthenticationLevelRules',
'upgradeSession',
'refreshSessions',
{
@ -792,6 +793,7 @@ sub tree {
'contextSwitchingRule',
'contextSwitchingIdRule',
'contextSwitchingUnrestrictedUsersRule',
'contextSwitchingAllowed2fModifications',
'contextSwitchingStopWithLogout',
]
},
@ -812,7 +814,11 @@ sub tree {
{
title => 'secondFactors',
help => 'secondfactor.html',
nodes => [ {
nodes => [
'sfManagerRule',
'sfRequired',
'sfOnlyUpgrade',
{
title => 'utotp2f',
help => 'utotp2f.html',
form => 'simpleInputContainer',
@ -930,9 +936,6 @@ sub tree {
'sfRemovedNotifMsg',
],
},
'sfOnlyUpgrade',
'sfManagerRule',
'sfRequired',
]
},
{
@ -1074,7 +1077,7 @@ sub tree {
help => 'samlservice.html#security-parameters',
nodes => [ {
title => 'samlServiceSecuritySig',
form => 'RSAKey',
form => 'RSACertKey',
group => [
'samlServicePrivateKeySig',
'samlServicePrivateKeySigPwd',
@ -1083,7 +1086,7 @@ sub tree {
},
{
title => 'samlServiceSecurityEnc',
form => 'RSAKey',
form => 'RSACertKey',
group => [
'samlServicePrivateKeyEnc',
'samlServicePrivateKeyEncPwd',

View File

@ -431,7 +431,6 @@ sub run {
unless (@_) {
die 'nothing to do, aborting';
}
$self->cfgNum( $self->lastCfg ) unless ( $self->cfgNum );
my $action = shift;
unless (
$action =~ /^(?:get|set|del|addKey|delKey|save|restore|rollback)$/ )
@ -440,6 +439,12 @@ sub run {
"Unknown action $action. Only get, set, del, addKey, delKey, save, restore, rollback allowed";
}
unless ( $action eq "restore" ) {
# This step prevents restoring when config DB is empty (#2340)
$self->cfgNum( $self->lastCfg ) unless ( $self->cfgNum );
}
$self->$action(@_);
}

View File

@ -15,6 +15,7 @@ use Lemonldap::NG::Common::EmailTransport;
use Crypt::OpenSSL::RSA;
use Convert::PEM;
use URI::URL;
use Net::SSLeay;
use feature 'state';
@ -59,10 +60,11 @@ sub init {
# New key and conf save
->addRoute(
confs => {
newRSAKey => 'newRSAKey',
sendTestMail => 'sendTestMail',
raw => 'newRawConf',
'*' => 'newConf'
newRSAKey => 'newRSAKey',
newCertificate => 'newCertificate',
sendTestMail => 'sendTestMail',
raw => 'newRawConf',
'*' => 'newConf'
},
['POST']
)
@ -76,6 +78,31 @@ sub init {
return 1;
}
# 35 - New Certificate on demand
# --------------------------
##@method public PSGI-JSON-response newRSAKey($req)
# Return a hashref containing private and public keys
# The posted data must contain a JSON object containing
# {"password":"newpassword"}
#
#@param $req Lemonldap::NG::Common::PSGI::Request object
#@return PSGI JSON response
sub newCertificate {
my ( $self, $req, @others ) = @_;
return $self->sendError( $req, 'There is no subkey for "newCertificate"',
400 )
if (@others);
my $query = $req->jsonBodyToObj;
my ( $private, $cert ) = $self->_generateX509( $query->{password} );
my $keys = {
'private' => $private,
'public' => $cert,
};
return $self->sendJSONresponse( $req, $keys );
}
# 35 - New RSA key pair on demand
# --------------------------
@ -90,8 +117,9 @@ sub newRSAKey {
my ( $self, $req, @others ) = @_;
return $self->sendError( $req, 'There is no subkey for "newRSAKey"', 400 )
if (@others);
my $rsa = Crypt::OpenSSL::RSA->generate_key(2048);
my $query = $req->jsonBodyToObj;
my $rsa = Crypt::OpenSSL::RSA->generate_key(2048);
my $keys = {
'private' => $rsa->get_private_key_string(),
'public' => $rsa->get_public_key_x509_string(),
@ -121,6 +149,81 @@ sub newRSAKey {
return $self->sendJSONresponse( $req, $keys );
}
# This function does the dirty X509 work,
# mostly copied from IO::Socket::SSL::Utils
# and adapter to work on old platforms (CentOS7)
sub _generateX509 {
my ( $self, $password ) = @_;
Net::SSLeay::SSLeay_add_ssl_algorithms();
my $conf = $self->confAcc->getConf();
# Generate 2048 bits RSA key
my $key = Net::SSLeay::EVP_PKEY_new();
Net::SSLeay::EVP_PKEY_assign_RSA( $key,
Net::SSLeay::RSA_generate_key( 2048, 0x10001 ) );
my $cert = Net::SSLeay::X509_new();
# Serial
Net::SSLeay::ASN1_INTEGER_set(
Net::SSLeay::X509_get_serialNumber($cert),
rand( 2**32 ),
);
# Version
Net::SSLeay::X509_set_version( $cert, 2 );
# Make it last 20 years
Net::SSLeay::ASN1_TIME_set( Net::SSLeay::X509_get_notBefore($cert),
time() );
Net::SSLeay::ASN1_TIME_set( Net::SSLeay::X509_get_notAfter($cert),
time() + 20 * 365 * 86400 );
# set subject
my $portal_uri = new URI::URL( $conf->{portal} || "http://localhost" );
my $portal_host = $portal_uri->host;
my $subj_e = Net::SSLeay::X509_get_subject_name($cert);
my $subj = { commonName => $portal_host, };
while ( my ( $k, $v ) = each %$subj ) {
# Not everything we get is nice - try with MBSTRING_UTF8 first and if it
# fails try V_ASN1_T61STRING and finally V_ASN1_OCTET_STRING
Net::SSLeay::X509_NAME_add_entry_by_txt( $subj_e, $k, 0x1000, $v, -1,
0 )
or
Net::SSLeay::X509_NAME_add_entry_by_txt( $subj_e, $k, 20, $v, -1, 0 )
or
Net::SSLeay::X509_NAME_add_entry_by_txt( $subj_e, $k, 4, $v, -1, 0 )
or croak( "failed to add entry for $k - "
. Net::SSLeay::ERR_error_string( Net::SSLeay::ERR_get_error() ) );
}
# Set to self-sign
Net::SSLeay::X509_set_pubkey( $cert, $key );
Net::SSLeay::X509_set_issuer_name( $cert,
Net::SSLeay::X509_get_subject_name($cert) );
# Sign with default alg
Net::SSLeay::X509_sign( $cert, $key, 0 );
my $strCert = Net::SSLeay::PEM_get_string_X509($cert);
my $strPrivate;
if ($password) {
$strPrivate = Net::SSLeay::PEM_get_string_PrivateKey( $key, $password );
}
else {
$strPrivate = Net::SSLeay::PEM_get_string_PrivateKey($key);
}
# Free OpenSSL objects
Net::SSLeay::X509_free($cert);
Net::SSLeay::EVP_PKEY_free($key);
return ( $strPrivate, $strCert );
}
# Sending a test Email
# --------------------

View File

@ -380,6 +380,31 @@ sub tests {
&& $conf->{samlServicePublicKeySig} );
return 1;
},
samlSignatureOverrideNeedsCertificate => sub {
return 1 if $conf->{samlServicePublicKeySig} =~ /CERTIFICATE/;
my @offenders;
for my $idp ( keys %{ $conf->{samlIDPMetaDataOptions} } ) {
if ( $conf->{samlIDPMetaDataOptions}->{$idp}
->{samlIDPMetaDataOptionsSignatureMethod} )
{
push @offenders, $idp;
}
}
for my $sp ( keys %{ $conf->{samlSPMetaDataOptions} } ) {
if ( $conf->{samlSPMetaDataOptions}->{$sp}
->{samlSPMetaDataOptionsSignatureMethod} )
{
push @offenders, $sp;
}
}
return 1 unless @offenders;
return ( 0,
"Cannot set non-default signature method on "
. join( ", ", @offenders )
. " unless SAML signature key is in certificate form" );
},
# Try to parse combination with declared modules
checkCombinations => sub {
@ -811,19 +836,27 @@ sub tests {
# Cookie SameSite=None requires Secure flag
# Same with SameSite=(auto) and SAML issuer in use
SameSiteNoneWithSecure => sub {
return ( 1, 'SameSite value = None requires the secured flag' )
return ( -1, 'SameSite value = None requires the secured flag' )
if ( getSameSite($conf) eq 'None' and !$conf->{securedCookie} );
return 1;
},
# Secure cookies require HTTPS
SecureCookiesRequireHttps => sub {
return ( 1, 'Secure cookies require a HTTPS portal URL' )
return ( -1, 'Secure cookies require a HTTPS portal URL' )
if ( $conf->{securedCookie} == 1
and $conf->{portal}
and $conf->{portal} !~ /^https:/ );
return 1;
},
# Password module requires a password backend
passwordModuleNeedsBackend => sub {
return ( -1, 'Password module is enabled without password backend' )
if ( $conf->{portalDisplayChangePassword}
and $conf->{passwordDB} eq 'Null' );
return 1;
},
};
}

View File

@ -691,6 +691,19 @@ llapp.controller 'TreeCtrl', [
console.log('Error sending test email')
# RSA keys generation
$scope.newCertificate = ->
$scope.showModal('password.html').then ->
$scope.waiting = true
currentNode = $scope.currentNode
password = $scope.result
$http.post("#{window.confPrefix}/newCertificate", {"password": password}).then (response) ->
currentNode.data[0].data = response.data.private
currentNode.data[1].data = password
currentNode.data[2].data = response.data.public
$scope.waiting = false
, readError
, ->
console.log('New key cancelled')
$scope.newRSAKey = ->
$scope.showModal('password.html').then ->
$scope.waiting = true

View File

@ -33,7 +33,7 @@
</div>
<script type="text/menu">
[{
"title": "newRSAKey",
"title": "newCertificate",
"icon": "plus-sign"
},{
"title": "download",

View File

@ -708,6 +708,35 @@ function templates(tpl,key) {
},
{
"_nodes" : [
{
"default" : "",
"get" : tpl+"s/"+key+"/"+"samlIDPMetaDataOptionsSignatureMethod",
"id" : tpl+"s/"+key+"/"+"samlIDPMetaDataOptionsSignatureMethod",
"select" : [
{
"k" : "",
"v" : "default"
},
{
"k" : "RSA_SHA1",
"v" : "RSA SHA1"
},
{
"k" : "RSA_SHA256",
"v" : "RSA SHA256"
},
{
"k" : "RSA_SHA384",
"v" : "RSA SHA384"
},
{
"k" : "RSA_SHA512",
"v" : "RSA SHA512"
}
],
"title" : "samlIDPMetaDataOptionsSignatureMethod",
"type" : "select"
},
{
"default" : -1,
"get" : tpl+"s/"+key+"/"+"samlIDPMetaDataOptionsSignSSOMessage",
@ -1102,6 +1131,35 @@ function templates(tpl,key) {
},
{
"_nodes" : [
{
"default" : "",
"get" : tpl+"s/"+key+"/"+"samlSPMetaDataOptionsSignatureMethod",
"id" : tpl+"s/"+key+"/"+"samlSPMetaDataOptionsSignatureMethod",
"select" : [
{
"k" : "",
"v" : "default"
},
{
"k" : "RSA_SHA1",
"v" : "RSA SHA1"
},
{
"k" : "RSA_SHA256",
"v" : "RSA SHA256"
},
{
"k" : "RSA_SHA384",
"v" : "RSA SHA384"
},
{
"k" : "RSA_SHA512",
"v" : "RSA SHA512"
}
],
"title" : "samlSPMetaDataOptionsSignatureMethod",
"type" : "select"
},
{
"default" : -1,
"get" : tpl+"s/"+key+"/"+"samlSPMetaDataOptionsSignSSOMessage",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -841,6 +841,24 @@ This file contains:
return console.log('Error sending test email');
});
};
$scope.newCertificate = function() {
return $scope.showModal('password.html').then(function() {
var currentNode, password;
$scope.waiting = true;
currentNode = $scope.currentNode;
password = $scope.result;
return $http.post(window.confPrefix + "/newCertificate", {
"password": password
}).then(function(response) {
currentNode.data[0].data = response.data["private"];
currentNode.data[1].data = password;
currentNode.data[2].data = response.data["public"];
return $scope.waiting = false;
}, readError);
}, function() {
return console.log('New key cancelled');
});
};
$scope.newRSAKey = function() {
return $scope.showModal('password.html').then(function() {
var currentNode, password;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -28,6 +28,7 @@
"2ndFA":"Second Factors",
"actives":"مفعلة",
"activeTimer":"قبول تلقائي للوقت",
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
"addAppCasPartner":"إضافة تطبيق كاس",
"addIDPSamlPartner":"أضف IDP SAML",
"addOidcOp":"إضافة أوبين أيدي كونيكت بروفيدر",
@ -161,6 +162,7 @@
"portalDisplayCertificateResetByMail":"Reset your Certificate",
"contentSecurityPolicy":"Content security policy",
"contextSwitching":"Switch context another user",
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
"contextSwitchingHiddenAttributes":"السمات المخفية",
"contextSwitchingIdRule":"Identities use rule",
"contextSwitchingRule":"استخدام القاعدة",
@ -517,6 +519,7 @@
"newApp":"تطبيق جديد",
"newChain":"سلسلة جديدة",
"newCat":"فئة جديدة",
"newCertificate":"New certificate",
"newCfgAvailable":"إعدادات جديد متاح",
"newCmbMod":"وحدة جديدة",
"newCmbOver":"معايير جديدة",
@ -1060,6 +1063,7 @@
"samlIDPMetaDataOptionsAuthnRequest":"طلب إثبات الهوية",
"samlIDPMetaDataOptionsSession":"جلسة",
"samlIDPMetaDataOptionsSignature":"توقيع",
"samlIDPMetaDataOptionsSignatureMethod":"Signature method",
"samlIDPMetaDataOptionsBinding":"ربط",
"samlIDPMetaDataOptionsDisplay":"عرض",
"samlIDPMetaDataOptionsDisplayName":"عرض الاسم",
@ -1083,6 +1087,7 @@
"samlSPMetaDataOptionsEncryptionMode":"أسلوب التشفير",
"samlSPMetaDataOptionsAuthnResponse":"رد إثبات الهوية",
"samlSPMetaDataOptionsSignature":"توقيع",
"samlSPMetaDataOptionsSignatureMethod":"Signature method",
"samlSPMetaDataOptionsSecurity":"الحماية",
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"تمكين استخدام عنوان يو آر إل IDP ",
"samlSPMetaDataOptionsNameIDSessionKey":"فرض اسم المعرف لمفتاح الجلسة",

View File

@ -28,6 +28,7 @@
"2ndFA":"Second Factors",
"actives":"Enabled",
"activeTimer":"Auto accept time",
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
"addAppCasPartner":"Add CAS application",
"addIDPSamlPartner":"Add SAML IDP",
"addOidcOp":"Add OpenID Connect Provider",
@ -161,6 +162,7 @@
"portalDisplayCertificateResetByMail":"Reset your certificate",
"contentSecurityPolicy":"Content security policy",
"contextSwitching":"Switch context another user",
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
"contextSwitchingHiddenAttributes":"Hidden attributes",
"contextSwitchingIdRule":"Identities use rule",
"contextSwitchingRule":"Use rule",
@ -517,6 +519,7 @@
"newApp":"New application",
"newChain":"New chain",
"newCat":"New category",
"newCertificate":"New certificate",
"newCfgAvailable":"A new configuration is available",
"newCmbMod":"New module",
"newCmbOver":"New parameter",
@ -1060,6 +1063,7 @@
"samlIDPMetaDataOptionsAuthnRequest":"Authentication request",
"samlIDPMetaDataOptionsSession":"Session",
"samlIDPMetaDataOptionsSignature":"Signature",
"samlIDPMetaDataOptionsSignatureMethod":"Signature method",
"samlIDPMetaDataOptionsBinding":"Binding",
"samlIDPMetaDataOptionsDisplay":"Display",
"samlIDPMetaDataOptionsDisplayName":"Display name",
@ -1083,6 +1087,7 @@
"samlSPMetaDataOptionsEncryptionMode":"Encryption mode",
"samlSPMetaDataOptionsAuthnResponse":"Authentication response",
"samlSPMetaDataOptionsSignature":"Signature",
"samlSPMetaDataOptionsSignatureMethod":"Signature method",
"samlSPMetaDataOptionsSecurity":"Security",
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Enable use of IDP initiated URL",
"samlSPMetaDataOptionsNameIDSessionKey":"Force NameID session key",

View File

@ -28,6 +28,7 @@
"2ndFA":"Second Factors",
"actives":"Enabled",
"activeTimer":"Auto accept time",
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
"addAppCasPartner":"Add CAS application",
"addIDPSamlPartner":"Add SAML IDP",
"addOidcOp":"Add OpenID Connect Provider",
@ -161,6 +162,7 @@
"portalDisplayCertificateResetByMail":"Reset your certificate",
"contentSecurityPolicy":"Content security policy",
"contextSwitching":"Switch context another user",
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
"contextSwitchingHiddenAttributes":"Hidden attributes",
"contextSwitchingIdRule":"Identities use rule",
"contextSwitchingRule":"Use rule",
@ -517,6 +519,7 @@
"newApp":"New application",
"newChain":"New chain",
"newCat":"New category",
"newCertificate":"New certificate",
"newCfgAvailable":"A new configuration is available",
"newCmbMod":"New module",
"newCmbOver":"New parameter",
@ -1060,6 +1063,7 @@
"samlIDPMetaDataOptionsAuthnRequest":"Authentication request",
"samlIDPMetaDataOptionsSession":"Session",
"samlIDPMetaDataOptionsSignature":"Signature",
"samlIDPMetaDataOptionsSignatureMethod":"Signature method",
"samlIDPMetaDataOptionsBinding":"Binding",
"samlIDPMetaDataOptionsDisplay":"Display",
"samlIDPMetaDataOptionsDisplayName":"Display name",
@ -1083,6 +1087,7 @@
"samlSPMetaDataOptionsEncryptionMode":"Encryption mode",
"samlSPMetaDataOptionsAuthnResponse":"Authentication response",
"samlSPMetaDataOptionsSignature":"Signature",
"samlSPMetaDataOptionsSignatureMethod":"Signature method",
"samlSPMetaDataOptionsSecurity":"Security",
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Enable use of IDP initiated URL",
"samlSPMetaDataOptionsNameIDSessionKey":"Force NameID session key",

View File

@ -28,6 +28,7 @@
"2ndFA":"Seconds Facteurs",
"actives":"Actives",
"activeTimer":"Délai d'acceptation automatique",
"adaptativeAuthenticationLevelRules":"Règles d'authentification adaptative",
"addAppCasPartner":"Ajouter une application CAS",
"addIDPSamlPartner":"Ajouter un FI SAML",
"addOidcOp":"Ajouter un fournisseur OpenID Connect",
@ -161,6 +162,7 @@
"portalDisplayCertificateResetByMail":"Réinitialiser votre certificat",
"contentSecurityPolicy":"Politique de sécurité de contenu",
"contextSwitching":"Endossement d'identité",
"contextSwitchingAllowed2fModifications":"Autoriser les modifications des SF",
"contextSwitchingHiddenAttributes":"Attributs masqués",
"contextSwitchingIdRule":"Règle d'utilisation des identités",
"contextSwitchingRule":"Règle d'utilisation",
@ -517,6 +519,7 @@
"newApp":"Nouvelle application",
"newChain":"Nouvelle chaîne",
"newCat":"Nouvelle catégorie",
"newCertificate":"Nouveau certificat",
"newCfgAvailable":"Une nouvelle configuration est disponible",
"newCmbMod":"Nouveau module",
"newCmbOver":"Nouveau paramètre",
@ -804,7 +807,7 @@
"remoteParams":"Paramètres Remote",
"remotePortal":"URL du portail",
"replaceByFile":"Remplacer par le fichier",
"requireToken":"Exige un jeton pour les formulaires",
"requireToken":"Exiger un jeton pour les formulaires",
"restAuthnLevel":"Niveau d'authentification",
"restAuthUrl":"URL d'authentification",
"restConfigServer":"Serveur de configurations REST",
@ -860,7 +863,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",
"sfOnlyUpgrade": "Utiliser le SF 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",
@ -1060,6 +1063,7 @@
"samlIDPMetaDataOptionsAuthnRequest":"Requête d'authentification",
"samlIDPMetaDataOptionsSession":"Session",
"samlIDPMetaDataOptionsSignature":"Signature",
"samlIDPMetaDataOptionsSignatureMethod":"Méthode pour la signature",
"samlIDPMetaDataOptionsBinding":"Méthode",
"samlIDPMetaDataOptionsDisplay":"Affichage",
"samlIDPMetaDataOptionsDisplayName":"Nom d'affichage",
@ -1083,6 +1087,7 @@
"samlSPMetaDataOptionsEncryptionMode":"Mode de chiffrement",
"samlSPMetaDataOptionsAuthnResponse":"Réponse d'authentification",
"samlSPMetaDataOptionsSignature":"Signature",
"samlSPMetaDataOptionsSignatureMethod":"Méthode pour la signature",
"samlSPMetaDataOptionsSecurity":"Sécurité",
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Autoriser l'utilisation d'URL SSO initié par l'IDP",
"samlSPMetaDataOptionsNameIDSessionKey":"Forcer la clef de session NameID",
@ -1103,7 +1108,7 @@
"samlSPName":"Nom du fournisseur de service SAML",
"samlSPSSODescriptor":"Fournisseur de service",
"samlSPSSODescriptorAuthnRequestsSigned":"Requêtes d'authentification signées",
"samlSPSSODescriptorWantAssertionsSigned":"Exige des assertions signées",
"samlSPSSODescriptorWantAssertionsSigned":"Exiger des assertions signées",
"samlSPSSODescriptorSingleLogoutService":"Single Logout",
"samlSPSSODescriptorSingleLogoutServiceHTTPRedirect":"Redirection HTTP",
"samlSPSSODescriptorSingleLogoutServiceHTTPPost":"POST HTTP",
@ -1114,7 +1119,7 @@
"samlSPSSODescriptorArtifactResolutionService":"Résolution d'Artifact",
"samlSPSSODescriptorArtifactResolutionServiceArtifact":"Service Artifact",
"samlIDPSSODescriptor":"Fournisseur d'identité",
"samlIDPSSODescriptorWantAuthnRequestsSigned":"Exige des requêtes d'authentification signées",
"samlIDPSSODescriptorWantAuthnRequestsSigned":"Exiger des requêtes d'authentification signées",
"samlIDPSSODescriptorSingleSignOnService":"Single Sign On",
"samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect":"Redirection HTTP",
"samlIDPSSODescriptorSingleSignOnServiceHTTPPost":"POST HTTP",

View File

@ -28,6 +28,7 @@
"2ndFA":"Secondi fattori",
"actives":"Attivi",
"activeTimer":"Auto accettazione tempo",
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
"addAppCasPartner":"Aggiungi applicazione CAS",
"addIDPSamlPartner":"Aggiungi SAML IDP",
"addOidcOp":"Aggiungere OpenID Connect Provider",
@ -161,6 +162,7 @@
"portalDisplayCertificateResetByMail":"Reset your certificate",
"contentSecurityPolicy":"Politica di protezione dei contenuti",
"contextSwitching":"Switch context another user",
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
"contextSwitchingHiddenAttributes":"Attributi nascosti",
"contextSwitchingIdRule":"Le identità usano la regola",
"contextSwitchingRule":"Utilizza la regola",
@ -517,6 +519,7 @@
"newApp":"Nuova applicazione",
"newChain":"Nuova catena",
"newCat":"NUova categoria",
"newCertificate":"New certificate",
"newCfgAvailable":"É disponibile una nuova configurazione",
"newCmbMod":"Nuovo modulo",
"newCmbOver":"Nuovo parametro",
@ -1060,6 +1063,7 @@
"samlIDPMetaDataOptionsAuthnRequest":"Richiesta di autenticazione",
"samlIDPMetaDataOptionsSession":"Sessioni",
"samlIDPMetaDataOptionsSignature":"Firma",
"samlIDPMetaDataOptionsSignatureMethod":"Metodo di firma",
"samlIDPMetaDataOptionsBinding":"Vincolante",
"samlIDPMetaDataOptionsDisplay":" Visualizza ",
"samlIDPMetaDataOptionsDisplayName":"Nome da visualizzare",
@ -1083,6 +1087,7 @@
"samlSPMetaDataOptionsEncryptionMode":"Modalità di crittografia",
"samlSPMetaDataOptionsAuthnResponse":"Risposta di autenticazione",
"samlSPMetaDataOptionsSignature":"Firma",
"samlSPMetaDataOptionsSignatureMethod":"Metodo di firma",
"samlSPMetaDataOptionsSecurity":"Sicurezza",
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Abilitare l'utilizzo dell'URL IDP avviata ",
"samlSPMetaDataOptionsNameIDSessionKey":"Forza la chiave di sessione NameID",

View File

@ -28,6 +28,7 @@
"2ndFA":"Drugie czynniki",
"actives":"Włączone",
"activeTimer":"Czas automatycznej akceptacji",
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
"addAppCasPartner":"Dodaj aplikację CAS",
"addIDPSamlPartner":"Dodaj SAML IDP",
"addOidcOp":"Dodaj dostawcę OpenID Connect",
@ -161,6 +162,7 @@
"portalDisplayCertificateResetByMail":"Zresetuj swój certyfikat",
"contentSecurityPolicy":"Polityka bezpieczeństwa treści",
"contextSwitching":"Przełącz kontekst innego użytkownika",
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
"contextSwitchingHiddenAttributes":"Ukryte atrybuty",
"contextSwitchingIdRule":"Reguła korzystania z tożsamości",
"contextSwitchingRule":"Użyj reguły",
@ -517,6 +519,7 @@
"newApp":"Nowa aplikacja",
"newChain":"Nowy łańcuch",
"newCat":"Nowa kategoria",
"newCertificate":"New certificate",
"newCfgAvailable":"Dostępna jest nowa konfiguracja",
"newCmbMod":"Nowy moduł",
"newCmbOver":"Nowy parametr",
@ -1060,6 +1063,7 @@
"samlIDPMetaDataOptionsAuthnRequest":"Żądanie uwierzytelnienia",
"samlIDPMetaDataOptionsSession":"Sesja",
"samlIDPMetaDataOptionsSignature":"Podpis",
"samlIDPMetaDataOptionsSignatureMethod":"Metoda podpisu",
"samlIDPMetaDataOptionsBinding":"Przywiązania",
"samlIDPMetaDataOptionsDisplay":"Wyświetl",
"samlIDPMetaDataOptionsDisplayName":"Wyświetlana nazwa",
@ -1083,6 +1087,7 @@
"samlSPMetaDataOptionsEncryptionMode":"Tryb szyfrowania",
"samlSPMetaDataOptionsAuthnResponse":"Odpowiedź uwierzytelnienia",
"samlSPMetaDataOptionsSignature":"Podpis",
"samlSPMetaDataOptionsSignatureMethod":"Metoda podpisu",
"samlSPMetaDataOptionsSecurity":"Bezpieczeństwo",
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Włącz korzystanie z adresu URL zainicjowanego przez IDP",
"samlSPMetaDataOptionsNameIDSessionKey":"Wymuś klucz sesji NameID",

View File

@ -28,6 +28,7 @@
"2ndFA":"İki Faktörlü Kimlik Doğrulama",
"actives":"Etkin",
"activeTimer":"Otomatik kabul süresi",
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
"addAppCasPartner":"CAS uygulaması ekle",
"addIDPSamlPartner":"SAML IDP ekle",
"addOidcOp":"OpenID Connect Sağlayıcısı Ekle",
@ -161,6 +162,7 @@
"portalDisplayCertificateResetByMail":"Sertifikanızı sıfırlayın",
"contentSecurityPolicy":"İçerik güvenlik ilkesi",
"contextSwitching":"İçeriği başka bir kullanıcıyla değiştir",
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
"contextSwitchingHiddenAttributes":"Gizli nitelikler",
"contextSwitchingIdRule":"Kimlik kullanım kuralı",
"contextSwitchingRule":"Kuralı kullan",
@ -517,6 +519,7 @@
"newApp":"Yeni uygulama",
"newChain":"Yeni zincir",
"newCat":"Yeni kategori",
"newCertificate":"New certificate",
"newCfgAvailable":"Yeni bir yapılandırma mevcut",
"newCmbMod":"Yeni modül",
"newCmbOver":"Yeni parametre",
@ -1060,6 +1063,7 @@
"samlIDPMetaDataOptionsAuthnRequest":"Doğrulama isteği",
"samlIDPMetaDataOptionsSession":"Oturum",
"samlIDPMetaDataOptionsSignature":"İmza",
"samlIDPMetaDataOptionsSignatureMethod":"İmzalama yöntemi",
"samlIDPMetaDataOptionsBinding":"Bağlayıcı",
"samlIDPMetaDataOptionsDisplay":"Görüntüle",
"samlIDPMetaDataOptionsDisplayName":"Görüntülenen ad",
@ -1083,6 +1087,7 @@
"samlSPMetaDataOptionsEncryptionMode":"Şifreleme modu",
"samlSPMetaDataOptionsAuthnResponse":"Doğrulama cevabı",
"samlSPMetaDataOptionsSignature":"İmza",
"samlSPMetaDataOptionsSignatureMethod":"İmzalama yöntemi",
"samlSPMetaDataOptionsSecurity":"Güvenlik",
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"IDP ile başlatılan URLnin kullanımını etkinleştir",
"samlSPMetaDataOptionsNameIDSessionKey":"NameID oturum anahtarını zorla",

View File

@ -28,6 +28,7 @@
"2ndFA":"Second Factors",
"actives":"Hoạt động",
"activeTimer":"Tự động chấp nhận thời gian",
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
"addAppCasPartner":"Thêm ứng dụng CAS",
"addIDPSamlPartner":"Thêm SAML IDP",
"addOidcOp":"Thêm nhà cung cấp kết nối OpenID",
@ -161,6 +162,7 @@
"portalDisplayCertificateResetByMail":"Reset your certificate",
"contentSecurityPolicy":"Chính sách bảo mật nội dung",
"contextSwitching":"Switch context another user",
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
"contextSwitchingHiddenAttributes":"Thuộc tính ẩn",
"contextSwitchingIdRule":"Identities use rule",
"contextSwitchingRule":"Quy tắc sử dụng",
@ -517,6 +519,7 @@
"newApp":"Ứng dụng mới",
"newChain":"Chuỗi mới",
"newCat":"Danh mục mới",
"newCertificate":"New certificate",
"newCfgAvailable":"Một cấu hình mới có sẵn",
"newCmbMod":"Mô-đun mới",
"newCmbOver":"Tham số mới",
@ -1060,6 +1063,7 @@
"samlIDPMetaDataOptionsAuthnRequest":"Yêu cầu xác thực",
"samlIDPMetaDataOptionsSession":"Phiên",
"samlIDPMetaDataOptionsSignature":"Chữ ký",
"samlIDPMetaDataOptionsSignatureMethod":"Signature method",
"samlIDPMetaDataOptionsBinding":"Liên kết",
"samlIDPMetaDataOptionsDisplay":"Hiển thị",
"samlIDPMetaDataOptionsDisplayName":"Tên hiển thị",
@ -1083,6 +1087,7 @@
"samlSPMetaDataOptionsEncryptionMode":"Chế độ mã hóa",
"samlSPMetaDataOptionsAuthnResponse":"Phản hồi xác thực",
"samlSPMetaDataOptionsSignature":"Chữ ký",
"samlSPMetaDataOptionsSignatureMethod":"Signature method",
"samlSPMetaDataOptionsSecurity":"Bảo mật",
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Cho phép sử dụng URL bắt đầu IDP",
"samlSPMetaDataOptionsNameIDSessionKey":"Bắt buộc khóa phiên NameID",

View File

@ -28,6 +28,7 @@
"2ndFA":"Second Factors",
"actives":"Enabled",
"activeTimer":"自动接收时间",
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
"addAppCasPartner":"增加CAS应用",
"addIDPSamlPartner":"增加SAML IDP",
"addOidcOp":"增加OpenID Connect Provider",
@ -161,6 +162,7 @@
"portalDisplayCertificateResetByMail":"Reset your certificate",
"contentSecurityPolicy":"Content security policy",
"contextSwitching":"Switch context another user",
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
"contextSwitchingHiddenAttributes":"Hidden attributes",
"contextSwitchingIdRule":"Identities use rule",
"contextSwitchingRule":"Use rule",
@ -517,6 +519,7 @@
"newApp":"New application",
"newChain":"New chain",
"newCat":"New category",
"newCertificate":"New certificate",
"newCfgAvailable":"A new configuration is available",
"newCmbMod":"New module",
"newCmbOver":"New parameter",
@ -1060,6 +1063,7 @@
"samlIDPMetaDataOptionsAuthnRequest":"Authentication request",
"samlIDPMetaDataOptionsSession":"Session",
"samlIDPMetaDataOptionsSignature":"Signature",
"samlIDPMetaDataOptionsSignatureMethod":"Signature method",
"samlIDPMetaDataOptionsBinding":"Binding",
"samlIDPMetaDataOptionsDisplay":"Display",
"samlIDPMetaDataOptionsDisplayName":"Display name",
@ -1083,6 +1087,7 @@
"samlSPMetaDataOptionsEncryptionMode":"Encryption mode",
"samlSPMetaDataOptionsAuthnResponse":"Authentication response",
"samlSPMetaDataOptionsSignature":"Signature",
"samlSPMetaDataOptionsSignatureMethod":"Signature method",
"samlSPMetaDataOptionsSecurity":"Security",
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Enable use of IDP initiated URL",
"samlSPMetaDataOptionsNameIDSessionKey":"Force NameID session key",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,16 @@ use JSON;
use IO::String;
require 't/test-lib.pm';
sub checkResult {
my $res = shift;
ok( $res->[0] == 200, "Result code is 200" );
my $key;
ok( $key = from_json( $res->[2]->[0] ), 'Response is JSON' );
like( $key->{private}, qr/BEGIN/, "is PEM formatted" );
like( $key->{public}, qr/BEGIN/, "is PEM formatted" );
count(4);
}
my $res;
ok(
$res = &client->_post(
@ -13,10 +23,8 @@ ok(
),
"Request succeed"
);
ok( $res->[0] == 200, "Result code is 200" );
my $key;
ok( $key = from_json( $res->[2]->[0] ), 'Response is JSON' );
count(3);
count(1);
checkResult($res);
ok(
$res = &client->_post(
@ -25,8 +33,28 @@ ok(
),
"Request succeed"
);
ok( $res->[0] == 200, "Result code is 200" );
ok( $key = from_json( $res->[2]->[0] ), 'Response is JSON' );
count(3);
count(1);
checkResult($res);
ok(
$res = &client->_post(
'/confs/newCertificate', '',
IO::String->new(''), 'application/json',
0,
),
"Request succeed"
);
count(1);
checkResult($res);
ok(
$res = &client->_post(
'/confs/newCertificate', '', IO::String->new('{"password":"hello"}'),
'application/json', 20,
),
"Request succeed"
);
count(1);
checkResult($res);
done_testing( count() );

View File

@ -101,6 +101,7 @@ lib/Lemonldap/NG/Portal/Password/Demo.pm
lib/Lemonldap/NG/Portal/Password/LDAP.pm
lib/Lemonldap/NG/Portal/Password/Null.pm
lib/Lemonldap/NG/Portal/Password/REST.pm
lib/Lemonldap/NG/Portal/Plugins/AdaptativeAuthenticationLevel.pm
lib/Lemonldap/NG/Portal/Plugins/AutoSignin.pm
lib/Lemonldap/NG/Portal/Plugins/BruteForceProtection.pm
lib/Lemonldap/NG/Portal/Plugins/CDA.pm
@ -638,6 +639,7 @@ t/59-Double-cookies-for-Double-sessions.t
t/59-Double-cookies-Refresh-and-Logout.t
t/59-Secured-cookie-Refresh-and-Logout.t
t/60-Status.t
t/61-AdaptativeAuthenticationLevel.t
t/61-BruteForceProtection-with-Incremental-lockTimes-and-TOTP.t
t/61-BruteForceProtection-with-Incremental-lockTimes.t
t/61-BruteForceProtection.t
@ -674,6 +676,7 @@ t/68-Impersonation-with-doubleCookies.t
t/68-Impersonation-with-filtered-merge.t
t/68-Impersonation-with-History.t
t/68-Impersonation-with-merge.t
t/68-Impersonation-with-SFA.t
t/68-Impersonation-with-TOTP.t
t/68-Impersonation-with-UnrestrictedUser.t
t/68-Impersonation.t

View File

@ -177,12 +177,10 @@ sub init {
# Registration base
$self->addAuthRoute( '2fregisters' => '_displayRegister', ['GET'] );
$self->addAuthRoute( '2fregisters' => 'register', ['POST'] );
if ( $self->conf->{sfRequired} ) {
$self->addUnauthRoute(
'2fregisters' => 'restoreSession',
[ 'GET', 'POST' ]
);
}
$self->addUnauthRoute(
'2fregisters' => 'restoreSession',
[ 'GET', 'POST' ]
) if ( $self->conf->{sfRequired} );
}
return 1;
}
@ -236,11 +234,11 @@ sub run {
$self->logger->error("Bad encoding in _2fDevices: $@");
return PE_ERROR;
}
$self->logger->debug(" -> 2F Device(s) found");
my $now = time();
my $removed = 0;
$self->logger->debug("Looking for expired 2F device(s)...");
my $removed = 0;
my $now = time();
foreach my $device (@$_2fDevices) {
my $type = lc( $device->{type} );
$type =~ s/2f$//i;
@ -449,14 +447,10 @@ sub _displayRegister {
if ($tpl) {
my ($m) =
grep { $_->{m}->prefix eq $tpl } @{ $self->sfRModules };
unless ($m) {
return $self->p->sendError( $req,
'Inexistent register module', 400 );
}
unless ( $m->{r}->( $req, $req->userData ) ) {
return $self->p->sendError( $req,
'Registration not authorized', 403 );
}
return $self->p->sendError( $req, 'Inexistent register module', 400 )
unless $m;
return $self->p->sendError( $req, 'Registration not authorized', 403 )
unless $m->{r}->( $req, $req->userData );
return $self->p->sendHtml(
$req,
$m->{m}->template,
@ -481,32 +475,35 @@ sub _displayRegister {
};
}
}
if (
return [ 302, [ Location => $self->conf->{portal} . $am[0]->{URL} ], [] ]
if (
@am == 1
and not( $req->userData->{_2fDevices}
or $req->data->{sfRegRequired} )
)
{
return [ 302, [ Location => $self->conf->{portal} . $am[0]->{URL} ],
[] ];
}
);
# Retrieve user all second factors
my $_2fDevices =
$req->userData->{_2fDevices}
? eval {
from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } ); }
: undef;
unless ($_2fDevices) {
$self->logger->debug("No 2F Device found");
$_2fDevices = [];
my $_2fDevices = [];
if ( $self->allowedUpdateSfa($req) ) {
$_2fDevices = $req->userData->{_2fDevices}
? eval {
from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } );
}
: undef;
unless ($_2fDevices) {
$self->logger->debug("No 2F Device found");
$_2fDevices = [];
}
}
else {
$self->userLogger->warn("Do not diplay 2F Devices!");
}
# Parse second factors to display delete button if allowed
my $action = '';
foreach
my $type ( split /,\s*/, $self->conf->{available2FSelfRegistration} )
{
foreach (@$_2fDevices) {
$_->{type} =~ s/^UBK$/Yubikey/;

View File

@ -11,13 +11,10 @@ extends 'Lemonldap::NG::Portal::Main::Plugin', 'Lemonldap::NG::Common::TOTP';
# INITIALIZATION
has prefix => ( is => 'rw', default => 'totp' );
has prefix => ( is => 'rw', default => 'totp' );
has template => ( is => 'ro', default => 'totp2fregister' );
has logo => ( is => 'rw', default => 'totp.png' );
has ott => (
has logo => ( is => 'rw', default => 'totp.png' );
has ott => (
is => 'rw',
lazy => 1,
default => sub {
@ -35,10 +32,13 @@ sub init {
sub run {
my ( $self, $req, $action ) = @_;
my $user = $req->userData->{ $self->conf->{whatToTrace} };
unless ($user) {
return $self->p->sendError( $req,
'No ' . $self->conf->{whatToTrace} . ' found in user data', 500 );
}
return $self->p->sendError( $req,
'No ' . $self->conf->{whatToTrace} . ' found in user data', 500 )
unless $user;
# Check if TOTP can be updated
return $self->p->sendError( $req, 'notAuthorized', 400 )
unless $self->allowedUpdateSfa($req, $action);
# Verification that user has a valid TOTP app
if ( $action eq 'verify' ) {
@ -75,7 +75,7 @@ sub run {
}
$TOTPName =
substr( $TOTPName, 0, $self->conf->{max2FDevicesNameLength} );
$self->logger->debug("TOTP name : $TOTPName");
$self->logger->debug("TOTP name: $TOTPName");
unless ($code) {
$self->userLogger->info('TOTP registration: empty validation form');
@ -102,11 +102,10 @@ sub run {
$self->logger->debug('TOTP code verified');
# Now code is verified, let's store the master key in persistent data
my $secret = '';
# Reading existing 2FDevices
$self->logger->debug("Looking for 2F Devices ...");
$self->logger->debug("Looking for 2F Devices...");
my $_2fDevices;
if ( $req->userData->{_2fDevices} ) {
$_2fDevices = eval {
@ -124,7 +123,7 @@ sub run {
}
# Reading existing TOTP
my @totp2f = grep { $_->{type} eq "TOTP" } @$_2fDevices;
my @totp2f = grep { $_->{type} eq 'TOTP' } @$_2fDevices;
unless (@totp2f) {
$self->logger->debug("No TOTP Device found");
@ -133,26 +132,20 @@ sub run {
}
# Loading TOTP secret
$self->logger->debug("Reading TOTP secret if exists ...");
$self->logger->debug("Reading TOTP secret if exists...");
$secret = $_->{_secret} foreach (@totp2f);
if ( $token->{_totp2fSecret} eq $secret ) {
return $self->p->sendError( $req, 'totpExistingKey', 200 );
}
return $self->p->sendError( $req, 'totpExistingKey', 200 )
if ( $token->{_totp2fSecret} eq $secret );
### USER CAN ONLY REGISTER ONE TOTP ###
# Delete TOTP previously registered
my @keep = ();
while (@$_2fDevices) {
my $element = shift @$_2fDevices;
$self->logger->debug("Looking for TOTP to delete ...");
push @keep, $element unless ( $element->{type} eq "TOTP" );
}
$self->logger->debug("Looking for TOTP to delete...");
my $size = my @keep =
map { $_->{type} eq 'TOTP' ? () : $_ } @$_2fDevices;
# Check if user can register one more device
my $size = @keep;
my $maxSize = $self->conf->{max2FDevices};
$self->logger->debug("Nbr 2FDevices = $size / $maxSize");
$self->logger->debug("Registered 2F Device(s): $size / $maxSize");
if ( $size >= $maxSize ) {
$self->userLogger->warn("Max number of 2F devices is reached");
return $self->p->sendError( $req, 'maxNumberof2FDevicesReached',
@ -167,9 +160,8 @@ sub run {
_secret => $token->{_totp2fSecret},
epoch => $epoch
};
$self->logger->debug(
"Append 2F Device : { type => 'TOTP', name => $TOTPName }");
"Append 2F Device: { type => 'TOTP', name => $TOTPName }");
$self->p->updatePersistentSession( $req,
{ _2fDevices => to_json( \@keep ) } );
$self->userLogger->notice(
@ -187,7 +179,7 @@ sub run {
my $secret = '';
# Read existing 2FDevices
$self->logger->debug("Looking for 2F Devices ...");
$self->logger->debug("Looking for 2F Devices...");
my $_2fDevices;
if ( $req->userData->{_2fDevices} ) {
$_2fDevices = eval {
@ -215,7 +207,7 @@ sub run {
}
# Loading TOTP secret
$self->logger->debug("Reading TOTP secret if exists ...");
$self->logger->debug("Reading TOTP secret if exists...");
$secret = $_->{_secret} foreach (@totp2f);
if ( ( $req->param('newkey') and $self->conf->{totp2fUserCanChangeKey} )
@ -278,8 +270,8 @@ sub run {
400 );
# Read existing 2FDevices
$self->logger->debug("Loading 2F Devices ...");
my $_2fDevices;
$self->logger->debug("Loading 2F Devices...");
my ( $_2fDevices, $TOTPName );
if ( $req->userData->{_2fDevices} ) {
$_2fDevices = eval {
from_json( $req->userData->{_2fDevices},
@ -296,13 +288,12 @@ sub run {
}
# Delete TOTP 2F device
my $TOTPName;
foreach (@$_2fDevices) {
$TOTPName = $_->{name} if $_->{epoch} eq $epoch;
}
@$_2fDevices = grep { $_->{epoch} ne $epoch } @$_2fDevices;
@$_2fDevices = map {
if ( $_->{epoch} eq $epoch ) { $TOTPName = $_->{name}; () }
else { $_ }
} @$_2fDevices;
$self->logger->debug(
"Delete 2F Device : { type => 'TOTP', epoch => $epoch, name => $TOTPName }"
"Delete 2F Device: { type => 'TOTP', epoch => $epoch, name => $TOTPName }"
);
$self->p->updatePersistentSession( $req,
{ _2fDevices => to_json($_2fDevices) } );

View File

@ -12,11 +12,9 @@ extends 'Lemonldap::NG::Portal::Main::Plugin',
# INITIALIZATION
has prefix => ( is => 'rw', default => 'u' );
has prefix => ( is => 'rw', default => 'u' );
has template => ( is => 'ro', default => 'u2fregister' );
has logo => ( is => 'rw', default => 'u2f.png' );
has logo => ( is => 'rw', default => 'u2f.png' );
sub init {
my ($self) = @_;
@ -30,15 +28,19 @@ sub init {
sub run {
my ( $self, $req, $action ) = @_;
my $user = $req->userData->{ $self->conf->{whatToTrace} };
unless ($user) {
return $self->p->sendError( $req,
'No ' . $self->conf->{whatToTrace} . ' found in user data', 500 );
}
return $self->p->sendError( $req,
'No ' . $self->conf->{whatToTrace} . ' found in user data', 500 )
unless $user;
# Check if U2F key can be updated
return $self->p->sendError( $req, 'notAuthorized', 400 )
unless $self->allowedUpdateSfa($req, $action);
if ( $action eq 'register' ) {
# Read existing 2FDevices
$self->logger->debug("Looking for 2F Devices ...");
$self->logger->debug("Looking for 2F Devices...");
my $_2fDevices;
if ( $req->userData->{_2fDevices} ) {
$_2fDevices = eval {
@ -50,7 +52,6 @@ sub run {
return $self->p->sendError( $req, "Corrupted session", 500 );
}
}
else {
$self->logger->debug("No 2F Device found");
$_2fDevices = [];
@ -59,7 +60,7 @@ sub run {
# Check if user can register one more 2F device
my $size = @$_2fDevices;
my $maxSize = $self->conf->{max2FDevices};
$self->logger->debug("Registered 2F Device(s) : $size / $maxSize");
$self->logger->debug("Registered 2F Device(s): $size / $maxSize");
if ( $size >= $maxSize ) {
$self->userLogger->warn("Max number of 2F devices is reached");
return $self->p->sendError( $req, 'maxNumberof2FDevicesReached',
@ -99,7 +100,7 @@ sub run {
if ( $keyHandle and $userKey ) {
# Read existing 2FDevices
$self->logger->debug("Looking for 2F Devices ...");
$self->logger->debug("Looking for 2F Devices...");
my $_2fDevices;
if ( $req->userData->{_2fDevices} ) {
$_2fDevices = eval {
@ -115,7 +116,6 @@ sub run {
500 );
}
}
else {
$self->logger->debug("No 2F Device found");
$_2fDevices = [];
@ -132,7 +132,7 @@ sub run {
}
$keyName =
substr( $keyName, 0, $self->conf->{max2FDevicesNameLength} );
$self->logger->debug("Key name : $keyName");
$self->logger->debug("Key name: $keyName");
push @{$_2fDevices},
{
@ -143,7 +143,7 @@ sub run {
epoch => $epoch
};
$self->logger->debug(
"Append 2F Device : { type => 'U2F', name => $keyName }");
"Append 2F Device: { type => 'U2F', name => $keyName }");
$self->p->updatePersistentSession( $req,
{ _2fDevices => to_json($_2fDevices) } );
$self->userLogger->notice(
@ -182,13 +182,6 @@ sub run {
}
# Get registered keys
# my @rk;
# foreach ( @{ $req->data->{crypter} } ) {
# my $k = push @rk,
# { keyHandle => $_->{keyHandle}, version => $data->{version} };
# }
my @rk =
map { { keyHandle => $_->{keyHandle}, version => $data->{version} } }
@{ $req->data->{crypter} };
@ -230,10 +223,6 @@ sub run {
return $self->p->sendError( $req, "U2FAnswerError" );
}
# my $crypter;
# foreach ( @{ $req->data->{crypter} } ) {
# $crypter = $_ if ( $_->{keyHandle} eq $data->{keyHandle} );
# }
$crypter = $_
foreach grep { $_->{keyHandle} eq $data->{keyHandle} }
@{ $req->data->{crypter} };
@ -246,7 +235,6 @@ sub run {
if ( not $crypter->setChallenge($challenge) ) {
$self->logger->error(
$@ ? $@ : Crypt::U2F::Server::Simple::lastError() );
return $self->p->sendError( $req, "U2FServerError" );
}
@ -269,7 +257,7 @@ sub run {
400 );
# Read existing 2FDevices
$self->logger->debug("Looking for 2F Devices ...");
$self->logger->debug("Looking for 2F Devices...");
my ( $_2fDevices, $keyName );
if ( $req->userData->{_2fDevices} ) {
$_2fDevices = eval {
@ -287,15 +275,10 @@ sub run {
}
# Delete U2F device
# my $keyName;
# foreach (@$_2fDevices) {
# $keyName = $_->{name} if $_->{epoch} eq $epoch;
# }
$keyName = $_->{name}
foreach grep { $_->{epoch} eq $epoch } @$_2fDevices;
@$_2fDevices = grep { $_->{epoch} ne $epoch } @$_2fDevices;
@$_2fDevices = map {
if ( $_->{epoch} eq $epoch ) { $keyName = $_->{name}; () }
else { $_ }
} @$_2fDevices;
$self->logger->debug(
"Delete 2F Device : { type => 'U2F', epoch => $epoch, name => $keyName }"
);
@ -318,10 +301,10 @@ sub run {
sub loadUser {
my ( $self, $req ) = @_;
$self->logger->debug("Loading user U2F Devices ...");
$self->logger->debug("Loading user U2F Devices...");
# Read existing 2FDevices
$self->logger->debug("Looking for 2F Devices ...");
$self->logger->debug("Looking for 2F Devices...");
my ( $kh, $uk, $_2fDevices );
my @u2fs = ();
@ -341,7 +324,7 @@ sub loadUser {
# Reading existing U2F keys
foreach (@$_2fDevices) {
$self->logger->debug("Looking for registered U2F key(s) ...");
$self->logger->debug("Looking for registered U2F key(s)...");
if ( $_->{type} eq 'U2F' ) {
unless ( $_->{_userKey} and $_->{_keyHandle} ) {
$self->logger->error(

View File

@ -15,11 +15,9 @@ extends 'Lemonldap::NG::Portal::Main::Plugin';
# INITIALIZATION
has prefix => ( is => 'rw', default => 'yubikey' );
has prefix => ( is => 'rw', default => 'yubikey' );
has template => ( is => 'ro', default => 'yubikey2fregister' );
has logo => ( is => 'rw', default => 'yubikey.png' );
has logo => ( is => 'rw', default => 'yubikey.png' );
sub init {
my ($self) = @_;
@ -36,6 +34,16 @@ sub run {
'No ' . $self->conf->{whatToTrace} . ' found in user data', 500 )
unless $user;
# Check if UBK key can be updated
return $self->p->sendHtml(
$req, 'error',
params => {
MAIN_LOGO => $self->conf->{portalMainLogo},
RAW_ERROR => 'notAuthorized',
AUTH_ERROR_TYPE => 'warning',
}
) unless $self->allowedUpdateSfa($req, $action);
if ( $action eq 'register' ) {
my $otp = $req->param('otp');
my $UBKName = $req->param('UBKName');
@ -48,7 +56,7 @@ sub run {
return $self->p->sendError( $req, 'badName', 200 );
}
$UBKName = substr( $UBKName, 0, $self->conf->{max2FDevicesNameLength} );
$self->logger->debug("Yubikey name : $UBKName");
$self->logger->debug("Yubikey name: $UBKName");
if ( $otp
and length($otp) > $self->conf->{yubikey2fPublicIDSize} )
@ -56,7 +64,7 @@ sub run {
my $key = substr( $otp, 0, $self->conf->{yubikey2fPublicIDSize} );
# Read existing 2FDevices
$self->logger->debug("Looking for 2F Devices ...");
$self->logger->debug("Looking for 2F Devices...");
my $_2fDevices;
if ( $req->userData->{_2fDevices} ) {
$_2fDevices = eval {
@ -78,7 +86,7 @@ sub run {
# Search if the Yubikey is already registered
my $SameUBKFound = 0;
foreach (@$_2fDevices) {
$self->logger->debug("Reading Yubikeys ...");
$self->logger->debug("Reading Yubikeys...");
if ( $_->{_yubikey} eq $key ) {
$SameUBKFound = 1;
last;
@ -100,7 +108,7 @@ sub run {
# Check if user can register one more device
my $size = @$_2fDevices;
my $maxSize = $self->conf->{max2FDevices};
$self->logger->debug("Nbr 2FDevices = $size / $maxSize");
$self->logger->debug("Registered 2F Device(s): $size / $maxSize");
if ( $size >= $maxSize ) {
$self->userLogger->warn("Max number of 2F devices is reached");
return $self->p->sendHtml(
@ -120,9 +128,8 @@ sub run {
_yubikey => $key,
epoch => $epoch
};
$self->logger->debug(
"Append 2F Device : { type => 'UBK', name => $UBKName }");
"Append 2F Device: { type => 'UBK', name => $UBKName }");
$self->p->updatePersistentSession( $req,
{ _2fDevices => to_json($_2fDevices) } );
$self->userLogger->notice(
@ -161,8 +168,8 @@ sub run {
400 );
# Read existing 2FDevices
$self->logger->debug("Looking for 2F Devices ...");
my $_2fDevices;
$self->logger->debug("Looking for 2F Devices...");
my ( $_2fDevices, $UBKName );
if ( $req->userData->{_2fDevices} ) {
$_2fDevices = eval {
from_json( $req->userData->{_2fDevices},
@ -179,13 +186,12 @@ sub run {
}
# Delete Yubikey device
my $UBKName;
foreach (@$_2fDevices) {
$UBKName = $_->{name} if $_->{epoch} eq $epoch;
}
@$_2fDevices = grep { $_->{epoch} ne $epoch } @$_2fDevices;
@$_2fDevices = map {
if ( $_->{epoch} eq $epoch ) { $UBKName = $_->{name}; () }
else { $_ }
} @$_2fDevices;
$self->logger->debug(
"Delete 2F Device : { type => 'UBK', epoch => $epoch, name => $UBKName }"
"Delete 2F Device: { type => 'UBK', epoch => $epoch, name => $UBKName }"
);
$self->p->updatePersistentSession( $req,
{ _2fDevices => to_json($_2fDevices) } );

View File

@ -23,7 +23,6 @@ extends 'Lemonldap::NG::Portal::Main::SecondFactor',
# INITIALIZATION
has prefix => ( is => 'ro', default => 'totp' );
has logo => ( is => 'rw', default => 'totp.png' );
sub init {

View File

@ -24,9 +24,7 @@ extends 'Lemonldap::NG::Portal::Main::SecondFactor',
# INITIALIZATION
has rule => ( is => 'rw' );
has prefix => ( is => 'ro', default => 'u' );
has logo => ( is => 'rw', default => 'u2f.png' );
sub init {
@ -109,6 +107,7 @@ sub run {
sub verify {
my ( $self, $req, $session ) = @_;
my $crypter;
# Check U2F signature
if ( my $resp = $req->param('signature')
@ -136,10 +135,9 @@ sub verify {
$req->error(PE_ERROR);
return $self->fail($req);
}
my $crypter;
foreach ( @{ $req->data->{crypter} } ) {
$crypter = $_ if ( $_->{keyHandle} eq $data->{keyHandle} );
}
$crypter = $_
foreach grep { $_->{keyHandle} eq $data->{keyHandle} }
@{ $req->data->{crypter} };
unless ($crypter) {
$self->userLogger->error("Unregistered U2F key");
$req->error(PE_BADCREDENTIALS);
@ -245,7 +243,7 @@ sub loadUser {
'U2F error: ' . Crypt::U2F::Server::u2fclib_getError() );
}
}
return -1 unless (@crypters);
return -1 unless @crypters;
$req->data->{crypter} = \@crypters;
return 1;

View File

@ -22,9 +22,7 @@ extends 'Lemonldap::NG::Portal::Main::SecondFactor';
# INITIALIZATION
has prefix => ( is => 'ro', default => 'yubikey' );
has logo => ( is => 'rw', default => 'yubikey.png' );
has yubi => ( is => 'rw' );
sub init {
@ -88,8 +86,7 @@ sub init {
sub _findYubikey {
my ( $self, $req, $sessionInfo ) = @_;
my $yubikey;
my $_2fDevices;
my ( $yubikey, $_2fDevices );
# First, lookup from session attribute
if ( $self->conf->{yubikey2fFromSessionAttribute} ) {

View File

@ -115,10 +115,7 @@ sub getUser {
my ( $self, $req, %args ) = @_;
$self->validateLdap;
unless ( $self->ldap ) {
return PE_LDAPCONNECTFAILED;
}
return PE_LDAPCONNECTFAILED unless $self->ldap;
$self->bind();
@ -144,7 +141,10 @@ sub getUser {
return PE_BADCREDENTIALS;
}
unless ( $req->data->{ldapentry} = $mesg->entry(0) ) {
$self->userLogger->warn("$req->{user} was not found in LDAP directory (".$req->address.")");
$self->userLogger->warn(
"$req->{user} was not found in LDAP directory ("
. $req->address
. ")" );
eval { $self->p->_authentication->setSecurity($req) };
return PE_BADCREDENTIALS;
}
@ -168,8 +168,8 @@ sub bind {
my $self = shift;
$self->validateLdap;
return undef unless ( $self->ldap );
return undef unless $self->ldap;
my $msg = $self->ldap->bind(@_);
if ( $msg->code ) {
$self->logger->error( $msg->error );

View File

@ -28,9 +28,8 @@ sub restCall {
$hreq->header( 'Content-Type' => 'application/json' );
$hreq->content( to_json($content) );
my $resp = $self->ua->request($hreq);
unless ( $resp->is_success ) {
die $resp->status_line;
}
die $resp->status_line unless $resp->is_success;
my $res = eval { from_json( $resp->content ) };
die "Bad REST response: $@" if ($@);
if ( ref($res) ne "HASH" ) {

View File

@ -308,6 +308,29 @@ sub loadIDPs {
}
$self->logger->debug("Set encryption mode $encryption_mode on IDP $_");
# Set signature method if overriden
my $signature_method = $self->conf->{samlIDPMetaDataOptions}->{$_}
->{samlIDPMetaDataOptionsSignatureMethod};
if ($signature_method) {
my $lasso_signature_method =
$self->getSignatureMethod($signature_method);
unless (
$self->setProviderSignatureMethod(
$self->lassoServer->get_provider($entityID),
$lasso_signature_method
)
)
{
$self->logger->error(
"Unable to set signature method $signature_method on IDP $_"
);
next;
}
$self->logger->debug(
"Set signature method $signature_method on IDP $_");
}
# Set display options
$self->idpList->{$entityID}->{displayName} =
$self->conf->{samlIDPMetaDataOptions}->{$_}
@ -414,6 +437,29 @@ sub loadSPs {
}
$self->logger->debug("Set encryption mode $encryption_mode on SP $_");
# Set signature method if overriden
my $signature_method = $self->conf->{samlSPMetaDataOptions}->{$_}
->{samlSPMetaDataOptionsSignatureMethod};
if ($signature_method) {
my $lasso_signature_method =
$self->getSignatureMethod($signature_method);
unless (
$self->setProviderSignatureMethod(
$self->lassoServer->get_provider($entityID),
$lasso_signature_method
)
)
{
$self->logger->error(
"Unable to set signature method $signature_method on SP $_"
);
next;
}
$self->logger->debug(
"Set signature method $signature_method on SP $_");
}
my $rule = $self->conf->{samlSPMetaDataOptions}->{$_}
->{samlSPMetaDataOptionsRule};
if ( length $rule ) {
@ -3166,15 +3212,44 @@ sub getSignatureMethod {
eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA1';
my $signature_method_rsa_sha256 =
eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA256';
my $signature_method_rsa_sha384 =
eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA384';
my $signature_method_rsa_sha512 =
eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA512';
my $signature_method_none = eval 'Lasso::Constants::SIGNATURE_METHOD_NONE';
return $signature_method_rsa_sha1
if ( $signature_method =~ /^RSA_SHA1$/i );
return $signature_method_rsa_sha256
if ( $signature_method =~ /^RSA_SHA256$/i );
return $signature_method_rsa_sha384
if ( $signature_method =~ /^RSA_SHA384$/i );
return $signature_method_rsa_sha512
if ( $signature_method =~ /^RSA_SHA512$/i );
return $signature_method_none;
}
## @method boolean setProviderSignatureMethod(Lasso::Provider provider, int signature_method)
# Set signature method on a provider
# @param provider Lasso::Provider object
# @param signature_method Lasso signature method
# @return result
sub setProviderSignatureMethod {
my ( $self, $provider, $signature_method ) = @_;
eval {
my $key = Lasso::Key::new_for_signature_from_file(
$self->conf->{samlServicePrivateKeySig},
$self->conf->{samlServicePrivateKeySigPwd} || '',
$signature_method,
$self->conf->{samlServicePublicKeySig}
);
$provider->set_server_signing_key($key);
};
return $self->checkLassoError($@);
}
1;
__END__
@ -3540,6 +3615,10 @@ Get query string with or without CGI query_string() method
Return Lasso signature method
=head2 setProviderSignatureMethod
Set signature method on a provider
=head1 SEE ALSO
L<Lemonldap::NG::Portal::Auth::SAML>, L<Lemonldap::NG::Portal::UserDBSAML>

View File

@ -104,6 +104,63 @@ sub createNotification {
}
}
sub allowedUpdateSfa {
my ( $self, $req, $action ) = @_;
my $user = $req->userData->{ $self->conf->{whatToTrace} };
my $res = 1;
# Test action
if ( $action && $action eq 'delete' ) {
my $module = lc ref $self;
$module = ( $module =~ /2f::register::(\w+)$/ )[0];
$module =~ s/2f//;
$self->logger->debug("$user request to delete ${module}2f device");
if ( $self->{conf}->{"${module}2fAuthnLevel"}
&& $req->userData->{authenticationLevel} <
$self->{conf}->{"${module}2fAuthnLevel"} )
{
$self->userLogger->warn(
"$user request to delete ${module}2f device rejected due to insufficient authentication level!"
);
$self->logger->debug(
"authLevel: $req->{userData}->{authenticationLevel} < requiredLevel: "
. $self->{conf}->{"${module}2fAuthnLevel"} );
undef $res;
}
}
# Test if impersonation is in progress
if ( $res && $self->conf->{impersonationRule} ) {
$self->logger->debug('Impersonation plugin is enabled');
if ( $req->userData->{"$self->{conf}->{impersonationPrefix}_user"}
&& $req->userData->{"$self->{conf}->{impersonationPrefix}_user"} ne
$req->userData->{_user} )
{
$self->userLogger->warn(
"Impersonation in progress! $user is not allowed to update 2FA."
);
undef $res;
}
}
# Test if contextSwitching is in progress
if ( $res && $self->conf->{contextSwitchingRule} ) {
$self->logger->debug('ContextSwitching plugin is enabled');
if (
$req->userData->{
"$self->{conf}->{contextSwitchingPrefix}_session_id"}
&& !$self->conf->{contextSwitchingAllowed2fModifications} )
{
$self->userLogger->warn(
"ContextSwitching in progress! $user is not allowed to update 2FA."
);
undef $res;
}
}
$self->userLogger->info("$user is allowed to update 2FA") if $res;
return $res;
}
1;
__END__

View File

@ -32,8 +32,10 @@ our @pList = (
portalDisplayFavApps => '::Plugins::FavApps',
contextSwitchingRule => '::Plugins::ContextSwitching',
decryptValueRule => '::Plugins::DecryptValue',
globalLogoutRule => '::Plugins::GlobalLogout',
refreshSessions => '::Plugins::Refresh',
adaptativeAuthenticationLevelRules =>
'::Plugins::AdaptativeAuthenticationLevel',
globalLogoutRule => '::Plugins::GlobalLogout',
refreshSessions => '::Plugins::Refresh',
);
##@method list enabledPlugins

View File

@ -0,0 +1,83 @@
package Lemonldap::NG::Portal::Plugins::AdaptativeAuthenticationLevel;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(
PE_OK
);
our $VERSION = '2.0.10';
extends 'Lemonldap::NG::Portal::Main::Plugin';
use constant afterData => 'adaptAuthenticationLevel';
has rules => ( is => 'rw', default => sub { {} } );
sub init {
my ($self) = @_;
$self->logger->debug('Init AdaptativeAuthenticationLevel plugin');
foreach (
keys %{ $self->conf->{adaptativeAuthenticationLevelRules} // {} } )
{
$self->logger->debug("adaptativeAuthenticationLevelRules key -> $_");
$self->logger->debug( "adaptativeAuthenticationLevelRules value -> "
. $self->conf->{adaptativeAuthenticationLevelRules}->{$_} );
my $rule =
$self->p->buildRule( $_, 'adaptativeAuthenticationLevelRules' );
next unless $rule;
$self->rules->{$_} = $rule;
}
return 1;
}
sub adaptAuthenticationLevel {
my ( $self, $req ) = @_;
my $userid = $req->sessionInfo->{ $self->conf->{whatToTrace} };
$self->logger->debug("Check adaptative authentication rules for $userid");
my $authenticationLevel = $req->sessionInfo->{authenticationLevel};
$self->logger->debug(
"Current authentication level for $userid is $authenticationLevel");
my $updatedAuthenticationLevel = $authenticationLevel;
foreach ( keys %{ $self->rules } ) {
my $rule = $_;
$self->logger->debug(
"Check adaptativeAuthenticationLevelRules -> $rule");
if ( $self->rules->{$_}->( $req, $req->sessionInfo ) ) {
my $levelOperation =
$self->conf->{adaptativeAuthenticationLevelRules}->{$_};
$self->logger->debug(
"User $userid match rule, apply $levelOperation on authentication level"
);
my ( $op, $level ) = ( $levelOperation =~ /([=+-])?(\d+)/ );
$updatedAuthenticationLevel = $level if ( !$op or $op eq '=' );
$updatedAuthenticationLevel += $level if ( $op and $op eq '+' );
$updatedAuthenticationLevel -= $level if ( $op and $op eq '-' );
$self->logger->debug(
"Authentication level for $userid is now $updatedAuthenticationLevel"
);
}
}
if ( $authenticationLevel ne $updatedAuthenticationLevel ) {
$self->logger->debug(
"Authentication level has changed for $userid, update session");
$self->p->updateSession(
$req,
{
'authenticationLevel' => $updatedAuthenticationLevel
}
);
}
return PE_OK;
}
1;

View File

@ -177,7 +177,18 @@ sub activeSessions {
$self->module->searchOn( $moduleOptions, $self->conf->{whatToTrace},
$user );
$self->logger->debug("Building array ref with sessions info...");
$self->logger->debug('Remove corrupted sessions...');
my $corrupted = 0;
foreach ( keys %$sessions ) {
unless ( $sessions->{$_}->{_session_kind} eq 'SSO' ) {
delete $sessions->{$_};
$corrupted++;
}
}
$self->logger->info("$corrupted corrupted session(s) removed")
if $corrupted;
$self->logger->debug('Build an array ref with sessions info...');
@$activeSessions =
map {
my $epoch;

View File

@ -1,6 +1,6 @@
{
"PE-7":"Vous avez été déconnecté",
"PE-6":"Le mot de passe a été changé",
"PE-6":"Le mot de passe a été changé avec succès",
"PE0":"Utilisateur authentifié",
"PE1":"Votre session a expiré, vous devez vous ré-authentifier",
"PE2":"Identifiant ou mot de passe non renseigné",
@ -237,7 +237,7 @@
"proxyError": "Mauvaise passerelle : impossible de joindre le serveur amont",
"pwd":"Mot de passe",
"pwdChange":"Changement de mot de passe",
"pwdChanged":"Your password has been successfully changed!",
"pwdChanged":"Votre mot de passe a été changé avec succès !",
"pwdResetAlreadyIssued":"Une demande de réinitialisation de mot de passe a déjà été faite le ",
"pwdWillExpire":"%s jours, %s heures, %s minutes et %s secondes avant expiration de votre mot de passe, pensez à le changer !",
"radius2f":"Radius",

View File

@ -1,6 +1,8 @@
use lib 'inc';
use Test::More;
use strict;
use URI;
use URI::QueryParam;
use IO::String;
use LWP::UserAgent;
use LWP::Protocol::PSGI;
@ -11,7 +13,7 @@ BEGIN {
require 't/saml-lib.pm';
}
my $maintests = 19;
my $maintests = 21;
my $debug = 'error';
my ( $issuer, $sp, $res );
@ -152,6 +154,14 @@ SKIP: {
expectForm( $res, 'auth.sp.com', '/saml/proxySingleSignOnPost',
'SAMLResponse', 'RelayState' );
my ($resp) = $query =~ qr/SAMLResponse=([^&]*)/;
my $message = decode_base64( URI::Escape::uri_unescape $resp);
like(
$message,
qr@SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"@,
"Signed using SHA-256"
);
# Post SAML response to SP
switch ('sp');
ok(
@ -190,6 +200,14 @@ SKIP: {
( $url, $query ) = expectRedirection( $res,
qr#^http://auth.idp.com(/saml/singleLogout)\?(SAMLRequest=.+)# );
my $uri = URI->new;
$uri->query($query);
is(
$uri->query_param("SigAlg"),
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384',
'SHA256 used to sign Logout Request'
);
# Push SAML logout request to IdP
switch ('issuer');
ok(
@ -264,6 +282,7 @@ sub issuer {
samlSPMetaDataOptionsSignSLOMessage => 1,
samlSPMetaDataOptionsCheckSSOMessageSignature => 1,
samlSPMetaDataOptionsCheckSLOMessageSignature => 1,
samlSPMetaDataOptionsSignatureMethod => "RSA_SHA256",
}
},
samlSPMetaDataExportedAttributes => {
@ -281,6 +300,7 @@ sub issuer {
samlServicePrivateKeySig => saml_key_idp_private_sig,
samlServicePublicKeyEnc => saml_key_idp_public_enc,
samlServicePublicKeySig => saml_key_idp_public_sig,
samlServiceSignatureMethod => "RSA_SHA1",
samlSPMetaDataXML => {
"sp.com" => {
samlSPMetaDataXML =>
@ -319,6 +339,7 @@ sub sp {
samlIDPMetaDataOptionsCheckSSOMessageSignature => 1,
samlIDPMetaDataOptionsCheckSLOMessageSignature => 1,
samlIDPMetaDataOptionsForceUTF8 => 1,
samlIDPMetaDataOptionsSignatureMethod => "RSA_SHA384",
}
},
samlIDPMetaDataExportedAttributes => {
@ -340,6 +361,7 @@ sub sp {
samlServicePrivateKeyEnc => saml_key_sp_private_enc,
samlServicePrivateKeySig => saml_key_sp_private_sig,
samlServicePublicKeyEnc => saml_key_sp_public_enc,
samlServiceSignatureMethod => "RSA_SHA1",
samlSPSSODescriptorAuthnRequestsSigned => 1,
},
}

View File

@ -0,0 +1,84 @@
use Test::More;
use strict;
use IO::String;
use Data::Dumper;
BEGIN {
require 't/test-lib.pm';
}
my $res;
my $id;
my $json;
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
authentication => 'Demo',
userDB => 'Same',
adaptativeAuthenticationLevelRules => {
'$uid eq "dwho"' => '+2',
'$uid eq "msmith"' => '=5',
},
restSessionServer => 1,
}
}
);
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
accept => 'text/html',
length => 23
),
'Auth query'
);
count(1);
$id = expectCookie($res);
ok(
$res = $client->_get(
'/session/my/global', cookie => "lemonldap=$id"
),
'Get session'
);
count(1);
$json = expectJSON($res);
ok( $json->{authenticationLevel} == 3, 'Authentication level upgraded' );
count(1);
ok( $client->logout($id), 'Logout' );
count(1);
ok(
$res = $client->_post(
'/',
IO::String->new('user=msmith&password=msmith'),
accept => 'text/html',
length => 27
),
'Auth query'
);
count(1);
$id = expectCookie($res);
ok(
$res = $client->_get(
'/session/my/global', cookie => "lemonldap=$id"
),
'Get session'
);
count(1);
$json = expectJSON($res);
ok( $json->{authenticationLevel} == 5, 'Authentication level upgraded' );
count(1);
ok( $client->logout($id), 'Logout' );
count(1);
clean_sessions();
done_testing( count() );

View File

@ -0,0 +1,408 @@
use Test::More;
use strict;
use IO::String;
BEGIN {
require 't/test-lib.pm';
}
my $maintests = 57;
SKIP: {
require Lemonldap::NG::Common::TOTP;
eval { require Crypt::U2F::Server; require Authen::U2F::Tester };
if ( $@ or $Crypt::U2F::Server::VERSION < 0.42 ) {
skip 'Missing U2F libraries', $maintests;
}
eval { require Convert::Base32 };
if ($@) {
skip 'Convert::Base32 is missing';
}
use_ok('Lemonldap::NG::Common::FormEncode');
my $res;
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
authentication => 'Demo',
userDB => 'Same',
portalMainLogo => 'common/logos/logo_llng_old.png',
impersonationRule => 1,
totp2fSelfRegistration => 1,
totp2fActivation => 1,
totp2fAuthnLevel => 8,
u2fSelfRegistration => 1,
u2fActivation => 1,
}
}
);
# Try to authenticate
# -------------------
ok(
$res = $client->_post(
'/', IO::String->new('user=rtyler&password=rtyler'),
length => 27
),
'Auth query'
);
my $id = expectCookie($res);
ok(
$res = $client->_get(
'/',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'Get Menu'
);
expectAuthenticatedAs( $res, 'rtyler' );
ok( $res->[2]->[0] =~ m%<span trspan="sfaManager">sfaManager</span>%,
'sfaManager link found' )
or print STDERR Dumper( $res->[2]->[0] );
## Try to register a TOTP
# TOTP form
ok(
$res = $client->_get(
'/2fregisters/totp',
cookie => "lemonldap=$id",
accept => 'text/html',
),
'Form registration'
);
ok( $res->[2]->[0] =~ /totpregistration\.(?:min\.)?js/, 'Found TOTP js' );
ok(
$res->[2]->[0] =~ qr%<img src="/static/common/logos/logo_llng_old.png"%,
'Found custom Main Logo'
) or print STDERR Dumper( $res->[2]->[0] );
# JS query
ok(
$res = $client->_post(
'/2fregisters/totp/getkey', IO::String->new(''),
cookie => "lemonldap=$id",
length => 0,
),
'Get new key'
);
eval { $res = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), 'Content is JSON' )
or explain( $res->[2]->[0], 'JSON content' );
my ( $key, $token );
ok( $key = $res->{secret}, 'Found secret' ) or print STDERR Dumper($res);
ok( $token = $res->{token}, 'Found token' ) or print STDERR Dumper($res);
ok( $res->{user} eq 'rtyler', 'Found user' )
or print STDERR Dumper($res);
$key = Convert::Base32::decode_base32($key);
# Post code
my $code;
ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ),
'Code' );
ok( $code =~ /^\d{6}$/, 'Code contains 6 digits' );
my $s = "code=$code&token=$token&TOTPName=myTOTP";
ok(
$res = $client->_post(
'/2fregisters/totp/verify',
IO::String->new($s),
length => length($s),
cookie => "lemonldap=$id",
),
'Post code'
);
eval { $res = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), 'Content is JSON' )
or explain( $res->[2]->[0], 'JSON content' );
ok( $res->{result} == 1, 'TOTP is registered' );
## Try to register an U2F key
ok(
$res = $client->_get(
'/2fregisters/u',
cookie => "lemonldap=$id",
accept => 'text/html',
),
'Form registration'
);
ok( $res->[2]->[0] =~ /u2fregistration\.(?:min\.)?js/, 'Found U2F js' );
ok(
$res->[2]->[0] =~ qr%<img src="/static/common/logos/logo_llng_old.png"%,
'Found custom Main Logo'
) or print STDERR Dumper( $res->[2]->[0] );
# Ajax registration request
ok(
$res = $client->_post(
'/2fregisters/u/register', IO::String->new(''),
accept => 'application/json',
cookie => "lemonldap=$id",
length => 0,
),
'Get registration challenge'
);
expectOK($res);
my $data;
eval { $data = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), ' Content is JSON' )
or explain( [ $@, $res->[2] ], 'JSON content' );
ok( ( $data->{challenge} and $data->{appId} ), ' Get challenge and appId' )
or explain( $data, 'challenge and appId' );
# Build U2F tester
my $tester = Authen::U2F::Tester->new(
certificate => Crypt::OpenSSL::X509->new_from_string(
'-----BEGIN CERTIFICATE-----
MIIB6DCCAY6gAwIBAgIJAJKuutkN2sAfMAoGCCqGSM49BAMCME8xCzAJBgNVBAYT
AlVTMQ4wDAYDVQQIDAVUZXhhczEaMBgGA1UECgwRVW50cnVzdGVkIFUyRiBPcmcx
FDASBgNVBAMMC3ZpcnR1YWwtdTJmMB4XDTE4MDMyODIwMTc1OVoXDTI3MTIyNjIw
MTc1OVowTzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRowGAYDVQQKDBFV
bnRydXN0ZWQgVTJGIE9yZzEUMBIGA1UEAwwLdmlydHVhbC11MmYwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAAQTij+9mI1FJdvKNHLeSQcOW4ob3prvIXuEGJMrQeJF
6OYcgwxrVqsmNMl5w45L7zx8ryovVOti/mtqkh2pQjtpo1MwUTAdBgNVHQ4EFgQU
QXKKf+rrZwA4WXDCU/Vebe4gYXEwHwYDVR0jBBgwFoAUQXKKf+rrZwA4WXDCU/Ve
be4gYXEwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAiCdOEmw5
hknzHR1FoyFZKRrcJu17a1PGcqTFMJHTC70CIHeCZ8KVuuMIPjoofQd1l1E221rv
RJY1Oz1fUNbrIPsL
-----END CERTIFICATE-----', Crypt::OpenSSL::X509::FORMAT_PEM()
),
key => Crypt::PK::ECC->new(
\'-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOdbZw1swQIL+RZoDQ9zwjWY5UjA1NO81WWjwbmznUbgoAoGCCqGSM49
AwEHoUQDQgAEE4o/vZiNRSXbyjRy3kkHDluKG96a7yF7hBiTK0HiRejmHIMMa1ar
JjTJecOOS+88fK8qL1TrYv5rapIdqUI7aQ==
-----END EC PRIVATE KEY-----'
),
);
my $r = $tester->register( $data->{appId}, $data->{challenge} );
ok( $r->is_success, ' Good challenge value' )
or diag( $r->error_message );
my $registrationData = JSON::to_json( {
clientData => $r->client_data,
errorCode => 0,
registrationData => $r->registration_data,
version => "U2F_V2"
}
);
my ( $host, $url, $query );
$query = Lemonldap::NG::Common::FormEncode::build_urlencoded(
registration => $registrationData,
challenge => $res->[2]->[0],
);
ok(
$res = $client->_post(
'/2fregisters/u/registration', IO::String->new($query),
length => length($query),
accept => 'application/json',
cookie => "lemonldap=$id",
),
'Push registration data'
);
expectOK($res);
eval { $data = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), ' Content is JSON' )
or explain( [ $@, $res->[2] ], 'JSON content' );
ok( $data->{result} == 1, 'U2F key is registered' )
or explain( $data, '"result":1' );
$client->logout($id);
## Try to impersonate
ok( $res = $client->_get( '/', accept => 'text/html' ), 'Get Menu', );
( $host, $url, $query ) =
expectForm( $res, '#', undef, 'user', 'password', 'spoofId' );
$query =~ s/user=/user=rtyler/;
$query =~ s/password=/password=rtyler/;
$query =~ s/spoofId=/spoofId=dwho/;
ok(
$res = $client->_post(
'/',
IO::String->new($query),
length => length($query),
accept => 'text/html',
),
'Auth query'
);
( $host, $url, $query ) = expectForm( $res, undef, '/2fchoice', 'token' );
$query .= '&sf=totp';
ok(
$res = $client->_post(
'/2fchoice',
IO::String->new($query),
length => length($query),
accept => 'text/html',
),
'Post TOTP choice'
);
( $host, $url, $query ) =
expectForm( $res, undef, '/totp2fcheck', 'token' );
ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ),
'Code' );
$query =~ s/code=/code=$code/;
ok(
$res = $client->_post(
'/totp2fcheck', IO::String->new($query),
length => length($query),
),
'Post code'
);
$id = expectCookie($res);
# Get Menu
# ------------------------
ok(
$res = $client->_get(
'/',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'Get Menu',
);
expectOK($res);
expectAuthenticatedAs( $res, 'dwho' );
# 2fregisters
ok(
$res = $client->_get(
'/2fregisters',
cookie => "lemonldap=$id",
accept => 'text/html',
),
'Form 2fregisters'
);
ok( $res->[2]->[0] =~ /<span id="msg" trspan="choose2f">/,
'Found choose 2F' )
or print STDERR Dumper( $res->[2]->[0] );
ok( $res->[2]->[0] !~ m%<span device=\'(TOTP|U2F)\' epoch=\'\d{10}\'%g,
'No 2F device found' )
or print STDERR Dumper( $res->[2]->[0] );
## Try to register a TOTP
# TOTP form
ok(
$res = $client->_get(
'/2fregisters/totp',
cookie => "lemonldap=$id",
accept => 'text/html',
),
'Form registration'
);
ok( $res->[2]->[0] =~ /totpregistration\.(?:min\.)?js/, 'Found TOTP js' )
or print STDERR Dumper( $res->[2]->[0] );
ok(
$res->[2]->[0] =~ qr%<img src="/static/common/logos/logo_llng_old.png"%,
'Found custom Main Logo'
) or print STDERR Dumper( $res->[2]->[0] );
# JS query
ok(
$res = $client->_post(
'/2fregisters/totp/getkey', IO::String->new(''),
cookie => "lemonldap=$id",
length => 0,
),
'Get new key'
);
eval { $res = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), 'Content is JSON' )
or explain( $res->[2]->[0], 'JSON content' );
ok( $res->{error} eq 'notAuthorized', 'Not authorized to register a TOTP' )
or explain( $res, 'Bad result' );
# Try to unregister TOTP
ok(
$res = $client->_post(
'/2fregisters/totp/delete',
IO::String->new("epoch=1234567890"),
length => 16,
cookie => "lemonldap=$id",
),
'Delete TOTP query'
);
eval { $data = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), ' Content is JSON' )
or explain( [ $@, $res->[2] ], 'JSON content' );
ok(
$data->{error} eq 'notAuthorized',
'Not authorized to unregister a TOTP'
) or explain( $data, 'Bad result' );
# Try to verify TOTP
$s = "code=123456&token=1234567890&TOTPName=myTOTP";
ok(
$res = $client->_post(
'/2fregisters/totp/verify',
IO::String->new($s),
length => length($s),
cookie => "lemonldap=$id",
),
'Post code'
);
eval { $data = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), ' Content is JSON' )
or explain( [ $@, $res->[2] ], 'JSON content' );
ok( $data->{error} eq 'notAuthorized', 'Not authorized to verify a TOTP' )
or explain( $data, 'Bad result' );
## Try to register an U2F key
# U2F form
ok(
$res = $client->_get(
'/2fregisters/u',
cookie => "lemonldap=$id",
accept => 'text/html',
),
'Form registration'
);
ok( $res->[2]->[0] =~ /u2fregistration\.(?:min\.)?js/, 'Found U2F js' );
ok(
$res->[2]->[0] =~ qr%<img src="/static/common/logos/logo_llng_old.png"%,
'Found custom Main Logo'
) or print STDERR Dumper( $res->[2]->[0] );
# Ajax registration request
ok(
$res = $client->_post(
'/2fregisters/u/register', IO::String->new(''),
accept => 'application/json',
cookie => "lemonldap=$id",
length => 0,
),
'Get registration challenge'
);
eval { $data = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), ' Content is JSON' )
or explain( [ $@, $res->[2] ], 'JSON content' );
ok(
$data->{error} eq 'notAuthorized',
'Not authorized to register an U2F key'
) or explain( $data, 'Bad result' );
# Try to unregister U2F key
ok(
$res = $client->_post(
'/2fregisters/u/delete',
IO::String->new("epoch=1234567890"),
length => 16,
cookie => "lemonldap=$id",
),
'Delete U2F key query'
);
eval { $data = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), ' Content is JSON' )
or explain( [ $@, $res->[2] ], 'JSON content' );
ok(
$data->{error} eq 'notAuthorized',
'Not authorized to unregister an U2F key'
) or explain( $data, 'Bad result' );
$client->logout($id);
}
count($maintests);
clean_sessions();
done_testing( count() );

View File

@ -23,13 +23,13 @@ SKIP: {
requireToken => 0,
checkUser => 1,
impersonationRule => 1,
checkUserDisplayPersistentInfo => 0,
checkUserDisplayEmptyValues => 0,
checkUserDisplayPersistentInfo => 0,
checkUserDisplayEmptyValues => 0,
checkUserDisplayComputedSession => 1,
impersonationMergeSSOgroups => 1,
totp2fSelfRegistration => 1,
totp2fActivation => 1,
totp2fAuthnLevel => 8
impersonationMergeSSOgroups => 1,
totp2fSelfRegistration => 1,
totp2fActivation => 1,
totp2fAuthnLevel => 8
}
}
);

View File

@ -47,7 +47,7 @@ SKIP: {
'Get Menu'
);
ok( $res->[2]->[0] !~ m%<span trspan="sfaManager">sfaManager</span>%,
'sfaManager link found' )
'sfaManager link not found' )
or print STDERR Dumper( $res->[2]->[0] );
# TOTP form
@ -113,7 +113,7 @@ SKIP: {
eval { $res = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), 'Content is JSON' )
or explain( $res->[2]->[0], 'JSON content' );
ok( $res->{result} == 1, 'Key is registered' );
ok( $res->{result} == 1, 'TOTP is registered' );
# Try to sign-in
$client->logout($id);

View File

@ -46,7 +46,7 @@ SKIP: {
'Get Menu'
);
ok( $res->[2]->[0] !~ m%<span trspan="sfaManager">sfaManager</span>%,
'sfaManager link found' )
'sfaManager link not found' )
or print STDERR Dumper( $res->[2]->[0] );
# U2F form
@ -210,8 +210,7 @@ JjTJecOOS+88fK8qL1TrYv5rapIdqUI7aQ==
);
}
}
count($maintests);
clean_sessions();
done_testing( count() );