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; #real_ip_header X-Forwarded-For;
location = /reload { location = /reload {
allow 127.0.0.1/8; allow 127.0.0.0/8;
allow ::1/128; allow ::1/128;
deny all; 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; 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 Jitsi Meet Virtual host in Manager
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

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

View File

@ -6,6 +6,9 @@ Your network infrastructure might require that LemonLDAP::NG components
|image0| |image0|
Transmitting the correct IP address to the portal
-------------------------------------------------
In this case, LemonLDAP::NG components will store the ip address of the In this case, LemonLDAP::NG components will store the ip address of the
connection between the reverse proxy and the webserver in the session, connection between the reverse proxy and the webserver in the session,
and in logs. This prevents features such as session restrictions and 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. In order to do this you have several options.
HTTP Header HTTP Header
----------- ~~~~~~~~~~~
This generic method is the most likely to work in your particular This generic method is the most likely to work in your particular
environment. environment.
@ -68,7 +71,7 @@ uncomment the relevant parts of the configuration file.
module documentation carefully. module documentation carefully.
PROXY Protocol PROXY Protocol
-------------- ~~~~~~~~~~~~~~
Alternatively, if your proxy supports the PROXY protocol (Nginx, Alternatively, if your proxy supports the PROXY protocol (Nginx,
HAProxy, Amazon ELB), you may use it to carry over the information HAProxy, Amazon ELB), you may use it to carry over the information
@ -86,6 +89,41 @@ Portal/Manager/Handler:
# or # or
# listen 443 ssl proxy_protocol; # listen 443 ssl proxy_protocol;
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
.. |image0| image:: /documentation/reverseproxy.png .. |image0| image:: /documentation/reverseproxy.png
:class: align-center :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 : Enable and start the service :
@ -40,25 +40,25 @@ Files
----- -----
With tarball installation, Nginx configuration files will be installed With tarball installation, Nginx configuration files will be installed
in ``/usr/local/lemonldap-ng/etc/``, else they are in in ``/usr/local/lemonldap-ng/etc/``, else they are directly in web server
``/etc/lemonldap-ng``. configuration.
You have to include them in Nginx main configuration.
.. _debianubuntu-1: .. _debianubuntu-1:
Debian/Ubuntu Debian/Ubuntu
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
Link files into ``sites-available`` directory (should already have been - Install log format *(automatically loaded when linked in this place)*
done if you used packages):
:: ::
ln -s /etc/lemonldap-ng/handler-nginx.conf /etc/nginx/sites-available/ ln -s /etc/lemonldap-ng/nginx-lmlog.conf /etc/nginx/conf.d/llng-lmlog.conf
ln -s /etc/lemonldap-ng/manager-nginx.conf /etc/nginx/sites-available/
ln -s /etc/lemonldap-ng/portal-nginx.conf /etc/nginx/sites-available/ - Install snippet for vhost configuration files:
ln -s /etc/lemonldap-ng/test-nginx.conf /etc/nginx/sites-available/
::
ln -s /etc/lemonldap-ng/nginx-lua-headers.conf /etc/nginx/snippets/llng-lua-headers.conf
- Enable sites: - 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/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/portal-nginx.conf /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/test-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 Reverse proxy
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
\* Example of a protected reverse-proxy: - Example of a protected reverse-proxy:
.. code-block:: nginx .. code-block:: nginx
@ -300,7 +300,7 @@ Reverse proxy
# PASSING HEADERS TO APPLICATION # # PASSING HEADERS TO APPLICATION #
################################## ##################################
# IF LUA IS SUPPORTED # IF LUA IS SUPPORTED
#include /path/to/nginx-lua-headers.conf #include /path/to/nginx-lua-headers.conf;
# ELSE # ELSE
# Set manually your headers # Set manually your headers
@ -309,8 +309,16 @@ Reverse proxy
} }
} }
\* Example of a Nginx Virtual Host using uWSGI with many URIs protected If /etc/nginx/proxy_params file does not exist, you can create it with this content:
by different types of handler :
.. 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 .. code-block:: nginx

View File

@ -15,7 +15,8 @@ can be forbidden to assume.
- **Parameters**: - **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 - **Identities use rule**: Rule to define which identities can be
assumed. Useful to prevent impersonation of certain sensitive assumed. Useful to prevent impersonation of certain sensitive
identities like CEO, administrators or anonymous/protected users. identities like CEO, administrators or anonymous/protected users.
@ -30,7 +31,7 @@ can be forbidden to assume.
During context switching authentication process, all During context switching authentication process, all
plugins are disabled. In other words, all entry points like afterData, plugins are disabled. In other words, all entry points like afterData,
endAuth and so on are skipped. Therefore, second factors or 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:: .. attention::
@ -39,6 +40,11 @@ can be forbidden to assume.
backend. You can not switch context with federated authentication. 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 contextSwitchingPrefix is used to store real user's session Id. You can
set this prefix ('switching' by default) by editing ``lemonldap-ng.ini`` set this prefix ('switching' by default) by editing ``lemonldap-ng.ini``
in [portal] section: in [portal] section:

