Merge branch 'v2.0' into 2600

This commit is contained in:
Alexandre KARIM 2021-09-08 17:25:46 +02:00
commit aa0db663c6
55 changed files with 1148 additions and 61 deletions

1
debian/control vendored
View File

@ -282,6 +282,7 @@ Depends: ${misc:Depends},
liblemonldap-ng-handler-perl (= ${binary:Version}),
libtext-unidecode-perl,
libregexp-assemble-perl,
liblist-moreutils-perl,
libemail-date-format-perl
Recommends: gsfonts,
libcrypt-openssl-bignum-perl,

View File

@ -17,6 +17,7 @@ Applications
applications/drupal
applications/fusiondirectory
applications/gerrit
applications/gitea
applications/gitlab
applications/glpi
applications/googleapps
@ -99,6 +100,7 @@ Application Configuration
.. image:: applications/fusiondirectory-logo.jpg :doc:`FusionDirectory<applications/fusiondirectory>`
.. image:: applications/gerrit_logo.png :doc:`Gerrit<applications/gerrit>`
.. image:: applications/gitlab_logo.png :doc:`Gitlab<applications/gitlab>` ✔ ✔
.. image:: applications/gitea_logo.png :doc:`Gitea<applications/gitea>`
.. image:: applications/glpi_logo.png :doc:`GLPI<applications/glpi>`
.. image:: applications/googleapps_logo.png :doc:`Google Apps<applications/googleapps>`
.. image:: applications/grafana_logo.png :doc:`Grafana<applications/grafana>`

View File

