Merge branch 'v2.0' into master
This commit is contained in:
commit
4583f3a9e6
|
@ -32,7 +32,7 @@ server {
|
|||
#real_ip_header X-Forwarded-For;
|
||||
|
||||
location = /reload {
|
||||
allow 127.0.0.1/8;
|
||||
allow 127.0.0.0/8;
|
||||
allow ::1/128;
|
||||
deny all;
|
||||
|
||||
|
|
|
@ -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``
|
||||
|
|
@ -95,6 +95,20 @@ configuration file:
|
|||
proxy_pass http://127.0.0.1:8888/login;
|
||||
}
|
||||
|
||||
|
||||
.. warning::
|
||||
|
||||
Thoses 2 blocks should be append BEFORE the following block::
|
||||
|
||||
#Anything that didn't match above, and isn't a real file,
|
||||
#assume it's a room name and redirect to /
|
||||
location ~ ^/([^/?&:'"]+)/(.*)$ {
|
||||
set $subdomain "$1.";
|
||||
set $subdir "$1/";
|
||||
rewrite ^/([^/?&:'"]+)/(.*)$ /$2;
|
||||
}
|
||||
|
||||
|
||||
Jitsi Meet Virtual host in Manager
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -175,6 +175,7 @@ Signature
|
|||
These options override service signature options (see
|
||||
:ref:`SAML service configuration<samlservice-general-options>`).
|
||||
|
||||
- **Signature method**: signature method for requests sent to this provider
|
||||
- **Sign SSO message**: sign SSO message
|
||||
- **Check SSO message signature**: check SSO message signature
|
||||
- **Sign SLO message**: sign SLO message
|
||||
|
|
|
@ -6,6 +6,9 @@ Your network infrastructure might require that LemonLDAP::NG components
|
|||
|
||||
|image0|
|
||||
|
||||
Transmitting the correct IP address to the portal
|
||||
-------------------------------------------------
|
||||
|
||||
In this case, LemonLDAP::NG components will store the ip address of the
|
||||
connection between the reverse proxy and the webserver in the session,
|
||||
and in logs. This prevents features such as session restrictions and
|
||||
|
@ -19,7 +22,7 @@ to forward the original IP address all the way to LemonLDAP::NG.
|
|||
In order to do this you have several options.
|
||||
|
||||
HTTP Header
|
||||
-----------
|
||||
~~~~~~~~~~~
|
||||
|
||||
This generic method is the most likely to work in your particular
|
||||
environment.
|
||||
|
@ -68,7 +71,7 @@ uncomment the relevant parts of the configuration file.
|
|||
module documentation carefully.
|
||||
|
||||
PROXY Protocol
|
||||
--------------
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Alternatively, if your proxy supports the PROXY protocol (Nginx,
|
||||
HAProxy, Amazon ELB), you may use it to carry over the information
|
||||
|
@ -86,6 +89,41 @@ Portal/Manager/Handler:
|
|||
# or
|
||||
# listen 443 ssl proxy_protocol;
|
||||
|
||||
set_real_ip_from 127.0.0.1;
|
||||
real_ip_header proxy_protocol;
|
||||
|
||||
.. |image0| image:: /documentation/reverseproxy.png
|
||||
:class: align-center
|
||||
|
||||
|
||||
Fixing handler redirections
|
||||
---------------------------
|
||||
|
||||
If your handler server runs behind a reverse proxy, it may have trouble figuring
|
||||
out the right URL to redirect you to after logging in.
|
||||
|
||||
In this case, you can force a particular port and scheme in the Virtual Host's
|
||||
Options.
|
||||
|
||||
But is instead you want this scheme to be auto-detected by LemonLDAP (in order to have a
|
||||
same VHost domain available over multiple schemes), you can also use the following
|
||||
declarations in the handler's virtual host to force LemonLDAP to use the correct port
|
||||
and scheme
|
||||
|
||||
Nginx
|
||||
~~~~~
|
||||
|
||||
::
|
||||
|
||||
fastcgi_param SERVER_PORT 443
|
||||
fastcgi_param HTTPS On
|
||||
|
||||
Apache
|
||||
~~~~~~
|
||||
|
||||
.. versionadded: 2.0.10
|
||||
|
||||
::
|
||||
|
||||
PerlSetEnv SERVER_PORT 443
|
||||
PerlSetEnv HTTPS On
|
||||
|
|
|
@ -27,7 +27,7 @@ Red Hat/CentOS
|
|||
|
||||
::
|
||||
|
||||
yum install lemonldap-ng-fastcgi-server
|
||||
yum install lemonldap-ng-nginx lemonldap-ng-fastcgi-server
|
||||
|
||||
Enable and start the service :
|
||||
|
||||
|
@ -40,25 +40,25 @@ Files
|
|||
-----
|
||||
|
||||
With tarball installation, Nginx configuration files will be installed
|
||||
in ``/usr/local/lemonldap-ng/etc/``, else they are in
|
||||
``/etc/lemonldap-ng``.
|
||||
|
||||
You have to include them in Nginx main configuration.
|
||||
in ``/usr/local/lemonldap-ng/etc/``, else they are directly in web server
|
||||
configuration.
|
||||
|
||||
.. _debianubuntu-1:
|
||||
|
||||
Debian/Ubuntu
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Link files into ``sites-available`` directory (should already have been
|
||||
done if you used packages):
|
||||
- Install log format *(automatically loaded when linked in this place)*
|
||||
|
||||
::
|
||||
|
||||
ln -s /etc/lemonldap-ng/handler-nginx.conf /etc/nginx/sites-available/
|
||||
ln -s /etc/lemonldap-ng/manager-nginx.conf /etc/nginx/sites-available/
|
||||
ln -s /etc/lemonldap-ng/portal-nginx.conf /etc/nginx/sites-available/
|
||||
ln -s /etc/lemonldap-ng/test-nginx.conf /etc/nginx/sites-available/
|
||||
ln -s /etc/lemonldap-ng/nginx-lmlog.conf /etc/nginx/conf.d/llng-lmlog.conf
|
||||
|
||||
- Install snippet for vhost configuration files:
|
||||
|
||||
::
|
||||
|
||||
ln -s /etc/lemonldap-ng/nginx-lua-headers.conf /etc/nginx/snippets/llng-lua-headers.conf
|
||||
|
||||
- Enable sites:
|
||||
|
||||
|
@ -68,18 +68,3 @@ done if you used packages):
|
|||
ln -s /etc/nginx/sites-available/manager-nginx.conf /etc/nginx/sites-enabled/
|
||||
ln -s /etc/nginx/sites-available/portal-nginx.conf /etc/nginx/sites-enabled/
|
||||
ln -s /etc/nginx/sites-available/test-nginx.conf /etc/nginx/sites-enabled/
|
||||
|
||||
.. _red-hatcentos-1:
|
||||
|
||||
Red Hat/CentOS
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Link files directly in ``conf.d`` directory:
|
||||
|
||||
::
|
||||
|
||||
ln -s /etc/lemonldap-ng/handler-nginx.conf /etc/nginx/conf.d/
|
||||
ln -s /etc/lemonldap-ng/manager-nginx.conf /etc/nginx/conf.d/
|
||||
ln -s /etc/lemonldap-ng/portal-nginx.conf /etc/nginx/conf.d/
|
||||
ln -s /etc/lemonldap-ng/test-nginx.conf /etc/nginx/conf.d/
|
||||
|
||||
|
|
|
@ -261,7 +261,7 @@ Example of a protected virtual host for a local application:
|
|||
Reverse proxy
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
\* Example of a protected reverse-proxy:
|
||||
- Example of a protected reverse-proxy:
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
|
@ -300,7 +300,7 @@ Reverse proxy
|
|||
# PASSING HEADERS TO APPLICATION #
|
||||
##################################
|
||||
# IF LUA IS SUPPORTED
|
||||
#include /path/to/nginx-lua-headers.conf
|
||||
#include /path/to/nginx-lua-headers.conf;
|
||||
|
||||
# ELSE
|
||||
# Set manually your headers
|
||||
|
@ -309,8 +309,16 @@ Reverse proxy
|
|||
}
|
||||
}
|
||||
|
||||
\* Example of a Nginx Virtual Host using uWSGI with many URIs protected
|
||||
by different types of handler :
|
||||
If /etc/nginx/proxy_params file does not exist, you can create it with this content:
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
- Example of a Nginx Virtual Host using uWSGI with many URIs protected by different types of handler:
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@ can be forbidden to assume.
|
|||
|
||||
- **Parameters**:
|
||||
|
||||
- **Use rule**: Select which users may use this plugin
|
||||
- **Use rule**: Rule to enable or define which users may use this plugin
|
||||
(By example: $uid eq 'dwho' && $authenticationLevel > 2).
|
||||
- **Identities use rule**: Rule to define which identities can be
|
||||
assumed. Useful to prevent impersonation of certain sensitive
|
||||
identities like CEO, administrators or anonymous/protected users.
|
||||
|
@ -30,7 +31,7 @@ can be forbidden to assume.
|
|||
During context switching authentication process, all
|
||||
plugins are disabled. In other words, all entry points like afterData,
|
||||
endAuth and so on are skipped. Therefore, second factors or
|
||||
notifications by example will not be prompted!
|
||||
notifications by example will not be prompted and login history is not updated!
|
||||
|
||||
|
||||
.. attention::
|
||||
|
@ -39,6 +40,11 @@ can be forbidden to assume.
|
|||
backend. You can not switch context with federated authentication.
|
||||
|
||||
|
||||
.. attention::
|
||||
|
||||
Used identity, start and end of switching context process are logged!
|
||||
|
||||
|
||||
contextSwitchingPrefix is used to store real user's session Id. You can
|
||||
set this prefix ('switching' by default) by editing ``lemonldap-ng.ini``
|
||||
in [portal] section:
|
||||
|
|
|
@ -154,6 +154,7 @@ Signature
|
|||
These options override service signature options (see
|
||||
:ref:`SAML service configuration<samlservice-general-options>`).
|
||||
|
||||
- **Signature method**: signature method for messages sent to this service
|
||||
- **Sign SSO message**: sign SSO message
|
||||
- **Check SSO message signature**: check SSO message signature
|
||||
- **Sign SLO message**: sign SLO message
|
||||
|
|
|
@ -4,6 +4,7 @@ Plugins
|
|||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
adaptativeauthenticationlevel
|
||||
autosignin
|
||||
bruteforceprotection
|
||||
cda
|
||||
|
|
|
@ -6,8 +6,8 @@ Presentation
|
|||
|
||||
Main settings:
|
||||
|
||||
- **REMOTE_USER** : session attribute used for logging user access.
|
||||
- **REMOTE_CUSTOM** : can be used for logging a second user attribute
|
||||
- **REMOTE_USER** : session attribute used for logging user access
|
||||
- **REMOTE_CUSTOM** : can be used for logging an another user attribute or a macro
|
||||
(optional)
|
||||
- **Hidden attributes** : session attributes never displayed or sent
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -63,3 +63,13 @@ Mail second factor".
|
|||
- **Logo** (Optional): logo file *(in static/<skin> directory)*
|
||||
- **Label** (Optional): label that should be displayed to the user on
|
||||
the choice screen
|
||||
|
||||
Vendor specific
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Some configuration examples for specific vendors:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
radius2f-inwebo
|
||||
|
|
|
@ -121,8 +121,12 @@ encryption.
|
|||
To define keys, you can:
|
||||
|
||||
- import your own private and public keys (``Replace by file`` input)
|
||||
- generate new public and private keys (``New keys`` button)
|
||||
- generate new public and private keys (``New certificate`` button)
|
||||
|
||||
.. versionchanged:: 2.0.10
|
||||
|
||||
A X.509 certificate is now generated instead of a plain public key. It has
|
||||
20 years of validity, and is self signed with the 2048bit RSA key.
|
||||
|
||||
.. tip::
|
||||
|
||||
|
@ -132,34 +136,35 @@ To define keys, you can:
|
|||
|
||||
|image1|
|
||||
|
||||
You can import a certificate containing the public key instead the raw
|
||||
public key. However, certificate will not be really validated by other
|
||||
SAML components (expiration date, common name, etc.), but will just be a
|
||||
public key wrapper.
|
||||
|
||||
|
||||
.. tip::
|
||||
|
||||
You can easily generate a certificate to replace your public
|
||||
key by saving the private key in a file, and use ``openssl`` commands to
|
||||
issue a self-signed certificate:
|
||||
|
||||
::
|
||||
|
||||
$ openssl req -new -key private.key -out cert.pem -x509 -days 3650
|
||||
|
||||
|
||||
|
||||
- **Use certificate in response**: Certificate will be sent inside SAML
|
||||
responses.
|
||||
- **Signature method**: set the signature algorithm
|
||||
|
||||
.. versionchanged:: 2.0.10
|
||||
|
||||
The signature method can now be overriden for a SP or IDP. This will only work
|
||||
if you are using a certificate for signature instead of a public key.
|
||||
|
||||
|
||||
.. attention::
|
||||
|
||||
Default value is RSA SHA1 for compatibility purpose but
|
||||
we recommend to use RSA SHA256. This requires to test all partners to
|
||||
check their compatibility.
|
||||
If you are running a version under 2.0.10, the choice of a signature
|
||||
algorithm will affect all SP and IDP.
|
||||
|
||||
Converting a RSA public key to a certificate
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If your application complains about the lack of certificate in SAML Metadatas, and you generated a public RSA key instead of a certificate in a previous version of LemonLDAP::NG, you can convert the public key into a certificate without changing the private key.
|
||||
|
||||
Save the private key in a file, and use the ``openssl`` commands to
|
||||
issue a self-signed certificate:
|
||||
|
||||
::
|
||||
|
||||
$ openssl req -new -key private.key -out cert.pem -x509 -days 3650
|
||||
|
||||
|
||||
NameID formats
|
||||
~~~~~~~~~~~~~~
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Plack app to use with Nginx
|
||||
#
|
||||
# This app can remplace FastCGI server using Starman, Twiggy, uWSGI-Plugin-PSGI,
|
||||
# This app can replace FastCGI server using Starman, Twiggy, uWSGI-Plugin-PSGI,
|
||||
# Feersum,...
|
||||
|
||||
my %builder = (
|
||||
|
|
|
@ -29,8 +29,8 @@ use constant DEFAULTCONFBACKEND => "File";
|
|||
use constant DEFAULTCONFBACKENDOPTIONS => (
|
||||
dirName => '/usr/local/lemonldap-ng/data/conf',
|
||||
);
|
||||
our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|macro)s|o(?:idc(?:S(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar|Macro)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option|Macro)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|c(?:as(?:A(?:ppMetaData(?:(?:ExportedVar|Option|Macro)s|Node)|ttributes)|S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions))|(?:ustom(?:Plugins|Add)Param|ombModule)s)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/;
|
||||
our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|f(?:RemovedUseNotif|OnlyUpgrade)|kip(?:Upgrade|Renew)Confirmation|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:Allow(?:PasswordGrant|Offline)|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|o(?:ntextSwitchingStopWithLogout|mpactConf|rsEnabled)|heck(?:State|User|XSS)|da)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|re(?:st(?:(?:Password|Session|Config|Auth)Server|ExportSecretKeys)|freshSessions)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|g(?:roupsBeforeMacros|lobalLogoutTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs))$/;
|
||||
our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|macro)s|o(?:idc(?:S(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar|Macro)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option|Macro)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|c(?:as(?:A(?:ppMetaData(?:(?:ExportedVar|Option|Macro)s|Node)|ttributes)|S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions))|(?:ustom(?:Plugins|Add)Param|ombModule)s)|a(?:(?:daptativeAuthenticationLevelR|ut(?:hChoiceMod|oSigninR))ules|pplicationList)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/;
|
||||
our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|f(?:RemovedUseNotif|OnlyUpgrade)|kip(?:Upgrade|Renew)Confirmation|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:Allow(?:PasswordGrant|Offline)|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|ForceAuthn|AntiFrame)|roxyUseSoap)|c(?:o(?:ntextSwitching(?:Allowed2fModifications|StopWithLogout)|mpactConf|rsEnabled)|a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:State|User|XSS)|da)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|re(?:st(?:(?:Password|Session|Config|Auth)Server|ExportSecretKeys)|freshSessions)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|g(?:roupsBeforeMacros|lobalLogoutTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs))$/;
|
||||
|
||||
our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' );
|
||||
|
||||
|
|
|
@ -312,7 +312,7 @@ sub defaultValues {
|
|||
'samlOrganizationURL' => 'http://www.example.com',
|
||||
'samlOverrideIDPEntityID' => '',
|
||||
'samlRelayStateTimeout' => 600,
|
||||
'samlServiceSignatureMethod' => 'RSA_SHA1',
|
||||
'samlServiceSignatureMethod' => 'RSA_SHA256',
|
||||
'samlSPSSODescriptorArtifactResolutionServiceArtifact' =>
|
||||
'1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;#PORTAL#/saml/artifact',
|
||||
'samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact' =>
|
||||
|
|
|
@ -22,14 +22,14 @@ our $specialNodeHash = {
|
|||
};
|
||||
|
||||
our $doubleHashKeys = 'issuerDBGetParameters';
|
||||
our $simpleHashKeys = '(?:(?:l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|c(?:as(?:StorageOption|Attribute)|ustom(?:Plugins|Add)Param|ombModule)|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|(?:(?:d(?:emo|bi)|facebook|webID)E|e)xportedVar|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|p(?:ersistentStorageOption|ortalSkinRule)|macro)s|o(?:idcS(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|penIdExportedVars)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember|fExtra)|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|S(?:MTPTLSOpts|SLVarIf))';
|
||||
our $simpleHashKeys = '(?:(?:l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|c(?:as(?:StorageOption|Attribute)|ustom(?:Plugins|Add)Param|ombModule)|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|(?:(?:d(?:emo|bi)|facebook|webID)E|e)xportedVar|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|p(?:ersistentStorageOption|ortalSkinRule)|macro)s|o(?:idcS(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|penIdExportedVars)|a(?:(?:daptativeAuthenticationLevelR|ut(?:hChoiceMod|oSigninR))ules|pplicationList)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember|fExtra)|S(?:MTPTLSOpts|SLVarIf))';
|
||||
our $specialNodeKeys = '(?:(?:(?:saml(?:ID|S)|oidc[OR])P|cas(?:App|Srv))MetaDataNode|virtualHost)s';
|
||||
our $casAppMetaDataNodeKeys = 'casAppMetaData(?:Options(?:(?:UserAttribut|Servic|Rul)e|AuthnLevel)|(?:ExportedVar|Macro)s)';
|
||||
our $casSrvMetaDataNodeKeys = 'casSrvMetaData(?:Options(?:ProxiedServices|DisplayName|SortNumber|Gateway|Renew|Icon|Url)|ExportedVars)';
|
||||
our $oidcOPMetaDataNodeKeys = 'oidcOPMetaData(?:Options(?:C(?:lient(?:Secret|ID)|heckJWTSignature|onfigurationURI)|S(?:toreIDToken|ortNumber|cope)|TokenEndpointAuthMethod|(?:JWKSTimeou|Promp)t|I(?:DTokenMaxAge|con)|U(?:iLocales|seNonce)|Display(?:Name)?|AcrValues|MaxAge)|ExportedVars|J(?:SON|WKS))';
|
||||
our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:A(?:uth(?:orizationCodeExpiration|nLevel)|llow(?:PasswordGrant|Offline)|ccessTokenExpiration|dditionalAudiences)|I(?:DToken(?:ForceClaims|Expiration|SignAlg)|con)|R(?:e(?:directUris|freshToken|quirePKCE)|ule)|Logout(?:SessionRequired|Type|Url)|P(?:ostLogoutRedirectUris|ublic)|OfflineSessionExpiration|Client(?:Secret|ID)|BypassConsent|DisplayName|ExtraClaims|UserIDAttr)|(?:ExportedVar|Macro)s)';
|
||||
our $samlIDPMetaDataNodeKeys = 'samlIDPMetaData(?:Options(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|EncryptionMod|UserAttribut|DisplayNam)e|S(?:ignS[LS]OMessage|toreSAMLToken|[LS]OBinding|ortNumber)|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Re(?:questedAuthnContext|solutionRule|layStateURL)|Force(?:Authn|UTF8)|I(?:sPassive|con)|NameIDFormat)|ExportedAttributes|XML)';
|
||||
our $samlSPMetaDataNodeKeys = 'samlSPMetaData(?:Options(?:N(?:ameID(?:SessionKey|Format)|otOnOrAfterTimeout)|S(?:essionNotOnOrAfterTimeout|ignS[LS]OMessage)|(?:CheckS[LS]OMessageSignatur|OneTimeUs|Rul)e|En(?:ableIDPInitiatedURL|cryptionMode)|AuthnLevel|ForceUTF8)|(?:ExportedAttribute|Macro)s|XML)';
|
||||
our $samlIDPMetaDataNodeKeys = 'samlIDPMetaData(?:Options(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|EncryptionMod|UserAttribut|DisplayNam)e|S(?:ign(?:S[LS]OMessage|atureMethod)|toreSAMLToken|[LS]OBinding|ortNumber)|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Re(?:questedAuthnContext|solutionRule|layStateURL)|Force(?:Authn|UTF8)|I(?:sPassive|con)|NameIDFormat)|ExportedAttributes|XML)';
|
||||
our $samlSPMetaDataNodeKeys = 'samlSPMetaData(?:Options(?:S(?:ign(?:S[LS]OMessage|atureMethod)|essionNotOnOrAfterTimeout)|N(?:ameID(?:SessionKey|Format)|otOnOrAfterTimeout)|(?:CheckS[LS]OMessageSignatur|OneTimeUs|Rul)e|En(?:ableIDPInitiatedURL|cryptionMode)|AuthnLevel|ForceUTF8)|(?:ExportedAttribute|Macro)s|XML)';
|
||||
our $virtualHostKeys = '(?:vhost(?:A(?:uthnLevel|liases)|(?:Maintenanc|Typ)e|ServiceTokenTTL|Https|Port)|(?:exportedHeader|locationRule)s|post)';
|
||||
|
||||
our $authParameters = {
|
||||
|
|
|
@ -25,7 +25,7 @@ sub new {
|
|||
QUERY_STRING => $args,
|
||||
REQUEST_URI => $uri_full,
|
||||
PATH_INFO => '',
|
||||
SERVER_PORT => $r->get_server_port,
|
||||
SERVER_PORT => $ENV{SERVER_PORT} || $r->get_server_port,
|
||||
REQUEST_METHOD => $r->method,
|
||||
'psgi.version' => [ 1, 1 ],
|
||||
'psgi.url_scheme' => ( $ENV{HTTPS} || 'off' ) =~ /^(?:on|1)$/i
|
||||
|
|
|
@ -614,7 +614,7 @@ sub _getPort {
|
|||
return $class->tsv->{port}->{_};
|
||||
}
|
||||
else {
|
||||
return $req->{env}->{SERVER_PORT};
|
||||
return $req->port;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -638,7 +638,7 @@ sub _isHttps {
|
|||
return $class->tsv->{https}->{_};
|
||||
}
|
||||
else {
|
||||
return ( uc( $req->{env}->{HTTPS} || "OFF" ) eq "ON" );
|
||||
return $req->secure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,7 +139,7 @@ site/htdocs/static/forms/post.html
|
|||
site/htdocs/static/forms/postContainer.html
|
||||
site/htdocs/static/forms/README.md
|
||||
site/htdocs/static/forms/restore.html
|
||||
site/htdocs/static/forms/RSAKey.html
|
||||
site/htdocs/static/forms/RSACertKey.html
|
||||
site/htdocs/static/forms/RSAKeyNoPassword.html
|
||||
site/htdocs/static/forms/rule.html
|
||||
site/htdocs/static/forms/ruleContainer.html
|
||||
|
|
|
@ -171,7 +171,7 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
|
|||
'RSAPrivateKey' => {
|
||||
'test' => sub {
|
||||
return $_[0] =~
|
||||
m[^(?:(?:\-+\s*BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$]s
|
||||
m[^(?:(?:\-+\s*BEGIN\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$]s
|
||||
? 1
|
||||
: ( 1, '__badPemEncoding__' );
|
||||
}
|
||||
|
@ -263,6 +263,18 @@ sub attributes {
|
|||
'default' => 1,
|
||||
'type' => 'bool'
|
||||
},
|
||||
'adaptativeAuthenticationLevelRules' => {
|
||||
'keyMsgFail' => '__badRegexp__',
|
||||
'keyTest' => sub {
|
||||
eval {
|
||||
do {
|
||||
qr/$_[0]/;
|
||||
}
|
||||
};
|
||||
return $@ ? 0 : 1;
|
||||
},
|
||||
'type' => 'keyTextContainer'
|
||||
},
|
||||
'ADPwdExpireWarning' => {
|
||||
'default' => 0,
|
||||
'type' => 'int'
|
||||
|
@ -1026,6 +1038,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
|
|||
],
|
||||
'type' => 'select'
|
||||
},
|
||||
'contextSwitchingAllowed2fModifications' => {
|
||||
'default' => 0,
|
||||
'type' => 'bool'
|
||||
},
|
||||
'contextSwitchingIdRule' => {
|
||||
'default' => 1,
|
||||
'test' => sub {
|
||||
|
@ -3203,6 +3219,31 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
|
|||
'default' => '',
|
||||
'type' => 'longtext'
|
||||
},
|
||||
'samlIDPMetaDataOptionsSignatureMethod' => {
|
||||
'default' => '',
|
||||
'select' => [ {
|
||||
'k' => '',
|
||||
'v' => 'default'
|
||||
},
|
||||
{
|
||||
'k' => 'RSA_SHA1',
|
||||
'v' => 'RSA SHA1'
|
||||
},
|
||||
{
|
||||
'k' => 'RSA_SHA256',
|
||||
'v' => 'RSA SHA256'
|
||||
},
|
||||
{
|
||||
'k' => 'RSA_SHA384',
|
||||
'v' => 'RSA SHA384'
|
||||
},
|
||||
{
|
||||
'k' => 'RSA_SHA512',
|
||||
'v' => 'RSA SHA512'
|
||||
}
|
||||
],
|
||||
'type' => 'select'
|
||||
},
|
||||
'samlIDPMetaDataOptionsSignSLOMessage' => {
|
||||
'default' => -1,
|
||||
'type' => 'trool'
|
||||
|
@ -3394,7 +3435,7 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
|
|||
'type' => 'RSAPublicKeyOrCertificate'
|
||||
},
|
||||
'samlServiceSignatureMethod' => {
|
||||
'default' => 'RSA_SHA1',
|
||||
'default' => 'RSA_SHA256',
|
||||
'select' => [ {
|
||||
'k' => 'RSA_SHA1',
|
||||
'v' => 'RSA SHA1'
|
||||
|
@ -3402,6 +3443,14 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
|
|||
{
|
||||
'k' => 'RSA_SHA256',
|
||||
'v' => 'RSA SHA256'
|
||||
},
|
||||
{
|
||||
'k' => 'RSA_SHA384',
|
||||
'v' => 'RSA SHA384'
|
||||
},
|
||||
{
|
||||
'k' => 'RSA_SHA512',
|
||||
'v' => 'RSA SHA512'
|
||||
}
|
||||
],
|
||||
'type' => 'select'
|
||||
|
@ -3539,6 +3588,31 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
|
|||
'default' => 72000,
|
||||
'type' => 'int'
|
||||
},
|
||||
'samlSPMetaDataOptionsSignatureMethod' => {
|
||||
'default' => '',
|
||||
'select' => [ {
|
||||
'k' => '',
|
||||
'v' => 'default'
|
||||
},
|
||||
{
|
||||
'k' => 'RSA_SHA1',
|
||||
'v' => 'RSA SHA1'
|
||||
},
|
||||
{
|
||||
'k' => 'RSA_SHA256',
|
||||
'v' => 'RSA SHA256'
|
||||
},
|
||||
{
|
||||
'k' => 'RSA_SHA384',
|
||||
'v' => 'RSA SHA384'
|
||||
},
|
||||
{
|
||||
'k' => 'RSA_SHA512',
|
||||
'v' => 'RSA SHA512'
|
||||
}
|
||||
],
|
||||
'type' => 'select'
|
||||
},
|
||||
'samlSPMetaDataOptionsSignSLOMessage' => {
|
||||
'default' => -1,
|
||||
'type' => 'trool'
|
||||
|
|
|
@ -154,7 +154,7 @@ sub types {
|
|||
test => sub {
|
||||
return (
|
||||
$_[0] =~
|
||||
/^(?:(?:\-+\s*BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$/s
|
||||
/^(?:(?:\-+\s*BEGIN\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$/s
|
||||
? (1)
|
||||
: ( 1, '__badPemEncoding__' )
|
||||
);
|
||||
|
@ -594,6 +594,12 @@ sub attributes {
|
|||
documentation => 'Stop context switching by logout',
|
||||
flags => 'p',
|
||||
},
|
||||
contextSwitchingAllowed2fModifications => {
|
||||
type => 'bool',
|
||||
default => 0,
|
||||
documentation => 'Allowed SFA modifications',
|
||||
flags => 'p',
|
||||
},
|
||||
contextSwitchingPrefix => {
|
||||
type => 'text',
|
||||
default => 'switching',
|
||||
|
@ -2119,6 +2125,18 @@ sub attributes {
|
|||
documentation => 'List of auto signin rules',
|
||||
},
|
||||
|
||||
# Adaptative Authentication Level plugin
|
||||
adaptativeAuthenticationLevelRules => {
|
||||
type => 'keyTextContainer',
|
||||
keyTest => sub {
|
||||
eval { qr/$_[0]/ };
|
||||
return $@ ? 0 : 1;
|
||||
},
|
||||
keyMsgFail => '__badRegexp__',
|
||||
documentation => 'Adaptative authentication level rules',
|
||||
flags => 'p',
|
||||
},
|
||||
|
||||
## Virtualhosts
|
||||
|
||||
# Fake attribute: used by manager REST API to agglomerate all other
|
||||
|
@ -2490,8 +2508,10 @@ sub attributes {
|
|||
select => [
|
||||
{ k => 'RSA_SHA1', v => 'RSA SHA1' },
|
||||
{ k => 'RSA_SHA256', v => 'RSA SHA256' },
|
||||
{ k => 'RSA_SHA384', v => 'RSA SHA384' },
|
||||
{ k => 'RSA_SHA512', v => 'RSA SHA512' },
|
||||
],
|
||||
default => 'RSA_SHA1',
|
||||
default => 'RSA_SHA256',
|
||||
},
|
||||
samlServiceUseCertificateInResponse => {
|
||||
type => 'bool',
|
||||
|
@ -2764,6 +2784,17 @@ sub attributes {
|
|||
type => 'trool',
|
||||
default => -1,
|
||||
},
|
||||
samlIDPMetaDataOptionsSignatureMethod => {
|
||||
type => 'select',
|
||||
select => [
|
||||
{ k => '', v => 'default' },
|
||||
{ k => 'RSA_SHA1', v => 'RSA SHA1' },
|
||||
{ k => 'RSA_SHA256', v => 'RSA SHA256' },
|
||||
{ k => 'RSA_SHA384', v => 'RSA SHA384' },
|
||||
{ k => 'RSA_SHA512', v => 'RSA SHA512' },
|
||||
],
|
||||
default => '',
|
||||
},
|
||||
samlIDPMetaDataOptionsCheckSLOMessageSignature => {
|
||||
type => 'bool',
|
||||
default => 1,
|
||||
|
@ -2920,6 +2951,17 @@ sub attributes {
|
|||
type => 'trool',
|
||||
default => -1,
|
||||
},
|
||||
samlSPMetaDataOptionsSignatureMethod => {
|
||||
type => 'select',
|
||||
select => [
|
||||
{ k => '', v => 'default' },
|
||||
{ k => 'RSA_SHA1', v => 'RSA SHA1' },
|
||||
{ k => 'RSA_SHA256', v => 'RSA SHA256' },
|
||||
{ k => 'RSA_SHA384', v => 'RSA SHA384' },
|
||||
{ k => 'RSA_SHA512', v => 'RSA SHA512' },
|
||||
],
|
||||
default => '',
|
||||
},
|
||||
samlSPMetaDataOptionsCheckSSOMessageSignature => {
|
||||
type => 'bool',
|
||||
default => 1,
|
||||
|
|
|
@ -52,6 +52,7 @@ sub cTrees {
|
|||
title => "samlIDPMetaDataOptionsSignature",
|
||||
form => 'simpleInputContainer',
|
||||
nodes => [
|
||||
"samlIDPMetaDataOptionsSignatureMethod",
|
||||
"samlIDPMetaDataOptionsSignSSOMessage",
|
||||
"samlIDPMetaDataOptionsCheckSSOMessageSignature",
|
||||
"samlIDPMetaDataOptionsSignSLOMessage",
|
||||
|
@ -122,10 +123,11 @@ sub cTrees {
|
|||
title => "samlSPMetaDataOptionsSignature",
|
||||
form => 'simpleInputContainer',
|
||||
nodes => [
|
||||
"samlSPMetaDataOptionsSignatureMethod",
|
||||
"samlSPMetaDataOptionsSignSSOMessage",
|
||||
"samlSPMetaDataOptionsCheckSSOMessageSignature",
|
||||
"samlSPMetaDataOptionsSignSLOMessage",
|
||||
"samlSPMetaDataOptionsCheckSLOMessageSignature"
|
||||
"samlSPMetaDataOptionsCheckSLOMessageSignature",
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -615,6 +615,7 @@ sub tree {
|
|||
nodes => [
|
||||
'stayConnected',
|
||||
'portalStatus',
|
||||
'adaptativeAuthenticationLevelRules',
|
||||
'upgradeSession',
|
||||
'refreshSessions',
|
||||
{
|
||||
|
@ -792,6 +793,7 @@ sub tree {
|
|||
'contextSwitchingRule',
|
||||
'contextSwitchingIdRule',
|
||||
'contextSwitchingUnrestrictedUsersRule',
|
||||
'contextSwitchingAllowed2fModifications',
|
||||
'contextSwitchingStopWithLogout',
|
||||
]
|
||||
},
|
||||
|
@ -812,7 +814,11 @@ sub tree {
|
|||
{
|
||||
title => 'secondFactors',
|
||||
help => 'secondfactor.html',
|
||||
nodes => [ {
|
||||
nodes => [
|
||||
'sfManagerRule',
|
||||
'sfRequired',
|
||||
'sfOnlyUpgrade',
|
||||
{
|
||||
title => 'utotp2f',
|
||||
help => 'utotp2f.html',
|
||||
form => 'simpleInputContainer',
|
||||
|
@ -930,9 +936,6 @@ sub tree {
|
|||
'sfRemovedNotifMsg',
|
||||
],
|
||||
},
|
||||
'sfOnlyUpgrade',
|
||||
'sfManagerRule',
|
||||
'sfRequired',
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -1074,7 +1077,7 @@ sub tree {
|
|||
help => 'samlservice.html#security-parameters',
|
||||
nodes => [ {
|
||||
title => 'samlServiceSecuritySig',
|
||||
form => 'RSAKey',
|
||||
form => 'RSACertKey',
|
||||
group => [
|
||||
'samlServicePrivateKeySig',
|
||||
'samlServicePrivateKeySigPwd',
|
||||
|
@ -1083,7 +1086,7 @@ sub tree {
|
|||
},
|
||||
{
|
||||
title => 'samlServiceSecurityEnc',
|
||||
form => 'RSAKey',
|
||||
form => 'RSACertKey',
|
||||
group => [
|
||||
'samlServicePrivateKeyEnc',
|
||||
'samlServicePrivateKeyEncPwd',
|
||||
|
|
|
@ -431,7 +431,6 @@ sub run {
|
|||
unless (@_) {
|
||||
die 'nothing to do, aborting';
|
||||
}
|
||||
$self->cfgNum( $self->lastCfg ) unless ( $self->cfgNum );
|
||||
my $action = shift;
|
||||
unless (
|
||||
$action =~ /^(?:get|set|del|addKey|delKey|save|restore|rollback)$/ )
|
||||
|
@ -440,6 +439,12 @@ sub run {
|
|||
"Unknown action $action. Only get, set, del, addKey, delKey, save, restore, rollback allowed";
|
||||
}
|
||||
|
||||
unless ( $action eq "restore" ) {
|
||||
|
||||
# This step prevents restoring when config DB is empty (#2340)
|
||||
$self->cfgNum( $self->lastCfg ) unless ( $self->cfgNum );
|
||||
}
|
||||
|
||||
$self->$action(@_);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ use Lemonldap::NG::Common::EmailTransport;
|
|||
use Crypt::OpenSSL::RSA;
|
||||
use Convert::PEM;
|
||||
use URI::URL;
|
||||
use Net::SSLeay;
|
||||
|
||||
use feature 'state';
|
||||
|
||||
|
@ -59,10 +60,11 @@ sub init {
|
|||
# New key and conf save
|
||||
->addRoute(
|
||||
confs => {
|
||||
newRSAKey => 'newRSAKey',
|
||||
sendTestMail => 'sendTestMail',
|
||||
raw => 'newRawConf',
|
||||
'*' => 'newConf'
|
||||
newRSAKey => 'newRSAKey',
|
||||
newCertificate => 'newCertificate',
|
||||
sendTestMail => 'sendTestMail',
|
||||
raw => 'newRawConf',
|
||||
'*' => 'newConf'
|
||||
},
|
||||
['POST']
|
||||
)
|
||||
|
@ -76,6 +78,31 @@ sub init {
|
|||
return 1;
|
||||
}
|
||||
|
||||
# 35 - New Certificate on demand
|
||||
# --------------------------
|
||||
|
||||
##@method public PSGI-JSON-response newRSAKey($req)
|
||||
# Return a hashref containing private and public keys
|
||||
# The posted data must contain a JSON object containing
|
||||
# {"password":"newpassword"}
|
||||
#
|
||||
#@param $req Lemonldap::NG::Common::PSGI::Request object
|
||||
#@return PSGI JSON response
|
||||
sub newCertificate {
|
||||
my ( $self, $req, @others ) = @_;
|
||||
return $self->sendError( $req, 'There is no subkey for "newCertificate"',
|
||||
400 )
|
||||
if (@others);
|
||||
my $query = $req->jsonBodyToObj;
|
||||
|
||||
my ( $private, $cert ) = $self->_generateX509( $query->{password} );
|
||||
my $keys = {
|
||||
'private' => $private,
|
||||
'public' => $cert,
|
||||
};
|
||||
return $self->sendJSONresponse( $req, $keys );
|
||||
}
|
||||
|
||||
# 35 - New RSA key pair on demand
|
||||
# --------------------------
|
||||
|
||||
|
@ -90,8 +117,9 @@ sub newRSAKey {
|
|||
my ( $self, $req, @others ) = @_;
|
||||
return $self->sendError( $req, 'There is no subkey for "newRSAKey"', 400 )
|
||||
if (@others);
|
||||
my $rsa = Crypt::OpenSSL::RSA->generate_key(2048);
|
||||
|
||||
my $query = $req->jsonBodyToObj;
|
||||
my $rsa = Crypt::OpenSSL::RSA->generate_key(2048);
|
||||
my $keys = {
|
||||
'private' => $rsa->get_private_key_string(),
|
||||
'public' => $rsa->get_public_key_x509_string(),
|
||||
|
@ -121,6 +149,81 @@ sub newRSAKey {
|
|||
return $self->sendJSONresponse( $req, $keys );
|
||||
}
|
||||
|
||||
# This function does the dirty X509 work,
|
||||
# mostly copied from IO::Socket::SSL::Utils
|
||||
# and adapter to work on old platforms (CentOS7)
|
||||
|
||||
sub _generateX509 {
|
||||
my ( $self, $password ) = @_;
|
||||
Net::SSLeay::SSLeay_add_ssl_algorithms();
|
||||
my $conf = $self->confAcc->getConf();
|
||||
|
||||
# Generate 2048 bits RSA key
|
||||
my $key = Net::SSLeay::EVP_PKEY_new();
|
||||
Net::SSLeay::EVP_PKEY_assign_RSA( $key,
|
||||
Net::SSLeay::RSA_generate_key( 2048, 0x10001 ) );
|
||||
|
||||
my $cert = Net::SSLeay::X509_new();
|
||||
|
||||
# Serial
|
||||
Net::SSLeay::ASN1_INTEGER_set(
|
||||
Net::SSLeay::X509_get_serialNumber($cert),
|
||||
rand( 2**32 ),
|
||||
);
|
||||
|
||||
# Version
|
||||
Net::SSLeay::X509_set_version( $cert, 2 );
|
||||
|
||||
# Make it last 20 years
|
||||
Net::SSLeay::ASN1_TIME_set( Net::SSLeay::X509_get_notBefore($cert),
|
||||
time() );
|
||||
Net::SSLeay::ASN1_TIME_set( Net::SSLeay::X509_get_notAfter($cert),
|
||||
time() + 20 * 365 * 86400 );
|
||||
|
||||
# set subject
|
||||
my $portal_uri = new URI::URL( $conf->{portal} || "http://localhost" );
|
||||
my $portal_host = $portal_uri->host;
|
||||
my $subj_e = Net::SSLeay::X509_get_subject_name($cert);
|
||||
my $subj = { commonName => $portal_host, };
|
||||
|
||||
while ( my ( $k, $v ) = each %$subj ) {
|
||||
|
||||
# Not everything we get is nice - try with MBSTRING_UTF8 first and if it
|
||||
# fails try V_ASN1_T61STRING and finally V_ASN1_OCTET_STRING
|
||||
Net::SSLeay::X509_NAME_add_entry_by_txt( $subj_e, $k, 0x1000, $v, -1,
|
||||
0 )
|
||||
or
|
||||
Net::SSLeay::X509_NAME_add_entry_by_txt( $subj_e, $k, 20, $v, -1, 0 )
|
||||
or
|
||||
Net::SSLeay::X509_NAME_add_entry_by_txt( $subj_e, $k, 4, $v, -1, 0 )
|
||||
or croak( "failed to add entry for $k - "
|
||||
. Net::SSLeay::ERR_error_string( Net::SSLeay::ERR_get_error() ) );
|
||||
}
|
||||
|
||||
# Set to self-sign
|
||||
Net::SSLeay::X509_set_pubkey( $cert, $key );
|
||||
Net::SSLeay::X509_set_issuer_name( $cert,
|
||||
Net::SSLeay::X509_get_subject_name($cert) );
|
||||
|
||||
# Sign with default alg
|
||||
Net::SSLeay::X509_sign( $cert, $key, 0 );
|
||||
|
||||
my $strCert = Net::SSLeay::PEM_get_string_X509($cert);
|
||||
my $strPrivate;
|
||||
if ($password) {
|
||||
$strPrivate = Net::SSLeay::PEM_get_string_PrivateKey( $key, $password );
|
||||
}
|
||||
else {
|
||||
$strPrivate = Net::SSLeay::PEM_get_string_PrivateKey($key);
|
||||
}
|
||||
|
||||
# Free OpenSSL objects
|
||||
Net::SSLeay::X509_free($cert);
|
||||
Net::SSLeay::EVP_PKEY_free($key);
|
||||
|
||||
return ( $strPrivate, $strCert );
|
||||
}
|
||||
|
||||
# Sending a test Email
|
||||
# --------------------
|
||||
|
||||
|
|
|
@ -380,6 +380,31 @@ sub tests {
|
|||
&& $conf->{samlServicePublicKeySig} );
|
||||
return 1;
|
||||
},
|
||||
samlSignatureOverrideNeedsCertificate => sub {
|
||||
|
||||
return 1 if $conf->{samlServicePublicKeySig} =~ /CERTIFICATE/;
|
||||
|
||||
my @offenders;
|
||||
for my $idp ( keys %{ $conf->{samlIDPMetaDataOptions} } ) {
|
||||
if ( $conf->{samlIDPMetaDataOptions}->{$idp}
|
||||
->{samlIDPMetaDataOptionsSignatureMethod} )
|
||||
{
|
||||
push @offenders, $idp;
|
||||
}
|
||||
}
|
||||
for my $sp ( keys %{ $conf->{samlSPMetaDataOptions} } ) {
|
||||
if ( $conf->{samlSPMetaDataOptions}->{$sp}
|
||||
->{samlSPMetaDataOptionsSignatureMethod} )
|
||||
{
|
||||
push @offenders, $sp;
|
||||
}
|
||||
}
|
||||
return 1 unless @offenders;
|
||||
return ( 0,
|
||||
"Cannot set non-default signature method on "
|
||||
. join( ", ", @offenders )
|
||||
. " unless SAML signature key is in certificate form" );
|
||||
},
|
||||
|
||||
# Try to parse combination with declared modules
|
||||
checkCombinations => sub {
|
||||
|
@ -811,19 +836,27 @@ sub tests {
|
|||
# Cookie SameSite=None requires Secure flag
|
||||
# Same with SameSite=(auto) and SAML issuer in use
|
||||
SameSiteNoneWithSecure => sub {
|
||||
return ( 1, 'SameSite value = None requires the secured flag' )
|
||||
return ( -1, 'SameSite value = None requires the secured flag' )
|
||||
if ( getSameSite($conf) eq 'None' and !$conf->{securedCookie} );
|
||||
return 1;
|
||||
},
|
||||
|
||||
# Secure cookies require HTTPS
|
||||
SecureCookiesRequireHttps => sub {
|
||||
return ( 1, 'Secure cookies require a HTTPS portal URL' )
|
||||
return ( -1, 'Secure cookies require a HTTPS portal URL' )
|
||||
if ( $conf->{securedCookie} == 1
|
||||
and $conf->{portal}
|
||||
and $conf->{portal} !~ /^https:/ );
|
||||
return 1;
|
||||
},
|
||||
|
||||
# Password module requires a password backend
|
||||
passwordModuleNeedsBackend => sub {
|
||||
return ( -1, 'Password module is enabled without password backend' )
|
||||
if ( $conf->{portalDisplayChangePassword}
|
||||
and $conf->{passwordDB} eq 'Null' );
|
||||
return 1;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -691,6 +691,19 @@ llapp.controller 'TreeCtrl', [
|
|||
console.log('Error sending test email')
|
||||
|
||||
# RSA keys generation
|
||||
$scope.newCertificate = ->
|
||||
$scope.showModal('password.html').then ->
|
||||
$scope.waiting = true
|
||||
currentNode = $scope.currentNode
|
||||
password = $scope.result
|
||||
$http.post("#{window.confPrefix}/newCertificate", {"password": password}).then (response) ->
|
||||
currentNode.data[0].data = response.data.private
|
||||
currentNode.data[1].data = password
|
||||
currentNode.data[2].data = response.data.public
|
||||
$scope.waiting = false
|
||||
, readError
|
||||
, ->
|
||||
console.log('New key cancelled')
|
||||
$scope.newRSAKey = ->
|
||||
$scope.showModal('password.html').then ->
|
||||
$scope.waiting = true
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
</div>
|
||||
<script type="text/menu">
|
||||
[{
|
||||
"title": "newRSAKey",
|
||||
"title": "newCertificate",
|
||||
"icon": "plus-sign"
|
||||
},{
|
||||
"title": "download",
|
|
@ -708,6 +708,35 @@ function templates(tpl,key) {
|
|||
},
|
||||
{
|
||||
"_nodes" : [
|
||||
{
|
||||
"default" : "",
|
||||
"get" : tpl+"s/"+key+"/"+"samlIDPMetaDataOptionsSignatureMethod",
|
||||
"id" : tpl+"s/"+key+"/"+"samlIDPMetaDataOptionsSignatureMethod",
|
||||
"select" : [
|
||||
{
|
||||
"k" : "",
|
||||
"v" : "default"
|
||||
},
|
||||
{
|
||||
"k" : "RSA_SHA1",
|
||||
"v" : "RSA SHA1"
|
||||
},
|
||||
{
|
||||
"k" : "RSA_SHA256",
|
||||
"v" : "RSA SHA256"
|
||||
},
|
||||
{
|
||||
"k" : "RSA_SHA384",
|
||||
"v" : "RSA SHA384"
|
||||
},
|
||||
{
|
||||
"k" : "RSA_SHA512",
|
||||
"v" : "RSA SHA512"
|
||||
}
|
||||
],
|
||||
"title" : "samlIDPMetaDataOptionsSignatureMethod",
|
||||
"type" : "select"
|
||||
},
|
||||
{
|
||||
"default" : -1,
|
||||
"get" : tpl+"s/"+key+"/"+"samlIDPMetaDataOptionsSignSSOMessage",
|
||||
|
@ -1102,6 +1131,35 @@ function templates(tpl,key) {
|
|||
},
|
||||
{
|
||||
"_nodes" : [
|
||||
{
|
||||
"default" : "",
|
||||
"get" : tpl+"s/"+key+"/"+"samlSPMetaDataOptionsSignatureMethod",
|
||||
"id" : tpl+"s/"+key+"/"+"samlSPMetaDataOptionsSignatureMethod",
|
||||
"select" : [
|
||||
{
|
||||
"k" : "",
|
||||
"v" : "default"
|
||||
},
|
||||
{
|
||||
"k" : "RSA_SHA1",
|
||||
"v" : "RSA SHA1"
|
||||
},
|
||||
{
|
||||
"k" : "RSA_SHA256",
|
||||
"v" : "RSA SHA256"
|
||||
},
|
||||
{
|
||||
"k" : "RSA_SHA384",
|
||||
"v" : "RSA SHA384"
|
||||
},
|
||||
{
|
||||
"k" : "RSA_SHA512",
|
||||
"v" : "RSA SHA512"
|
||||
}
|
||||
],
|
||||
"title" : "samlSPMetaDataOptionsSignatureMethod",
|
||||
"type" : "select"
|
||||
},
|
||||
{
|
||||
"default" : -1,
|
||||
"get" : tpl+"s/"+key+"/"+"samlSPMetaDataOptionsSignSSOMessage",
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -841,6 +841,24 @@ This file contains:
|
|||
return console.log('Error sending test email');
|
||||
});
|
||||
};
|
||||
$scope.newCertificate = function() {
|
||||
return $scope.showModal('password.html').then(function() {
|
||||
var currentNode, password;
|
||||
$scope.waiting = true;
|
||||
currentNode = $scope.currentNode;
|
||||
password = $scope.result;
|
||||
return $http.post(window.confPrefix + "/newCertificate", {
|
||||
"password": password
|
||||
}).then(function(response) {
|
||||
currentNode.data[0].data = response.data["private"];
|
||||
currentNode.data[1].data = password;
|
||||
currentNode.data[2].data = response.data["public"];
|
||||
return $scope.waiting = false;
|
||||
}, readError);
|
||||
}, function() {
|
||||
return console.log('New key cancelled');
|
||||
});
|
||||
};
|
||||
$scope.newRSAKey = function() {
|
||||
return $scope.showModal('password.html').then(function() {
|
||||
var currentNode, password;
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -28,6 +28,7 @@
|
|||
"2ndFA":"Second Factors",
|
||||
"actives":"مفعلة",
|
||||
"activeTimer":"قبول تلقائي للوقت",
|
||||
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
|
||||
"addAppCasPartner":"إضافة تطبيق كاس",
|
||||
"addIDPSamlPartner":"أضف IDP SAML",
|
||||
"addOidcOp":"إضافة أوبين أيدي كونيكت بروفيدر",
|
||||
|
@ -161,6 +162,7 @@
|
|||
"portalDisplayCertificateResetByMail":"Reset your Certificate",
|
||||
"contentSecurityPolicy":"Content security policy",
|
||||
"contextSwitching":"Switch context another user",
|
||||
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
|
||||
"contextSwitchingHiddenAttributes":"السمات المخفية",
|
||||
"contextSwitchingIdRule":"Identities use rule",
|
||||
"contextSwitchingRule":"استخدام القاعدة",
|
||||
|
@ -517,6 +519,7 @@
|
|||
"newApp":"تطبيق جديد",
|
||||
"newChain":"سلسلة جديدة",
|
||||
"newCat":"فئة جديدة",
|
||||
"newCertificate":"New certificate",
|
||||
"newCfgAvailable":"إعدادات جديد متاح",
|
||||
"newCmbMod":"وحدة جديدة",
|
||||
"newCmbOver":"معايير جديدة",
|
||||
|
@ -1060,6 +1063,7 @@
|
|||
"samlIDPMetaDataOptionsAuthnRequest":"طلب إثبات الهوية",
|
||||
"samlIDPMetaDataOptionsSession":"جلسة",
|
||||
"samlIDPMetaDataOptionsSignature":"توقيع",
|
||||
"samlIDPMetaDataOptionsSignatureMethod":"Signature method",
|
||||
"samlIDPMetaDataOptionsBinding":"ربط",
|
||||
"samlIDPMetaDataOptionsDisplay":"عرض",
|
||||
"samlIDPMetaDataOptionsDisplayName":"عرض الاسم",
|
||||
|
@ -1083,6 +1087,7 @@
|
|||
"samlSPMetaDataOptionsEncryptionMode":"أسلوب التشفير",
|
||||
"samlSPMetaDataOptionsAuthnResponse":"رد إثبات الهوية",
|
||||
"samlSPMetaDataOptionsSignature":"توقيع",
|
||||
"samlSPMetaDataOptionsSignatureMethod":"Signature method",
|
||||
"samlSPMetaDataOptionsSecurity":"الحماية",
|
||||
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"تمكين استخدام عنوان يو آر إل IDP ",
|
||||
"samlSPMetaDataOptionsNameIDSessionKey":"فرض اسم المعرف لمفتاح الجلسة",
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"2ndFA":"Second Factors",
|
||||
"actives":"Enabled",
|
||||
"activeTimer":"Auto accept time",
|
||||
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
|
||||
"addAppCasPartner":"Add CAS application",
|
||||
"addIDPSamlPartner":"Add SAML IDP",
|
||||
"addOidcOp":"Add OpenID Connect Provider",
|
||||
|
@ -161,6 +162,7 @@
|
|||
"portalDisplayCertificateResetByMail":"Reset your certificate",
|
||||
"contentSecurityPolicy":"Content security policy",
|
||||
"contextSwitching":"Switch context another user",
|
||||
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
|
||||
"contextSwitchingHiddenAttributes":"Hidden attributes",
|
||||
"contextSwitchingIdRule":"Identities use rule",
|
||||
"contextSwitchingRule":"Use rule",
|
||||
|
@ -517,6 +519,7 @@
|
|||
"newApp":"New application",
|
||||
"newChain":"New chain",
|
||||
"newCat":"New category",
|
||||
"newCertificate":"New certificate",
|
||||
"newCfgAvailable":"A new configuration is available",
|
||||
"newCmbMod":"New module",
|
||||
"newCmbOver":"New parameter",
|
||||
|
@ -1060,6 +1063,7 @@
|
|||
"samlIDPMetaDataOptionsAuthnRequest":"Authentication request",
|
||||
"samlIDPMetaDataOptionsSession":"Session",
|
||||
"samlIDPMetaDataOptionsSignature":"Signature",
|
||||
"samlIDPMetaDataOptionsSignatureMethod":"Signature method",
|
||||
"samlIDPMetaDataOptionsBinding":"Binding",
|
||||
"samlIDPMetaDataOptionsDisplay":"Display",
|
||||
"samlIDPMetaDataOptionsDisplayName":"Display name",
|
||||
|
@ -1083,6 +1087,7 @@
|
|||
"samlSPMetaDataOptionsEncryptionMode":"Encryption mode",
|
||||
"samlSPMetaDataOptionsAuthnResponse":"Authentication response",
|
||||
"samlSPMetaDataOptionsSignature":"Signature",
|
||||
"samlSPMetaDataOptionsSignatureMethod":"Signature method",
|
||||
"samlSPMetaDataOptionsSecurity":"Security",
|
||||
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Enable use of IDP initiated URL",
|
||||
"samlSPMetaDataOptionsNameIDSessionKey":"Force NameID session key",
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"2ndFA":"Second Factors",
|
||||
"actives":"Enabled",
|
||||
"activeTimer":"Auto accept time",
|
||||
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
|
||||
"addAppCasPartner":"Add CAS application",
|
||||
"addIDPSamlPartner":"Add SAML IDP",
|
||||
"addOidcOp":"Add OpenID Connect Provider",
|
||||
|
@ -161,6 +162,7 @@
|
|||
"portalDisplayCertificateResetByMail":"Reset your certificate",
|
||||
"contentSecurityPolicy":"Content security policy",
|
||||
"contextSwitching":"Switch context another user",
|
||||
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
|
||||
"contextSwitchingHiddenAttributes":"Hidden attributes",
|
||||
"contextSwitchingIdRule":"Identities use rule",
|
||||
"contextSwitchingRule":"Use rule",
|
||||
|
@ -517,6 +519,7 @@
|
|||
"newApp":"New application",
|
||||
"newChain":"New chain",
|
||||
"newCat":"New category",
|
||||
"newCertificate":"New certificate",
|
||||
"newCfgAvailable":"A new configuration is available",
|
||||
"newCmbMod":"New module",
|
||||
"newCmbOver":"New parameter",
|
||||
|
@ -1060,6 +1063,7 @@
|
|||
"samlIDPMetaDataOptionsAuthnRequest":"Authentication request",
|
||||
"samlIDPMetaDataOptionsSession":"Session",
|
||||
"samlIDPMetaDataOptionsSignature":"Signature",
|
||||
"samlIDPMetaDataOptionsSignatureMethod":"Signature method",
|
||||
"samlIDPMetaDataOptionsBinding":"Binding",
|
||||
"samlIDPMetaDataOptionsDisplay":"Display",
|
||||
"samlIDPMetaDataOptionsDisplayName":"Display name",
|
||||
|
@ -1083,6 +1087,7 @@
|
|||
"samlSPMetaDataOptionsEncryptionMode":"Encryption mode",
|
||||
"samlSPMetaDataOptionsAuthnResponse":"Authentication response",
|
||||
"samlSPMetaDataOptionsSignature":"Signature",
|
||||
"samlSPMetaDataOptionsSignatureMethod":"Signature method",
|
||||
"samlSPMetaDataOptionsSecurity":"Security",
|
||||
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Enable use of IDP initiated URL",
|
||||
"samlSPMetaDataOptionsNameIDSessionKey":"Force NameID session key",
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"2ndFA":"Seconds Facteurs",
|
||||
"actives":"Actives",
|
||||
"activeTimer":"Délai d'acceptation automatique",
|
||||
"adaptativeAuthenticationLevelRules":"Règles d'authentification adaptative",
|
||||
"addAppCasPartner":"Ajouter une application CAS",
|
||||
"addIDPSamlPartner":"Ajouter un FI SAML",
|
||||
"addOidcOp":"Ajouter un fournisseur OpenID Connect",
|
||||
|
@ -161,6 +162,7 @@
|
|||
"portalDisplayCertificateResetByMail":"Réinitialiser votre certificat",
|
||||
"contentSecurityPolicy":"Politique de sécurité de contenu",
|
||||
"contextSwitching":"Endossement d'identité",
|
||||
"contextSwitchingAllowed2fModifications":"Autoriser les modifications des SF",
|
||||
"contextSwitchingHiddenAttributes":"Attributs masqués",
|
||||
"contextSwitchingIdRule":"Règle d'utilisation des identités",
|
||||
"contextSwitchingRule":"Règle d'utilisation",
|
||||
|
@ -517,6 +519,7 @@
|
|||
"newApp":"Nouvelle application",
|
||||
"newChain":"Nouvelle chaîne",
|
||||
"newCat":"Nouvelle catégorie",
|
||||
"newCertificate":"Nouveau certificat",
|
||||
"newCfgAvailable":"Une nouvelle configuration est disponible",
|
||||
"newCmbMod":"Nouveau module",
|
||||
"newCmbOver":"Nouveau paramètre",
|
||||
|
@ -804,7 +807,7 @@
|
|||
"remoteParams":"Paramètres Remote",
|
||||
"remotePortal":"URL du portail",
|
||||
"replaceByFile":"Remplacer par le fichier",
|
||||
"requireToken":"Exige un jeton pour les formulaires",
|
||||
"requireToken":"Exiger un jeton pour les formulaires",
|
||||
"restAuthnLevel":"Niveau d'authentification",
|
||||
"restAuthUrl":"URL d'authentification",
|
||||
"restConfigServer":"Serveur de configurations REST",
|
||||
|
@ -860,7 +863,7 @@
|
|||
"sfaTitle":"Seconds facteurs d'authentification",
|
||||
"sfExtra":"Seconds facteurs additionnels",
|
||||
"sfManagerRule":"Afficher le lien du Gestionnaire",
|
||||
"sfOnlyUpgrade": "Utiliser le second facteur pour augmenter le niveau d'authentification",
|
||||
"sfOnlyUpgrade": "Utiliser le SF pour augmenter le niveau d'authentification",
|
||||
"sfRequired":"Exiger l'enrôlement d'un SF à l'authentification",
|
||||
"sfRemovedNotification":"Avertir si un SF expiré est supprimé",
|
||||
"sfRemovedMsgRule":"Activation",
|
||||
|
@ -1060,6 +1063,7 @@
|
|||
"samlIDPMetaDataOptionsAuthnRequest":"Requête d'authentification",
|
||||
"samlIDPMetaDataOptionsSession":"Session",
|
||||
"samlIDPMetaDataOptionsSignature":"Signature",
|
||||
"samlIDPMetaDataOptionsSignatureMethod":"Méthode pour la signature",
|
||||
"samlIDPMetaDataOptionsBinding":"Méthode",
|
||||
"samlIDPMetaDataOptionsDisplay":"Affichage",
|
||||
"samlIDPMetaDataOptionsDisplayName":"Nom d'affichage",
|
||||
|
@ -1083,6 +1087,7 @@
|
|||
"samlSPMetaDataOptionsEncryptionMode":"Mode de chiffrement",
|
||||
"samlSPMetaDataOptionsAuthnResponse":"Réponse d'authentification",
|
||||
"samlSPMetaDataOptionsSignature":"Signature",
|
||||
"samlSPMetaDataOptionsSignatureMethod":"Méthode pour la signature",
|
||||
"samlSPMetaDataOptionsSecurity":"Sécurité",
|
||||
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Autoriser l'utilisation d'URL SSO initié par l'IDP",
|
||||
"samlSPMetaDataOptionsNameIDSessionKey":"Forcer la clef de session NameID",
|
||||
|
@ -1103,7 +1108,7 @@
|
|||
"samlSPName":"Nom du fournisseur de service SAML",
|
||||
"samlSPSSODescriptor":"Fournisseur de service",
|
||||
"samlSPSSODescriptorAuthnRequestsSigned":"Requêtes d'authentification signées",
|
||||
"samlSPSSODescriptorWantAssertionsSigned":"Exige des assertions signées",
|
||||
"samlSPSSODescriptorWantAssertionsSigned":"Exiger des assertions signées",
|
||||
"samlSPSSODescriptorSingleLogoutService":"Single Logout",
|
||||
"samlSPSSODescriptorSingleLogoutServiceHTTPRedirect":"Redirection HTTP",
|
||||
"samlSPSSODescriptorSingleLogoutServiceHTTPPost":"POST HTTP",
|
||||
|
@ -1114,7 +1119,7 @@
|
|||
"samlSPSSODescriptorArtifactResolutionService":"Résolution d'Artifact",
|
||||
"samlSPSSODescriptorArtifactResolutionServiceArtifact":"Service Artifact",
|
||||
"samlIDPSSODescriptor":"Fournisseur d'identité",
|
||||
"samlIDPSSODescriptorWantAuthnRequestsSigned":"Exige des requêtes d'authentification signées",
|
||||
"samlIDPSSODescriptorWantAuthnRequestsSigned":"Exiger des requêtes d'authentification signées",
|
||||
"samlIDPSSODescriptorSingleSignOnService":"Single Sign On",
|
||||
"samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect":"Redirection HTTP",
|
||||
"samlIDPSSODescriptorSingleSignOnServiceHTTPPost":"POST HTTP",
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"2ndFA":"Secondi fattori",
|
||||
"actives":"Attivi",
|
||||
"activeTimer":"Auto accettazione tempo",
|
||||
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
|
||||
"addAppCasPartner":"Aggiungi applicazione CAS",
|
||||
"addIDPSamlPartner":"Aggiungi SAML IDP",
|
||||
"addOidcOp":"Aggiungere OpenID Connect Provider",
|
||||
|
@ -161,6 +162,7 @@
|
|||
"portalDisplayCertificateResetByMail":"Reset your certificate",
|
||||
"contentSecurityPolicy":"Politica di protezione dei contenuti",
|
||||
"contextSwitching":"Switch context another user",
|
||||
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
|
||||
"contextSwitchingHiddenAttributes":"Attributi nascosti",
|
||||
"contextSwitchingIdRule":"Le identità usano la regola",
|
||||
"contextSwitchingRule":"Utilizza la regola",
|
||||
|
@ -517,6 +519,7 @@
|
|||
"newApp":"Nuova applicazione",
|
||||
"newChain":"Nuova catena",
|
||||
"newCat":"NUova categoria",
|
||||
"newCertificate":"New certificate",
|
||||
"newCfgAvailable":"É disponibile una nuova configurazione",
|
||||
"newCmbMod":"Nuovo modulo",
|
||||
"newCmbOver":"Nuovo parametro",
|
||||
|
@ -1060,6 +1063,7 @@
|
|||
"samlIDPMetaDataOptionsAuthnRequest":"Richiesta di autenticazione",
|
||||
"samlIDPMetaDataOptionsSession":"Sessioni",
|
||||
"samlIDPMetaDataOptionsSignature":"Firma",
|
||||
"samlIDPMetaDataOptionsSignatureMethod":"Metodo di firma",
|
||||
"samlIDPMetaDataOptionsBinding":"Vincolante",
|
||||
"samlIDPMetaDataOptionsDisplay":" Visualizza ",
|
||||
"samlIDPMetaDataOptionsDisplayName":"Nome da visualizzare",
|
||||
|
@ -1083,6 +1087,7 @@
|
|||
"samlSPMetaDataOptionsEncryptionMode":"Modalità di crittografia",
|
||||
"samlSPMetaDataOptionsAuthnResponse":"Risposta di autenticazione",
|
||||
"samlSPMetaDataOptionsSignature":"Firma",
|
||||
"samlSPMetaDataOptionsSignatureMethod":"Metodo di firma",
|
||||
"samlSPMetaDataOptionsSecurity":"Sicurezza",
|
||||
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Abilitare l'utilizzo dell'URL IDP avviata ",
|
||||
"samlSPMetaDataOptionsNameIDSessionKey":"Forza la chiave di sessione NameID",
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"2ndFA":"Drugie czynniki",
|
||||
"actives":"Włączone",
|
||||
"activeTimer":"Czas automatycznej akceptacji",
|
||||
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
|
||||
"addAppCasPartner":"Dodaj aplikację CAS",
|
||||
"addIDPSamlPartner":"Dodaj SAML IDP",
|
||||
"addOidcOp":"Dodaj dostawcę OpenID Connect",
|
||||
|
@ -161,6 +162,7 @@
|
|||
"portalDisplayCertificateResetByMail":"Zresetuj swój certyfikat",
|
||||
"contentSecurityPolicy":"Polityka bezpieczeństwa treści",
|
||||
"contextSwitching":"Przełącz kontekst innego użytkownika",
|
||||
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
|
||||
"contextSwitchingHiddenAttributes":"Ukryte atrybuty",
|
||||
"contextSwitchingIdRule":"Reguła korzystania z tożsamości",
|
||||
"contextSwitchingRule":"Użyj reguły",
|
||||
|
@ -517,6 +519,7 @@
|
|||
"newApp":"Nowa aplikacja",
|
||||
"newChain":"Nowy łańcuch",
|
||||
"newCat":"Nowa kategoria",
|
||||
"newCertificate":"New certificate",
|
||||
"newCfgAvailable":"Dostępna jest nowa konfiguracja",
|
||||
"newCmbMod":"Nowy moduł",
|
||||
"newCmbOver":"Nowy parametr",
|
||||
|
@ -1060,6 +1063,7 @@
|
|||
"samlIDPMetaDataOptionsAuthnRequest":"Żądanie uwierzytelnienia",
|
||||
"samlIDPMetaDataOptionsSession":"Sesja",
|
||||
"samlIDPMetaDataOptionsSignature":"Podpis",
|
||||
"samlIDPMetaDataOptionsSignatureMethod":"Metoda podpisu",
|
||||
"samlIDPMetaDataOptionsBinding":"Przywiązania",
|
||||
"samlIDPMetaDataOptionsDisplay":"Wyświetl",
|
||||
"samlIDPMetaDataOptionsDisplayName":"Wyświetlana nazwa",
|
||||
|
@ -1083,6 +1087,7 @@
|
|||
"samlSPMetaDataOptionsEncryptionMode":"Tryb szyfrowania",
|
||||
"samlSPMetaDataOptionsAuthnResponse":"Odpowiedź uwierzytelnienia",
|
||||
"samlSPMetaDataOptionsSignature":"Podpis",
|
||||
"samlSPMetaDataOptionsSignatureMethod":"Metoda podpisu",
|
||||
"samlSPMetaDataOptionsSecurity":"Bezpieczeństwo",
|
||||
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Włącz korzystanie z adresu URL zainicjowanego przez IDP",
|
||||
"samlSPMetaDataOptionsNameIDSessionKey":"Wymuś klucz sesji NameID",
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"2ndFA":"İki Faktörlü Kimlik Doğrulama",
|
||||
"actives":"Etkin",
|
||||
"activeTimer":"Otomatik kabul süresi",
|
||||
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
|
||||
"addAppCasPartner":"CAS uygulaması ekle",
|
||||
"addIDPSamlPartner":"SAML IDP ekle",
|
||||
"addOidcOp":"OpenID Connect Sağlayıcısı Ekle",
|
||||
|
@ -161,6 +162,7 @@
|
|||
"portalDisplayCertificateResetByMail":"Sertifikanızı sıfırlayın",
|
||||
"contentSecurityPolicy":"İçerik güvenlik ilkesi",
|
||||
"contextSwitching":"İçeriği başka bir kullanıcıyla değiştir",
|
||||
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
|
||||
"contextSwitchingHiddenAttributes":"Gizli nitelikler",
|
||||
"contextSwitchingIdRule":"Kimlik kullanım kuralı",
|
||||
"contextSwitchingRule":"Kuralı kullan",
|
||||
|
@ -517,6 +519,7 @@
|
|||
"newApp":"Yeni uygulama",
|
||||
"newChain":"Yeni zincir",
|
||||
"newCat":"Yeni kategori",
|
||||
"newCertificate":"New certificate",
|
||||
"newCfgAvailable":"Yeni bir yapılandırma mevcut",
|
||||
"newCmbMod":"Yeni modül",
|
||||
"newCmbOver":"Yeni parametre",
|
||||
|
@ -1060,6 +1063,7 @@
|
|||
"samlIDPMetaDataOptionsAuthnRequest":"Doğrulama isteği",
|
||||
"samlIDPMetaDataOptionsSession":"Oturum",
|
||||
"samlIDPMetaDataOptionsSignature":"İmza",
|
||||
"samlIDPMetaDataOptionsSignatureMethod":"İmzalama yöntemi",
|
||||
"samlIDPMetaDataOptionsBinding":"Bağlayıcı",
|
||||
"samlIDPMetaDataOptionsDisplay":"Görüntüle",
|
||||
"samlIDPMetaDataOptionsDisplayName":"Görüntülenen ad",
|
||||
|
@ -1083,6 +1087,7 @@
|
|||
"samlSPMetaDataOptionsEncryptionMode":"Şifreleme modu",
|
||||
"samlSPMetaDataOptionsAuthnResponse":"Doğrulama cevabı",
|
||||
"samlSPMetaDataOptionsSignature":"İmza",
|
||||
"samlSPMetaDataOptionsSignatureMethod":"İmzalama yöntemi",
|
||||
"samlSPMetaDataOptionsSecurity":"Güvenlik",
|
||||
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"IDP ile başlatılan URL’nin kullanımını etkinleştir",
|
||||
"samlSPMetaDataOptionsNameIDSessionKey":"NameID oturum anahtarını zorla",
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"2ndFA":"Second Factors",
|
||||
"actives":"Hoạt động",
|
||||
"activeTimer":"Tự động chấp nhận thời gian",
|
||||
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
|
||||
"addAppCasPartner":"Thêm ứng dụng CAS",
|
||||
"addIDPSamlPartner":"Thêm SAML IDP",
|
||||
"addOidcOp":"Thêm nhà cung cấp kết nối OpenID",
|
||||
|
@ -161,6 +162,7 @@
|
|||
"portalDisplayCertificateResetByMail":"Reset your certificate",
|
||||
"contentSecurityPolicy":"Chính sách bảo mật nội dung",
|
||||
"contextSwitching":"Switch context another user",
|
||||
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
|
||||
"contextSwitchingHiddenAttributes":"Thuộc tính ẩn",
|
||||
"contextSwitchingIdRule":"Identities use rule",
|
||||
"contextSwitchingRule":"Quy tắc sử dụng",
|
||||
|
@ -517,6 +519,7 @@
|
|||
"newApp":"Ứng dụng mới",
|
||||
"newChain":"Chuỗi mới",
|
||||
"newCat":"Danh mục mới",
|
||||
"newCertificate":"New certificate",
|
||||
"newCfgAvailable":"Một cấu hình mới có sẵn",
|
||||
"newCmbMod":"Mô-đun mới",
|
||||
"newCmbOver":"Tham số mới",
|
||||
|
@ -1060,6 +1063,7 @@
|
|||
"samlIDPMetaDataOptionsAuthnRequest":"Yêu cầu xác thực",
|
||||
"samlIDPMetaDataOptionsSession":"Phiên",
|
||||
"samlIDPMetaDataOptionsSignature":"Chữ ký",
|
||||
"samlIDPMetaDataOptionsSignatureMethod":"Signature method",
|
||||
"samlIDPMetaDataOptionsBinding":"Liên kết",
|
||||
"samlIDPMetaDataOptionsDisplay":"Hiển thị",
|
||||
"samlIDPMetaDataOptionsDisplayName":"Tên hiển thị",
|
||||
|
@ -1083,6 +1087,7 @@
|
|||
"samlSPMetaDataOptionsEncryptionMode":"Chế độ mã hóa",
|
||||
"samlSPMetaDataOptionsAuthnResponse":"Phản hồi xác thực",
|
||||
"samlSPMetaDataOptionsSignature":"Chữ ký",
|
||||
"samlSPMetaDataOptionsSignatureMethod":"Signature method",
|
||||
"samlSPMetaDataOptionsSecurity":"Bảo mật",
|
||||
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Cho phép sử dụng URL bắt đầu IDP",
|
||||
"samlSPMetaDataOptionsNameIDSessionKey":"Bắt buộc khóa phiên NameID",
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"2ndFA":"Second Factors",
|
||||
"actives":"Enabled",
|
||||
"activeTimer":"自动接收时间",
|
||||
"adaptativeAuthenticationLevelRules":"Adaptative authentication rules",
|
||||
"addAppCasPartner":"增加CAS应用",
|
||||
"addIDPSamlPartner":"增加SAML IDP",
|
||||
"addOidcOp":"增加OpenID Connect Provider",
|
||||
|
@ -161,6 +162,7 @@
|
|||
"portalDisplayCertificateResetByMail":"Reset your certificate",
|
||||
"contentSecurityPolicy":"Content security policy",
|
||||
"contextSwitching":"Switch context another user",
|
||||
"contextSwitchingAllowed2fModifications":"Allow 2FA modifications",
|
||||
"contextSwitchingHiddenAttributes":"Hidden attributes",
|
||||
"contextSwitchingIdRule":"Identities use rule",
|
||||
"contextSwitchingRule":"Use rule",
|
||||
|
@ -517,6 +519,7 @@
|
|||
"newApp":"New application",
|
||||
"newChain":"New chain",
|
||||
"newCat":"New category",
|
||||
"newCertificate":"New certificate",
|
||||
"newCfgAvailable":"A new configuration is available",
|
||||
"newCmbMod":"New module",
|
||||
"newCmbOver":"New parameter",
|
||||
|
@ -1060,6 +1063,7 @@
|
|||
"samlIDPMetaDataOptionsAuthnRequest":"Authentication request",
|
||||
"samlIDPMetaDataOptionsSession":"Session",
|
||||
"samlIDPMetaDataOptionsSignature":"Signature",
|
||||
"samlIDPMetaDataOptionsSignatureMethod":"Signature method",
|
||||
"samlIDPMetaDataOptionsBinding":"Binding",
|
||||
"samlIDPMetaDataOptionsDisplay":"Display",
|
||||
"samlIDPMetaDataOptionsDisplayName":"Display name",
|
||||
|
@ -1083,6 +1087,7 @@
|
|||
"samlSPMetaDataOptionsEncryptionMode":"Encryption mode",
|
||||
"samlSPMetaDataOptionsAuthnResponse":"Authentication response",
|
||||
"samlSPMetaDataOptionsSignature":"Signature",
|
||||
"samlSPMetaDataOptionsSignatureMethod":"Signature method",
|
||||
"samlSPMetaDataOptionsSecurity":"Security",
|
||||
"samlSPMetaDataOptionsEnableIDPInitiatedURL":"Enable use of IDP initiated URL",
|
||||
"samlSPMetaDataOptionsNameIDSessionKey":"Force NameID session key",
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -6,6 +6,16 @@ use JSON;
|
|||
use IO::String;
|
||||
require 't/test-lib.pm';
|
||||
|
||||
sub checkResult {
|
||||
my $res = shift;
|
||||
ok( $res->[0] == 200, "Result code is 200" );
|
||||
my $key;
|
||||
ok( $key = from_json( $res->[2]->[0] ), 'Response is JSON' );
|
||||
like( $key->{private}, qr/BEGIN/, "is PEM formatted" );
|
||||
like( $key->{public}, qr/BEGIN/, "is PEM formatted" );
|
||||
count(4);
|
||||
}
|
||||
|
||||
my $res;
|
||||
ok(
|
||||
$res = &client->_post(
|
||||
|
@ -13,10 +23,8 @@ ok(
|
|||
),
|
||||
"Request succeed"
|
||||
);
|
||||
ok( $res->[0] == 200, "Result code is 200" );
|
||||
my $key;
|
||||
ok( $key = from_json( $res->[2]->[0] ), 'Response is JSON' );
|
||||
count(3);
|
||||
count(1);
|
||||
checkResult($res);
|
||||
|
||||
ok(
|
||||
$res = &client->_post(
|
||||
|
@ -25,8 +33,28 @@ ok(
|
|||
),
|
||||
"Request succeed"
|
||||
);
|
||||
ok( $res->[0] == 200, "Result code is 200" );
|
||||
ok( $key = from_json( $res->[2]->[0] ), 'Response is JSON' );
|
||||
count(3);
|
||||
count(1);
|
||||
checkResult($res);
|
||||
|
||||
ok(
|
||||
$res = &client->_post(
|
||||
'/confs/newCertificate', '',
|
||||
IO::String->new(''), 'application/json',
|
||||
0,
|
||||
),
|
||||
"Request succeed"
|
||||
);
|
||||
count(1);
|
||||
checkResult($res);
|
||||
|
||||
ok(
|
||||
$res = &client->_post(
|
||||
'/confs/newCertificate', '', IO::String->new('{"password":"hello"}'),
|
||||
'application/json', 20,
|
||||
),
|
||||
"Request succeed"
|
||||
);
|
||||
count(1);
|
||||
checkResult($res);
|
||||
|
||||
done_testing( count() );
|
||||
|
|
|
@ -101,6 +101,7 @@ lib/Lemonldap/NG/Portal/Password/Demo.pm
|
|||
lib/Lemonldap/NG/Portal/Password/LDAP.pm
|
||||
lib/Lemonldap/NG/Portal/Password/Null.pm
|
||||
lib/Lemonldap/NG/Portal/Password/REST.pm
|
||||
lib/Lemonldap/NG/Portal/Plugins/AdaptativeAuthenticationLevel.pm
|
||||
lib/Lemonldap/NG/Portal/Plugins/AutoSignin.pm
|
||||
lib/Lemonldap/NG/Portal/Plugins/BruteForceProtection.pm
|
||||
lib/Lemonldap/NG/Portal/Plugins/CDA.pm
|
||||
|
@ -638,6 +639,7 @@ t/59-Double-cookies-for-Double-sessions.t
|
|||
t/59-Double-cookies-Refresh-and-Logout.t
|
||||
t/59-Secured-cookie-Refresh-and-Logout.t
|
||||
t/60-Status.t
|
||||
t/61-AdaptativeAuthenticationLevel.t
|
||||
t/61-BruteForceProtection-with-Incremental-lockTimes-and-TOTP.t
|
||||
t/61-BruteForceProtection-with-Incremental-lockTimes.t
|
||||
t/61-BruteForceProtection.t
|
||||
|
@ -674,6 +676,7 @@ t/68-Impersonation-with-doubleCookies.t
|
|||
t/68-Impersonation-with-filtered-merge.t
|
||||
t/68-Impersonation-with-History.t
|
||||
t/68-Impersonation-with-merge.t
|
||||
t/68-Impersonation-with-SFA.t
|
||||
t/68-Impersonation-with-TOTP.t
|
||||
t/68-Impersonation-with-UnrestrictedUser.t
|
||||
t/68-Impersonation.t
|
||||
|
|
|
@ -177,12 +177,10 @@ sub init {
|
|||
# Registration base
|
||||
$self->addAuthRoute( '2fregisters' => '_displayRegister', ['GET'] );
|
||||
$self->addAuthRoute( '2fregisters' => 'register', ['POST'] );
|
||||
if ( $self->conf->{sfRequired} ) {
|
||||
$self->addUnauthRoute(
|
||||
'2fregisters' => 'restoreSession',
|
||||
[ 'GET', 'POST' ]
|
||||
);
|
||||
}
|
||||
$self->addUnauthRoute(
|
||||
'2fregisters' => 'restoreSession',
|
||||
[ 'GET', 'POST' ]
|
||||
) if ( $self->conf->{sfRequired} );
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
@ -236,11 +234,11 @@ sub run {
|
|||
$self->logger->error("Bad encoding in _2fDevices: $@");
|
||||
return PE_ERROR;
|
||||
}
|
||||
|
||||
$self->logger->debug(" -> 2F Device(s) found");
|
||||
my $now = time();
|
||||
my $removed = 0;
|
||||
|
||||
$self->logger->debug("Looking for expired 2F device(s)...");
|
||||
my $removed = 0;
|
||||
my $now = time();
|
||||
foreach my $device (@$_2fDevices) {
|
||||
my $type = lc( $device->{type} );
|
||||
$type =~ s/2f$//i;
|
||||
|
@ -449,14 +447,10 @@ sub _displayRegister {
|
|||
if ($tpl) {
|
||||
my ($m) =
|
||||
grep { $_->{m}->prefix eq $tpl } @{ $self->sfRModules };
|
||||
unless ($m) {
|
||||
return $self->p->sendError( $req,
|
||||
'Inexistent register module', 400 );
|
||||
}
|
||||
unless ( $m->{r}->( $req, $req->userData ) ) {
|
||||
return $self->p->sendError( $req,
|
||||
'Registration not authorized', 403 );
|
||||
}
|
||||
return $self->p->sendError( $req, 'Inexistent register module', 400 )
|
||||
unless $m;
|
||||
return $self->p->sendError( $req, 'Registration not authorized', 403 )
|
||||
unless $m->{r}->( $req, $req->userData );
|
||||
return $self->p->sendHtml(
|
||||
$req,
|
||||
$m->{m}->template,
|
||||
|
@ -481,32 +475,35 @@ sub _displayRegister {
|
|||
};
|
||||
}
|
||||
}
|
||||
if (
|
||||
|
||||
return [ 302, [ Location => $self->conf->{portal} . $am[0]->{URL} ], [] ]
|
||||
if (
|
||||
@am == 1
|
||||
and not( $req->userData->{_2fDevices}
|
||||
or $req->data->{sfRegRequired} )
|
||||
)
|
||||
{
|
||||
return [ 302, [ Location => $self->conf->{portal} . $am[0]->{URL} ],
|
||||
[] ];
|
||||
}
|
||||
);
|
||||
|
||||
# Retrieve user all second factors
|
||||
my $_2fDevices =
|
||||
$req->userData->{_2fDevices}
|
||||
? eval {
|
||||
from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } ); }
|
||||
: undef;
|
||||
unless ($_2fDevices) {
|
||||
$self->logger->debug("No 2F Device found");
|
||||
$_2fDevices = [];
|
||||
my $_2fDevices = [];
|
||||
if ( $self->allowedUpdateSfa($req) ) {
|
||||
$_2fDevices = $req->userData->{_2fDevices}
|
||||
? eval {
|
||||
from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } );
|
||||
}
|
||||
: undef;
|
||||
unless ($_2fDevices) {
|
||||
$self->logger->debug("No 2F Device found");
|
||||
$_2fDevices = [];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$self->userLogger->warn("Do not diplay 2F Devices!");
|
||||
}
|
||||
|
||||
# Parse second factors to display delete button if allowed
|
||||
my $action = '';
|
||||
foreach
|
||||
my $type ( split /,\s*/, $self->conf->{available2FSelfRegistration} )
|
||||
|
||||
{
|
||||
foreach (@$_2fDevices) {
|
||||
$_->{type} =~ s/^UBK$/Yubikey/;
|
||||
|
|
|
@ -11,13 +11,10 @@ extends 'Lemonldap::NG::Portal::Main::Plugin', 'Lemonldap::NG::Common::TOTP';
|
|||
|
||||
# INITIALIZATION
|
||||
|
||||
has prefix => ( is => 'rw', default => 'totp' );
|
||||
|
||||
has prefix => ( is => 'rw', default => 'totp' );
|
||||
has template => ( is => 'ro', default => 'totp2fregister' );
|
||||
|
||||
has logo => ( is => 'rw', default => 'totp.png' );
|
||||
|
||||
has ott => (
|
||||
has logo => ( is => 'rw', default => 'totp.png' );
|
||||
has ott => (
|
||||
is => 'rw',
|
||||
lazy => 1,
|
||||
default => sub {
|
||||
|
@ -35,10 +32,13 @@ sub init {
|
|||
sub run {
|
||||
my ( $self, $req, $action ) = @_;
|
||||
my $user = $req->userData->{ $self->conf->{whatToTrace} };
|
||||
unless ($user) {
|
||||
return $self->p->sendError( $req,
|
||||
'No ' . $self->conf->{whatToTrace} . ' found in user data', 500 );
|
||||
}
|
||||
return $self->p->sendError( $req,
|
||||
'No ' . $self->conf->{whatToTrace} . ' found in user data', 500 )
|
||||
unless $user;
|
||||
|
||||
# Check if TOTP can be updated
|
||||
return $self->p->sendError( $req, 'notAuthorized', 400 )
|
||||
unless $self->allowedUpdateSfa($req, $action);
|
||||
|
||||
# Verification that user has a valid TOTP app
|
||||
if ( $action eq 'verify' ) {
|
||||
|
@ -75,7 +75,7 @@ sub run {
|
|||
}
|
||||
$TOTPName =
|
||||
substr( $TOTPName, 0, $self->conf->{max2FDevicesNameLength} );
|
||||
$self->logger->debug("TOTP name : $TOTPName");
|
||||
$self->logger->debug("TOTP name: $TOTPName");
|
||||
|
||||
unless ($code) {
|
||||
$self->userLogger->info('TOTP registration: empty validation form');
|
||||
|
@ -102,11 +102,10 @@ sub run {
|
|||
$self->logger->debug('TOTP code verified');
|
||||
|
||||
# Now code is verified, let's store the master key in persistent data
|
||||
|
||||
my $secret = '';
|
||||
|
||||
# Reading existing 2FDevices
|
||||
$self->logger->debug("Looking for 2F Devices ...");
|
||||
$self->logger->debug("Looking for 2F Devices...");
|
||||
my $_2fDevices;
|
||||
if ( $req->userData->{_2fDevices} ) {
|
||||
$_2fDevices = eval {
|
||||
|
@ -124,7 +123,7 @@ sub run {
|
|||
}
|
||||
|
||||
# Reading existing TOTP
|
||||
my @totp2f = grep { $_->{type} eq "TOTP" } @$_2fDevices;
|
||||
my @totp2f = grep { $_->{type} eq 'TOTP' } @$_2fDevices;
|
||||
unless (@totp2f) {
|
||||
$self->logger->debug("No TOTP Device found");
|
||||
|
||||
|
@ -133,26 +132,20 @@ sub run {
|
|||
}
|
||||
|
||||
# Loading TOTP secret
|
||||
$self->logger->debug("Reading TOTP secret if exists ...");
|
||||
$self->logger->debug("Reading TOTP secret if exists...");
|
||||
$secret = $_->{_secret} foreach (@totp2f);
|
||||
|
||||
if ( $token->{_totp2fSecret} eq $secret ) {
|
||||
return $self->p->sendError( $req, 'totpExistingKey', 200 );
|
||||
}
|
||||
return $self->p->sendError( $req, 'totpExistingKey', 200 )
|
||||
if ( $token->{_totp2fSecret} eq $secret );
|
||||
|
||||
### USER CAN ONLY REGISTER ONE TOTP ###
|
||||
# Delete TOTP previously registered
|
||||
my @keep = ();
|
||||
while (@$_2fDevices) {
|
||||
my $element = shift @$_2fDevices;
|
||||
$self->logger->debug("Looking for TOTP to delete ...");
|
||||
push @keep, $element unless ( $element->{type} eq "TOTP" );
|
||||
}
|
||||
$self->logger->debug("Looking for TOTP to delete...");
|
||||
my $size = my @keep =
|
||||
map { $_->{type} eq 'TOTP' ? () : $_ } @$_2fDevices;
|
||||
|
||||
# Check if user can register one more device
|
||||
my $size = @keep;
|
||||
my $maxSize = $self->conf->{max2FDevices};
|
||||
$self->logger->debug("Nbr 2FDevices = $size / $maxSize");
|
||||
$self->logger->debug("Registered 2F Device(s): $size / $maxSize");
|
||||
if ( $size >= $maxSize ) {
|
||||
$self->userLogger->warn("Max number of 2F devices is reached");
|
||||
return $self->p->sendError( $req, 'maxNumberof2FDevicesReached',
|
||||
|
@ -167,9 +160,8 @@ sub run {
|
|||
_secret => $token->{_totp2fSecret},
|
||||
epoch => $epoch
|
||||
};
|
||||
|
||||
$self->logger->debug(
|
||||
"Append 2F Device : { type => 'TOTP', name => $TOTPName }");
|
||||
"Append 2F Device: { type => 'TOTP', name => $TOTPName }");
|
||||
$self->p->updatePersistentSession( $req,
|
||||
{ _2fDevices => to_json( \@keep ) } );
|
||||
$self->userLogger->notice(
|
||||
|
@ -187,7 +179,7 @@ sub run {
|
|||
my $secret = '';
|
||||
|
||||
# Read existing 2FDevices
|
||||
$self->logger->debug("Looking for 2F Devices ...");
|
||||
$self->logger->debug("Looking for 2F Devices...");
|
||||
my $_2fDevices;
|
||||
if ( $req->userData->{_2fDevices} ) {
|
||||
$_2fDevices = eval {
|
||||
|
@ -215,7 +207,7 @@ sub run {
|
|||
}
|
||||
|
||||
# Loading TOTP secret
|
||||
$self->logger->debug("Reading TOTP secret if exists ...");
|
||||
$self->logger->debug("Reading TOTP secret if exists...");
|
||||
$secret = $_->{_secret} foreach (@totp2f);
|
||||
|
||||
if ( ( $req->param('newkey') and $self->conf->{totp2fUserCanChangeKey} )
|
||||
|
@ -278,8 +270,8 @@ sub run {
|
|||
400 );
|
||||
|
||||
# Read existing 2FDevices
|
||||
$self->logger->debug("Loading 2F Devices ...");
|
||||
my $_2fDevices;
|
||||
$self->logger->debug("Loading 2F Devices...");
|
||||
my ( $_2fDevices, $TOTPName );
|
||||
if ( $req->userData->{_2fDevices} ) {
|
||||
$_2fDevices = eval {
|
||||
from_json( $req->userData->{_2fDevices},
|
||||
|
@ -296,13 +288,12 @@ sub run {
|
|||
}
|
||||
|
||||
# Delete TOTP 2F device
|
||||
my $TOTPName;
|
||||
foreach (@$_2fDevices) {
|
||||
$TOTPName = $_->{name} if $_->{epoch} eq $epoch;
|
||||
}
|
||||
@$_2fDevices = grep { $_->{epoch} ne $epoch } @$_2fDevices;
|
||||
@$_2fDevices = map {
|
||||
if ( $_->{epoch} eq $epoch ) { $TOTPName = $_->{name}; () }
|
||||
else { $_ }
|
||||
} @$_2fDevices;
|
||||
$self->logger->debug(
|
||||
"Delete 2F Device : { type => 'TOTP', epoch => $epoch, name => $TOTPName }"
|
||||
"Delete 2F Device: { type => 'TOTP', epoch => $epoch, name => $TOTPName }"
|
||||
);
|
||||
$self->p->updatePersistentSession( $req,
|
||||
{ _2fDevices => to_json($_2fDevices) } );
|
||||
|
|
|
@ -12,11 +12,9 @@ extends 'Lemonldap::NG::Portal::Main::Plugin',
|
|||
|
||||
# INITIALIZATION
|
||||
|
||||
has prefix => ( is => 'rw', default => 'u' );
|
||||
|
||||
has prefix => ( is => 'rw', default => 'u' );
|
||||
has template => ( is => 'ro', default => 'u2fregister' );
|
||||
|
||||
has logo => ( is => 'rw', default => 'u2f.png' );
|
||||
has logo => ( is => 'rw', default => 'u2f.png' );
|
||||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
|
@ -30,15 +28,19 @@ sub init {
|
|||
sub run {
|
||||
my ( $self, $req, $action ) = @_;
|
||||
my $user = $req->userData->{ $self->conf->{whatToTrace} };
|
||||
unless ($user) {
|
||||
return $self->p->sendError( $req,
|
||||
'No ' . $self->conf->{whatToTrace} . ' found in user data', 500 );
|
||||
}
|
||||
|
||||
return $self->p->sendError( $req,
|
||||
'No ' . $self->conf->{whatToTrace} . ' found in user data', 500 )
|
||||
unless $user;
|
||||
|
||||
# Check if U2F key can be updated
|
||||
return $self->p->sendError( $req, 'notAuthorized', 400 )
|
||||
unless $self->allowedUpdateSfa($req, $action);
|
||||
|
||||
if ( $action eq 'register' ) {
|
||||
|
||||
# Read existing 2FDevices
|
||||
$self->logger->debug("Looking for 2F Devices ...");
|
||||
$self->logger->debug("Looking for 2F Devices...");
|
||||
my $_2fDevices;
|
||||
if ( $req->userData->{_2fDevices} ) {
|
||||
$_2fDevices = eval {
|
||||
|
@ -50,7 +52,6 @@ sub run {
|
|||
return $self->p->sendError( $req, "Corrupted session", 500 );
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
$self->logger->debug("No 2F Device found");
|
||||
$_2fDevices = [];
|
||||
|
@ -59,7 +60,7 @@ sub run {
|
|||
# Check if user can register one more 2F device
|
||||
my $size = @$_2fDevices;
|
||||
my $maxSize = $self->conf->{max2FDevices};
|
||||
$self->logger->debug("Registered 2F Device(s) : $size / $maxSize");
|
||||
$self->logger->debug("Registered 2F Device(s): $size / $maxSize");
|
||||
if ( $size >= $maxSize ) {
|
||||
$self->userLogger->warn("Max number of 2F devices is reached");
|
||||
return $self->p->sendError( $req, 'maxNumberof2FDevicesReached',
|
||||
|
@ -99,7 +100,7 @@ sub run {
|
|||
if ( $keyHandle and $userKey ) {
|
||||
|
||||
# Read existing 2FDevices
|
||||
$self->logger->debug("Looking for 2F Devices ...");
|
||||
$self->logger->debug("Looking for 2F Devices...");
|
||||
my $_2fDevices;
|
||||
if ( $req->userData->{_2fDevices} ) {
|
||||
$_2fDevices = eval {
|
||||
|
@ -115,7 +116,6 @@ sub run {
|
|||
500 );
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
$self->logger->debug("No 2F Device found");
|
||||
$_2fDevices = [];
|
||||
|
@ -132,7 +132,7 @@ sub run {
|
|||
}
|
||||
$keyName =
|
||||
substr( $keyName, 0, $self->conf->{max2FDevicesNameLength} );
|
||||
$self->logger->debug("Key name : $keyName");
|
||||
$self->logger->debug("Key name: $keyName");
|
||||
|
||||
push @{$_2fDevices},
|
||||
{
|
||||
|
@ -143,7 +143,7 @@ sub run {
|
|||
epoch => $epoch
|
||||
};
|
||||
$self->logger->debug(
|
||||
"Append 2F Device : { type => 'U2F', name => $keyName }");
|
||||
"Append 2F Device: { type => 'U2F', name => $keyName }");
|
||||
$self->p->updatePersistentSession( $req,
|
||||
{ _2fDevices => to_json($_2fDevices) } );
|
||||
$self->userLogger->notice(
|
||||
|
@ -182,13 +182,6 @@ sub run {
|
|||
}
|
||||
|
||||
# Get registered keys
|
||||
|
||||
# my @rk;
|
||||
# foreach ( @{ $req->data->{crypter} } ) {
|
||||
# my $k = push @rk,
|
||||
# { keyHandle => $_->{keyHandle}, version => $data->{version} };
|
||||
# }
|
||||
|
||||
my @rk =
|
||||
map { { keyHandle => $_->{keyHandle}, version => $data->{version} } }
|
||||
@{ $req->data->{crypter} };
|
||||
|
@ -230,10 +223,6 @@ sub run {
|
|||
return $self->p->sendError( $req, "U2FAnswerError" );
|
||||
}
|
||||
|
||||
# my $crypter;
|
||||
# foreach ( @{ $req->data->{crypter} } ) {
|
||||
# $crypter = $_ if ( $_->{keyHandle} eq $data->{keyHandle} );
|
||||
# }
|
||||
$crypter = $_
|
||||
foreach grep { $_->{keyHandle} eq $data->{keyHandle} }
|
||||
@{ $req->data->{crypter} };
|
||||
|
@ -246,7 +235,6 @@ sub run {
|
|||
if ( not $crypter->setChallenge($challenge) ) {
|
||||
$self->logger->error(
|
||||
$@ ? $@ : Crypt::U2F::Server::Simple::lastError() );
|
||||
|
||||
return $self->p->sendError( $req, "U2FServerError" );
|
||||
}
|
||||
|
||||
|
@ -269,7 +257,7 @@ sub run {
|
|||
400 );
|
||||
|
||||
# Read existing 2FDevices
|
||||
$self->logger->debug("Looking for 2F Devices ...");
|
||||
$self->logger->debug("Looking for 2F Devices...");
|
||||
my ( $_2fDevices, $keyName );
|
||||
if ( $req->userData->{_2fDevices} ) {
|
||||
$_2fDevices = eval {
|
||||
|
@ -287,15 +275,10 @@ sub run {
|
|||
}
|
||||
|
||||
# Delete U2F device
|
||||
|
||||
# my $keyName;
|
||||
# foreach (@$_2fDevices) {
|
||||
# $keyName = $_->{name} if $_->{epoch} eq $epoch;
|
||||
# }
|
||||
|
||||
$keyName = $_->{name}
|
||||
foreach grep { $_->{epoch} eq $epoch } @$_2fDevices;
|
||||
@$_2fDevices = grep { $_->{epoch} ne $epoch } @$_2fDevices;
|
||||
@$_2fDevices = map {
|
||||
if ( $_->{epoch} eq $epoch ) { $keyName = $_->{name}; () }
|
||||
else { $_ }
|
||||
} @$_2fDevices;
|
||||
$self->logger->debug(
|
||||
"Delete 2F Device : { type => 'U2F', epoch => $epoch, name => $keyName }"
|
||||
);
|
||||
|
@ -318,10 +301,10 @@ sub run {
|
|||
|
||||
sub loadUser {
|
||||
my ( $self, $req ) = @_;
|
||||
$self->logger->debug("Loading user U2F Devices ...");
|
||||
$self->logger->debug("Loading user U2F Devices...");
|
||||
|
||||
# Read existing 2FDevices
|
||||
$self->logger->debug("Looking for 2F Devices ...");
|
||||
$self->logger->debug("Looking for 2F Devices...");
|
||||
my ( $kh, $uk, $_2fDevices );
|
||||
my @u2fs = ();
|
||||
|
||||
|
@ -341,7 +324,7 @@ sub loadUser {
|
|||
|
||||
# Reading existing U2F keys
|
||||
foreach (@$_2fDevices) {
|
||||
$self->logger->debug("Looking for registered U2F key(s) ...");
|
||||
$self->logger->debug("Looking for registered U2F key(s)...");
|
||||
if ( $_->{type} eq 'U2F' ) {
|
||||
unless ( $_->{_userKey} and $_->{_keyHandle} ) {
|
||||
$self->logger->error(
|
||||
|
|
|
@ -15,11 +15,9 @@ extends 'Lemonldap::NG::Portal::Main::Plugin';
|
|||
|
||||
# INITIALIZATION
|
||||
|
||||
has prefix => ( is => 'rw', default => 'yubikey' );
|
||||
|
||||
has prefix => ( is => 'rw', default => 'yubikey' );
|
||||
has template => ( is => 'ro', default => 'yubikey2fregister' );
|
||||
|
||||
has logo => ( is => 'rw', default => 'yubikey.png' );
|
||||
has logo => ( is => 'rw', default => 'yubikey.png' );
|
||||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
|
@ -36,6 +34,16 @@ sub run {
|
|||
'No ' . $self->conf->{whatToTrace} . ' found in user data', 500 )
|
||||
unless $user;
|
||||
|
||||
# Check if UBK key can be updated
|
||||
return $self->p->sendHtml(
|
||||
$req, 'error',
|
||||
params => {
|
||||
MAIN_LOGO => $self->conf->{portalMainLogo},
|
||||
RAW_ERROR => 'notAuthorized',
|
||||
AUTH_ERROR_TYPE => 'warning',
|
||||
}
|
||||
) unless $self->allowedUpdateSfa($req, $action);
|
||||
|
||||
if ( $action eq 'register' ) {
|
||||
my $otp = $req->param('otp');
|
||||
my $UBKName = $req->param('UBKName');
|
||||
|
@ -48,7 +56,7 @@ sub run {
|
|||
return $self->p->sendError( $req, 'badName', 200 );
|
||||
}
|
||||
$UBKName = substr( $UBKName, 0, $self->conf->{max2FDevicesNameLength} );
|
||||
$self->logger->debug("Yubikey name : $UBKName");
|
||||
$self->logger->debug("Yubikey name: $UBKName");
|
||||
|
||||
if ( $otp
|
||||
and length($otp) > $self->conf->{yubikey2fPublicIDSize} )
|
||||
|
@ -56,7 +64,7 @@ sub run {
|
|||
my $key = substr( $otp, 0, $self->conf->{yubikey2fPublicIDSize} );
|
||||
|
||||
# Read existing 2FDevices
|
||||
$self->logger->debug("Looking for 2F Devices ...");
|
||||
$self->logger->debug("Looking for 2F Devices...");
|
||||
my $_2fDevices;
|
||||
if ( $req->userData->{_2fDevices} ) {
|
||||
$_2fDevices = eval {
|
||||
|
@ -78,7 +86,7 @@ sub run {
|
|||
# Search if the Yubikey is already registered
|
||||
my $SameUBKFound = 0;
|
||||
foreach (@$_2fDevices) {
|
||||
$self->logger->debug("Reading Yubikeys ...");
|
||||
$self->logger->debug("Reading Yubikeys...");
|
||||
if ( $_->{_yubikey} eq $key ) {
|
||||
$SameUBKFound = 1;
|
||||
last;
|
||||
|
@ -100,7 +108,7 @@ sub run {
|
|||
# Check if user can register one more device
|
||||
my $size = @$_2fDevices;
|
||||
my $maxSize = $self->conf->{max2FDevices};
|
||||
$self->logger->debug("Nbr 2FDevices = $size / $maxSize");
|
||||
$self->logger->debug("Registered 2F Device(s): $size / $maxSize");
|
||||
if ( $size >= $maxSize ) {
|
||||
$self->userLogger->warn("Max number of 2F devices is reached");
|
||||
return $self->p->sendHtml(
|
||||
|
@ -120,9 +128,8 @@ sub run {
|
|||
_yubikey => $key,
|
||||
epoch => $epoch
|
||||
};
|
||||
|
||||
$self->logger->debug(
|
||||
"Append 2F Device : { type => 'UBK', name => $UBKName }");
|
||||
"Append 2F Device: { type => 'UBK', name => $UBKName }");
|
||||
$self->p->updatePersistentSession( $req,
|
||||
{ _2fDevices => to_json($_2fDevices) } );
|
||||
$self->userLogger->notice(
|
||||
|
@ -161,8 +168,8 @@ sub run {
|
|||
400 );
|
||||
|
||||
# Read existing 2FDevices
|
||||
$self->logger->debug("Looking for 2F Devices ...");
|
||||
my $_2fDevices;
|
||||
$self->logger->debug("Looking for 2F Devices...");
|
||||
my ( $_2fDevices, $UBKName );
|
||||
if ( $req->userData->{_2fDevices} ) {
|
||||
$_2fDevices = eval {
|
||||
from_json( $req->userData->{_2fDevices},
|
||||
|
@ -179,13 +186,12 @@ sub run {
|
|||
}
|
||||
|
||||
# Delete Yubikey device
|
||||
my $UBKName;
|
||||
foreach (@$_2fDevices) {
|
||||
$UBKName = $_->{name} if $_->{epoch} eq $epoch;
|
||||
}
|
||||
@$_2fDevices = grep { $_->{epoch} ne $epoch } @$_2fDevices;
|
||||
@$_2fDevices = map {
|
||||
if ( $_->{epoch} eq $epoch ) { $UBKName = $_->{name}; () }
|
||||
else { $_ }
|
||||
} @$_2fDevices;
|
||||
$self->logger->debug(
|
||||
"Delete 2F Device : { type => 'UBK', epoch => $epoch, name => $UBKName }"
|
||||
"Delete 2F Device: { type => 'UBK', epoch => $epoch, name => $UBKName }"
|
||||
);
|
||||
$self->p->updatePersistentSession( $req,
|
||||
{ _2fDevices => to_json($_2fDevices) } );
|
||||
|
|
|
@ -23,7 +23,6 @@ extends 'Lemonldap::NG::Portal::Main::SecondFactor',
|
|||
# INITIALIZATION
|
||||
|
||||
has prefix => ( is => 'ro', default => 'totp' );
|
||||
|
||||
has logo => ( is => 'rw', default => 'totp.png' );
|
||||
|
||||
sub init {
|
||||
|
|
|
@ -24,9 +24,7 @@ extends 'Lemonldap::NG::Portal::Main::SecondFactor',
|
|||
# INITIALIZATION
|
||||
|
||||
has rule => ( is => 'rw' );
|
||||
|
||||
has prefix => ( is => 'ro', default => 'u' );
|
||||
|
||||
has logo => ( is => 'rw', default => 'u2f.png' );
|
||||
|
||||
sub init {
|
||||
|
@ -109,6 +107,7 @@ sub run {
|
|||
|
||||
sub verify {
|
||||
my ( $self, $req, $session ) = @_;
|
||||
my $crypter;
|
||||
|
||||
# Check U2F signature
|
||||
if ( my $resp = $req->param('signature')
|
||||
|
@ -136,10 +135,9 @@ sub verify {
|
|||
$req->error(PE_ERROR);
|
||||
return $self->fail($req);
|
||||
}
|
||||
my $crypter;
|
||||
foreach ( @{ $req->data->{crypter} } ) {
|
||||
$crypter = $_ if ( $_->{keyHandle} eq $data->{keyHandle} );
|
||||
}
|
||||
$crypter = $_
|
||||
foreach grep { $_->{keyHandle} eq $data->{keyHandle} }
|
||||
@{ $req->data->{crypter} };
|
||||
unless ($crypter) {
|
||||
$self->userLogger->error("Unregistered U2F key");
|
||||
$req->error(PE_BADCREDENTIALS);
|
||||
|
@ -245,7 +243,7 @@ sub loadUser {
|
|||
'U2F error: ' . Crypt::U2F::Server::u2fclib_getError() );
|
||||
}
|
||||
}
|
||||
return -1 unless (@crypters);
|
||||
return -1 unless @crypters;
|
||||
|
||||
$req->data->{crypter} = \@crypters;
|
||||
return 1;
|
||||
|
|
|
@ -22,9 +22,7 @@ extends 'Lemonldap::NG::Portal::Main::SecondFactor';
|
|||
# INITIALIZATION
|
||||
|
||||
has prefix => ( is => 'ro', default => 'yubikey' );
|
||||
|
||||
has logo => ( is => 'rw', default => 'yubikey.png' );
|
||||
|
||||
has yubi => ( is => 'rw' );
|
||||
|
||||
sub init {
|
||||
|
@ -88,8 +86,7 @@ sub init {
|
|||
|
||||
sub _findYubikey {
|
||||
my ( $self, $req, $sessionInfo ) = @_;
|
||||
my $yubikey;
|
||||
my $_2fDevices;
|
||||
my ( $yubikey, $_2fDevices );
|
||||
|
||||
# First, lookup from session attribute
|
||||
if ( $self->conf->{yubikey2fFromSessionAttribute} ) {
|
||||
|
|
|
@ -115,10 +115,7 @@ sub getUser {
|
|||
my ( $self, $req, %args ) = @_;
|
||||
|
||||
$self->validateLdap;
|
||||
|
||||
unless ( $self->ldap ) {
|
||||
return PE_LDAPCONNECTFAILED;
|
||||
}
|
||||
return PE_LDAPCONNECTFAILED unless $self->ldap;
|
||||
|
||||
$self->bind();
|
||||
|
||||
|
@ -144,7 +141,10 @@ sub getUser {
|
|||
return PE_BADCREDENTIALS;
|
||||
}
|
||||
unless ( $req->data->{ldapentry} = $mesg->entry(0) ) {
|
||||
$self->userLogger->warn("$req->{user} was not found in LDAP directory (".$req->address.")");
|
||||
$self->userLogger->warn(
|
||||
"$req->{user} was not found in LDAP directory ("
|
||||
. $req->address
|
||||
. ")" );
|
||||
eval { $self->p->_authentication->setSecurity($req) };
|
||||
return PE_BADCREDENTIALS;
|
||||
}
|
||||
|
@ -168,8 +168,8 @@ sub bind {
|
|||
my $self = shift;
|
||||
|
||||
$self->validateLdap;
|
||||
|
||||
return undef unless ( $self->ldap );
|
||||
return undef unless $self->ldap;
|
||||
|
||||
my $msg = $self->ldap->bind(@_);
|
||||
if ( $msg->code ) {
|
||||
$self->logger->error( $msg->error );
|
||||
|
|
|
@ -28,9 +28,8 @@ sub restCall {
|
|||
$hreq->header( 'Content-Type' => 'application/json' );
|
||||
$hreq->content( to_json($content) );
|
||||
my $resp = $self->ua->request($hreq);
|
||||
unless ( $resp->is_success ) {
|
||||
die $resp->status_line;
|
||||
}
|
||||
die $resp->status_line unless $resp->is_success;
|
||||
|
||||
my $res = eval { from_json( $resp->content ) };
|
||||
die "Bad REST response: $@" if ($@);
|
||||
if ( ref($res) ne "HASH" ) {
|
||||
|
|
|
@ -308,6 +308,29 @@ sub loadIDPs {
|
|||
}
|
||||
$self->logger->debug("Set encryption mode $encryption_mode on IDP $_");
|
||||
|
||||
# Set signature method if overriden
|
||||
my $signature_method = $self->conf->{samlIDPMetaDataOptions}->{$_}
|
||||
->{samlIDPMetaDataOptionsSignatureMethod};
|
||||
if ($signature_method) {
|
||||
my $lasso_signature_method =
|
||||
$self->getSignatureMethod($signature_method);
|
||||
|
||||
unless (
|
||||
$self->setProviderSignatureMethod(
|
||||
$self->lassoServer->get_provider($entityID),
|
||||
$lasso_signature_method
|
||||
)
|
||||
)
|
||||
{
|
||||
$self->logger->error(
|
||||
"Unable to set signature method $signature_method on IDP $_"
|
||||
);
|
||||
next;
|
||||
}
|
||||
$self->logger->debug(
|
||||
"Set signature method $signature_method on IDP $_");
|
||||
}
|
||||
|
||||
# Set display options
|
||||
$self->idpList->{$entityID}->{displayName} =
|
||||
$self->conf->{samlIDPMetaDataOptions}->{$_}
|
||||
|
@ -414,6 +437,29 @@ sub loadSPs {
|
|||
}
|
||||
$self->logger->debug("Set encryption mode $encryption_mode on SP $_");
|
||||
|
||||
# Set signature method if overriden
|
||||
my $signature_method = $self->conf->{samlSPMetaDataOptions}->{$_}
|
||||
->{samlSPMetaDataOptionsSignatureMethod};
|
||||
if ($signature_method) {
|
||||
my $lasso_signature_method =
|
||||
$self->getSignatureMethod($signature_method);
|
||||
|
||||
unless (
|
||||
$self->setProviderSignatureMethod(
|
||||
$self->lassoServer->get_provider($entityID),
|
||||
$lasso_signature_method
|
||||
)
|
||||
)
|
||||
{
|
||||
$self->logger->error(
|
||||
"Unable to set signature method $signature_method on SP $_"
|
||||
);
|
||||
next;
|
||||
}
|
||||
$self->logger->debug(
|
||||
"Set signature method $signature_method on SP $_");
|
||||
}
|
||||
|
||||
my $rule = $self->conf->{samlSPMetaDataOptions}->{$_}
|
||||
->{samlSPMetaDataOptionsRule};
|
||||
if ( length $rule ) {
|
||||
|
@ -3166,15 +3212,44 @@ sub getSignatureMethod {
|
|||
eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA1';
|
||||
my $signature_method_rsa_sha256 =
|
||||
eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA256';
|
||||
my $signature_method_rsa_sha384 =
|
||||
eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA384';
|
||||
my $signature_method_rsa_sha512 =
|
||||
eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA512';
|
||||
my $signature_method_none = eval 'Lasso::Constants::SIGNATURE_METHOD_NONE';
|
||||
|
||||
return $signature_method_rsa_sha1
|
||||
if ( $signature_method =~ /^RSA_SHA1$/i );
|
||||
return $signature_method_rsa_sha256
|
||||
if ( $signature_method =~ /^RSA_SHA256$/i );
|
||||
return $signature_method_rsa_sha384
|
||||
if ( $signature_method =~ /^RSA_SHA384$/i );
|
||||
return $signature_method_rsa_sha512
|
||||
if ( $signature_method =~ /^RSA_SHA512$/i );
|
||||
return $signature_method_none;
|
||||
}
|
||||
|
||||
## @method boolean setProviderSignatureMethod(Lasso::Provider provider, int signature_method)
|
||||
# Set signature method on a provider
|
||||
# @param provider Lasso::Provider object
|
||||
# @param signature_method Lasso signature method
|
||||
# @return result
|
||||
sub setProviderSignatureMethod {
|
||||
my ( $self, $provider, $signature_method ) = @_;
|
||||
|
||||
eval {
|
||||
my $key = Lasso::Key::new_for_signature_from_file(
|
||||
$self->conf->{samlServicePrivateKeySig},
|
||||
$self->conf->{samlServicePrivateKeySigPwd} || '',
|
||||
$signature_method,
|
||||
$self->conf->{samlServicePublicKeySig}
|
||||
);
|
||||
$provider->set_server_signing_key($key);
|
||||
};
|
||||
|
||||
return $self->checkLassoError($@);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
@ -3540,6 +3615,10 @@ Get query string with or without CGI query_string() method
|
|||
|
||||
Return Lasso signature method
|
||||
|
||||
=head2 setProviderSignatureMethod
|
||||
|
||||
Set signature method on a provider
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Lemonldap::NG::Portal::Auth::SAML>, L<Lemonldap::NG::Portal::UserDBSAML>
|
||||
|
|
|
@ -104,6 +104,63 @@ sub createNotification {
|
|||
}
|
||||
}
|
||||
|
||||
sub allowedUpdateSfa {
|
||||
my ( $self, $req, $action ) = @_;
|
||||
my $user = $req->userData->{ $self->conf->{whatToTrace} };
|
||||
my $res = 1;
|
||||
|
||||
# Test action
|
||||
if ( $action && $action eq 'delete' ) {
|
||||
my $module = lc ref $self;
|
||||
$module = ( $module =~ /2f::register::(\w+)$/ )[0];
|
||||
$module =~ s/2f//;
|
||||
$self->logger->debug("$user request to delete ${module}2f device");
|
||||
if ( $self->{conf}->{"${module}2fAuthnLevel"}
|
||||
&& $req->userData->{authenticationLevel} <
|
||||
$self->{conf}->{"${module}2fAuthnLevel"} )
|
||||
{
|
||||
$self->userLogger->warn(
|
||||
"$user request to delete ${module}2f device rejected due to insufficient authentication level!"
|
||||
);
|
||||
$self->logger->debug(
|
||||
"authLevel: $req->{userData}->{authenticationLevel} < requiredLevel: "
|
||||
. $self->{conf}->{"${module}2fAuthnLevel"} );
|
||||
undef $res;
|
||||
}
|
||||
}
|
||||
|
||||
# Test if impersonation is in progress
|
||||
if ( $res && $self->conf->{impersonationRule} ) {
|
||||
$self->logger->debug('Impersonation plugin is enabled');
|
||||
if ( $req->userData->{"$self->{conf}->{impersonationPrefix}_user"}
|
||||
&& $req->userData->{"$self->{conf}->{impersonationPrefix}_user"} ne
|
||||
$req->userData->{_user} )
|
||||
{
|
||||
$self->userLogger->warn(
|
||||
"Impersonation in progress! $user is not allowed to update 2FA."
|
||||
);
|
||||
undef $res;
|
||||
}
|
||||
}
|
||||
|
||||
# Test if contextSwitching is in progress
|
||||
if ( $res && $self->conf->{contextSwitchingRule} ) {
|
||||
$self->logger->debug('ContextSwitching plugin is enabled');
|
||||
if (
|
||||
$req->userData->{
|
||||
"$self->{conf}->{contextSwitchingPrefix}_session_id"}
|
||||
&& !$self->conf->{contextSwitchingAllowed2fModifications} )
|
||||
{
|
||||
$self->userLogger->warn(
|
||||
"ContextSwitching in progress! $user is not allowed to update 2FA."
|
||||
);
|
||||
undef $res;
|
||||
}
|
||||
}
|
||||
$self->userLogger->info("$user is allowed to update 2FA") if $res;
|
||||
return $res;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
|
|
|
@ -32,8 +32,10 @@ our @pList = (
|
|||
portalDisplayFavApps => '::Plugins::FavApps',
|
||||
contextSwitchingRule => '::Plugins::ContextSwitching',
|
||||
decryptValueRule => '::Plugins::DecryptValue',
|
||||
globalLogoutRule => '::Plugins::GlobalLogout',
|
||||
refreshSessions => '::Plugins::Refresh',
|
||||
adaptativeAuthenticationLevelRules =>
|
||||
'::Plugins::AdaptativeAuthenticationLevel',
|
||||
globalLogoutRule => '::Plugins::GlobalLogout',
|
||||
refreshSessions => '::Plugins::Refresh',
|
||||
);
|
||||
|
||||
##@method list enabledPlugins
|
||||
|
|
|
@ -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;
|
|
@ -177,7 +177,18 @@ sub activeSessions {
|
|||
$self->module->searchOn( $moduleOptions, $self->conf->{whatToTrace},
|
||||
$user );
|
||||
|
||||
$self->logger->debug("Building array ref with sessions info...");
|
||||
$self->logger->debug('Remove corrupted sessions...');
|
||||
my $corrupted = 0;
|
||||
foreach ( keys %$sessions ) {
|
||||
unless ( $sessions->{$_}->{_session_kind} eq 'SSO' ) {
|
||||
delete $sessions->{$_};
|
||||
$corrupted++;
|
||||
}
|
||||
}
|
||||
$self->logger->info("$corrupted corrupted session(s) removed")
|
||||
if $corrupted;
|
||||
|
||||
$self->logger->debug('Build an array ref with sessions info...');
|
||||
@$activeSessions =
|
||||
map {
|
||||
my $epoch;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"PE-7":"Vous avez été déconnecté",
|
||||
"PE-6":"Le mot de passe a été changé",
|
||||
"PE-6":"Le mot de passe a été changé avec succès",
|
||||
"PE0":"Utilisateur authentifié",
|
||||
"PE1":"Votre session a expiré, vous devez vous ré-authentifier",
|
||||
"PE2":"Identifiant ou mot de passe non renseigné",
|
||||
|
@ -237,7 +237,7 @@
|
|||
"proxyError": "Mauvaise passerelle : impossible de joindre le serveur amont",
|
||||
"pwd":"Mot de passe",
|
||||
"pwdChange":"Changement de mot de passe",
|
||||
"pwdChanged":"Your password has been successfully changed!",
|
||||
"pwdChanged":"Votre mot de passe a été changé avec succès !",
|
||||
"pwdResetAlreadyIssued":"Une demande de réinitialisation de mot de passe a déjà été faite le ",
|
||||
"pwdWillExpire":"%s jours, %s heures, %s minutes et %s secondes avant expiration de votre mot de passe, pensez à le changer !",
|
||||
"radius2f":"Radius",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use lib 'inc';
|
||||
use Test::More;
|
||||
use strict;
|
||||
use URI;
|
||||
use URI::QueryParam;
|
||||
use IO::String;
|
||||
use LWP::UserAgent;
|
||||
use LWP::Protocol::PSGI;
|
||||
|
@ -11,7 +13,7 @@ BEGIN {
|
|||
require 't/saml-lib.pm';
|
||||
}
|
||||
|
||||
my $maintests = 19;
|
||||
my $maintests = 21;
|
||||
my $debug = 'error';
|
||||
my ( $issuer, $sp, $res );
|
||||
|
||||
|
@ -152,6 +154,14 @@ SKIP: {
|
|||
expectForm( $res, 'auth.sp.com', '/saml/proxySingleSignOnPost',
|
||||
'SAMLResponse', 'RelayState' );
|
||||
|
||||
my ($resp) = $query =~ qr/SAMLResponse=([^&]*)/;
|
||||
my $message = decode_base64( URI::Escape::uri_unescape $resp);
|
||||
like(
|
||||
$message,
|
||||
qr@SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"@,
|
||||
"Signed using SHA-256"
|
||||
);
|
||||
|
||||
# Post SAML response to SP
|
||||
switch ('sp');
|
||||
ok(
|
||||
|
@ -190,6 +200,14 @@ SKIP: {
|
|||
( $url, $query ) = expectRedirection( $res,
|
||||
qr#^http://auth.idp.com(/saml/singleLogout)\?(SAMLRequest=.+)# );
|
||||
|
||||
my $uri = URI->new;
|
||||
$uri->query($query);
|
||||
is(
|
||||
$uri->query_param("SigAlg"),
|
||||
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384',
|
||||
'SHA256 used to sign Logout Request'
|
||||
);
|
||||
|
||||
# Push SAML logout request to IdP
|
||||
switch ('issuer');
|
||||
ok(
|
||||
|
@ -264,6 +282,7 @@ sub issuer {
|
|||
samlSPMetaDataOptionsSignSLOMessage => 1,
|
||||
samlSPMetaDataOptionsCheckSSOMessageSignature => 1,
|
||||
samlSPMetaDataOptionsCheckSLOMessageSignature => 1,
|
||||
samlSPMetaDataOptionsSignatureMethod => "RSA_SHA256",
|
||||
}
|
||||
},
|
||||
samlSPMetaDataExportedAttributes => {
|
||||
|
@ -281,6 +300,7 @@ sub issuer {
|
|||
samlServicePrivateKeySig => saml_key_idp_private_sig,
|
||||
samlServicePublicKeyEnc => saml_key_idp_public_enc,
|
||||
samlServicePublicKeySig => saml_key_idp_public_sig,
|
||||
samlServiceSignatureMethod => "RSA_SHA1",
|
||||
samlSPMetaDataXML => {
|
||||
"sp.com" => {
|
||||
samlSPMetaDataXML =>
|
||||
|
@ -319,6 +339,7 @@ sub sp {
|
|||
samlIDPMetaDataOptionsCheckSSOMessageSignature => 1,
|
||||
samlIDPMetaDataOptionsCheckSLOMessageSignature => 1,
|
||||
samlIDPMetaDataOptionsForceUTF8 => 1,
|
||||
samlIDPMetaDataOptionsSignatureMethod => "RSA_SHA384",
|
||||
}
|
||||
},
|
||||
samlIDPMetaDataExportedAttributes => {
|
||||
|
@ -340,6 +361,7 @@ sub sp {
|
|||
samlServicePrivateKeyEnc => saml_key_sp_private_enc,
|
||||
samlServicePrivateKeySig => saml_key_sp_private_sig,
|
||||
samlServicePublicKeyEnc => saml_key_sp_public_enc,
|
||||
samlServiceSignatureMethod => "RSA_SHA1",
|
||||
samlSPSSODescriptorAuthnRequestsSigned => 1,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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() );
|
|
@ -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() );
|
|
@ -23,13 +23,13 @@ SKIP: {
|
|||
requireToken => 0,
|
||||
checkUser => 1,
|
||||
impersonationRule => 1,
|
||||
checkUserDisplayPersistentInfo => 0,
|
||||
checkUserDisplayEmptyValues => 0,
|
||||
checkUserDisplayPersistentInfo => 0,
|
||||
checkUserDisplayEmptyValues => 0,
|
||||
checkUserDisplayComputedSession => 1,
|
||||
impersonationMergeSSOgroups => 1,
|
||||
totp2fSelfRegistration => 1,
|
||||
totp2fActivation => 1,
|
||||
totp2fAuthnLevel => 8
|
||||
impersonationMergeSSOgroups => 1,
|
||||
totp2fSelfRegistration => 1,
|
||||
totp2fActivation => 1,
|
||||
totp2fAuthnLevel => 8
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -47,7 +47,7 @@ SKIP: {
|
|||
'Get Menu'
|
||||
);
|
||||
ok( $res->[2]->[0] !~ m%<span trspan="sfaManager">sfaManager</span>%,
|
||||
'sfaManager link found' )
|
||||
'sfaManager link not found' )
|
||||
or print STDERR Dumper( $res->[2]->[0] );
|
||||
|
||||
# TOTP form
|
||||
|
@ -113,7 +113,7 @@ SKIP: {
|
|||
eval { $res = JSON::from_json( $res->[2]->[0] ) };
|
||||
ok( not($@), 'Content is JSON' )
|
||||
or explain( $res->[2]->[0], 'JSON content' );
|
||||
ok( $res->{result} == 1, 'Key is registered' );
|
||||
ok( $res->{result} == 1, 'TOTP is registered' );
|
||||
|
||||
# Try to sign-in
|
||||
$client->logout($id);
|
||||
|
|
|
@ -46,7 +46,7 @@ SKIP: {
|
|||
'Get Menu'
|
||||
);
|
||||
ok( $res->[2]->[0] !~ m%<span trspan="sfaManager">sfaManager</span>%,
|
||||
'sfaManager link found' )
|
||||
'sfaManager link not found' )
|
||||
or print STDERR Dumper( $res->[2]->[0] );
|
||||
|
||||
# U2F form
|
||||
|
@ -210,8 +210,7 @@ JjTJecOOS+88fK8qL1TrYv5rapIdqUI7aQ==
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
count($maintests);
|
||||
|
||||
clean_sessions();
|
||||
|
||||
done_testing( count() );
|
||||
|
|
Loading…
Reference in New Issue