View File

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

View File

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

View File

@ -6,8 +6,8 @@ Presentation
Main settings: Main settings:
- **REMOTE_USER** : session attribute used for logging user access. - **REMOTE_USER** : session attribute used for logging user access
- **REMOTE_CUSTOM** : can be used for logging a second user attribute - **REMOTE_CUSTOM** : can be used for logging an another user attribute or a macro
(optional) (optional)
- **Hidden attributes** : session attributes never displayed or sent - **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)* - **Logo** (Optional): logo file *(in static/<skin> directory)*
- **Label** (Optional): label that should be displayed to the user on - **Label** (Optional): label that should be displayed to the user on
the choice screen 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: To define keys, you can:
- import your own private and public keys (``Replace by file`` input) - 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:: .. tip::
@ -132,34 +136,35 @@ To define keys, you can:
|image1| |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 - **Use certificate in response**: Certificate will be sent inside SAML
responses. responses.
- **Signature method**: set the signature algorithm - **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:: .. attention::
Default value is RSA SHA1 for compatibility purpose but If you are running a version under 2.0.10, the choice of a signature
we recommend to use RSA SHA256. This requires to test all partners to algorithm will affect all SP and IDP.
check their compatibility.
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 NameID formats
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~

View File

@ -1,6 +1,6 @@
# Plack app to use with Nginx # 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,... # Feersum,...
my %builder = ( my %builder = (

View File

@ -29,8 +29,8 @@ use constant DEFAULTCONFBACKEND => "File";
use constant DEFAULTCONFBACKENDOPTIONS => ( use constant DEFAULTCONFBACKENDOPTIONS => (
dirName => '/usr/local/lemonldap-ng/data/conf', 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 $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)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|o(?:ntextSwitchingStopWithLogout|mpactConf|rsEnabled)|heck(?:State|User|XSS)|da)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|re(?:st(?:(?:Password|Session|Config|Auth)Server|ExportSecretKeys)|freshSessions)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|g(?:roupsBeforeMacros|lobalLogoutTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs))$/; our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|f(?:RemovedUseNotif|OnlyUpgrade)|kip(?:Upgrade|Renew)Confirmation|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:Allow(?:PasswordGrant|Offline)|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|ForceAuthn|AntiFrame)|roxyUseSoap)|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' ); our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' );

View File

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

View File