@ -0,0 +1,67 @@
Gitea
=====
|logo|
Presentation
------------
`Gitea <https://gitea.io/>`__ is a community managed lightweight
code hosting solution written in Go. It is published under the MIT license.
It can be configured to authenticate users with :doc:`OpenID Connect <../idpopenidconnect>`.
Configuration
--------------
LL:NG
~~~~~
Make sure you have already
:doc:`enabled OpenID Connect<../idpopenidconnect>` on your LemonLDAP::NG
server
Make sure you have generated a set of signing keys in
``OpenID Connect Service`` » ``Security`` » ``Keys``
You also need to set a Signing key ID to a non-empty value of your choice.
Then, add a Relaying Party with the following configuration:
- Options » Basic » Client ID : choose a client ID, such as ``gitea``
- Options » Basic » Client Secret : choose a client secret, such as ``xxxx``
- Options » Basic » Allowed redirection address : ``https://git.example.com/user/oauth2/NAME/callback``
- Options » ID Token Signature Algorithm : ``RS256``
- No Exported Attributes needed
.. note::
The redirection address is built like this: ``<Gitea service URL>`` ``/user/oauth2/`` ``<Name of the OIDC authentication source in Gitea>`` ``/callback``
Gitea
~~~~~
Go in administration panel and create a new authentication source:
|screenshot_admin|
Configure settings:
- Authentication name: set here the value used for the redirection address
- OAuth2 Provider: set OpenID Connect
- Client ID: the Client ID configured on LL::NG side
- Client Secret: the Client Secret configured on LL::NG side
- OpenID Connect Auto Discovery URL: use the default OIDC configuration URL of your LL::NG server
- Enable the authentication source
Usage
-----
In Gitea login screen, a new OpenID logo appears at the bottom. Click on it to authenticate.
At first connection, the user must associate his account to an existing one (local or LDAP). The assocation is then remembered for further connections.
.. |logo| image:: /applications/gitea_logo.png
:class: align-center
.. |screenshot_admin| image:: /applications/gitea_oidc_config.png
:class: align-center

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -31,7 +31,7 @@ As an RP, LL::NG supports a lot of OpenID Connect features:
- Logout on EndSession end point
You can use this authentication module to link your LL::NG server to any
OpenID Connect Provider. Here are some examples, witch their specific
OpenID Connect Provider. Here are some examples, with their specific
documentation:
@ -40,13 +40,14 @@ documentation:
authopenidconnect_google
authopenidconnect_franceconnect
authopenidconnect_prosanteconnect
=============== ==================
Google France Connect
=============== ==================
|google| |franceconnect|
=============== ==================
=============== ================== ==================
Google France Connect Pro Santé Connect
=============== ================== ==================
|google| |franceconnect| |prosanteconnect|
=============== ================== ==================
.. |google| image:: applications/google_logo.png
:target: authopenidconnect_google.html
@ -54,11 +55,14 @@ Google France Connect
.. |franceconnect| image:: applications/franceconnect_logo.png
:target: authopenidconnect_franceconnect.html
.. |prosanteconnect| image:: applications/prosanteconnect_logo.png
:target: authopenidconnect_prosanteconnect.html
.. attention::
OpenID-Connect specification is not finished for logout
OpenID Connect specification is not finished for logout
propagation. So logout initiated by relaying-party will be forward to
OpenID-Connect provider but logout initiated by the provider (or another
OpenID Connect provider but logout initiated by the provider (or another
RP) will not be propagated. LLNG will implement this when spec will be
published.
@ -68,7 +72,7 @@ Configuration
OpenID Connect Service
~~~~~~~~~~~~~~~~~~~~~~
See :doc:`OpenIDConnect service<openidconnectservice>` configuration
See :doc:`OpenID Connect service<openidconnectservice>` configuration
chapter.
Authentication and UserDB

View File

@ -0,0 +1,209 @@
Pro Santé Connect
=================
|logo|
Presentation
------------
`Pro Santé Connect <https://tech.esante.gouv.fr/outils-services/pro-sante-connect-e-cps/presentation-generale>`__ is
a French identity provider for healthcare professionals. It relies on OpenID Connect protocol.
Register on Pro Santé Connect
-----------------------------
Once :doc:`OpenID Connect service<openidconnectservice>` is configured,
you need to register to Pro Santé Connect.
Go on https://integrateurs-cps.asipsante.fr.
You need to provide the callback URLs, for example
https://auth.domain.com/?openidcallback=1.
And also a logout URL, for example
https://auth.domain.com/?logout=1.
You will then get a ``client_id`` and a ``client_secret``.
Declare Pro Santé Connect in your LL::NG server
-----------------------------------------------
Go in Manager and create a new OpenID Connect provider. You can call it
``psc-connect`` for example.
Click on ``Metadata`` and set manually the metadata of the service.
For the sandbox server:
.. code-block:: javascript
{
"issuer": "https://auth.bas.esw.esante.gouv.fr/auth/realms/esante-wallet",
"authorization_endpoint": "https://wallet.bas.esw.esante.gouv.fr/auth",
"token_endpoint": "https://auth.bas.esw.esante.gouv.fr/auth/realms/esante-wallet/protocol/openid-connect/token",
"introspection_endpoint": "https://auth.bas.esw.esante.gouv.fr/auth/realms/esante-wallet/protocol/openid-connect/token/introspect",
"userinfo_endpoint": "https://auth.bas.esw.esante.gouv.fr/auth/realms/esante-wallet/protocol/openid-connect/userinfo",
"end_session_endpoint": "https://auth.bas.esw.esante.gouv.fr/auth/realms/esante-wallet/protocol/openid-connect/logout",
"jwks_uri": "https://auth.bas.esw.esante.gouv.fr/auth/realms/esante-wallet/protocol/openid-connect/certs",
"check_session_iframe": "https://auth.bas.esw.esante.gouv.fr/auth/realms/esante-wallet/protocol/openid-connect/login-status-iframe.html",
"grant_types_supported": [
"authorization_code",
"implicit",
"refresh_token",
"password",
"client_credentials"
],
"response_types_supported": [
"code",
"none",
"id_token",
"token",
"id_token token",
"code id_token",
"code token",
"code id_token token"
],
"subject_types_supported": [
"public",
"pairwise"
],
"id_token_signing_alg_values_supported": [
"PS384",
"ES384",
"RS384",
"HS256",
"HS512",
"ES256",
"RS256",
"HS384",
"ES512",
"PS256",
"PS512",
"RS512"
],
"id_token_encryption_alg_values_supported": [
"RSA-OAEP",
"RSA1_5"
],
"id_token_encryption_enc_values_supported": [
"A256GCM",
"A192GCM",
"A128GCM",
"A128CBC-HS256",
"A192CBC-HS384",
"A256CBC-HS512"
],
"userinfo_signing_alg_values_supported": [
"PS384",
"ES384",
"RS384",
"HS256",
"HS512",
"ES256",
"RS256",
"HS384",
"ES512",
"PS256",
"PS512",
"RS512",
"none"
],
"request_object_signing_alg_values_supported": [
"PS384",
"ES384",
"RS384",
"HS256",
"HS512",
"ES256",
"RS256",
"HS384",
"ES512",
"PS256",
"PS512",
"RS512",
"none"
],
"response_modes_supported": [
"query",
"fragment",
"form_post"
],
"registration_endpoint": "https://auth.bas.esw.esante.gouv.fr/auth/realms/esante-wallet/clients-registrations/openid-connect",
"token_endpoint_auth_methods_supported": [
"private_key_jwt",
"client_secret_basic",
"client_secret_post",
"tls_client_auth",
"client_secret_jwt"
],
"token_endpoint_auth_signing_alg_values_supported": [
"PS384",
"ES384",
"RS384",
"HS256",
"HS512",
"ES256",
"RS256",
"HS384",
"ES512",
"PS256",
"PS512",
"RS512"
],
"claims_supported": [
"aud",
"sub",
"iss",
"auth_time",
"name",
"given_name",
"family_name",
"preferred_username",
"email",
"acr"
],
"claim_types_supported": [
"normal"
],
"claims_parameter_supported": false,
"scopes_supported": [
"openid",
"address",
"email",
"identity",
"microprofile-jwt",
"offline_access",
"phone",
"profile",
"roles",
"scope_1",
"scope_2",
"scope_all",
"web-origins",
"eidas2"
],
"request_parameter_supported": true,
"request_uri_parameter_supported": true,
"code_challenge_methods_supported": [
"plain",
"S256"
],
"tls_client_certificate_bound_access_tokens": true
}
You should alos import JWKS data from https://auth.bas.esw.esante.gouv.fr/auth/realms/esante-wallet/protocol/openid-connect/certs
directly in configuration to avoid requests to reload them.
Go in ``Exported attributes`` to choose which attributes you want to collect.
Read the technical documentation to know available attributes:
https://tech.esante.gouv.fr/outils-services/pro-sante-connect-e-cps/documentation-technique
Now go in ``Options``:
- Register the ``client_id`` and ``client_secret`` given by Pro Santé Connect
- In ``Scopes`` set ``openid scope_all``
- In ``ACR values`` set ``eidas2``
- You can also set the name and the logo
.. |logo| image:: /applications/prosanteconnect_logo.png
:class: align-center

View File

@ -5,4 +5,5 @@ Attacks and Protection
:maxdepth: 1
bruteforceprotection
newlocationwarning
safejail

View File

@ -0,0 +1,55 @@
|image0|
New Location Warning Plugin
===========================
Presentation
------------
This plugin allows LL::NG to send a warning message to the user's email
address when their account connects from a new location.
By default, the location is the IP address. Meaning that any connection from a
different IP address will send a warning. If this is not what you want, you can
change the way location is computed (see below).
Following steps are performed when the user logs in
#. Extract the location from session info (by default, the IP address is used)
#. Compare the current location to the previous locations saved in history
#. If it is a new location, send an email to warn the user
#. On the next login, the location will no longer be considered as new
The very first time a user logs in (empty login history), no email is sent.
Configuration
-------------
Just enable it in the Manager (section ``General Parameters`` > ``Advanced parameters`` > ``Security`` > ``New location warning``:
- **Activation**: Enable this plugin *(default: disabled)*
- **Session attribute containing location**: Indicate the session attribute you are using to store the location. You can use `ipAddr`, or a custom macro.
- **Session attribute to display**: By default, the raw value of the location session attribute is displayed in the warning email. If you want to use a different session attribute in the warning email, you can specify it here.
- **Maximum number of locations to consider**: By default, all previous value of the location are checked
- **Session mail attribute**: Session key containing mail address *(default: mail)*
- **Warning mail subject**: Subject of the email containing the warning
- **Warning mail content**: Content of the email containing the warning
.. warning::
If you use a macro instead of ``ipAddr`` as the location value, be sure to add the name of this macro to
General Parameters » Plugins » Login History » Session data to store
Otherwise, the value of the macro will not be remembered across logins
Email body variables
~~~~~~~~~~~~~~~~~~~~
Following variables are available in the Warning email body:
* ``$location``: the location value, from **Session attribute to display**
* ``$date``: the date of login
* ``$ua``: the full user agent string
.. |image0| image:: /documentation/beta.png
:width: 100px

View File

@ -285,12 +285,13 @@ Name Description
:doc:`Grant Sessions<grantsession>` Rules to apply before allowing a user to open a session
:doc:`Impersonation<impersonation>` [11]_\ |new| Allow users to use another identity
:doc:`Find user<finduser>` [12]_\ |new| Search for user account
:doc:`Notifications system<notifications>` DIsplay a message during log in process
:doc:`NewLocationWarning<newlocationwarning>` [13]_\ |beta| Send an email when user sign in from a new location
:doc:`Notifications system<notifications>` Display a message during log in process
:doc:`Portal Status<status>` Experimental portal status page
:doc:`Public pages<public_pages>` Enable public pages system
:doc:`Refresh session API<refreshsessionapi>` [13]_ Plugin that provides an API to refresh a user session
:doc:`Refresh session API<refreshsessionapi>` [14]_ Plugin that provides an API to refresh a user session
:doc:`Reset password by mail<resetpassword>` Send a mail to reset its password
:doc:`Reset certificate by mail<resetcertificate>` [14]_\ |new| Allow users to reset their certificate
:doc:`Reset certificate by mail<resetcertificate>` [15]_\ |new| Allow users to reset their certificate
:doc:`REST services<restservices>` |new| REST server for :doc:`Proxy<authproxy>`
:doc:`SOAP services<soapservices>` |deprecated| SOAP server for :doc:`Proxy<authproxy>`
:doc:`Stay connected<stayconnected>` |new| Enable persistent connection on same browser
@ -308,12 +309,12 @@ Handlers are software control agents to be installed on your web servers
==================================================================== ========== ============================================================= =========================================== ================================================================================== =============================================== ======================================================================================================================
Handler type Apache LLNG FastCGI/uWSGI server (Nginx, or :doc:`SSOaaS<ssoaas>`) `Plack servers <https://plackperl.org>`__ Node.js ( `express apps <http://expressjs.com/>`__\ or :doc:`SSOaaS<ssoaas>`) :doc:`Self protected apps<selfmadeapplication>` Comment
==================================================================== ========== ============================================================= =========================================== ================================================================================== =============================================== ======================================================================================================================
Main *(default handler)* ✔ ✔ ✔ :doc:`Partial<nodehandler>` ** [15]_ ** ✔
Main *(default handler)* ✔ ✔ ✔ :doc:`Partial<nodehandler>` ** [16]_ ** ✔
:doc:`AuthBasic<handlerauthbasic>` ✔ ✔ ✔ ✔ Designed for some server-to-server applications
:doc:`CDA<cda>` ✔ ✔ ✔ ✔ For Cross Domain Authentication
:doc:`DevOps<devopshandler>` (:doc:`SSOaaS<ssoaas>`) |new| ✔ ✔ ✔ ✔ Allows application developers to define their own rules and headers inside their applications
:doc:`DevOpsST<devopssthandler>` (:doc:`SSOaaS<ssoaas>`) |new| ✔ ✔ ✔ ✔ Enables both :doc:`DevOps<devopshandler>` and :doc:`Service Token<servertoserver>`
:doc:`OAuth2<oauth2handler>` [16]_\ |new| ✔ ✔ ✔ ✔ Uses OpenID Connect/OAuth2 access token to check authentication and authorization, can be used to protect Web Services
:doc:`OAuth2<oauth2handler>` [17]_\ |new| ✔ ✔ ✔ ✔ Uses OpenID Connect/OAuth2 access token to check authentication and authorization, can be used to protect Web Services
:doc:`Secure Token<securetoken>` ✔ ✔ ✔ Designed to secure exchanges between a LLNG reverse-proxy and a remote app
:doc:`Service Token<servertoserver>` |new| *(Server-to-Server)* ✔ ✔ ✔ ✔ ✔ Designed to permit underlying requests *(API-Based Infrastructure)*
:doc:`Zimbra PreAuth<applications/zimbra>` ✔ ✔ ✔
@ -598,18 +599,22 @@ by your language code):
2.0.11
.. [13]
:doc:`NewLocationWarning<newlocationwarning>` is available
with LLNG ≥ 2.0.14
.. [14]
:doc:`Refresh session API plugin<refreshsessionapi>` is available
with LLNG ≥ 2.0.7
.. [14]
.. [15]
:doc:`Reset certificate by mail plugin<resetcertificate>` is
available with LLNG ≥ 2.0.7
.. [15]
.. [16]
:doc:`Node.js handler<nodehandler>` has not yet reached the same
level of functionalities
.. [16]
.. [17]
:doc:`OAuth2 Handler<oauth2handler>` is available with LLNG ≥ 2.0.4
.. |image0| image:: /icons/kthememgr.png

View File

@ -8,9 +8,9 @@ We use in this example a public OIDC provider based on LL::NG: `<https://oidctes
Authentication
--------------
The first step is to obtain a valid SSO session on the portal. Several solutions:
* Use a web browser and log into the portal, then get the value of the SSO cookie
* Use portal REST API, and adapt the `requireToken` configuration to get cookie value in JSON response (see :doc:`REST services<restservices>`)
The first step is to obtain a valid SSO session on the portal. The standard solution is to use a web browser and log into the portal, then get the value of the SSO cookie.
In our case, to be able to use only command lines, we will use portal REST API (which requires to adapt the `requireToken` configuration to get cookie value in JSON response (see :doc:`REST services<restservices>`). This should not be what you will on a production service.
Example of REST service usage, with credentials `dwho`/`dwho`:
@ -130,3 +130,68 @@ JSON response:
"preferred_username" : "dwho",
"sub" : "dwho"
}
Introspection
-------------
You can the validity of the access token with the introspection endpoint.
Parameters needed:
* Client ID and Client Secret, used as basic authorization
* Access token, sent as POST data
.. code-block:: shell
curl -u private:tardis -X POST -d 'token=a88b8dde538719e55c3cb8fbd14d06ed77853c685a62abf6ecb88d86228a9c64' 'https://oidctest.wsweet.org/oauth2/introspect' | json_pp
JSON response:
.. code-block:: javascript
{
"active" : true,
"client_id" : "private",
"exp" : 1630684115,
"iss" : "https://oidctest.wsweet.org/",
"scope" : "openid profile email",
"sub" : "dwho"
}
Refresh an access token
-----------------------
If the access token has expired, you can get a new one with the refresh token.
Parameters needed:
* Grant type: we use here `refresh_token`, sent as POST data
* Refresh token, sent as POST data
* Client ID and Client Secret, used as basic authorization
.. code-block:: shell
curl -X POST -d grant_type=refresh_token -d refresh_token=19434440ed4da2803e8ba9d91cb2eabd5b8bd12af2609429bda03ed487e6ef57 -u 'private:tardis' 'https://oidctest.wsweet.org/oauth2/token' | json_pp
JSON response:
.. code-block:: javascript
{
"access_token" : "78929118546b1a11a2e3b607f607d0ccb73d72bbd95c59d0b03ae69ffa17f41a",
"expires_in" : 3600,
"id_token" : "eyJhbGciOiJSUzI1NiIsImtpZCI6Im9pZGN0ZXN0IiwidHlwIjoiSldUIn0.eyJhdXRoX3RpbWUiOjE2MTQxNjAwMDYsImlhdCI6MTYxNDE2MzIxOCwiaXNzIjoiaHR0cHM6Ly9vaWRjdGVzdC53c3dlZXQub3JnLyIsImF0X2hhc2giOiJIVGswOVNjSjRObEFua3k5SGFFX2VRIiwiYWNyIjoibG9hLTIiLCJleHAiOjE2MTQxNjY4MTgsInN1YiI6ImR3aG8iLCJhenAiOiJwcml2YXRlIiwiYXVkIjpbInByaXZhdGUiXX0.N3TNufjKLzKM3qiIitA7JHUei4L572XjF6AcVl7UAFB6efdGUCiAL7amlUl0FgjZfzW9bzvulBVDidoYSicIaysIdI4KkjmjpVN0Z3gOSu0ecuk5p8fD1KbX6-tmA3txeR18nzfhdckq-S-6Lx7wrWpPNyrzGx-FImbOaUPN2yeVhKPXhdyHJbzI0RqJETxnBkyW-CLEzAJyq3rCUVX-D8kHADvg6a42QQyPdxvBuGrdBfyDDDb_Py13H1qhn40NnuFknR1wSahsY6U97uUooyk-0_U4J3XJAHySjCtivtSeP0fM_5eblMuh6WdVjrfnUF0xnCTbCa2gYRlTS38BkqcsWY26PXoRAOo31a1cmB5sMSZyPtRF9UZcmGiNBIymMMdFgVAJONb6uliiTS5j9-nkmHOqVC-XJ6tuiU3ZSBQ8nCRyNW2LaCzpJ5c3ytP9yYQtyT8HmhN0VnXob3K1uJEA_Xcu4sADjtrm-LbrGiwaVMkfu-C6YIrbuC9riOW6TneV2gAzAjXPOW_UZeXrCrx66GHIJPsJIq29UfbTN5Pxo9SH2yKw6PSfxevkZhBIhEXCOMaIUHrlWz2jDBBzPIWeiSRbK_MRtejQmdRUs8nqdq-McVwnFiUMDt1KZXxqScTtMDF_Lo9oK2RaCijEJ7MSPEscr_YOyp3KIq2FLVg",
"token_type" : "Bearer"
}
Logout
------
To kill SSO session, call the OIDC logout endpoint. By default a confirmation is requested, but you can bypass it by adding `confirm=1` to URL.
Parameters needed:
* SSO session id (will be passed in `lemonldap` cookie)
.. code-block:: shell
curl -s -D - -o /dev/null -b lemonldap=0640f95827111f00ba7ad5863ba819fe46cfbcecdb18ce525836369fb4c8350b 'https://oidctest.wsweet.org/oauth2/logout?confirm=1'
The session is deleted on server side and the cookie is destroyed in the browser. You can use the introspection endpoint to verify that the access token is no longer valid.

View File

@ -1,4 +1,4 @@
.\" Automatically generated by Pod::Man 4.11 (Pod::Simple 3.35)
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40)
.\"
.\" Standard preamble:
.\" ========================================================================
@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "llng-fastcgi-server 8"
.TH llng-fastcgi-server 8 "2021-07-03" "perl v5.30.0" "User Contributed Perl Documentation"
.TH llng-fastcgi-server 8 "2021-08-10" "perl v5.32.1" "User Contributed Perl Documentation"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l

View File

@ -31,7 +31,7 @@ use constant DEFAULTCONFBACKENDOPTIONS => (
);
our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|f(?:indUser(?:Exclud|Search)ingAttribute|acebookExportedVar)|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|ScopeRule|Macro)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node))|penIdExportedVars)|c(?:as(?:A(?:ppMetaData(?:(?:ExportedVar|Option|Macro)s|Node)|ttributes)|S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions))|(?:ustom(?:Plugins|Add)Param|heckUserHiddenHeader|ombModule)s)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option|Macro)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|a(?:(?:daptativeAuthenticationLevelR|ut(?:hChoiceMod|oSigninR))ules|pplicationList)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/;
our $arrayParameters = qr/^mySessionAuthorizedRWKeys$/;
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(?:A(?:llow(?:(?:ClientCredentials|Password)Grant|Offline)|ccessToken(?:Claims|JWT))|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration|OnlyDeclaredScopes)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|CertificateResetByMail|GeneratePassword|PasswordPolicy)|E(?:rrorOn(?:ExpiredSession|MailNotFound)|nablePasswordDisplay)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|ForceAuthn|AntiFrame)|roxy(?:AuthServiceImpersonation|UseSoap))|c(?:a(?:sS(?:rvMetaDataOptions(?:Gateway|Renew)|trictMatching)|ptcha_(?:register|login|mail)_enabled)|o(?:ntextSwitching(?:Allowed2fModifications|StopWithLogout)|mpactConf|rsEnabled)|heck(?:DevOps(?:D(?:isplayNormalizedHeaders|ownload))?|State|User|XSS)|rowdsec|da)|l(?:dap(?:(?:G(?:roup(?:DecodeSearchedValu|Recursiv)|etUserBeforePasswordChang)|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)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|re(?:st(?:(?:Password|Session|Config|Auth)Server|ExportSecretKeys)|freshSessions)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|d(?:is(?:ablePersistentStorage|playSessionId)|biDynamicHashEnabled)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|to(?:tp2fUserCanRemoveKey|kenUseGlobalStorage)|g(?:roupsBeforeMacros|lobalLogoutTimer)|a(?:voidAssignment|ctiveTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|krb(?:RemoveDomain|ByJs)|(?:wsdlServ|findUs)er)$/;
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(?:A(?:llow(?:(?:ClientCredentials|Password)Grant|Offline)|ccessToken(?:Claims|JWT))|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration|OnlyDeclaredScopes)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|CertificateResetByMail|GeneratePassword|PasswordPolicy)|E(?:rrorOn(?:ExpiredSession|MailNotFound)|nablePasswordDisplay)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|ForceAuthn|AntiFrame)|roxy(?:AuthServiceImpersonation|UseSoap))|c(?:a(?:sS(?:rvMetaDataOptions(?:Gateway|Renew)|trictMatching)|ptcha_(?:register|login|mail)_enabled)|o(?:ntextSwitching(?:Allowed2fModifications|StopWithLogout)|mpactConf|rsEnabled)|heck(?:DevOps(?:D(?:isplayNormalizedHeaders|ownload))?|State|User|XSS)|rowdsec|da)|l(?:dap(?:(?:G(?:roup(?:DecodeSearchedValu|Recursiv)|etUserBeforePasswordChang)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|n(?:o(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|ewLocationWarning)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|re(?:st(?:(?:Password|Session|Config|Auth)Server|ExportSecretKeys)|freshSessions)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|d(?:is(?:ablePersistentStorage|playSessionId)|biDynamicHashEnabled)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|to(?:tp2fUserCanRemoveKey|kenUseGlobalStorage)|g(?:roupsBeforeMacros|lobalLogoutTimer)|a(?:voidAssignment|ctiveTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|krb(?:RemoveDomain|ByJs)|(?:wsdlServ|findUs)er)$/;
our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' );

View File

@ -186,8 +186,11 @@ sub defaultValues {
'multiValuesSeparator' => '; ',
'mySessionAuthorizedRWKeys' =>
[ '_appsListOrder', '_oidcConnectedRP', '_oidcConsents' ],
'notificationDefaultCond' => '',
'notificationServerPOST' => 1,
'newLocationWarningLocationAttribute' => 'ipAddr',
'newLocationWarningLocationDisplayAttribute' => '',
'newLocationWarningMaxValues' => '0',
'notificationDefaultCond' => '',
'notificationServerPOST' => 1,
'notificationServerSentAttributes' =>
'uid reference date title subtitle text check',
'notificationsMaxRetrieve' => 3,

View File

@ -2067,6 +2067,31 @@ m[^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
[ '_appsListOrder', '_oidcConnectedRP', '_oidcConsents' ],
'type' => 'array'
},
'newLocationWarning' => {
'default' => 0,
'type' => 'bool'
},
'newLocationWarningLocationAttribute' => {
'default' => 'ipAddr',
'type' => 'text'
},
'newLocationWarningLocationDisplayAttribute' => {
'default' => '',
'type' => 'text'
},
'newLocationWarningMailAttribute' => {
'type' => 'text'
},
'newLocationWarningMailBody' => {
'type' => 'longtext'
},
'newLocationWarningMailSubject' => {
'type' => 'text'
},
'newLocationWarningMaxValues' => {
'default' => '0',
'type' => 'int'
},
'nginxCustomHandlers' => {
'keyTest' => qr/^\w+$/,
'msgFail' => '__badPerlPackageName__',

View File

@ -579,6 +579,38 @@ sub attributes {
default => '^[*\w]+$',
documentation => 'Regular expression to validate parameters',
},
newLocationWarning => {
default => 0,
type => 'bool',
documentation => 'Enable New Location Warning',
},
newLocationWarningLocationAttribute => {
type => 'text',
default => 'ipAddr',
documentation => 'New location session attribute',
},
newLocationWarningLocationDisplayAttribute => {
type => 'text',
default => '',
documentation => 'New location session attribute for user display',
},
newLocationWarningMaxValues => {
type => 'int',
default => '0',
documentation => 'How many previous locations should be compared',
},
newLocationWarningMailAttribute => {
type => 'text',
documentation => 'New location warning mail session attribute',
},
newLocationWarningMailBody => {
type => 'longtext',
documentation => 'Mail body for new location warning',
},
newLocationWarningMailSubject=> {
type => 'text',
documentation => 'Mail subject for new location warning',
},
globalLogoutRule => {
type => 'boolOrExpr',
default => 0,

View File

@ -1048,11 +1048,26 @@ sub tree {
{
title => 'CrowdSecPlugin',
help => 'crowdsec.html',
form => 'simpleInputContainer',
nodes => [
'crowdsec', 'crowdsecAction',
'crowdsecUrl', 'crowdsecKey',
],
},
{
title => 'newLocationWarnings',
help => 'newlocationwarning.html',
form => 'simpleInputContainer',
nodes => [
'newLocationWarning',
'newLocationWarningLocationAttribute',
'newLocationWarningLocationDisplayAttribute',
'newLocationWarningMaxValues',
'newLocationWarningMailAttribute',
'newLocationWarningMailSubject',
'newLocationWarningMailBody'
]
},
{
title => 'bruteForceAttackProtection',
help => 'bruteforceprotection.html',

View File

@ -180,8 +180,8 @@
"cfgLog":"Summary",
"cfgVersion":"عملية ضبط الإصدارات",
"checkDevOps":"تفعيل",
"checkDevOpsDownload":"Download file",
"checkDevOpsDisplayNormalizedHeaders":"Display normalized headers",
"checkDevOpsDownload":"Download file",
"checkState":"تفعيل",
"checkStateSecret":"سر مشترك",
"checkUser":"تفعيل",
@ -568,6 +568,14 @@
"newEntry":"أنتري جديد",
"newGrantRule":"قاعدة منح جديدة",
"newHost":"خادم جديد",
"newLocationWarning":"Activation",
"newLocationWarningLocationAttribute":"Session attribute containing location",
"newLocationWarningLocationDisplayAttribute":"Session attribute to display",
"newLocationWarningMailAttribute":"Session mail attribute",
"newLocationWarningMailBody":"Warning mail content",
"newLocationWarningMailSubject":"Warning mail subject",
"newLocationWarningMaxValues":"Maximum number of locations to consider",
"newLocationWarnings":"New location warning",
"newPost":"استمارة وظيفة replay جديدة",
"newPostVar":"متغير جديد",
"newRSAKey":"مفاتيح جديدة",
@ -1214,4 +1222,4 @@
"yubikey2fUrl":"خدمة أل يو أر ل",
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
"zeroConfExplanations":"لا يحتوي الخادم على إعدادات. استخدام قالب لحفظ الأول"
}
}

View File

@ -180,8 +180,8 @@
"cfgLog":"Summary",
"cfgVersion":"Configuration version",
"checkDevOps":"Activation",
"checkDevOpsDownload":"Download file",
"checkDevOpsDisplayNormalizedHeaders":"Display normalized headers",
"checkDevOpsDownload":"Download file",
"checkState":"Activation",
"checkStateSecret":"Shared secret",
"checkUser":"Activation",
@ -568,6 +568,14 @@
"newEntry":"New entry",
"newGrantRule":"New grant rule",
"newHost":"New host",
"newLocationWarning":"Activation",
"newLocationWarningLocationAttribute":"Session attribute containing location",
"newLocationWarningLocationDisplayAttribute":"Session attribute to display",
"newLocationWarningMailAttribute":"Session mail attribute",
"newLocationWarningMailBody":"Warning mail content",
"newLocationWarningMailSubject":"Warning mail subject",
"newLocationWarningMaxValues":"Maximum number of locations to consider",
"newLocationWarnings":"New location warning",
"newPost":"New form replay",
"newPostVar":"New variable",
"newRSAKey":"New keys",
@ -1214,4 +1222,4 @@
"yubikey2fUrl":"Service URL",
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
"zeroConfExplanations":"Server has no configuration. Use template to save the first."
}
}

View File

@ -180,8 +180,8 @@
"cfgLog":"Summary",
"cfgVersion":"Configuration version",
"checkDevOps":"Activation",
"checkDevOpsDownload":"Download file",
"checkDevOpsDisplayNormalizedHeaders":"Display normalized headers",
"checkDevOpsDownload":"Download file",
"checkState":"Activation",
"checkStateSecret":"Shared secret",
"checkUser":"Activation",
@ -568,6 +568,14 @@
"newEntry":"New entry",
"newGrantRule":"New grant rule",
"newHost":"New host",
"newLocationWarning":"Activation",
"newLocationWarningLocationAttribute":"Session attribute containing location",
"newLocationWarningLocationDisplayAttribute":"Session attribute to display",
"newLocationWarningMailAttribute":"Session mail attribute",
"newLocationWarningMailBody":"Warning mail content",
"newLocationWarningMailSubject":"Warning mail subject",
"newLocationWarningMaxValues":"Maximum number of locations to consider",
"newLocationWarnings":"New location warning",
"newPost":"New form replay",
"newPostVar":"New variable",
"newRSAKey":"New keys",

View File

@ -180,8 +180,8 @@
"cfgLog":"Sumario",
"cfgVersion":"Configuration version",
"checkDevOps":"Activación",
"checkDevOpsDownload":"Download file",
"checkDevOpsDisplayNormalizedHeaders":"Display normalized headers",
"checkDevOpsDownload":"Download file",
"checkState":"Activación",
"checkStateSecret":"Secreto compartido",
"checkUser":"Activación",
@ -568,6 +568,14 @@
"newEntry":"Nueva entrada",
"newGrantRule":"Nueva regla de admisión",
"newHost":"Nuevo host",
"newLocationWarning":"Activation",
"newLocationWarningLocationAttribute":"Session attribute containing location",
"newLocationWarningLocationDisplayAttribute":"Session attribute to display",
"newLocationWarningMailAttribute":"Session mail attribute",
"newLocationWarningMailBody":"Warning mail content",
"newLocationWarningMailSubject":"Warning mail subject",
"newLocationWarningMaxValues":"Maximum number of locations to consider",
"newLocationWarnings":"New location warning",
"newPost":"New form replay",
"newPostVar":"Nueva variable",
"newRSAKey":"Nuevas claves",
@ -1214,4 +1222,4 @@
"yubikey2fUrl":"Service URL",
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
"zeroConfExplanations":"Server has no configuration. Use template to save the first."
}
}

View File

@ -180,8 +180,8 @@
"cfgLog":"Résumé",
"cfgVersion":"Version de la configuration",
"checkDevOps":"Activation",
"checkDevOpsDownload":"Télécharger un fichier",
"checkDevOpsDisplayNormalizedHeaders":"Afficher les entêtes normalisés",
"checkDevOpsDownload":"Télécharger un fichier",
"checkState":"Activation",
"checkStateSecret":"Secret partagé",
"checkUser":"Activation",
@ -568,6 +568,14 @@
"newEntry":"Nouvelle entrée",
"newGrantRule":"Nouvelle règle d'accès",
"newHost":"Nouvel hôte",
"newLocationWarning":"Activation",
"newLocationWarningLocationAttribute":"Attribut de session contenant la localisation",
"newLocationWarningLocationDisplayAttribute":"Attribut de session à afficher",
"newLocationWarningMailAttribute":"Attribut utilisateur contenant le mail ",
"newLocationWarningMailBody":"Contenu du mail d'avertissement",
"newLocationWarningMailSubject":"Sujet du mail d'avertissement",
"newLocationWarningMaxValues":"Nombre maximum de localisations à mémoriser",
"newLocationWarnings":"Avertissement de nouvelle connexion",
"newPost":"Nouveau rejeu de formulaire",
"newPostVar":"Nouvelle variable",
"newRSAKey":"Nouvelles clefs",

View File

@ -180,8 +180,8 @@
"cfgLog":"Summary",
"cfgVersion":"Versione configurazione",
"checkDevOps":"Activation",
"checkDevOpsDownload":"Download file",
"checkDevOpsDisplayNormalizedHeaders":"Display normalized headers",
"checkDevOpsDownload":"Download file",
"checkState":"Attivazione",
"checkStateSecret":"Segreto condiviso",
"checkUser":"Attivazione",
@ -568,6 +568,14 @@
"newEntry":"Nuova entrata",
"newGrantRule":"Nuova regola di autorizzazione",
"newHost":"Nuovo host",
"newLocationWarning":"Activation",
"newLocationWarningLocationAttribute":"Session attribute containing location",
"newLocationWarningLocationDisplayAttribute":"Session attribute to display",
"newLocationWarningMailAttribute":"Session mail attribute",
"newLocationWarningMailBody":"Warning mail content",
"newLocationWarningMailSubject":"Warning mail subject",
"newLocationWarningMaxValues":"Maximum number of locations to consider",
"newLocationWarnings":"New location warning",
"newPost":"Nuovo formulario di risposta",
"newPostVar":"Nuova variabile",
"newRSAKey":"Nuove chiavi",
@ -1214,4 +1222,4 @@
"yubikey2fUrl":"URL del servizio",
"yubikey2fUserCanRemoveKey":"Autorizza l'utente a rimuovere la Yubikey",
"zeroConfExplanations":"Il server non ha alcuna configurazione. Utilizza il modello per salvare il primo."
}
}

View File

@ -180,8 +180,8 @@
"cfgLog":"Podsumowanie",
"cfgVersion":"Wersja konfiguracji",
"checkDevOps":"Aktywacja",
"checkDevOpsDownload":"Download file",
"checkDevOpsDisplayNormalizedHeaders":"Display normalized headers",
"checkDevOpsDownload":"Download file",
"checkState":"Aktywacja",
"checkStateSecret":"Współdzielony sekret",
"checkUser":"Aktywacja",
@ -568,6 +568,14 @@
"newEntry":"Nowy wpis",
"newGrantRule":"Nowa reguła przyznawania",
"newHost":"Nowy host",
"newLocationWarning":"Activation",
"newLocationWarningLocationAttribute":"Session attribute containing location",
"newLocationWarningLocationDisplayAttribute":"Session attribute to display",
"newLocationWarningMailAttribute":"Session mail attribute",
"newLocationWarningMailBody":"Warning mail content",
"newLocationWarningMailSubject":"Warning mail subject",
"newLocationWarningMaxValues":"Maximum number of locations to consider",
"newLocationWarnings":"New location warning",
"newPost":"Nowy formularz powtórzenia",
"newPostVar":"Nowa zmienna",
"newRSAKey":"Nowe klucze",
@ -1214,4 +1222,4 @@
"yubikey2fUrl":"URL usługi",
"yubikey2fUserCanRemoveKey":"Pozwól użytkownikowi usunąć Yubikey",
"zeroConfExplanations":"Serwer nie ma konfiguracji. Użyj szablonu, aby zapisać pierwszy."
}
}

View File

@ -180,8 +180,8 @@
"cfgLog":"Özet",
"cfgVersion":"Yapılandırma sürümü",
"checkDevOps":"Aktivasyon",
"checkDevOpsDownload":"Dosyayı indir",
"checkDevOpsDisplayNormalizedHeaders":"Display normalized headers",
"checkDevOpsDownload":"Dosyayı indir",
"checkState":"Aktivasyon",
"checkStateSecret":"Paylaşılan sır",
"checkUser":"Aktivasyon",
@ -568,6 +568,14 @@
"newEntry":"Yeni kayıt",
"newGrantRule":"Yeni imtiyaz kuralı",
"newHost":"Yeni konak",
"newLocationWarning":"Activation",
"newLocationWarningLocationAttribute":"Session attribute containing location",
"newLocationWarningLocationDisplayAttribute":"Session attribute to display",
"newLocationWarningMailAttribute":"Session mail attribute",
"newLocationWarningMailBody":"Warning mail content",
"newLocationWarningMailSubject":"Warning mail subject",
"newLocationWarningMaxValues":"Maximum number of locations to consider",
"newLocationWarnings":"New location warning",
"newPost":"Yeni form tekrarı",
"newPostVar":"Yeni değişken",
"newRSAKey":"Yeni anahtarlar",
@ -1214,4 +1222,4 @@
"yubikey2fUrl":"Servis URL'si",
"yubikey2fUserCanRemoveKey":"Yubikey'i kaldırmak için kullanıcıya izin ver",
"zeroConfExplanations":"Sunucunun yapılandırması yok. Şimdi bir tane kaydetmek için şablonu kullanın."
}
}

View File

@ -180,8 +180,8 @@
"cfgLog":"Summary",
"cfgVersion":"Phiên bản cấu hình",
"checkDevOps":"Activation",
"checkDevOpsDownload":"Download file",
"checkDevOpsDisplayNormalizedHeaders":"Display normalized headers",
"checkDevOpsDownload":"Download file",
"checkState":"Kích hoạt",
"checkStateSecret":"Chia sẻ bí mật",
"checkUser":"Kích hoạt",
@ -568,6 +568,14 @@
"newEntry":"Mục nhập mới",
"newGrantRule":"Quy tắc cấp mới",
"newHost":"Máy chủ mới",
"newLocationWarning":"Activation",
"newLocationWarningLocationAttribute":"Session attribute containing location",
"newLocationWarningLocationDisplayAttribute":"Session attribute to display",
"newLocationWarningMailAttribute":"Session mail attribute",
"newLocationWarningMailBody":"Warning mail content",
"newLocationWarningMailSubject":"Warning mail subject",
"newLocationWarningMaxValues":"Maximum number of locations to consider",
"newLocationWarnings":"New location warning",
"newPost":"Phát lại mẫu mới",
"newPostVar":"Biến mới",
"newRSAKey":"Khóa mới",
@ -1214,4 +1222,4 @@
"yubikey2fUrl":"Dịch vụ URL",
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
"zeroConfExplanations":"Máy chủ không có cấu hình. Sử dụng mẫu để lưu đầu tiên. "
}
}

View File

@ -180,8 +180,8 @@
"cfgLog":"Summary",
"cfgVersion":"配置信息",
"checkDevOps":"Activation",
"checkDevOpsDownload":"Download file",
"checkDevOpsDisplayNormalizedHeaders":"Display normalized headers",
"checkDevOpsDownload":"Download file",
"checkState":"激活",
"checkStateSecret":"Shared secret",
"checkUser":"激活",
@ -568,6 +568,14 @@
"newEntry":"New entry",
"newGrantRule":"New grant rule",
"newHost":"New host",
"newLocationWarning":"Activation",
"newLocationWarningLocationAttribute":"Session attribute containing location",
"newLocationWarningLocationDisplayAttribute":"Session attribute to display",
"newLocationWarningMailAttribute":"Session mail attribute",
"newLocationWarningMailBody":"Warning mail content",
"newLocationWarningMailSubject":"Warning mail subject",
"newLocationWarningMaxValues":"Maximum number of locations to consider",
"newLocationWarnings":"New location warning",
"newPost":"New form replay",
"newPostVar":"New variable",
"newRSAKey":"New keys",
@ -1214,4 +1222,4 @@
"yubikey2fUrl":"Service URL",
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
"zeroConfExplanations":"Server has no configuration. Use template to save the first."
}
}

View File

@ -180,8 +180,8 @@
"cfgLog":"概要",
"cfgVersion":"設定版本",
"checkDevOps":"啟用",
"checkDevOpsDownload":"Download file",
"checkDevOpsDisplayNormalizedHeaders":"Display normalized headers",
"checkDevOpsDownload":"Download file",
"checkState":"啟用",
"checkStateSecret":"已分享的祕密",
"checkUser":"啟用",
@ -568,6 +568,14 @@
"newEntry":"新項目",
"newGrantRule":"新授權規則",
"newHost":"新主機",
"newLocationWarning":"Activation",
"newLocationWarningLocationAttribute":"Session attribute containing location",
"newLocationWarningLocationDisplayAttribute":"Session attribute to display",
"newLocationWarningMailAttribute":"Session mail attribute",
"newLocationWarningMailBody":"Warning mail content",
"newLocationWarningMailSubject":"Warning mail subject",
"newLocationWarningMaxValues":"Maximum number of locations to consider",
"newLocationWarnings":"New location warning",
"newPost":"新表單重新進行",
"newPostVar":"新變數",
"newRSAKey":"新金鑰",
@ -1214,4 +1222,4 @@
"yubikey2fUrl":"服務 URL",
"yubikey2fUserCanRemoveKey":"允許使用者移除 Yubikey",
"zeroConfExplanations":"伺服器未設定。使用飯本來儲存第一個。"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -120,6 +120,7 @@ lib/Lemonldap/NG/Portal/Plugins/GrantSession.pm
lib/Lemonldap/NG/Portal/Plugins/History.pm
lib/Lemonldap/NG/Portal/Plugins/Impersonation.pm
lib/Lemonldap/NG/Portal/Plugins/MailPasswordReset.pm
lib/Lemonldap/NG/Portal/Plugins/NewLocationWarning.pm
lib/Lemonldap/NG/Portal/Plugins/Notifications.pm
lib/Lemonldap/NG/Portal/Plugins/PublicPages.pm
lib/Lemonldap/NG/Portal/Plugins/Refresh.pm
@ -470,6 +471,7 @@ site/templates/common/mail_certificateReset.tpl
site/templates/common/mail_confirm.tpl
site/templates/common/mail_footer.tpl
site/templates/common/mail_header.tpl
site/templates/common/mail_new_location_warning.tpl
site/templates/common/mail_password.tpl
site/templates/common/mail_register_confirm.tpl
site/templates/common/mail_register_done.tpl
@ -510,7 +512,7 @@ t/24-AuthKerberos.t
t/25-AuthSlave-with-Choice.t
t/25-AuthSlave-with-Credentials.t
t/26-AuthRemote.t
t/27-AuthProxy-choice.t
t/27-AuthProxy-with-choice.t
t/27-AuthProxy.t
t/28-AuthChoice-and-password.t
t/28-AuthChoice-with-captcha.t
@ -675,6 +677,8 @@ t/61-CrowdSec-warn.t
t/61-CrowdSec.t
t/61-ForceAuthn.t
t/61-GrantSession.t
t/61-NewLocationWarning-Custom.t
t/61-NewLocationWarning.t
t/61-Session-ActivityTimeout.t
t/61-Session-Timeout.t
t/62-Refresh-plugin.t

View File

@ -33,7 +33,8 @@ our @pList = (
contextSwitchingRule => '::Plugins::ContextSwitching',
decryptValueRule => '::Plugins::DecryptValue',
findUser => '::Plugins::FindUser',
adaptativeAuthenticationLevelRules =>
newLocationWarning => '::Plugins::NewLocationWarning',
adaptativeAuthenticationLevelRules =>
'::Plugins::AdaptativeAuthenticationLevel',
globalLogoutRule => '::Plugins::GlobalLogout',
refreshSessions => '::Plugins::Refresh',

View File

@ -0,0 +1,171 @@
package Lemonldap::NG::Portal::Plugins::NewLocationWarning;
use strict;
use Mouse;
use POSIX qw(strftime);
use Lemonldap::NG::Portal::Main::Constants qw(PE_OK);
use List::MoreUtils qw/uniq/;
our $VERSION = '2.0.14';
has locationAttribute => ( is => 'rw' );
has locationDisplayAttribute => ( is => 'rw' );
has locationMaxValues => ( is => 'rw' );
has mailSessionKey => (
is => 'rw',
lazy => 1,
default => sub {
return
$_[0]->{conf}->{newLocationWarningMailAttribute}
|| $_[0]->{conf}->{mailSessionKey}
|| 'mail';
}
);
extends qw(
Lemonldap::NG::Portal::Lib::SMTP
Lemonldap::NG::Portal::Main::Plugin
);
# Entrypoint
use constant afterSub => { setLocalGroups => 'checkNewLocation' };
use constant endAuth => 'sendWarningEmail';
sub init {
my ($self) = @_;
if ( $self->conf->{disablePersistentStorage} ) {
$self->logger->error(
'"NewLocationWarning" plugin enabled WITHOUT persistent session storage"'
);
return 0;
}
unless ( $self->conf->{loginHistoryEnabled} ) {
$self->logger->error(
'"NewLocationWarning" plugin enabled WITHOUT "History" plugin');
return 0;
}
$self->locationAttribute( $self->conf->{newLocationWarningLocationAttribute}
|| 'ipAddr' );
$self->locationDisplayAttribute(
$self->conf->{newLocationWarningLocationDisplayAttribute}
|| $self->locationAttribute );
$self->locationMaxValues( $self->conf->{newLocationWarningMaxValues} || 0 );
return 1;
}
sub checkNewLocation {
my ( $self, $req ) = @_;
my $successLogin = $req->sessionInfo->{_loginHistory}->{successLogin} || [];
my $location = $req->sessionInfo->{ $self->locationAttribute };
unless ($location) {
$self->logger->debug( "Could not find location of user " . $req->user );
}
# Get all non-empty, unique values of location attribute through list of
# successful logins
my @envHistory =
grep { $_ // "" }
uniq( map { $_->{ $self->locationAttribute } // "" } @{$successLogin} );
# Only consider some of the past unique locations
my $maxLocations = $self->locationMaxValues;
splice @envHistory, $maxLocations
if ( $maxLocations and ( scalar @envHistory > $maxLocations ) );
if ( grep { $_ eq $location } @envHistory ) {
$self->userLogger->debug(
"User " . $req->user . " logged in from known location $location" );
}
else {
# Not the first location in history, warn if new location
if (@envHistory) {
$self->userLogger->info( "User "
. $req->user
. " logged in from unknown location $location" );
my $riskLevel = ( $req->sessionInfo->{_riskLevel} || 0 ) + 1;
$req->sessionInfo->{_riskLevel} = $riskLevel;
$req->sessionInfo->{_riskDetails}->{newLocation} =
$req->sessionInfo->{ $self->locationDisplayAttribute };
}
else {
$self->userLogger->info( "User "
. $req->user
. " logged with empty location history from location $location"
);
}
}
return PE_OK;
}
sub sendWarningEmail {
my ( $self, $req ) = @_;
if ( $req->sessionInfo->{_riskDetails}->{newLocation} ) {
return $self->_sendMail($req);
}
return PE_OK;
}
sub _sendMail {
my ( $self, $req ) = @_;
my $date = strftime( '%F %X', localtime );
my $location = $req->sessionInfo->{_riskDetails}->{newLocation};
my $ua = $req->env->{HTTP_USER_AGENT};
my $mail = $req->sessionInfo->{ $self->mailSessionKey };
# Build mail content
my $tr = $self->translate($req);
my $subject = $self->conf->{newLocationWarningMailSubject};
unless ($subject) {
$self->logger->debug('Use default warning subject');
$subject = 'newLocationWarningMailSubject';
$tr->( \$subject );
}
my ( $body, $html );
if ( $self->conf->{newLocationWarningMailBody} ) {
# We use a specific text message, no html
$self->logger->debug('Use specific warning body message');
$body = $self->conf->{newLocationWarningMailBody};
# Replace variables in body
$body =~ s/\$ua\b/$ua/ge;
$body =~ s/\$location\b/$location/ge;
$body =~ s/\$date\b/$date/ge;
$body =~ s/\$(\w+)/$req->{sessionInfo}->{$1} || ''/ge;
}
else {
# Use HTML template
$body = $self->loadMailTemplate(
$req,
'mail_new_location_warning',
filter => $tr,
params => {
location => $location,
date => $date,
ua => $ua
},
);
$html = 1;
}
if ( $mail && $subject && $body ) {
$self->logger->warn("User $mail is signing in from a new location");
# Send mail
$self->logger->debug('Unable to send new location warning mail')
unless ( $self->send_mail( $mail, $subject, $body, $html ) );
}
else {
$self->logger->error(
'Unable to send new location warning mail: missing parameter(s)');
}
return PE_OK;
}
1;

View File

@ -618,11 +618,11 @@ $(window).on 'load', () ->
user = data.user
console.log 'Suggested spoofId=', user
$("input[name=spoofId]").each ->
$(this).attr 'value', user
$(this).val user
$('#captcha').attr 'src', data.captcha if data.captcha
if data.token
$('#finduserToken').attr 'value', data.token
$('#token').attr 'value', data.token
$('#finduserToken').val data.token
$('#token').val data.token
error: (j, status, err) ->
document.body.style.cursor = 'default'
console.log 'Error', err if err

View File

@ -687,14 +687,14 @@ LemonLDAP::NG Portal jQuery scripts
user = data.user;
console.log('Suggested spoofId=', user);
$("input[name=spoofId]").each(function() {
return $(this).attr('value', user);
return $(this).val(user);
});
if (data.captcha) {
$('#captcha').attr('src', data.captcha);
}
if (data.token) {
$('#finduserToken').attr('value', data.token);
return $('#token').attr('value', data.token);
$('#finduserToken').val(data.token);
return $('#token').val(data.token);
}
},
error: function(j, status, err) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(function(){var r,e,s,o,t;s=function(e,r){return $("#msg").attr("trspan",e),$("#msg").html(window.translate(e)),$("#color").removeClass("message-positive message-warning message-danger alert-success alert-warning alert-danger"),$("#color").addClass("message-"+r),"positive"===r&&(r="success"),$("#color").addClass("alert-"+r)},r=function(e,r,t){var o;if(console.log("Error",t),(o=JSON.parse(e.responseText))&&o.error)return o=o.error.replace(/.* /,""),console.log("Returned error",o),s(o,"warning")},o="",e=function(){return s("yourTotpKey","warning"),$.ajax({type:"POST",url:portal+"/2fregisters/totp/getkey",dataType:"json",error:r,success:function(e){var r,t;return e.error?(e.error.match(/totpExistingKey/)&&$("#divToHide").hide(),s(e.error,"warning")):e.portal&&e.user&&e.secret?($("#divToHide").show(),r="otpauth://totp/"+escape(e.portal)+":"+escape(e.user)+"?secret="+e.secret+"&issuer="+escape(e.portal),6!==e.digits&&(r+="&digits="+e.digits),30!==e.interval&&(r+="&period="+e.interval),new QRious({element:document.getElementById("qr"),value:r,size:150}),t=e.secret||"",$("#secret").text(t.toUpperCase().replace(/(.{4})/g,"$1 ").trim()),e.newkey?s("yourNewTotpKey","warning"):s("yourTotpKey","success"),o=e.token):s("PE24","danger")}})},t=function(){var e;return(e=$("#code").val())?$.ajax({type:"POST",url:portal+"/2fregisters/totp/verify",dataType:"json",data:{token:o,code:e,TOTPName:$("#TOTPName").val()},error:r,success:function(e){return e.error?e.error.match(/bad(Code|Name)/)?s(e.error,"warning"):s(e.error,"danger"):s("yourKeyIsRegistered","success")}}):(s("totpMissingCode","warning"),$("#code").focus())},$(document).ready(function(){return e(),$("#verify").on("click",function(){return t()})})}).call(this);
!function(){var o=function(e,r){return $("#msg").attr("trspan",e),$("#msg").html(window.translate(e)),$("#color").removeClass("message-positive message-warning message-danger alert-success alert-warning alert-danger"),$("#color").addClass("message-"+r),"positive"===r&&(r="success"),$("#color").addClass("alert-"+r)},r=function(e,r,t){if(console.log("Error",t),(e=JSON.parse(e.responseText))&&e.error)return e=e.error.replace(/.* /,""),console.log("Returned error",e),o(e,"warning")},t="",e=function(){return o("yourTotpKey","warning"),$.ajax({type:"POST",url:portal+"/2fregisters/totp/getkey",dataType:"json",error:r,success:function(e){var r;return e.error?(e.error.match(/totpExistingKey/)&&$("#divToHide").hide(),o(e.error,"warning")):e.portal&&e.user&&e.secret?($("#divToHide").show(),r="otpauth://totp/"+escape(e.portal)+":"+escape(e.user)+"?secret="+e.secret+"&issuer="+escape(e.portal),6!==e.digits&&(r+="&digits="+e.digits),30!==e.interval&&(r+="&period="+e.interval),new QRious({element:document.getElementById("qr"),value:r,size:150}),r=e.secret||"",$("#secret").text(r.toUpperCase().replace(/(.{4})/g,"$1 ").trim()),e.newkey?o("yourNewTotpKey","warning"):o("yourTotpKey","success"),t=e.token):o("PE24","danger")}})},s=function(){var e=$("#code").val();return e?$.ajax({type:"POST",url:portal+"/2fregisters/totp/verify",dataType:"json",data:{token:t,code:e,TOTPName:$("#TOTPName").val()},error:r,success:function(e){return e.error?e.error.match(/bad(Code|Name)/)?o(e.error,"warning"):o(e.error,"danger"):o("yourKeyIsRegistered","success")}}):(o("totpMissingCode","warning"),$("#code").focus())};$(document).ready(function(){return e(),$("#verify").on("click",s)})}.call(this);

View File

@ -1 +1 @@
{"version":3,"sources":["totpregistration.js"],"names":["displayError","getKey","setMsg","token","verify","msg","level","$","attr","html","window","translate","removeClass","addClass","j","status","err","res","console","log","JSON","parse","responseText","error","replace","ajax","type","url","portal","dataType","success","data","s","secret","match","hide","user","show","escape","digits","interval","QRious","element","document","getElementById","value","size","text","toUpperCase","trim","newkey","val","code","TOTPName","focus","ready","on","call","this"],"mappings":"CAMA,WACE,IAAIA,EAAcC,EAAQC,EAAQC,EAAOC,EAEzCF,EAAS,SAASG,EAAKC,GAQrB,OAPAC,EAAE,QAAQC,KAAK,SAAUH,GACzBE,EAAE,QAAQE,KAAKC,OAAOC,UAAUN,IAChCE,EAAE,UAAUK,YAAY,4FACxBL,EAAE,UAAUM,SAAS,WAAaP,GACpB,aAAVA,IACFA,EAAQ,WAEHC,EAAE,UAAUM,SAAS,SAAWP,IAGzCN,EAAe,SAASc,EAAGC,EAAQC,GACjC,IAAIC,EAGJ,GAFAC,QAAQC,IAAI,QAASH,IACrBC,EAAMG,KAAKC,MAAMP,EAAEQ,gBACRL,EAAIM,MAGb,OAFAN,EAAMA,EAAIM,MAAMC,QAAQ,MAAO,IAC/BN,QAAQC,IAAI,iBAAkBF,GACvBf,EAAOe,EAAK,YAIvBd,EAAQ,GAERF,EAAS,WAEP,OADAC,EAAO,cAAe,WACfK,EAAEkB,KAAK,CACZC,KAAM,OACNC,IAAKC,OAAS,2BACdC,SAAU,OACVN,MAAOvB,EACP8B,QAAS,SAASC,GAChB,IAAQC,EAAGC,EACX,OAAIF,EAAKR,OACHQ,EAAKR,MAAMW,MAAM,oBACnB3B,EAAE,cAAc4B,OAEXjC,EAAO6B,EAAKR,MAAO,YAEtBQ,EAAKH,QAAUG,EAAKK,MAAQL,EAAKE,QAGvC1B,EAAE,cAAc8B,OAChBL,EAAI,kBAAqBM,OAAOP,EAAKH,QAAW,IAAOU,OAAOP,EAAKK,MAAS,WAAaL,EAAKE,OAAS,WAAcK,OAAOP,EAAKH,QAC7G,IAAhBG,EAAKQ,SACPP,GAAK,WAAaD,EAAKQ,QAEH,KAAlBR,EAAKS,WACPR,GAAK,WAAaD,EAAKS,UAEpB,IAAIC,OAAO,CACdC,QAASC,SAASC,eAAe,MACjCC,MAAOb,EACPc,KAAM,MAERb,EAASF,EAAKE,QAAU,GACxB1B,EAAE,WAAWwC,KAAKd,EAAOe,cAAcxB,QAAQ,UAAW,OAAOyB,QAC7DlB,EAAKmB,OACPhD,EAAO,iBAAkB,WAEzBA,EAAO,cAAe,WAEjBC,EAAQ4B,EAAK5B,OAtBXD,EAAO,OAAQ,cA2B9BE,EAAS,WACP,IAAI+C,EAEJ,OADAA,EAAM5C,EAAE,SAAS4C,OAKR5C,EAAEkB,KAAK,CACZC,KAAM,OACNC,IAAKC,OAAS,2BACdC,SAAU,OACVE,KAAM,CACJ5B,MAAOA,EACPiD,KAAMD,EACNE,SAAU9C,EAAE,aAAa4C,OAE3B5B,MAAOvB,EACP8B,QAAS,SAASC,GAChB,OAAIA,EAAKR,MACHQ,EAAKR,MAAMW,MAAM,kBACZhC,EAAO6B,EAAKR,MAAO,WAEnBrB,EAAO6B,EAAKR,MAAO,UAGrBrB,EAAO,sBAAuB,eArB3CA,EAAO,kBAAmB,WACnBK,EAAE,SAAS+C,UA2BtB/C,EAAEoC,UAAUY,MAAM,WAEhB,OADAtD,IACOM,EAAE,WAAWiD,GAAG,QAAS,WAC9B,OAAOpD,UAIVqD,KAAKC"}
{"version":3,"sources":["totpregistration.js"],"names":["setMsg","msg","level","$","attr","html","window","translate","removeClass","addClass","displayError","j","status","err","console","log","res","JSON","parse","responseText","error","replace","token","getKey","ajax","type","url","portal","dataType","success","data","secret","match","hide","user","show","s","escape","digits","interval","QRious","element","document","getElementById","value","size","text","toUpperCase","trim","newkey","verify","val","code","TOTPName","focus","ready","on","call","this"],"mappings":"CAMA,WACE,IAEAA,EAAS,SAASC,EAAKC,GAQrB,OAPAC,EAAE,QAAQC,KAAK,SAAUH,GACzBE,EAAE,QAAQE,KAAKC,OAAOC,UAAUN,IAChCE,EAAE,UAAUK,YAAY,4FACxBL,EAAE,UAAUM,SAAS,WAAaP,GACpB,aAAVA,IACFA,EAAQ,WAEHC,EAAE,UAAUM,SAAS,SAAWP,IAGzCQ,EAAe,SAASC,EAAGC,EAAQC,GAIjC,GAFAC,QAAQC,IAAI,QAASF,IACrBG,EAAMC,KAAKC,MAAMP,EAAEQ,gBACRH,EAAII,MAGb,OAFAJ,EAAMA,EAAII,MAAMC,QAAQ,MAAO,IAC/BP,QAAQC,IAAI,iBAAkBC,GACvBhB,EAAOgB,EAAK,YAIvBM,EAAQ,GAERC,EAAS,WAEP,OADAvB,EAAO,cAAe,WACfG,EAAEqB,KAAK,CACZC,KAAM,OACNC,IAAKC,OAAS,2BACdC,SAAU,OACVR,MAAOV,EACPmB,QAAS,SAASC,GAChB,IAAWC,EACX,OAAID,EAAKV,OACHU,EAAKV,MAAMY,MAAM,oBACnB7B,EAAE,cAAc8B,OAEXjC,EAAO8B,EAAKV,MAAO,YAEtBU,EAAKH,QAAUG,EAAKI,MAAQJ,EAAKC,QAGvC5B,EAAE,cAAcgC,OAChBC,EAAI,kBAAqBC,OAAOP,EAAKH,QAAW,IAAOU,OAAOP,EAAKI,MAAS,WAAaJ,EAAKC,OAAS,WAAcM,OAAOP,EAAKH,QAC7G,IAAhBG,EAAKQ,SACPF,GAAK,WAAaN,EAAKQ,QAEH,KAAlBR,EAAKS,WACPH,GAAK,WAAaN,EAAKS,UAEpB,IAAIC,OAAO,CACdC,QAASC,SAASC,eAAe,MACjCC,MAAOR,EACPS,KAAM,MAERd,EAASD,EAAKC,QAAU,GACxB5B,EAAE,WAAW2C,KAAKf,EAAOgB,cAAc1B,QAAQ,UAAW,OAAO2B,QAC7DlB,EAAKmB,OACPjD,EAAO,iBAAkB,WAEzBA,EAAO,cAAe,WAEjBsB,EAAQQ,EAAKR,OAtBXtB,EAAO,OAAQ,cA2B9BkD,EAAS,WACP,IACAC,EAAMhD,EAAE,SAASgD,MACjB,OAAKA,EAIIhD,EAAEqB,KAAK,CACZC,KAAM,OACNC,IAAKC,OAAS,2BACdC,SAAU,OACVE,KAAM,CACJR,MAAOA,EACP8B,KAAMD,EACNE,SAAUlD,EAAE,aAAagD,OAE3B/B,MAAOV,EACPmB,QAAS,SAASC,GAChB,OAAIA,EAAKV,MACHU,EAAKV,MAAMY,MAAM,kBACZhC,EAAO8B,EAAKV,MAAO,WAEnBpB,EAAO8B,EAAKV,MAAO,UAGrBpB,EAAO,sBAAuB,eArB3CA,EAAO,kBAAmB,WACnBG,EAAE,SAASmD,UA2BtBnD,EAAEuC,UAAUa,MAAM,WAEhB,OADAhC,IACOpB,EAAE,WAAWqD,GAAG,QACdN,MAIVO,KAAKC"}

View File

@ -6,15 +6,20 @@
"click2Reset":"انقر هنا لإعادة تعيين كلمة المرور",
"goToPortal":"انتقل إلى البوابة",
"hello":"مرحبا ",
"host":"Host",
"location": "Location",
"mail2fSubject":"[LemonLDAP::NG] تسجيل الدخول الخاص بك هو ",
"mailConfirmSubject":"تأكيد إعادة تعيين كلمة المرور[LemonLDAP::NG]",
"mailSubject":"كلمة المرور الجديدة [LemonLDAP::NG]",
"newLocationWarningMailBody":"Your account was signed in to from a new location.",
"newLocationWarningMailSubject":"[LemonLDAP::NG] Sign-in from a new location",
"newPwdIs":"كلمة المرور الجديدة هي",
"pwdChanged":"تم تغيير كلمة المرور الخاصة بك",
"pwdIs":"كلمة المرور الخاصة بك هي",
"registerConfirmSubject":"تأكيد تسجيل الحساب[LemonLDAP::NG] ",
"registerDoneSubject":"حسابك الجديد[LemonLDAP::NG]",
"requestIssuedFromIP":"الطلب قد أرسل من عنوان الآي بي",
"date":"Date",
"yourLoginCodeIs":"Your login code is",
"yourLoginIs":"تسجيل الدخول الخاص بك هو"
}

View File

@ -6,15 +6,20 @@
"click2Reset":"Click here to reset your password",
"goToPortal":"Go to portal",
"hello":"Hello",
"host":"Host",
"location": "Location",
"mail2fSubject":"[LemonLDAP::NG] Your login code",
"mailConfirmSubject": "[LemonLDAP::NG] Password reset confirmation",
"mailSubject": "[LemonLDAP::NG] Your new password",
"newLocationWarningMailBody":"Your account was signed in to from a new location.",
"newLocationWarningMailSubject":"[LemonLDAP::NG] Sign-in from a new location",
"newPwdIs":"Your new password is",
"pwdChanged":"Your password was changed.",
"pwdIs":"Your password is",
"registerConfirmSubject": "[LemonLDAP::NG] Account register confirmation",
"registerDoneSubject": "[LemonLDAP::NG] Your new account",
"requestIssuedFromIP":"The request was issued from IP",
"date":"Date",
"yourLoginCodeIs":"Your login code is",
"yourLoginIs":"Your login is"
}

View File

@ -6,15 +6,20 @@
"click2Reset":"Haga clic para reiniciar su contraseña",
"goToPortal":"Ir al portal",
"hello":"Hola",
"host":"Host",
"location": "Location",
"mail2fSubject":"[LemonLDAP::NG] Su código de acceso",
"mailConfirmSubject":"[LemonLDAP::NG] Confirmación de reinicio de contraseña",
"mailSubject":"[LemonLDAP::NG] Su nueva contraseña",
"newLocationWarningMailBody":"Your account was signed in to from a new location.",
"newLocationWarningMailSubject":"[LemonLDAP::NG] Sign-in from a new location",
"newPwdIs":"Su nueva contraseña es",
"pwdChanged":"Su contraseña ha sido cambiada.",
"pwdIs":"Su contraseña es",
"registerConfirmSubject":"[LemonLDAP::NG] Confirmación de creación de cuenta",
"registerDoneSubject":"[LemonLDAP::NG] Su nueva cuenta",
"requestIssuedFromIP":"El pedido fue emitido desde la IP",
"date":"Date",
"yourLoginCodeIs":"Su código de acceso es ",
"yourLoginIs":"Su nombre de usuario es"
}

View File

@ -6,15 +6,20 @@
"click2Reset":"Klikkaa tästä nollataksesi salasanasi",
"goToPortal":"Siirry portaaliin",
"hello":"Hei",
"host":"Host",
"location":"Location",
"mail2fSubject":"[LemonLDAP::NG] Your login code",
"mailConfirmSubject":"[LemonLDAP::NG] Salasanan nollauksen vahvistus",
"mailSubject":"[LemonLDAP::NG] Uusi salasanasi",
"newLocationWarningMailBody":"Your account was signed in to from a new location.",
"newLocationWarningMailSubject":"[LemonLDAP::NG] Sign-in from a new location",
"newPwdIs":"Uusi salasanasi on",
"pwdChanged":"Salasanasi on vaihdettu.",
"pwdIs":"Sinun salasanasi on",
"registerConfirmSubject":"[LemonLDAP::NG] Tunnuksen rekisteröinnin vahvistus",
"registerDoneSubject":"[LemonLDAP::NG] Uusi käyttäjätunnuksesi",
"requestIssuedFromIP":"The request was issued from IP",
"date":"Date",
"yourLoginCodeIs":"Your login code is",
"yourLoginIs":"Your login is"
}

View File

@ -6,15 +6,20 @@
"click2Reset":"Cliquez ici pour réinitialiser votre mot de passe",
"goToPortal":"Aller au portail",
"hello":"Bonjour",
"host":"Hôte",
"location":"Localisation",
"mail2fSubject":"[LemonLDAP::NG] Votre code de connexion",
"mailConfirmSubject": "[LemonLDAP::NG] Confirmation de réinitialisation de mot de passe",
"mailSubject": "[LemonLDAP::NG] Votre nouveau mot de passe",
"newLocationWarningMailBody":"Une nouvelle connexion à votre compte a été détectée",
"newLocationWarningMailSubject":"[LemonLDAP::NG] Nouvelle connexion à votre compte",
"newPwdIs":"Votre nouveau mot de passe est",
"pwdChanged":"Votre mot de passe a été changé.",
"pwdIs":"Votre mot de passe est",
"registerConfirmSubject": "[LemonLDAP::NG] Confirmation d'enregistrement de compte",
"registerDoneSubject": "[LemonLDAP::NG] Votre nouveau compte",
"requestIssuedFromIP":"La demande provient de l'IP",
"date":"Date",
"yourLoginCodeIs":"Votre code de connexion est",
"yourLoginIs":"Votre identifiant est"
}

View File

@ -6,15 +6,20 @@
"click2Reset":"Clicca qui per reimpostare la password",
"goToPortal":"Vai al portale",
"hello":"Salve",
"host":"Host",
"location":"Location",
"mail2fSubject":"[LemonLDAP :: NG] Il tuo codice di accesso",
"mailConfirmSubject":"Conferma reimpostazione password [LemonLDAP::NG] ",
"mailSubject":"[LemonLDAP::NG] La tua nuova password",
"newLocationWarningMailBody":"Your account was signed in to from a new location.",
"newLocationWarningMailSubject":"[LemonLDAP::NG] Sign-in from a new location",
"newPwdIs":"La tua nuova password é",
"pwdChanged":"La tua password é stata cambiata",
"pwdIs":"La tua password é",
"registerConfirmSubject":"[LemonLDAP :: NG] Conferma registro account",
"registerDoneSubject":"[LemonLDAP::NG] Il tuo nuovo account",
"requestIssuedFromIP":"La richiesta è stata emessa da IP",
"date":"Date",
"yourLoginCodeIs":"Il tuo codice di accesso è",
"yourLoginIs":"Il tuo login é"
}

View File

@ -6,15 +6,20 @@
"click2Reset":"Kilik disini untuk menetapkan semula kata laluan anda",
"goToPortal":"Go to portal",
"hello":"Hello",
"host":"Host",
"location":"Location",
"mail2fSubject":"[LemonLDAP::NG] Kod login anda",
"mailConfirmSubject":"[LemonLDAP::NG] Pengesahan penetapan semula kata laluan",
"mailSubject":"[LemonLDAP::NG] Kata laluan baru anda",
"newLocationWarningMailBody":"Your account was signed in to from a new location.",
"newLocationWarningMailSubject":"[LemonLDAP::NG] Sign-in from a new location",
"newPwdIs":"Kata laluan baru anda ialah",
"pwdChanged":"Kata laluan anda telah ditukar",
"pwdIs":"Kata laluan anda ialah",
"registerConfirmSubject":"[LemonLDAP::NG] Pengesahan pendaftaran akaun",
"registerDoneSubject":"[LemonLDAP::NG] Akaun baru anda",
"requestIssuedFromIP":"Permintaan itu dari alamat IP",
"date":"Date",
"yourLoginCodeIs":"Kod login anda ialah",
"yourLoginIs":"Login anda ialah"
}

View File

@ -6,15 +6,20 @@
"click2Reset":"Parolanızı sıfırlamak için buraya tıklayın",
"goToPortal":"Portala git",
"hello":"Merhaba",
"host":"Host",
"location":"Location",
"mail2fSubject":"[LemonLDAP::NG] Giriş kodunuz",
"mailConfirmSubject":"[LemonLDAP::NG] Parola sıfırlama onayı",
"mailSubject":"[LemonLDAP::NG] Yeni parolanız",
"newLocationWarningMailBody":"Your account was signed in to from a new location.",
"newLocationWarningMailSubject":"[LemonLDAP::NG] Sign-in from a new location",
"newPwdIs":"Yeni parolanız",
"pwdChanged":"Parolanız değiştirildi.",
"pwdIs":"Parolanız",
"registerConfirmSubject":"[LemonLDAP::NG] Hesap açma onayı",
"registerDoneSubject":"[LemonLDAP::NG] Yeni hesabınız",
"requestIssuedFromIP":"İstek IP'den gönderildi",
"date":"Date",
"yourLoginCodeIs":"Giriş kodunuz",
"yourLoginIs":"Giriş bilgileriniz"
}

View File

@ -6,15 +6,20 @@
"click2Reset":"Nhấn ở đây để thiết lập lại mật khẩu của bạn",
"goToPortal":"Đi tới cổng thông tin",
"hello":"Xin chào",
"host":"Host",
"location":"Location",
"mail2fSubject":"[LemonLDAP::NG] Your login code",
"mailConfirmSubject":"[LemonLDAP::NG] Xác nhận thiết lập lại mật khẩu",
"mailSubject":"[LemonLDAP::NG] Mật khẩu mới của bạn",
"newLocationWarningMailBody":"Your account was signed in to from a new location.",
"newLocationWarningMailSubject":"[LemonLDAP::NG] Sign-in from a new location",
"newPwdIs":"Mật khẩu mới của bạn là",
"pwdChanged":"Mật khẩu của bạn đã được thay đổi.",
"pwdIs":"Mật khẩu của bạn là",
"registerConfirmSubject":"[LemonLDAP::NG] Xác nhận đăng ký tài khoản",
"registerDoneSubject":"[LemonLDAP::NG] Tài khoản mới của bạn",
"requestIssuedFromIP":"Yêu cầu được gửi đi từ địa chỉ IP",
"date":"Date",
"yourLoginCodeIs":"Your login code is",
"yourLoginIs":"Đăng nhập của bạn là"
}

View File

@ -6,15 +6,20 @@
"click2Reset":"请点击此处充值您的密码",
"goToPortal":"回到首页",
"hello":"您好",
"host":"Host",
"location":"Location",
"mail2fSubject":"[LemonLDAP::NG] Your login code",
"mailConfirmSubject":"[LemonLDAP::NG] 密码重置确认",
"mailSubject":"[LemonLDAP::NG] 您的新密码",
"newLocationWarningMailBody":"Your account was signed in to from a new location.",
"newLocationWarningMailSubject":"[LemonLDAP::NG] Sign-in from a new location",
"newPwdIs":"您的新密码是",
"pwdChanged":"您的密码已经修改",
"pwdIs":"您的密码是",
"registerConfirmSubject":"[LemonLDAP::NG] 账号注册确认",
"registerDoneSubject":"[LemonLDAP::NG] 您的新账号",
"requestIssuedFromIP":"此请求来自IP地址",
"date":"Date",
"yourLoginCodeIs":"Your login code is",
"yourLoginIs":"您登陆的账户是"
}

View File

@ -6,15 +6,20 @@
"click2Reset":"點擊此處以重設您的密碼",
"goToPortal":"回到首頁",
"hello":"您好",
"host":"Host",
"location":"Location",
"mail2fSubject":"[LemonLDAP::NG] 您的登入代碼",
"mailConfirmSubject":"[LemonLDAP::NG] 確認重設密碼",
"mailSubject":"[LemonLDAP::NG] 您的新密碼",
"newLocationWarningMailBody":"Your account was signed in to from a new location.",
"newLocationWarningMailSubject":"[LemonLDAP::NG] Sign-in from a new location",
"newPwdIs":"您的新密碼為",
"pwdChanged":"您的密碼已變更",
"pwdIs":"您的密碼為",
"registerConfirmSubject":"[LemonLDAP::NG] 確認帳號註冊",
"registerDoneSubject":"[LemonLDAP::NG] 您的新帳號",
"requestIssuedFromIP":"請求發出從 IP",
"date":"Date",
"yourLoginCodeIs":"您的登入代碼為",
"yourLoginIs":"您的登入為"
}

View File

@ -0,0 +1,12 @@
<TMPL_INCLUDE NAME="mail_header.tpl">
<p>
<span trspan="hello">Hello</span> <TMPL_VAR NAME="session_cn" ESCAPE=HTML>,<br />
<br />
<h3><span trspan="newLocationWarningMailBody">Your account was signed in to from a new location</span></h3></br>
<span trspan="location">Location</span> <b><TMPL_VAR NAME="location"></b></br>
<span trspan="date">Date</span> <b><TMPL_VAR NAME="date"></b></br>
<span trspan="UA">UA</span> <b><TMPL_VAR NAME="ua"></b></br>
</p>
<TMPL_INCLUDE NAME="mail_footer.tpl">

View File

@ -0,0 +1,79 @@
use Test::More;
use strict;
use IO::String;
use POSIX qw(locale_h);
use locale;
setlocale( LC_TIME, "C" );
BEGIN {
eval {
require 't/test-lib.pm';
require 't/smtp.pm';
};
}
my $res;
my $maintests = 5;
SKIP: {
eval 'require Email::Sender::Simple;';
if ($@) {
skip 'Missing dependencies', $maintests;
}
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
useSafeJail => 1,
authentication => 'Demo',
userDB => 'Same',
passwordDB => 'Demo',
captcha_mail_enabled => 0,
portalMainLogo => 'common/logos/logo_llng_old.png',
newLocationWarning => 1,
loginHistoryEnabled => 1,
newLocationWarningMailSubject => 'Test new location mail',
newLocationWarningMailBody => 'Test $location $date $ua',
}
}
);
## Simple access
ok( $res = $client->_get( '/', accept => 'text/html' ), 'Get Portal', );
my ( $host, $url, $query ) =
expectForm( $res, '#', undef, 'user', 'password' );
## Authentication #1 with IP #1 (Test 1)
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'First auth query'
);
## Authentication #1 with IP #2 (Test 2)
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
ip => '127.0.0.2',
),
'Second auth query'
);
like( subject(), qr#Test new location mail#, ' Subject found' );
like(
mail(),
qr#^Test 127\.0\.0\.2 \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} Mozilla/5\.0 \(VAX-4000; rv:36\.0\) Gecko/20350101 Firefox$#,
' Mail sent (IP, Date and UA found)'
);
}
count($maintests);
clean_sessions();
done_testing( count() );

View File

@ -0,0 +1,187 @@
use Test::More;
use strict;
use IO::String;
use POSIX qw(locale_h);
use locale;
setlocale( LC_TIME, "C" );
BEGIN {
eval {
require 't/test-lib.pm';
require 't/smtp.pm';
};
}
my $res;
my $maintests = 20;
SKIP: {
eval 'require Email::Sender::Simple;';
if ($@) {
skip 'Missing dependencies', $maintests;
}
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
useSafeJail => 1,
authentication => 'Demo',
userDB => 'Same',
passwordDB => 'Demo',
captcha_mail_enabled => 0,
portalMainLogo => 'common/logos/logo_llng_old.png',
newLocationWarning => 1,
loginHistoryEnabled => 1
}
}
);
## Simple access
ok( $res = $client->_get( '/', accept => 'text/html' ), 'Get Portal', );
my ( $host, $url, $query ) =
expectForm( $res, '#', undef, 'user', 'password' );
## Authentication #1 with IP #1
clear_mail();
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'First auth query'
);
my $id = expectCookie($res);
$client->logout($id);
ok( !mail(), "First time seeing a new IP, no mail sent" );
## Authentication #2 with IP #1
clear_mail();
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'Second auth query'
);
$id = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
$client->logout($id);
ok( !mail(), "Second time seeing a new IP, no mail sent" );
## Authentication #3 with IP #2
clear_mail();
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
ip => '127.0.0.2',
),
'Third auth query'
);
$id = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
$client->logout($id);
like(
mail(),
qr#<h3><span>Your account was signed in to from a new location\.</span></h3></br>
#, 'First login on a new IP, email sent'
);
## Authentication #1 with IP #3 wrong password
clear_mail();
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=ohwd'),
length => 23,
accept => 'text/html',
ip => "127.0.0.3",
),
'Fourth auth query'
);
ok( $res->[2]->[0] =~ /<span trmsg="5"><\/span>/, ' Bad credential' )
or print STDERR Dumper( $res->[2]->[0] );
ok( !mail(), "Failed login with a new IP, no email sent" );
## Authentication #2 with IP #3
clear_mail();
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
ip => '127.0.0.3',
),
'Fifth auth query'
);
$id = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
like(
subject(),
qr#\[LemonLDAP::NG\] Sign-in from a new location#,
' Subject found'
);
like(
mail(),
qr#<h3><span>Your account was signed in to from a new location\.</span></h3></br>#,
' Mail sent (Wrong password)'
);
like(
mail(),
qr#<span>Location</span> <b>127.0.0.3</b>#,
' Location found in mail body'
);
like(
mail(),
qr#<span>Date</span> <b>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}</b>#,
' Date found in mail body'
);
like(
mail(),
qr#<span>UA</span> <b>Mozilla/5\.0 \(VAX-4000; rv:36\.0\) Gecko/20350101 Firefox</b></br>#,
' UserAgent found in mail body'
);
## Authentication #3 with IP #3
clear_mail();
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
ip => '127.0.0.3',
),
'Fifth auth query'
);
$id = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
ok( !mail(), "Login on newly learned address, no email" );
## Authentication #3 with IP #1
clear_mail();
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'Fifth auth query'
);
$id = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
ok( !mail(), "Login on previously learned address, no email" );
}
count($maintests);
clean_sessions();
done_testing( count() );

View File

@ -1,17 +1,23 @@
package main;
my ($mail, $mail_envelope, $mail_subject);
my ( $mail, $mail_envelope, $mail_subject );
sub mail {
return $mail;
}
sub clear_mail {
$mail = undef;
$mail_envelope = undef;
$mail_subject = undef;
}
sub envelope {
return $mail_envelope;
}
sub subject {
my $subject = ($mail_subject =~ /=\?utf-8\?B\?(.+?)\?=/)[0];
my $subject = ( $mail_subject =~ /=\?utf-8\?B\?(.+?)\?=/ )[0];
return decode_base64($subject);
}