@ -22,14 +22,14 @@ our $specialNodeHash = {
}; };
our $doubleHashKeys = 'issuerDBGetParameters'; 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 $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 $casAppMetaDataNodeKeys = 'casAppMetaData(?:Options(?:(?:UserAttribut|Servic|Rul)e|AuthnLevel)|(?:ExportedVar|Macro)s)';
our $casSrvMetaDataNodeKeys = 'casSrvMetaData(?:Options(?:ProxiedServices|DisplayName|SortNumber|Gateway|Renew|Icon|Url)|ExportedVars)'; 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 $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 $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 $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(?: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 $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 $virtualHostKeys = '(?:vhost(?:A(?:uthnLevel|liases)|(?:Maintenanc|Typ)e|ServiceTokenTTL|Https|Port)|(?:exportedHeader|locationRule)s|post)';
our $authParameters = { our $authParameters = {

View File

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

View File

@ -614,7 +614,7 @@ sub _getPort {
return $class->tsv->{port}->{_}; return $class->tsv->{port}->{_};
} }
else { else {
return $req->{env}->{SERVER_PORT}; return $req->port;
} }
} }
} }
@ -638,7 +638,7 @@ sub _isHttps {
return $class->tsv->{https}->{_}; return $class->tsv->{https}->{_};
} }
else { 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/postContainer.html
site/htdocs/static/forms/README.md site/htdocs/static/forms/README.md
site/htdocs/static/forms/restore.html 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/RSAKeyNoPassword.html
site/htdocs/static/forms/rule.html site/htdocs/static/forms/rule.html
site/htdocs/static/forms/ruleContainer.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' => { 'RSAPrivateKey' => {
'test' => sub { 'test' => sub {
return $_[0] =~ 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
: ( 1, '__badPemEncoding__' ); : ( 1, '__badPemEncoding__' );
} }
@ -263,6 +263,18 @@ sub attributes {
'default' => 1, 'default' => 1,
'type' => 'bool' 'type' => 'bool'
}, },
'adaptativeAuthenticationLevelRules' => {
'keyMsgFail' => '__badRegexp__',
'keyTest' => sub {
eval {
do {
qr/$_[0]/;
}
};
return $@ ? 0 : 1;
},
'type' => 'keyTextContainer'
},
'ADPwdExpireWarning' => { 'ADPwdExpireWarning' => {
'default' => 0, 'default' => 0,
'type' => 'int' 'type' => 'int'
@ -1026,6 +1038,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
], ],
'type' => 'select' 'type' => 'select'
}, },
'contextSwitchingAllowed2fModifications' => {
'default' => 0,
'type' => 'bool'
},
'contextSwitchingIdRule' => { 'contextSwitchingIdRule' => {
'default' => 1, 'default' => 1,
'test' => sub { 'test' => sub {
@ -3203,6 +3219,31 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'default' => '', 'default' => '',
'type' => 'longtext' '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' => { 'samlIDPMetaDataOptionsSignSLOMessage' => {
'default' => -1, 'default' => -1,
'type' => 'trool' 'type' => 'trool'
@ -3394,7 +3435,7 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'type' => 'RSAPublicKeyOrCertificate' 'type' => 'RSAPublicKeyOrCertificate'
}, },
'samlServiceSignatureMethod' => { 'samlServiceSignatureMethod' => {
'default' => 'RSA_SHA1', 'default' => 'RSA_SHA256',
'select' => [ { 'select' => [ {
'k' => 'RSA_SHA1', 'k' => 'RSA_SHA1',
'v' => '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', 'k' => 'RSA_SHA256',
'v' => 'RSA SHA256' 'v' => 'RSA SHA256'
},
{
'k' => 'RSA_SHA384',
'v' => 'RSA SHA384'
},
{
'k' => 'RSA_SHA512',
'v' => 'RSA SHA512'
} }
], ],
'type' => 'select' 'type' => 'select'
@ -3539,6 +3588,31 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'default' => 72000, 'default' => 72000,
'type' => 'int' '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' => { 'samlSPMetaDataOptionsSignSLOMessage' => {
'default' => -1, 'default' => -1,
'type' => 'trool' 'type' => 'trool'

View File

@ -154,7 +154,7 @@ sub types {
test => sub { test => sub {
return ( return (
$_[0] =~ $_[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)
: ( 1, '__badPemEncoding__' ) : ( 1, '__badPemEncoding__' )
); );
@ -594,6 +594,12 @@ sub attributes {
documentation => 'Stop context switching by logout', documentation => 'Stop context switching by logout',
flags => 'p', flags => 'p',
}, },
contextSwitchingAllowed2fModifications => {
type => 'bool',
default => 0,
documentation => 'Allowed SFA modifications',
flags => 'p',
},
contextSwitchingPrefix => { contextSwitchingPrefix => {
type => 'text', type => 'text',
default => 'switching', default => 'switching',
@ -2119,6 +2125,18 @@ sub attributes {
documentation => 'List of auto signin rules', 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 ## Virtualhosts
# Fake attribute: used by manager REST API to agglomerate all other # Fake attribute: used by manager REST API to agglomerate all other
@ -2490,8 +2508,10 @@ sub attributes {
select => [ select => [
{ k => 'RSA_SHA1', v => 'RSA SHA1' }, { k => 'RSA_SHA1', v => 'RSA SHA1' },
{ k => 'RSA_SHA256', v => 'RSA SHA256' }, { 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 => { samlServiceUseCertificateInResponse => {
type => 'bool', type => 'bool',
@ -2764,6 +2784,17 @@ sub attributes {
type => 'trool', type => 'trool',
default => -1, 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 => { samlIDPMetaDataOptionsCheckSLOMessageSignature => {
type => 'bool', type => 'bool',
default => 1, default => 1,
@ -2920,6 +2951,17 @@ sub attributes {
type => 'trool', type => 'trool',
default => -1, 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 => { samlSPMetaDataOptionsCheckSSOMessageSignature => {
type => 'bool', type => 'bool',
default => 1, default => 1,

View File

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

View File

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

View File

@ -431,7 +431,6 @@ sub run {
unless (@_) { unless (@_) {
die 'nothing to do, aborting'; die 'nothing to do, aborting';
} }
$self->cfgNum( $self->lastCfg ) unless ( $self->cfgNum );
my $action = shift; my $action = shift;
unless ( unless (
$action =~ /^(?:get|set|del|addKey|delKey|save|restore|rollback)$/ ) $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"; "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(@_); $self->$action(@_);
} }

View File

@ -15,6 +15,7 @@ use Lemonldap::NG::Common::EmailTransport;
use Crypt::OpenSSL::RSA; use Crypt::OpenSSL::RSA;
use Convert::PEM; use Convert::PEM;
use URI::URL; use URI::URL;
use Net::SSLeay;
use feature 'state'; use feature 'state';
@ -59,10 +60,11 @@ sub init {
# New key and conf save # New key and conf save
->addRoute( ->addRoute(
confs => { confs => {
newRSAKey => 'newRSAKey', newRSAKey => 'newRSAKey',
sendTestMail => 'sendTestMail', newCertificate => 'newCertificate',
raw => 'newRawConf', sendTestMail => 'sendTestMail',
'*' => 'newConf' raw => 'newRawConf',
'*' => 'newConf'
}, },
['POST'] ['POST']
) )
@ -76,6 +78,31 @@ sub init {
return 1; 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 # 35 - New RSA key pair on demand
# -------------------------- # --------------------------
@ -90,8 +117,9 @@ sub newRSAKey {
my ( $self, $req, @others ) = @_; my ( $self, $req, @others ) = @_;
return $self->sendError( $req, 'There is no subkey for "newRSAKey"', 400 ) return $self->sendError( $req, 'There is no subkey for "newRSAKey"', 400 )
if (@others); if (@others);
my $rsa = Crypt::OpenSSL::RSA->generate_key(2048);
my $query = $req->jsonBodyToObj; my $query = $req->jsonBodyToObj;
my $rsa = Crypt::OpenSSL::RSA->generate_key(2048);
my $keys = { my $keys = {
'private' => $rsa->get_private_key_string(), 'private' => $rsa->get_private_key_string(),
'public' => $rsa->get_public_key_x509_string(), 'public' => $rsa->get_public_key_x509_string(),
@ -121,6 +149,81 @@ sub newRSAKey {
return $self->sendJSONresponse( $req, $keys ); 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 # Sending a test Email
# -------------------- # --------------------

View File

@ -380,6 +380,31 @@ sub tests {
&& $conf->{samlServicePublicKeySig} ); && $conf->{samlServicePublicKeySig} );
return 1; 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 # Try to parse combination with declared modules
checkCombinations => sub { checkCombinations => sub {
@ -811,19 +836,27 @@ sub tests {
# Cookie SameSite=None requires Secure flag # Cookie SameSite=None requires Secure flag
# Same with SameSite=(auto) and SAML issuer in use # Same with SameSite=(auto) and SAML issuer in use
SameSiteNoneWithSecure => sub { 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} ); if ( getSameSite($conf) eq 'None' and !$conf->{securedCookie} );
return 1; return 1;
}, },
# Secure cookies require HTTPS # Secure cookies require HTTPS
SecureCookiesRequireHttps => sub { SecureCookiesRequireHttps => sub {
return ( 1, 'Secure cookies require a HTTPS portal URL' ) return ( -1, 'Secure cookies require a HTTPS portal URL' )
if ( $conf->{securedCookie} == 1 if ( $conf->{securedCookie} == 1
and $conf->{portal} and $conf->{portal}
and $conf->{portal} !~ /^https:/ ); and $conf->{portal} !~ /^https:/ );
return 1; 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') console.log('Error sending test email')
# RSA keys generation # 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.newRSAKey = ->
$scope.showModal('password.html').then -> $scope.showModal('password.html').then ->
$scope.waiting = true $scope.waiting = true

View File

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

View File

@ -708,6 +708,35 @@ function templates(tpl,key) {
}, },
{ {
"_nodes" : [ "_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, "default" : -1,
"get" : tpl+"s/"+key+"/"+"samlIDPMetaDataOptionsSignSSOMessage", "get" : tpl+"s/"+key+"/"+"samlIDPMetaDataOptionsSignSSOMessage",
@ -1102,6 +1131,35 @@ function templates(tpl,key) {
}, },
{ {
"_nodes" : [ "_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, "default" : -1,
"get" : tpl+"s/"+key+"/"+"samlSPMetaDataOptionsSignSSOMessage", "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'); 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() { $scope.newRSAKey = function() {
return $scope.showModal('password.html').then(function() { return $scope.showModal('password.html').then(function() {
var currentNode, password; 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", "2ndFA":"Second Factors",
"actives":"مفعلة", "actives":"مفعلة",
"activeTimer":"قبول تلقائي للوقت", "activeTimer":"قبول تلقائي للوقت",
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
"addAppCasPartner":"إضافة تطبيق كاس", "addAppCasPartner":"إضافة تطبيق كاس",
"addIDPSamlPartner":"أضف IDP SAML", "addIDPSamlPartner":"أضف IDP SAML",
"addOidcOp":"إضافة أوبين أيدي كونيكت بروفيدر", "addOidcOp":"إضافة أوبين أيدي كونيكت بروفيدر",
@ -161,6 +162,7 @@
"portalDisplayCertificateResetByMail":"Reset your Certificate", "portalDisplayCertificateResetByMail":"Reset your Certificate",
"contentSecurityPolicy":"Content security policy", "contentSecurityPolicy":"Content security policy",
"contextSwitching":"Switch context another user", "contextSwitching":"Switch context another user",
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
"contextSwitchingHiddenAttributes":"السمات المخفية", "contextSwitchingHiddenAttributes":"السمات المخفية",
"contextSwitchingIdRule":"Identities use rule", "contextSwitchingIdRule":"Identities use rule",
"contextSwitchingRule":"استخدام القاعدة", "contextSwitchingRule":"استخدام القاعدة",
@ -517,6 +519,7 @@
"newApp":"تطبيق جديد", "newApp":"تطبيق جديد",
"newChain":"سلسلة جديدة", "newChain":"سلسلة جديدة",
"newCat":"فئة جديدة", "newCat":"فئة جديدة",
"newCertificate":"New certificate",
"newCfgAvailable":"إعدادات جديد متاح", "newCfgAvailable":"إعدادات جديد متاح",
"newCmbMod":"وحدة جديدة", "newCmbMod":"وحدة جديدة",
"newCmbOver":"معايير جديدة", "newCmbOver":"معايير جديدة",
@ -1060,6 +1063,7 @@
"samlIDPMetaDataOptionsAuthnRequest":"طلب إثبات الهوية", "samlIDPMetaDataOptionsAuthnRequest":"طلب إثبات الهوية",
"samlIDPMetaDataOptionsSession":"جلسة", "samlIDPMetaDataOptionsSession":"جلسة",
"samlIDPMetaDataOptionsSignature":"توقيع", "samlIDPMetaDataOptionsSignature":"توقيع",
"samlIDPMetaDataOptionsSignatureMethod":"Signature method",
"samlIDPMetaDataOptionsBinding":"ربط", "samlIDPMetaDataOptionsBinding":"ربط",
"samlIDPMetaDataOptionsDisplay":"عرض", "samlIDPMetaDataOptionsDisplay":"عرض",
"samlIDPMetaDataOptionsDisplayName":"عرض الاسم", "samlIDPMetaDataOptionsDisplayName":"عرض الاسم",
@ -1083,6 +1087,7 @@
"samlSPMetaDataOptionsEncryptionMode":"أسلوب التشفير", "samlSPMetaDataOptionsEncryptionMode":"أسلوب التشفير",
"samlSPMetaDataOptionsAuthnResponse":"رد إثبات الهوية", "samlSPMetaDataOptionsAuthnResponse":"رد إثبات الهوية",
"samlSPMetaDataOptionsSignature":"توقيع", "samlSPMetaDataOptionsSignature":"توقيع",
"samlSPMetaDataOptionsSignatureMethod":"Signature method",
"samlSPMetaDataOptionsSecurity":"الحماية", "samlSPMetaDataOptionsSecurity":"الحماية",
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"تمكين استخدام عنوان يو آر إل IDP ", "samlSPMetaDataOptionsEnableIDPInitiatedURL":"تمكين استخدام عنوان يو آر إل IDP ",
"samlSPMetaDataOptionsNameIDSessionKey":"فرض اسم المعرف لمفتاح الجلسة", "samlSPMetaDataOptionsNameIDSessionKey":"فرض اسم المعرف لمفتاح الجلسة",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,7 @@
"2ndFA":"Second Factors", "2ndFA":"Second Factors",
"actives":"Enabled", "actives":"Enabled",
"activeTimer":"自动接收时间", "activeTimer":"自动接收时间",
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
"addAppCasPartner":"增加CAS应用", "addAppCasPartner":"增加CAS应用",
"addIDPSamlPartner":"增加SAML IDP", "addIDPSamlPartner":"增加SAML IDP",
"addOidcOp":"增加OpenID Connect Provider", "addOidcOp":"增加OpenID Connect Provider",
@ -161,6 +162,7 @@
"portalDisplayCertificateResetByMail":"Reset your certificate", "portalDisplayCertificateResetByMail":"Reset your certificate",
"contentSecurityPolicy":"Content security policy", "contentSecurityPolicy":"Content security policy",
"contextSwitching":"Switch context another user", "contextSwitching":"Switch context another user",
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
"contextSwitchingHiddenAttributes":"Hidden attributes", "contextSwitchingHiddenAttributes":"Hidden attributes",
"contextSwitchingIdRule":"Identities use rule", "contextSwitchingIdRule":"Identities use rule",
"contextSwitchingRule":"Use rule", "contextSwitchingRule":"Use rule",
@ -517,6 +519,7 @@
"newApp":"New application", "newApp":"New application",
"newChain":"New chain", "newChain":"New chain",
"newCat":"New category", "newCat":"New category",
"newCertificate":"New certificate",
"newCfgAvailable":"A new configuration is available", "newCfgAvailable":"A new configuration is available",
"newCmbMod":"New module", "newCmbMod":"New module",
"newCmbOver":"New parameter", "newCmbOver":"New parameter",
@ -1060,6 +1063,7 @@
"samlIDPMetaDataOptionsAuthnRequest":"Authentication request", "samlIDPMetaDataOptionsAuthnRequest":"Authentication request",
"samlIDPMetaDataOptionsSession":"Session", "samlIDPMetaDataOptionsSession":"Session",
"samlIDPMetaDataOptionsSignature":"Signature", "samlIDPMetaDataOptionsSignature":"Signature",
"samlIDPMetaDataOptionsSignatureMethod":"Signature method",
"samlIDPMetaDataOptionsBinding":"Binding", "samlIDPMetaDataOptionsBinding":"Binding",
"samlIDPMetaDataOptionsDisplay":"Display", "samlIDPMetaDataOptionsDisplay":"Display",
"samlIDPMetaDataOptionsDisplayName":"Display name", "samlIDPMetaDataOptionsDisplayName":"Display name",
@ -1083,6 +1087,7 @@
"samlSPMetaDataOptionsEncryptionMode":"Encryption mode", "samlSPMetaDataOptionsEncryptionMode":"Encryption mode",
"samlSPMetaDataOptionsAuthnResponse":"Authentication response", "samlSPMetaDataOptionsAuthnResponse":"Authentication response",
"samlSPMetaDataOptionsSignature":"Signature", "samlSPMetaDataOptionsSignature":"Signature",
"samlSPMetaDataOptionsSignatureMethod":"Signature method",
"samlSPMetaDataOptionsSecurity":"Security", "samlSPMetaDataOptionsSecurity":"Security",
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Enable use of IDP initiated URL", "samlSPMetaDataOptionsEnableIDPInitiatedURL":"Enable use of IDP initiated URL",
"samlSPMetaDataOptionsNameIDSessionKey":"Force NameID session key", "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; use IO::String;
require 't/test-lib.pm'; 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; my $res;
ok( ok(
$res = &client->_post( $res = &client->_post(
@ -13,10 +23,8 @@ ok(
), ),
"Request succeed" "Request succeed"
); );
ok( $res->[0] == 200, "Result code is 200" ); count(1);
my $key; checkResult($res);
ok( $key = from_json( $res->[2]->[0] ), 'Response is JSON' );
count(3);
ok( ok(
$res = &client->_post( $res = &client->_post(
@ -25,8 +33,28 @@ ok(
), ),
"Request succeed" "Request succeed"
); );
ok( $res->[0] == 200, "Result code is 200" ); count(1);
ok( $key = from_json( $res->[2]->[0] ), 'Response is JSON' ); checkResult($res);
count(3);
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() ); 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/LDAP.pm
lib/Lemonldap/NG/Portal/Password/Null.pm lib/Lemonldap/NG/Portal/Password/Null.pm
lib/Lemonldap/NG/Portal/Password/REST.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/AutoSignin.pm
lib/Lemonldap/NG/Portal/Plugins/BruteForceProtection.pm lib/Lemonldap/NG/Portal/Plugins/BruteForceProtection.pm
lib/Lemonldap/NG/Portal/Plugins/CDA.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-Double-cookies-Refresh-and-Logout.t
t/59-Secured-cookie-Refresh-and-Logout.t t/59-Secured-cookie-Refresh-and-Logout.t
t/60-Status.t t/60-Status.t
t/61-AdaptativeAuthenticationLevel.t
t/61-BruteForceProtection-with-Incremental-lockTimes-and-TOTP.t t/61-BruteForceProtection-with-Incremental-lockTimes-and-TOTP.t
t/61-BruteForceProtection-with-Incremental-lockTimes.t t/61-BruteForceProtection-with-Incremental-lockTimes.t
t/61-BruteForceProtection.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-filtered-merge.t
t/68-Impersonation-with-History.t t/68-Impersonation-with-History.t
t/68-Impersonation-with-merge.t t/68-Impersonation-with-merge.t
t/68-Impersonation-with-SFA.t
t/68-Impersonation-with-TOTP.t t/68-Impersonation-with-TOTP.t
t/68-Impersonation-with-UnrestrictedUser.t t/68-Impersonation-with-UnrestrictedUser.t
t/68-Impersonation.t t/68-Impersonation.t

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -115,10 +115,7 @@ sub getUser {
my ( $self, $req, %args ) = @_; my ( $self, $req, %args ) = @_;
$self->validateLdap; $self->validateLdap;
return PE_LDAPCONNECTFAILED unless $self->ldap;
unless ( $self->ldap ) {
return PE_LDAPCONNECTFAILED;
}
$self->bind(); $self->bind();
@ -144,7 +141,10 @@ sub getUser {
return PE_BADCREDENTIALS; return PE_BADCREDENTIALS;
} }
unless ( $req->data->{ldapentry} = $mesg->entry(0) ) { 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) }; eval { $self->p->_authentication->setSecurity($req) };
return PE_BADCREDENTIALS; return PE_BADCREDENTIALS;
} }
@ -168,8 +168,8 @@ sub bind {
my $self = shift; my $self = shift;
$self->validateLdap; $self->validateLdap;
return undef unless $self->ldap;
return undef unless ( $self->ldap );
my $msg = $self->ldap->bind(@_); my $msg = $self->ldap->bind(@_);
if ( $msg->code ) { if ( $msg->code ) {
$self->logger->error( $msg->error ); $self->logger->error( $msg->error );

View File

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

View File

@ -308,6 +308,29 @@ sub loadIDPs {
} }
$self->logger->debug("Set encryption mode $encryption_mode on IDP $_"); $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 # Set display options
$self->idpList->{$entityID}->{displayName} = $self->idpList->{$entityID}->{displayName} =
$self->conf->{samlIDPMetaDataOptions}->{$_} $self->conf->{samlIDPMetaDataOptions}->{$_}
@ -414,6 +437,29 @@ sub loadSPs {
} }
$self->logger->debug("Set encryption mode $encryption_mode on SP $_"); $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}->{$_} my $rule = $self->conf->{samlSPMetaDataOptions}->{$_}
->{samlSPMetaDataOptionsRule}; ->{samlSPMetaDataOptionsRule};
if ( length $rule ) { if ( length $rule ) {
@ -3166,15 +3212,44 @@ sub getSignatureMethod {
eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA1'; eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA1';
my $signature_method_rsa_sha256 = my $signature_method_rsa_sha256 =
eval 'Lasso::Constants::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'; my $signature_method_none = eval 'Lasso::Constants::SIGNATURE_METHOD_NONE';
return $signature_method_rsa_sha1 return $signature_method_rsa_sha1
if ( $signature_method =~ /^RSA_SHA1$/i ); if ( $signature_method =~ /^RSA_SHA1$/i );
return $signature_method_rsa_sha256 return $signature_method_rsa_sha256
if ( $signature_method =~ /^RSA_SHA256$/i ); 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; 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; 1;
__END__ __END__
@ -3540,6 +3615,10 @@ Get query string with or without CGI query_string() method
Return Lasso signature method Return Lasso signature method
=head2 setProviderSignatureMethod
Set signature method on a provider
=head1 SEE ALSO =head1 SEE ALSO
L<Lemonldap::NG::Portal::Auth::SAML>, L<Lemonldap::NG::Portal::UserDBSAML> 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; 1;
__END__ __END__

View File

@ -32,8 +32,10 @@ our @pList = (
portalDisplayFavApps => '::Plugins::FavApps', portalDisplayFavApps => '::Plugins::FavApps',
contextSwitchingRule => '::Plugins::ContextSwitching', contextSwitchingRule => '::Plugins::ContextSwitching',
decryptValueRule => '::Plugins::DecryptValue', decryptValueRule => '::Plugins::DecryptValue',
globalLogoutRule => '::Plugins::GlobalLogout', adaptativeAuthenticationLevelRules =>
refreshSessions => '::Plugins::Refresh', '::Plugins::AdaptativeAuthenticationLevel',
globalLogoutRule => '::Plugins::GlobalLogout',
refreshSessions => '::Plugins::Refresh',
); );
##@method list enabledPlugins ##@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}, $self->module->searchOn( $moduleOptions, $self->conf->{whatToTrace},
$user ); $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 = @$activeSessions =
map { map {
my $epoch; my $epoch;

View File

@ -1,6 +1,6 @@
{ {
"PE-7":"Vous avez été déconnecté", "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é", "PE0":"Utilisateur authentifié",
"PE1":"Votre session a expiré, vous devez vous ré-authentifier", "PE1":"Votre session a expiré, vous devez vous ré-authentifier",
"PE2":"Identifiant ou mot de passe non renseigné", "PE2":"Identifiant ou mot de passe non renseigné",
@ -237,7 +237,7 @@
"proxyError": "Mauvaise passerelle : impossible de joindre le serveur amont", "proxyError": "Mauvaise passerelle : impossible de joindre le serveur amont",
"pwd":"Mot de passe", "pwd":"Mot de passe",
"pwdChange":"Changement de 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 ", "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 !", "pwdWillExpire":"%s jours, %s heures, %s minutes et %s secondes avant expiration de votre mot de passe, pensez à le changer !",
"radius2f":"Radius", "radius2f":"Radius",

View File

@ -1,6 +1,8 @@
use lib 'inc'; use lib 'inc';
use Test::More; use Test::More;
use strict; use strict;
use URI;
use URI::QueryParam;
use IO::String; use IO::String;
use LWP::UserAgent; use LWP::UserAgent;
use LWP::Protocol::PSGI; use LWP::Protocol::PSGI;
@ -11,7 +13,7 @@ BEGIN {
require 't/saml-lib.pm'; require 't/saml-lib.pm';
} }
my $maintests = 19; my $maintests = 21;
my $debug = 'error'; my $debug = 'error';
my ( $issuer, $sp, $res ); my ( $issuer, $sp, $res );
@ -152,6 +154,14 @@ SKIP: {
expectForm( $res, 'auth.sp.com', '/saml/proxySingleSignOnPost', expectForm( $res, 'auth.sp.com', '/saml/proxySingleSignOnPost',
'SAMLResponse', 'RelayState' ); '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 # Post SAML response to SP
switch ('sp'); switch ('sp');
ok( ok(
@ -190,6 +200,14 @@ SKIP: {
( $url, $query ) = expectRedirection( $res, ( $url, $query ) = expectRedirection( $res,
qr#^http://auth.idp.com(/saml/singleLogout)\?(SAMLRequest=.+)# ); 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 # Push SAML logout request to IdP
switch ('issuer'); switch ('issuer');
ok( ok(
@ -264,6 +282,7 @@ sub issuer {
samlSPMetaDataOptionsSignSLOMessage => 1, samlSPMetaDataOptionsSignSLOMessage => 1,
samlSPMetaDataOptionsCheckSSOMessageSignature => 1, samlSPMetaDataOptionsCheckSSOMessageSignature => 1,
samlSPMetaDataOptionsCheckSLOMessageSignature => 1, samlSPMetaDataOptionsCheckSLOMessageSignature => 1,
samlSPMetaDataOptionsSignatureMethod => "RSA_SHA256",
} }
}, },
samlSPMetaDataExportedAttributes => { samlSPMetaDataExportedAttributes => {
@ -281,6 +300,7 @@ sub issuer {
samlServicePrivateKeySig => saml_key_idp_private_sig, samlServicePrivateKeySig => saml_key_idp_private_sig,
samlServicePublicKeyEnc => saml_key_idp_public_enc, samlServicePublicKeyEnc => saml_key_idp_public_enc,
samlServicePublicKeySig => saml_key_idp_public_sig, samlServicePublicKeySig => saml_key_idp_public_sig,
samlServiceSignatureMethod => "RSA_SHA1",
samlSPMetaDataXML => { samlSPMetaDataXML => {
"sp.com" => { "sp.com" => {
samlSPMetaDataXML => samlSPMetaDataXML =>
@ -319,6 +339,7 @@ sub sp {
samlIDPMetaDataOptionsCheckSSOMessageSignature => 1, samlIDPMetaDataOptionsCheckSSOMessageSignature => 1,
samlIDPMetaDataOptionsCheckSLOMessageSignature => 1, samlIDPMetaDataOptionsCheckSLOMessageSignature => 1,
samlIDPMetaDataOptionsForceUTF8 => 1, samlIDPMetaDataOptionsForceUTF8 => 1,
samlIDPMetaDataOptionsSignatureMethod => "RSA_SHA384",
} }
}, },
samlIDPMetaDataExportedAttributes => { samlIDPMetaDataExportedAttributes => {
@ -340,6 +361,7 @@ sub sp {
samlServicePrivateKeyEnc => saml_key_sp_private_enc, samlServicePrivateKeyEnc => saml_key_sp_private_enc,
samlServicePrivateKeySig => saml_key_sp_private_sig, samlServicePrivateKeySig => saml_key_sp_private_sig,
samlServicePublicKeyEnc => saml_key_sp_public_enc, samlServicePublicKeyEnc => saml_key_sp_public_enc,
samlServiceSignatureMethod => "RSA_SHA1",
samlSPSSODescriptorAuthnRequestsSigned => 1, 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, requireToken => 0,
checkUser => 1, checkUser => 1,
impersonationRule => 1, impersonationRule => 1,
checkUserDisplayPersistentInfo => 0, checkUserDisplayPersistentInfo => 0,
checkUserDisplayEmptyValues => 0, checkUserDisplayEmptyValues => 0,
checkUserDisplayComputedSession => 1, checkUserDisplayComputedSession => 1,
impersonationMergeSSOgroups => 1, impersonationMergeSSOgroups => 1,
totp2fSelfRegistration => 1, totp2fSelfRegistration => 1,
totp2fActivation => 1, totp2fActivation => 1,
totp2fAuthnLevel => 8 totp2fAuthnLevel => 8
} }
} }
); );

View File

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

View File

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