rememberAuthChoice: reconciliation with v2.0 branch (#2737)

This commit is contained in:
David Coutadeur 2022-05-16 10:06:23 +00:00
commit 8f4eb83a2e
52 changed files with 561 additions and 243 deletions

View File

@ -887,23 +887,9 @@ install_examples_site:
install_doc_site: install_doc_site:
# Offline documentation install # Offline documentation install
@rm -rf $(RDEFDOCDIR) @rm -rf $(RDEFDOCDIR)
# Install doc directories
@install -v -d -m 755 $(RDEFDOCDIR) @install -v -d -m 755 $(RDEFDOCDIR)
@cd doc && find * -type d |(cd $(RDEFDOCDIR); xargs install -v -d -m 755) && cd - @cd doc && find index.html pages/* -type f ! -path '*/.*' -exec install -v -m 644 -D '{}' $(RDEFDOCDIR)/'{}' \; && cd -
# Install HTML files
@cd doc && for f in `find * -type f -name '*.html'`; do \
echo "Installing $$f"; \
../scripts/transform-templates \
usedebianlibs $(USEDEBIANLIBS) \
useexternallibs $(USEEXTERNALLIBS) \
jsminified $(JSCOMPRESS) \
cssminified $(CSSCOMPRESS) <$$f \
> $(RDEFDOCDIR)/$$f; \
done && cd -
# Install other files
@cd doc && for f in `find * -type f ! -name '*.html'`; do \
install -v -m 644 $$f $(RDEFDOCDIR)/$$f; \
done && cd -
# Remove js # Remove js
@cd $(RDEFDOCDIR) && if test "$(USEEXTERNALLIBS)" = "yes"; then \ @cd $(RDEFDOCDIR) && if test "$(USEEXTERNALLIBS)" = "yes"; then \
rm -rvf $(DOCEXTERNALLIBS); \ rm -rvf $(DOCEXTERNALLIBS); \
@ -1105,7 +1091,7 @@ debian-dist: clean
@cp lemonldap-ng-$(VERSION)/_example/etc/api-apache2.X.conf lemonldap-ng-$(VERSION)/_example/etc/api-apache2.conf @cp lemonldap-ng-$(VERSION)/_example/etc/api-apache2.X.conf lemonldap-ng-$(VERSION)/_example/etc/api-apache2.conf
@cp lemonldap-ng-$(VERSION)/_example/etc/test-apache2.X.conf lemonldap-ng-$(VERSION)/_example/etc/test-apache2.conf @cp lemonldap-ng-$(VERSION)/_example/etc/test-apache2.X.conf lemonldap-ng-$(VERSION)/_example/etc/test-apache2.conf
@rm -rf lemonldap-ng-$(VERSION)/lemonldap-ng-$(VERSION) @rm -rf lemonldap-ng-$(VERSION)/lemonldap-ng-$(VERSION)
@find lemonldap-ng-$(VERSION)/ -name node_modules -exec rm -rf '{}' \; -@find lemonldap-ng-$(VERSION)/ -name node_modules -exec rm -rf '{}' \;
@$(COMPRESS) lemonldap-ng_$(VERSION).orig.$(COMPRESSSUFFIX) lemonldap-ng-$(VERSION) @$(COMPRESS) lemonldap-ng_$(VERSION).orig.$(COMPRESSSUFFIX) lemonldap-ng-$(VERSION)
@rm -rf lemonldap-ng-$(VERSION) @rm -rf lemonldap-ng-$(VERSION)

View File

@ -136,7 +136,7 @@ Application Configuration
.. image:: applications/simplesamlphp_logo.png :doc:`simpleSAMLphp<applications/simplesamlphp>` .. image:: applications/simplesamlphp_logo.png :doc:`simpleSAMLphp<applications/simplesamlphp>`
.. image:: applications/spring_logo.png :doc:`Spring<applications/spring>` .. image:: applications/spring_logo.png :doc:`Spring<applications/spring>`
.. image:: applications/symfony_logo.png :doc:`Symfony<applications/symfony>` .. image:: applications/symfony_logo.png :doc:`Symfony<applications/symfony>`
.. image:: applications/sympa_logo.png :doc:`Sympa<applications/sympa>` .. image:: applications/sympa_logo.png :doc:`Sympa<applications/sympa>`
.. image:: applications/tomcat_logo.png :doc:`Tomcat<applications/tomcat>` .. image:: applications/tomcat_logo.png :doc:`Tomcat<applications/tomcat>`
.. image:: applications/wekan-logo.png :doc:`Wekan<applications/wekan>` .. image:: applications/wekan-logo.png :doc:`Wekan<applications/wekan>`
.. image:: applications/wiki.js.svg :doc:`Wiki.js<applications/wikijs>` .. image:: applications/wiki.js.svg :doc:`Wiki.js<applications/wikijs>`

View File

@ -8,9 +8,59 @@ Presentation
`Sympa <http://www.sympa.org>`__ is a mailing list manager. `Sympa <http://www.sympa.org>`__ is a mailing list manager.
To configure SSO with Sympa, use **Magic authentication**: a special SSO To configure SSO with Sympa, you have the choice between:
URL is protected by LL::NG, Sympa will display a button for users who * CAS
wants to use this feature. * **Magic authentication**: a special SSO URL is protected by LL::NG, Sympa will display a button for users who wants to use this feature.
We recommend to use CAS.
CAS
---
Sympa configuration
~~~~~~~~~~~~~~~~~~~
Edit the file "auth.conf", for example:
::
vi /etc/sympa/auth.conf
And fill it:
::
cas
base_url https://auth.example.com/cas
non_blocking_redirection on
auth_service_name SSO
ldap_host ldap.example.com:389
ldap_get_email_by_uid_filter (uid=[uid])
ldap_timeout 7
ldap_suffix dc=example,dc=com
ldap_scope sub
ldap_email_attribute mail
Restart services:
::
service sympa restart
service apache2 restart
See also `official documentation <https://sympa-community.github.io/manual/customize/cas.html>`__
LemonLDAP::NG configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Declare CAS application in the configuration, register the service URL.
No attributes are needed.
Magic authentication
--------------------
.. tip:: .. tip::
@ -19,9 +69,6 @@ wants to use this feature.
removed since it works only with Sympa-5 which has been deprecated removed since it works only with Sympa-5 which has been deprecated
Configuration
-------------
Sympa configuration Sympa configuration
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~

View File

@ -39,7 +39,7 @@ must set:
- overloaded parameters: you can redefine any LLNG string parameters. - overloaded parameters: you can redefine any LLNG string parameters.
For example, if you use 2 different LDAP, the first can use normal For example, if you use 2 different LDAP, the first can use normal
configuration and for the second, overwritten parameter can redefine configuration and for the second, overwritten parameter can redefine
ldapServer,... ldapServer or any existing parameter.
.. note:: .. note::
@ -63,12 +63,13 @@ parameters.
For example, if DBI is configured to use PostgreSQL but DB2 is a MySQL For example, if DBI is configured to use PostgreSQL but DB2 is a MySQL
DB, you can override the "dbiChain" parameter. DB, you can override the "dbiChain" parameter.
You can also override a complex key like ldapExportedVars, by setting a The over parameter is a HASH ref where keys are attributes names and values are the overriden value.
JSON value: To override a complex key like ldapExportedVars, you must use a JSON value, as the over parameter
expect string values:
.. code-block:: javascript .. code-block:: javascript
{"cn" => "cn", "uid" => "sAMAccounName", "mail" => "mail"} {"cn": "cn", "uid": "sAMAccounName", "mail": "mail"}
.. attention:: .. attention::

View File

@ -301,7 +301,7 @@ In this example we have:
/usr/share/lemonldap-ng/bin/lemonldap-ng-cli -yes 1 \ /usr/share/lemonldap-ng/bin/lemonldap-ng-cli -yes 1 \
addKey \ addKey \
casAppMetaDataExportedVars/testapp mail mail \ casAppMetaDataExportedVars/testapp mail mail \
casAppMetaDataExportedVars/testapp cn cn casAppMetaDataExportedVars/testapp cn cn \
casAppMetaDataOptions/testapp casAppMetaDataOptionsService 'https://testapp.example.com/' casAppMetaDataOptions/testapp casAppMetaDataOptionsService 'https://testapp.example.com/'
Configure SAML Identity Provider Configure SAML Identity Provider

View File

@ -138,6 +138,8 @@ if 'LLNGSPHINXWEBSITE' in os.environ:
import sphinx_rtd_theme import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme' html_theme = 'sphinx_rtd_theme'
html_theme_options = {} html_theme_options = {}
else:
html_copy_source = False
# html_theme_options = {} # html_theme_options = {}

View File

@ -58,7 +58,7 @@ As *root:*
apt install aptitude apt install aptitude
aptitude install vim make devscripts yui-compressor git git-gui libjs-uglify coffeescript cpanminus autopkgtest pkg-perl-autopkgtest aptitude install vim make devscripts yui-compressor git git-gui libjs-uglify coffeescript cpanminus autopkgtest pkg-perl-autopkgtest
aptitude install libauth-yubikey-webclient-perl libnet-smtp-server-perl libtime-fake-perl libtest-output-perl libtest-pod-perl libtest-leaktrace-perl libtest-mockobject-perl uglifyjs aptitude install libauth-yubikey-webclient-perl libnet-smtp-server-perl libtime-fake-perl libtest-output-perl libtest-pod-perl libtest-leaktrace-perl libtest-mockobject-perl uglifyjs libdbd-sqlite3-perl libauthen-webauthn-perl libauthen-oath-perl
cpanm Authen::U2F Authen::U2F::Tester Crypt::U2F::Server::Simple cpanm Authen::U2F Authen::U2F::Tester Crypt::U2F::Server::Simple

View File

@ -51,13 +51,20 @@ portal:
- macros are used to extend (or rewrite) - macros are used to extend (or rewrite)
:doc:`exported variables<exportedvars>`. A macro is stored as :doc:`exported variables<exportedvars>`. A macro is stored as
attributes: it can contain boolean results or any string attributes: it can contain boolean results or any string
- macros can also be used to import environment variables *(these - macros can also be used for importing environment variables *(these
variables are in CGI format)*. Example: ``$ENV{HTTP_COOKIE}`` variables are in CGI format)*. Example: ``$ENV{HTTP_COOKIE}``
- groups are stored as a string with values separated by ''; '' - groups are stored as a string with values separated by ''; ''
(default values separator) in the special attribute ``groups``: it (default multivalues separator) in the special attribute ``groups``: it
contains the names of groups whose rules were returned true for the contains names of groups whose rules were returned true for the
current user. For example: current user. For example:
.. danger::
Macros can be used for rewriting or overloading exported variables
but it can lead to some side effects. Be aware of alphabetical order
and keep in mind that exported variables are set. Then macros and
groups are computed.
.. code-block:: perl .. code-block:: perl
$groups = group3; admin $groups = group3; admin

View File

@ -28,6 +28,7 @@ Plugins
resetpassword resetpassword
resetcertificate resetcertificate
restservices restservices
restauthuserpwdbackend
soapservices soapservices
stayconnected stayconnected
rememberauthchoice rememberauthchoice

View File

@ -361,7 +361,7 @@ Password Policy
- **Minimal upper characters**: leave 0 to bypass the check - **Minimal upper characters**: leave 0 to bypass the check
- **Minimal digit characters**: leave 0 to bypass the check - **Minimal digit characters**: leave 0 to bypass the check
- **Minimal special characters**: leave 0 to bypass the check - **Minimal special characters**: leave 0 to bypass the check
- **Allowed special characters**: set '__ALL__' value to allow ALL special characters. A blanck value forbids ALL special characters (Note that ``_`` is not a special character) - **Allowed special characters**: set '__ALL__' value to allow ALL special characters. A blank value forbids ALL special characters (Note that ``_`` is not a special character)
.. _portalcustom-other-parameters: .. _portalcustom-other-parameters:

View File

@ -95,6 +95,8 @@ Second factor
- Crypt::U2F::Server::Simple (U2F keys) - Crypt::U2F::Server::Simple (U2F keys)
- Convert::Base32 (TOTP) - Convert::Base32 (TOTP)
- Authen::WebAuthn (FIDO2 WebAuthen)
- Authen::OATH (OTP)
Specific authentication backends Specific authentication backends
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -138,6 +140,7 @@ Unit tests
- Authen::U2F::Tester - Authen::U2F::Tester
- Crypt::U2F::Server - Crypt::U2F::Server
- Test::MockObject - Test::MockObject
- DBD::SQLite
- Test::Output - Test::Output
- Test::POD - Test::POD
- Time::Fake - Time::Fake

View File

@ -84,8 +84,6 @@ Then go in Manager, ``General Parameters`` » ``Plugins`` »
- **Display generate password box**: display a checkbox to allow - **Display generate password box**: display a checkbox to allow
user to generate a new password instead of choosing one (default: user to generate a new password instead of choosing one (default:
disabled) disabled)
- **Regexp for password generation**: Regular expression used to generate the password. Set a blank value to use
:: password policy if enabled or default regexp will be employed: [A-Z]{3}[a-z]{5}.\d{2}
* **Regexp for password generation**: Regular expression used to generate the password (default: [A-Z]{3}[a-z]{5}.\d{2})

View File

@ -0,0 +1,90 @@
REST auth/user/password backend
===============================
LL::NG Portal provides REST end points for auth/user/password:
- POST /proxy/pwdConfirm: check password
- POST /proxy/getUser: get user data
- POST /proxy/pwdReset: update password
These end points can be used to connect another LemonLDAP::NG server using :doc:`REST authentication backend<authrest>`.
API
---
Password confirm
~~~~~~~~~~~~~~~~
POST a JSON structure with ``user`` and ``password``.
It will return a JSON structure with ``result`` parameter (``true`` or ``false``).
Request:
.. code::
curl -H "Accept: application/json" -d '{"user":"dwho","password":"dwho"}' https://auth.example.com/proxy/pwdConfirm
Response:
.. code-block:: javascript
{"result":true}
Get user data
~~~~~~~~~~~~~
POST a JSON structure with ``user``.
It will return a JSON structure with ``result`` and ``info`` parameters.
Request:
.. code::
curl -H "Accept: application/json" -d '{"user":"rtyler"}' https://auth.example.com/proxy/getUser
Response:
.. code-block:: javascript
{"info":{"_utime":1651055131,"hGroups":{"users":{"name":"users"},"earthlings":{"name":"earthlings"}},"ipAddr":"127.0.0.1","_auth":"Demo","_url":null,"uid":"rtyler","mail":"rtyler@badwolf.org","_userDB":"Demo","_startTime":"20220427122531","UA":"curl/7.68.0","cn":"Rose Tyler","_user":"rtyler","_language":"en","groups":"users; earthlings","_whatToTrace":"rtyler"},"result":true}
Update password
~~~~~~~~~~~~~~~
POST a JSON structure with ``user`` or ``mail`` and ``password``.
It will return a JSON structure with ``result`` parameter.
Request:
.. code::
curl -H "Accept: application/json" -d '{"user":"rtyler","password":"secret"}' https://auth.example.com/proxy/pwdReset
Response:
.. code-block:: javascript
{"result":true}
Setup
-----
Manager
~~~~~~~
First, activate REST in ``General parameters`` » ``Plugins`` »
``Portal servers`` » ``REST authentication server`` and ``REST password reset server``.
Apache
~~~~~~
REST end points access must be allowed in Apache portal
configuration (for example, access by IP range):
.. code-block:: apache
# REST/SOAP functions for proxy auth and password reset (disabled by default)
<Location /index.fcgi/proxy>
Require ip 192.168.2.0/24
</Location>

View File

@ -21,6 +21,9 @@ example:
$env->{HTTP_ACCEPT} !~ m:application/json: $env->{HTTP_ACCEPT} !~ m:application/json:
Another solution is using the :doc:`REST auth/user/password backend<restauthuserpwdbackend>`.
API API
^^^ ^^^

View File

@ -5,16 +5,16 @@ In modern applications, web application may need to request some other
web applications on behalf of the authenticated users. There are three web applications on behalf of the authenticated users. There are three
ways to do this: ways to do this:
- the Ugly : provide to all applications SSO cookie. Not secured - the Ugly: provide to all applications SSO cookie. Not secured
because SSO cookie can be caught and used everywhere, every time by because SSO cookie can be caught and used everywhere, every time by
everyone!!! **NOT RECOMMENDED**. everyone!!! **NOT RECOMMENDED**.
- the Bad (:doc:`Secure Token Handler<securetoken>`) - the Bad (:doc:`Secure Token Handler<securetoken>`): **Deprecated**.
: **Deprecated**. Can be used in specific cases Should be used for specific cases
- the Good (Service Token Handler): See below ! (Thanks Sergio...) - the Good (Service Token Handler): See below! (Thanks Sergio...)
The "Bad" method consists to give the token (cookie value) to WebApp1 The "Bad" method consists to give the token (cookie value) to WebApp1
which uses it as cookie header in its request. Since 2.0 version, LL::NG which uses it as cookie header in its request. Since 2.0 version, LL::NG
gives a better way (the Good !) to do this by using limited scope gives a better way (the Good!) to do this by using limited scope
tokens. tokens.
Tokens are time limited (30 seconds by default) and URL restricted. Tokens are time limited (30 seconds by default) and URL restricted.
@ -24,22 +24,22 @@ Tokens are time limited (30 seconds by default) and URL restricted.
Webapp1 handler configuration Webapp1 handler configuration
----------------------------- -----------------------------
Select **Main** handler type to protect WebApp1 and insert a header Select **Main** handler type to protect WebApp1 and append a header containing:
named **X-Llng-Token** filled with this value:
.. code-block:: perl .. code-block:: perl
token( $_session_id, 'webapp2.example.com', 'webapp3.example.com', 'serviceHeader1=webapp1.example.com', "testHeader=$uid" ) token( $_session_id, 'webapp2.example.com', 'webapp3.example.com', 'serviceHeader1=webapp1.example.com', "testHeader=$uid" )
WebApp1 can read this header and use it in its requests by setting the WebApp1 can read this header and use it in its requests by setting the
``X-Llng-Token`` header. The token is built by using the session ID and ``X-LLNG-TOKEN`` header. The token is built by using the ``token`` extended
authorized virtualhosts list. By default, the Service Token is only with session ID and authorized virtualhosts list parameters. A Service Token is valide
available during 30 seconds and for specified virtualhosts. The token for the specified virtual hosts only and during 30 seconds by default. It can also be
can be use to send service headers to webapp2 like origin host by used for sending service headers (headerName1=headerValue1) to requested
example. apps. Can be useful to send the origin host by example. Service headers are
sent to ALL requested applications.
You can set ServiceToken TTL in the virtualHost options in Manager for You can set Service Token TTL by editing virtualHost options in Manager
each required virtualHost. for each requested virtualHost.
You can also set ServiceToken default timeout (30 seconds) by editing You can also set ServiceToken default timeout (30 seconds) by editing
``lemonldap-ng.ini`` in section [handler]: ``lemonldap-ng.ini`` in section [handler]:
@ -58,7 +58,7 @@ Webapp2 handler configuration
----------------------------- -----------------------------
Change handler type to **ServiceToken**. So it is able to manage both Change handler type to **ServiceToken**. So it is able to manage both
user and server connections. And that's all ! user and server connections. And that's all!
.. |Kinematic| image:: documentation/server_to_server.png .. |Kinematic| image:: documentation/server_to_server.png

View File

@ -22,22 +22,22 @@ user attributes to an application
``*aaS`` means that application can drive underlying layer (IaaS for ``*aaS`` means that application can drive underlying layer (IaaS for
infrastructure, PaaS for platform,…). So for us, ``SSOaaS`` must provide infrastructure, PaaS for platform,…). So for us, ``SSOaaS`` must provide
the ability for an application to manage authorizations and choose user the ability for an application to manage authorizations and choose user
attributes to set. Authentication can not be really ``*aaS``: application attributes to receive. Authentication can not be really ``*aaS``: application
must just use it but not manage it. can just use it but not manage it.
LL::NG affords some features that can be used for providing SSO as a LL::NG affords some features that can be used for providing SSO as a
service. So a web application can manage its rules and headers. service. So a web application can manage its rules and headers.
Docker or VM images (Nginx only) includes LL::NG Nginx configuration that Docker or VM images (Nginx only) includes LL::NG Nginx configuration that
aims to a aims to a
:ref:`central LL::NG authorization server<platformsoverview-external-servers-for-nginx>`. :ref:`Central LL::NG authorization server<platformsoverview-external-servers-for-nginx>`.
By default, all authenticated users can access and just one header is set: By default, all authenticated users can access and just one header is set:
``Auth-User``. If application defines a ``RULES_URL`` parameter that refers to ``Auth-User``. If application defines a ``RULES_URL`` parameter that refers to
a JSON file, authorization server will read it, apply specified rules a JSON file, authorization server will read it, apply specified rules
and set required headers (see :doc:`DevOps Handler<devopshandler>`). and set required headers (see :doc:`DevOps Handler<devopshandler>`).
Two different kind of architecture are existing to do this: Two different kinds of architecture are existing to do this:
- Using a :doc:`central FastCGI (or uWSGI) server<psgi>` - Using a :doc:`Central FastCGI (or uWSGI) server<psgi>`
- Using front Reverse-Proxies *(some cloud or HA installations use - Using front Reverse-Proxies *(some cloud or HA installations use
reverse-proxies in front-end)* reverse-proxies in front-end)*
@ -52,7 +52,7 @@ Two different kind of architecture are existing to do this:
```route-remote-addr = ^127\.0\.0\.25[34]$ break: 403 Forbidden for IP ${REMOTE_ADDR}``` ```route-remote-addr = ^127\.0\.0\.25[34]$ break: 403 Forbidden for IP ${REMOTE_ADDR}```
Example of a central FastCGI architecture: Example of a Central FastCGI architecture:
|image0| |image0|
@ -69,7 +69,8 @@ Nginx
Examples below are customized web server templates for Examples below are customized web server templates for
requesting authorization from a Central FastCGI server. requesting authorization from a Central FastCGI server.
You can use 'uwsgi_param' directive for requesting a Central uWSGI server (Nginx only): You can replace 'fastcgi_*' directives by 'uwsgi_*' for
requesting a Central uWSGI server (Nginx only):
.. code-block:: nginx .. code-block:: nginx
@ -93,12 +94,12 @@ You can use 'uwsgi_param' directive for requesting a Central uWSGI server (Nginx
fastcgi_pass_request_body off; fastcgi_pass_request_body off;
fastcgi_param CONTENT_LENGTH ""; fastcgi_param CONTENT_LENGTH "";
# Keep original hostname
fastcgi_param HOST $http_host;
# Keep original request (LL::NG server will receive /lmauth) # Keep original request (LL::NG server will receive /lmauth)
fastcgi_param X_ORIGINAL_URI $original_uri; fastcgi_param X_ORIGINAL_URI $original_uri;
# Keep original hostname
fastcgi_param HOST $http_host;
# Set redirection parameters # Set redirection parameters
fastcgi_param HTTPS_REDIRECT "$https"; fastcgi_param HTTPS_REDIRECT "$https";
fastcgi_param PORT_REDIRECT $server_port; fastcgi_param PORT_REDIRECT $server_port;
@ -130,7 +131,7 @@ You can use 'uwsgi_param' directive for requesting a Central uWSGI server (Nginx
fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
} }
# Example as ReverseProxy: # Example as Reverse-Proxy:
location /api/ { location /api/ {
auth_request /lmauth; auth_request /lmauth;
set $original_uri $uri$is_args$args; set $original_uri $uri$is_args$args;
@ -147,14 +148,15 @@ You can use 'uwsgi_param' directive for requesting a Central uWSGI server (Nginx
} }
} }
Apache Apache
^^^^^^ ^^^^^^
LL::NG provides an experimental FastCGI client. You have to LL::NG provides a dedicated FastCGI client. You have to
install LemonLDAP::NG handler (LL::NG FastCGI client), install LemonLDAP::NG handler (LL::NG FastCGI client),
FCGI::Client (Perl FastCGI dependency) and Mod_Perl2 (Apache module) FCGI::Client (Perl FastCGI dependency) and Mod_Perl2 (Apache module
used for parsing HTTP headers. used for parsing HTTP headers).
Then, add this in your apache2.conf web applications or reverse-proxies. Then, add this in your apache2.conf web applications or Reverse-Proxies.
.. code-block:: apache .. code-block:: apache
@ -182,25 +184,25 @@ Then, add this in your apache2.conf web applications or reverse-proxies.
# Keep original hostname # Keep original hostname
PerlSetVar HOST HTTP_HOST PerlSetVar HOST HTTP_HOST
# This URL will be fetched by the Central FastCGI server then
# used for compliling access rules and headers about this VirtualHost
# CHECK THAT IT CAN BE REACHED BY THE CENTRAL FASTCGI SERVER
# PerlSetVar RULES_URL http://rulesserver/my.json
PerlSetVar RULES_URL http://myapp.domain.com/rules.json
# Set redirection parameters # Set redirection parameters
PerlSetVar PORT_REDIRECT SERVER_PORT PerlSetVar PORT_REDIRECT SERVER_PORT
PerlSetVar HTTPS_REDIRECT HTTPS PerlSetVar HTTPS_REDIRECT HTTPS
</LocationMatch>
# This URL will be fetched by the Central FastCGI server every 10 mn and
# then used for compliling access rules and headers relative to this VirtualHost
# CHECK THAT IT CAN BE REACHED BY THE CENTRAL FASTCGI SERVER
# PerlSetVar RULES_URL http://rulesserver/my.json
PerlSetVar RULES_URL http://myapp.domain.com/rules.json
</LocationMatch>
</VirtualHost> </VirtualHost>
Node.js Node.js
^^^^^^^ ^^^^^^^
Using `express <https://github.com/expressjs/express#readme>`__ and Using `express <https://github.com/expressjs/express#readme>`__ and
`fastcgi-authz-client <https://github.com/LemonLDAPNG/node-fastcgi-authz-client>`__, `fastcgi-authz-client <https://github.com/LemonLDAPNG/node-fastcgi-authz-client>`__,
you can protect also an Express server. Example: you can also protect an Express server. Example:
.. code-block:: javascript .. code-block:: javascript
@ -229,6 +231,7 @@ you can protect also an Express server. Example:
return console.log('Example app listening on port 3000!'); return console.log('Example app listening on port 3000!');
}); });
Plack application Plack application
^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
@ -259,7 +262,7 @@ Simple example:
host => '127.0.0.1', host => '127.0.0.1',
port => '9090', port => '9090',
fcgi_auth_params => { fcgi_auth_params => {
RULES_URL => 'https://my-server/my.json', RULES_URL => 'https://my-server/rules.json',
HTTPS_REDIRECT => 'ON', HTTPS_REDIRECT => 'ON',
PORT_REDIRECT => 443 PORT_REDIRECT => 443
}, },
@ -293,31 +296,40 @@ directory.
internal; internal;
include /etc/nginx/fastcgi_params; include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock; fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
# Force handler type: # Force handler type:
fastcgi_param VHOSTTYPE DevOps; fastcgi_param VHOSTTYPE DevOps;
# Drop post datas # Drop post datas
fastcgi_pass_request_body off; fastcgi_pass_request_body off;
fastcgi_param CONTENT_LENGTH ""; fastcgi_param CONTENT_LENGTH "";
# Keep original hostname # Keep original hostname
fastcgi_param HOST $http_host; fastcgi_param HOST $http_host;
# Keep original request (LL::NG server will received /lmauth) # Keep original request (LL::NG server will received /lmauth)
fastcgi_param X_ORIGINAL_URI $original_uri; fastcgi_param X_ORIGINAL_URI $original_uri;
# Set redirection params # Set redirection params
fastcgi_param HTTPS_REDIRECT "$https"; fastcgi_param HTTPS_REDIRECT "$https";
fastcgi_param PORT_REDIRECT $server_port; fastcgi_param PORT_REDIRECT $server_port;
} }
location /rules.json { location /rules.json {
auth_request off; auth_request off;
allow 127.0.0.0/8; allow 127.0.0.0/8;
deny all; deny all;
} }
location / { location / {
auth_request /lmauth; auth_request /lmauth;
set $original_uri $uri$is_args$args; set $original_uri $uri$is_args$args;
auth_request_set $lmremote_user $upstream_http_lm_remote_user; auth_request_set $lmremote_user $upstream_http_lm_remote_user;
auth_request_set $lmlocation $upstream_http_location; auth_request_set $lmlocation $upstream_http_location;
error_page 401 $lmlocation; error_page 401 $lmlocation;
include /etc/nginx/nginx-lua-headers.conf; include /etc/nginx/nginx-lua-headers.conf;
proxy_pass https://$vhost.internal.domain; proxy_pass https://$vhost.internal.domain;
} }
} }

View File

@ -46,6 +46,7 @@ Key Description
\_auth Authentication module \_auth Authentication module
\_userDB User module \_userDB User module
\_passwordDB Password module \_passwordDB Password module
\_2f Second factor (if 2FA was used)
\_issuerDB Issuer module (can be multivalued) \_issuerDB Issuer module (can be multivalued)
\_authChoice User choice done if :doc:`authentication choice<authchoice>` was used \_authChoice User choice done if :doc:`authentication choice<authchoice>` was used
\_authMulti Full name of authentication module (with ``#label``) used in Multi \_authMulti Full name of authentication module (with ``#label``) used in Multi

View File

@ -115,7 +115,7 @@ sub new {
# Serialize $conf and call store(). # Serialize $conf and call store().
# @param $conf Lemonldap::NG configuration hashRef # @param $conf Lemonldap::NG configuration hashRef
# @param %args Parameters # @param %args Parameters
# @return Number of the saved configuration, 0 in case of error. # @return Number of the saved configuration, <=0 in case of error.
sub saveConf { sub saveConf {
my ( $self, $conf, %args ) = @_; my ( $self, $conf, %args ) = @_;

View File

@ -112,7 +112,7 @@ foreach (@available) {
next if ( $opts{force} ); next if ( $opts{force} );
exit 6; exit 6;
} }
if ( my $r = $new->saveConf( $conf, %newargs ) ) { if ( $new->saveConf( $conf, %newargs ) > 0 ) {
print "Conf $conf->{cfgNum} stored\n"; print "Conf $conf->{cfgNum} stored\n";
next; next;
} }

View File

@ -466,7 +466,7 @@ if ( !$opts{'dry-run'} ) {
print "[OK] Configuration $numConf saved\n"; print "[OK] Configuration $numConf saved\n";
$exitCode = 0; $exitCode = 0;
} }
unless ($numConf) { unless ( $numConf > 0 ) {
print "[ERROR] Unable to save configuration\n"; print "[ERROR] Unable to save configuration\n";
$exitCode = 1; $exitCode = 1;
} }

View File

@ -50,7 +50,7 @@ $conf->{oidcServicePrivateKeySig} = $keys->{private};
$conf->{oidcServicePublicKeySig} = $keys->{public}; $conf->{oidcServicePublicKeySig} = $keys->{public};
$conf->{oidcServiceKeyIdSig} = $keys->{id}; $conf->{oidcServiceKeyIdSig} = $keys->{id};
$lmconf->saveConf($conf) or die $Lemonldap::NG::Common::Conf::msg; ( $lmconf->saveConf($conf) > 0 ) or die $Lemonldap::NG::Common::Conf::msg;
print "Configuration saved\n" if $debug; print "Configuration saved\n" if $debug;

View File

@ -1,4 +1,4 @@
# Apache2 FastCGI client to query remote LLNG FastCGI server # Apache2 FastCGI client to query remote LL::NG FastCGI server
# #
package Lemonldap::NG::Handler::ApacheMP2::FCGIClient; package Lemonldap::NG::Handler::ApacheMP2::FCGIClient;
@ -21,7 +21,7 @@ use constant REDIRECT => Apache2::Const::REDIRECT;
use constant DECLINED => Apache2::Const::DECLINED; use constant DECLINED => Apache2::Const::DECLINED;
use constant SERVER_ERROR => Apache2::Const::SERVER_ERROR; use constant SERVER_ERROR => Apache2::Const::SERVER_ERROR;
our $VERSION = '2.0.14'; our $VERSION = '2.0.15';
sub handler { sub handler {
my ( $class, $r ) = @_; my ( $class, $r ) = @_;
@ -43,11 +43,13 @@ sub handler {
SERVER_PORT => $r->get_server_port, SERVER_PORT => $r->get_server_port,
REQUEST_METHOD => $r->method, REQUEST_METHOD => $r->method,
}; };
foreach (qw(VHOSTTYPE RULES_URL HTTPS_REDIRECT PORT_REDIRECT)) { foreach (qw(VHOSTTYPE RULES_URL HTTPS_REDIRECT PORT_REDIRECT)) {
if ( my $t = $r->dir_config($_) ) { if ( my $t = $r->dir_config($_) ) {
$env->{$_} = $t; $env->{$_} = $t;
} }
} }
$r->headers_in->do( $r->headers_in->do(
sub { sub {
my $h = shift; my $h = shift;
@ -89,17 +91,14 @@ sub handler {
return REDIRECT; return REDIRECT;
} }
if ( $hdrs{'Lm-Remote-User'} ) { $r->user( $hdrs{'Lm-Remote-User'} ) if $hdrs{'Lm-Remote-User'};
$r->user( $hdrs{'Lm-Remote-User'} ); $r->subprocess_env( REMOTE_CUSTOM => $hdrs{'Lm-Remote-Custom'} )
} if $hdrs{'Lm-Remote-Custom'};
if ( $hdrs{'Lm-Remote-Custom'} ) {
$r->subprocess_env( REMOTE_CUSTOM => $hdrs{'Lm-Remote-Custom'} );
}
my $i = 1; my $i = 1;
while ( $hdrs{"Headername$i"} ) { while ( $hdrs{"Headername$i"} ) {
$r->headers_in->set( $hdrs{"Headername$i"} => $hdrs{"Headervalue$i"} ) $r->headers_in->set( $hdrs{"Headername$i"} => $hdrs{"Headervalue$i"} )
if ( $hdrs{"Headervalue$i"} ); if $hdrs{"Headervalue$i"};
$i++; $i++;
} }
$status = DECLINED if ( $status < 300 ); $status = DECLINED if ( $status < 300 );
@ -129,6 +128,9 @@ In apache2.conf:
PerlSetVar VHOSTTYPE DevOps PerlSetVar VHOSTTYPE DevOps
# or PerlSetVar VHOSTTYPE DevOpsST # or PerlSetVar VHOSTTYPE DevOpsST
PerlSetVar RULES_URL http://app.tld/rules.json PerlSetVar RULES_URL http://app.tld/rules.json
PerlSetVar HOST HTTP_HOST
PerlSetVar PORT_REDIRECT SERVER_PORT
PerlSetVar HTTPS_REDIRECT HTTPS
... ...
</VirtualHost> </VirtualHost>
@ -148,7 +150,7 @@ L<https://lemonldap-ng.org/documentation/latest/ssoaas>
=over =over
=item LemonLDAP::NG team L<http://lemonldap-ng.org/team> =item LemonLDAP::NG team L<https://lemonldap-ng.org/team.html>
=back =back
@ -160,7 +162,7 @@ L<https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/issues>
=head1 DOWNLOAD =head1 DOWNLOAD
Lemonldap::NG is available at Lemonldap::NG is available at
L<https://lemonldap-ng.org/download> L<https://lemonldap-ng.org/download.html>
=head1 COPYRIGHT AND LICENSE =head1 COPYRIGHT AND LICENSE

View File

@ -11,12 +11,23 @@ use Lemonldap::NG::Common::UserAgent;
use Lemonldap::NG::Common::FormEncode; use Lemonldap::NG::Common::FormEncode;
use Lemonldap::NG::Common::Session; use Lemonldap::NG::Common::Session;
our $VERSION = '2.0.7'; our $VERSION = '2.0.15';
our @ISA = ('Exporter'); our @ISA = ('Exporter');
our @EXPORT = qw(fetchId retrieveSession createSession hideCookie goToPortal); our @EXPORT = qw(fetchId retrieveSession createSession hideCookie goToPortal);
our @EXPORT_OK = @EXPORT; our @EXPORT_OK = @EXPORT;
our $_ua; our $_ua;
sub ua {
my ($class) = @_;
return $_ua if $_ua;
$_ua = Lemonldap::NG::Common::UserAgent->new( {
lwpOpts => $class->tsv->{lwpOpts},
lwpSslOpts => $class->tsv->{lwpSslOpts}
}
);
return $_ua;
}
## @rmethod protected fetchId ## @rmethod protected fetchId
# Get user session id from Authorization header # Get user session id from Authorization header
# Unlike usual processing, session id is computed from user creds, # Unlike usual processing, session id is computed from user creds,
@ -163,15 +174,4 @@ sub goToPortal {
} }
} }
sub ua {
my ($class) = @_;
return $_ua if ($_ua);
$_ua = Lemonldap::NG::Common::UserAgent->new( {
lwpOpts => $class->tsv->{lwpOpts},
lwpSslOpts => $class->tsv->{lwpSslOpts}
}
);
return $_ua;
}
1; 1;

View File

@ -8,9 +8,14 @@ our $VERSION = '2.0.15';
our $_ua; our $_ua;
sub ua { sub ua {
return $_ua my ($class) = @_;
? $_ua return $_ua if $_ua;
: Lemonldap::NG::Common::UserAgent->new( $_[0]->localConfig ); $_ua = Lemonldap::NG::Common::UserAgent->new( {
lwpOpts => $class->tsv->{lwpOpts},
lwpSslOpts => $class->tsv->{lwpSslOpts}
}
);
return $_ua;
} }
sub checkMaintenanceMode { sub checkMaintenanceMode {

View File

@ -110,7 +110,7 @@ categories =
dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen'] dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen']
connectionTitle: ['ipAddr', '_timezone', '_url'] connectionTitle: ['ipAddr', '_timezone', '_url']
authenticationTitle:['_session_id', '_user', '_password', 'authenticationLevel'] authenticationTitle:['_session_id', '_user', '_password', 'authenticationLevel']
modulesTitle: ['_auth', '_userDB', '_passwordDB', '_issuerDB', '_authChoice', '_authMulti', '_userDBMulti'] modulesTitle: ['_auth', '_userDB', '_passwordDB', '_issuerDB', '_authChoice', '_authMulti', '_userDBMulti', '_2f']
saml: ['_idp', '_idpConfKey', '_samlToken', '_lassoSessionDump', '_lassoIdentityDump'] saml: ['_idp', '_idpConfKey', '_samlToken', '_lassoSessionDump', '_lassoIdentityDump']
groups: ['groups', 'hGroups'] groups: ['groups', 'hGroups']
ldap: ['dn'] ldap: ['dn']

View File

@ -122,7 +122,7 @@
dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen'], dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen'],
connectionTitle: ['ipAddr', '_timezone', '_url'], connectionTitle: ['ipAddr', '_timezone', '_url'],
authenticationTitle: ['_session_id', '_user', '_password', 'authenticationLevel'], authenticationTitle: ['_session_id', '_user', '_password', 'authenticationLevel'],
modulesTitle: ['_auth', '_userDB', '_passwordDB', '_issuerDB', '_authChoice', '_authMulti', '_userDBMulti'], modulesTitle: ['_auth', '_userDB', '_passwordDB', '_issuerDB', '_authChoice', '_authMulti', '_userDBMulti', '_2f'],
saml: ['_idp', '_idpConfKey', '_samlToken', '_lassoSessionDump', '_lassoIdentityDump'], saml: ['_idp', '_idpConfKey', '_samlToken', '_lassoSessionDump', '_lassoIdentityDump'],
groups: ['groups', 'hGroups'], groups: ['groups', 'hGroups'],
ldap: ['dn'], ldap: ['dn'],

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -555,7 +555,6 @@ t/30-Auth-and-issuer-SAML-Artifact-with-SOAP-SLO-IdP-initiated.t
t/30-Auth-and-issuer-SAML-Artifact-with-SOAP-SLO.t t/30-Auth-and-issuer-SAML-Artifact-with-SOAP-SLO.t
t/30-Auth-and-issuer-SAML-Metadata.t t/30-Auth-and-issuer-SAML-Metadata.t
t/30-Auth-and-issuer-SAML-NameID.t t/30-Auth-and-issuer-SAML-NameID.t
t/30-Auth-and-issuer-SAML-POST-Hook.t
t/30-Auth-and-issuer-SAML-POST-IdP-initiated.t t/30-Auth-and-issuer-SAML-POST-IdP-initiated.t
t/30-Auth-and-issuer-SAML-POST-Missing-SLO.t t/30-Auth-and-issuer-SAML-POST-Missing-SLO.t
t/30-Auth-and-issuer-SAML-POST.t t/30-Auth-and-issuer-SAML-POST.t

View File

@ -367,6 +367,10 @@ sub run {
# If only one 2F is authorized, display it # If only one 2F is authorized, display it
unless ($#am) { unless ($#am) {
$self->userLogger->info( 'Second factor '
. $am[0]->prefix
. '2F selected for '
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
my $res = $am[0]->run( $req, $token ); my $res = $am[0]->run( $req, $token );
$req->authResult($res); $req->authResult($res);
return $res; return $res;
@ -437,6 +441,10 @@ sub _choice {
my $ch = $req->param('sf'); my $ch = $req->param('sf');
foreach my $m ( @{ $self->sfModules } ) { foreach my $m ( @{ $self->sfModules } ) {
if ( $m->{m}->prefix eq $ch ) { if ( $m->{m}->prefix eq $ch ) {
$self->userLogger->info( 'Second factor '
. $m->{m}->prefix
. '2F selected for '
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
my $res = $m->{m}->run( $req, $token ); my $res = $m->{m}->run( $req, $token );
$req->authResult($res); $req->authResult($res);
return $self->p->do( return $self->p->do(

View File

@ -1962,7 +1962,7 @@ sub registration {
$self->conf->{oidcServiceDynamicRegistrationExtraClaims}; $self->conf->{oidcServiceDynamicRegistrationExtraClaims};
} }
if ( $self->confAcc->saveConf($conf) ) { if ( $self->confAcc->saveConf($conf) > 0 ) {
# Reload RP list # Reload RP list
$self->loadRPs(); $self->loadRPs();

View File

@ -58,7 +58,6 @@ sub displayInit {
$self->logger->error("Bad passwordPolicyActivation rule: $error"); $self->logger->error("Bad passwordPolicyActivation rule: $error");
} }
$self->passwordPolicyActivation($rule); $self->passwordPolicyActivation($rule);
$rule = $rule =
HANDLER->buildSub( HANDLER->substitute( $self->conf->{rememberAuthChoiceRule} ) ); HANDLER->buildSub( HANDLER->substitute( $self->conf->{rememberAuthChoiceRule} ) );
unless ($rule) { unless ($rule) {

View File

@ -134,9 +134,9 @@ sub _verify {
"Update sessionInfo with new authenticationLevel: $l"); "Update sessionInfo with new authenticationLevel: $l");
$req->sessionInfo->{authenticationLevel} = $l; $req->sessionInfo->{authenticationLevel} = $l;
# Compute macros & local groups again with new authenticationLevel # Compute macros & groups with new authenticationLevel
$self->logger->debug("Compute macros and local groups..."); $self->logger->debug("Compute macros and groups...");
$req->steps( [ 'setMacros', 'setLocalGroups' ] ); $req->steps( [ $self->p->groupsAndMacros, 'setLocalGroups' ] );
if ( my $error = $self->p->process($req) ) { if ( my $error = $self->p->process($req) ) {
$self->logger->debug("SFA: Process returned error: $error"); $self->logger->debug("SFA: Process returned error: $error");
$req->error($error); $req->error($error);
@ -165,9 +165,16 @@ sub _verify {
authenticationLevel => $l, authenticationLevel => $l,
groups => $req->sessionInfo->{groups}, groups => $req->sessionInfo->{groups},
hGroups => $req->sessionInfo->{hGroups}, hGroups => $req->sessionInfo->{hGroups},
_2f => $self->prefix,
%macros %macros
} }
); );
} else {
# Only update _2f session key
$self->p->updateSession($req,
{
_2f => $self->prefix,
});
} }
$req->authResult(PE_SENDRESPONSE); $req->authResult(PE_SENDRESPONSE);

View File

@ -17,7 +17,7 @@ use Lemonldap::NG::Portal::Main::Constants qw(
extends 'Lemonldap::NG::Portal::Main::Plugin'; extends 'Lemonldap::NG::Portal::Main::Plugin';
our $VERSION = '2.0.12'; our $VERSION = '2.0.14';
# INITIALIZATION # INITIALIZATION
@ -59,7 +59,8 @@ sub _modifyPassword {
) )
); );
unless ($oldPwdRule) { unless ($oldPwdRule) {
my $error = $self->p->HANDLER->tsv->{jail}->error || '???'; my $error =
$self->p->HANDLER->tsv->{jail}->error || 'Unable to compile rule';
} }
my $pwdPolicyRule = $self->p->HANDLER->buildSub( my $pwdPolicyRule = $self->p->HANDLER->buildSub(
@ -68,7 +69,8 @@ sub _modifyPassword {
) )
); );
unless ($pwdPolicyRule) { unless ($pwdPolicyRule) {
my $error = $self->p->HANDLER->tsv->{jail}->error || '???'; my $error =
$self->p->HANDLER->tsv->{jail}->error || 'Unable to compile rule';
} }
# Check if portal require old password # Check if portal require old password

View File

@ -32,7 +32,7 @@ use Lemonldap::NG::Portal::Main::Constants qw(
PE_PP_INSUFFICIENT_PASSWORD_QUALITY PE_PP_INSUFFICIENT_PASSWORD_QUALITY
); );
our $VERSION = '2.0.12'; our $VERSION = '2.0.14';
extends qw( extends qw(
Lemonldap::NG::Portal::Lib::SMTP Lemonldap::NG::Portal::Lib::SMTP
@ -58,6 +58,9 @@ has ott => (
# Captcha generator # Captcha generator
has captcha => ( is => 'rw' ); has captcha => ( is => 'rw' );
# Password policy activation rule
has passwordPolicyActivationRule => ( is => 'rw', default => sub { 0 } );
# INITIALIZATION # INITIALIZATION
sub init { sub init {
@ -70,6 +73,15 @@ sub init {
if ( $self->conf->{captcha_mail_enabled} ) { if ( $self->conf->{captcha_mail_enabled} ) {
$self->captcha( $self->p->loadModule('::Lib::Captcha') ) or return 0; $self->captcha( $self->p->loadModule('::Lib::Captcha') ) or return 0;
} }
# Parse password policy activation rule
$self->passwordPolicyActivationRule(
$self->p->buildRule(
$self->conf->{passwordPolicyActivation},
'passwordPolicyActivation'
)
);
return 0 unless $self->passwordPolicyActivationRule;
return 1; return 1;
} }
@ -442,8 +454,32 @@ sub changePwd {
"Reset password request for $req->{sessionInfo}->{_user}"); "Reset password request for $req->{sessionInfo}->{_user}");
# Generate a complex password # Generate a complex password
my $password = my $pwdRegEx;
$self->gen_password( $self->conf->{randomPasswordRegexp} ); if ( $self->passwordPolicyActivationRule->( $req, $req->sessionInfo )
&& !$self->conf->{randomPasswordRegexp} )
{
my $uppers = $self->conf->{passwordPolicyMinUpper} || 3;
my $lowers = $self->conf->{passwordPolicyMinLower} || 5;
my $digits = $self->conf->{passwordPolicyMinDigit} || 2;
my $chars =
$self->conf->{passwordPolicyMinSize} -
$self->conf->{passwordPolicyMinUpper} -
$self->conf->{passwordPolicyMinLower} -
$self->conf->{passwordPolicyMinDigit};
$chars = 1 if $chars < 1;
$pwdRegEx = "[A-Z]{$uppers}[a-z]{$lowers}\\d{$digits}";
$pwdRegEx .=
$self->conf->{passwordPolicySpecialChar} eq '__ALL__'
? "\\W{$chars}"
: "[$self->{conf}->{passwordPolicySpecialChar}]{$chars}";
$self->logger->debug("Generated password RegEx: $pwdRegEx");
}
else {
$pwdRegEx =
$self->conf->{randomPasswordRegexp} || '[A-Z]{3}[a-z]{5}.\d{2}';
$self->logger->debug("Used password RegEx: $pwdRegEx");
}
my $password = $self->gen_password($pwdRegEx);
$self->logger->debug("Generated password: $password"); $self->logger->debug("Generated password: $password");
$req->data->{newpassword} = $password; $req->data->{newpassword} = $password;
$req->data->{confirmpassword} = $password; $req->data->{confirmpassword} = $password;
@ -467,11 +503,13 @@ sub changePwd {
} }
} }
# Check password quality # Check password quality if enabled
require Lemonldap::NG::Portal::Password::Base; require Lemonldap::NG::Portal::Password::Base;
my $cpq = my $cpq =
$self->Lemonldap::NG::Portal::Password::Base::checkPasswordQuality( $self->passwordPolicyActivationRule->( $req, $req->sessionInfo )
$req->data->{newpassword} ); ? $self->Lemonldap::NG::Portal::Password::Base::checkPasswordQuality(
$req->data->{newpassword} )
: PE_OK;
unless ( $cpq == PE_OK ) { unless ( $cpq == PE_OK ) {
$self->ott->setToken( $req, $req->sessionInfo ); $self->ott->setToken( $req, $req->sessionInfo );
return $cpq; return $cpq;
@ -555,9 +593,19 @@ sub setSecurity {
sub display { sub display {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
my $speChars = $self->conf->{passwordPolicySpecialChar}; my $speChars =
$self->conf->{passwordPolicySpecialChar} eq '__ALL__'
? ''
: $self->conf->{passwordPolicySpecialChar};
$speChars =~ s/\s+/ /g; $speChars =~ s/\s+/ /g;
$speChars =~ s/(?:^\s|\s$)//g; $speChars =~ s/(?:^\s|\s$)//g;
my $isPP =
$self->conf->{passwordPolicyMinSize}
|| $self->conf->{passwordPolicyMinLower}
|| $self->conf->{passwordPolicyMinUpper}
|| $self->conf->{passwordPolicyMinDigit}
|| $self->conf->{passwordPolicyMinSpeChar}
|| $speChars;
$self->logger->debug( 'Display called with code: ' . $req->error ); $self->logger->debug( 'Display called with code: ' . $req->error );
my %tplPrm = ( my %tplPrm = (
@ -576,7 +624,8 @@ sub display {
STARTMAILTIME => $req->data->{startMailTime}, STARTMAILTIME => $req->data->{startMailTime},
MAILALREADYSENT => $req->data->{mailAlreadySent}, MAILALREADYSENT => $req->data->{mailAlreadySent},
MAIL => ( MAIL => (
$self->p->checkXSSAttack( 'mail', $req->{user} ) ? '' $self->p->checkXSSAttack( 'mail', $req->{user} )
? ''
: $req->{user} : $req->{user}
), ),
DISPLAY_FORM => 0, DISPLAY_FORM => 0,
@ -584,17 +633,13 @@ sub display {
DISPLAY_CONFIRMMAILSENT => 0, DISPLAY_CONFIRMMAILSENT => 0,
DISPLAY_MAILSENT => 0, DISPLAY_MAILSENT => 0,
DISPLAY_PASSWORD_FORM => 0, DISPLAY_PASSWORD_FORM => 0,
DISPLAY_PPOLICY => $self->conf->{portalDisplayPasswordPolicy}, DISPLAY_PPOLICY => $self->conf->{portalDisplayPasswordPolicy} && $isPP,
PPOLICY_MINSIZE => $self->conf->{passwordPolicyMinSize}, PPOLICY_MINSIZE => $self->conf->{passwordPolicyMinSize},
PPOLICY_MINLOWER => $self->conf->{passwordPolicyMinLower}, PPOLICY_MINLOWER => $self->conf->{passwordPolicyMinLower},
PPOLICY_MINUPPER => $self->conf->{passwordPolicyMinUpper}, PPOLICY_MINUPPER => $self->conf->{passwordPolicyMinUpper},
PPOLICY_MINDIGIT => $self->conf->{passwordPolicyMinDigit}, PPOLICY_MINDIGIT => $self->conf->{passwordPolicyMinDigit},
PPOLICY_ALLOWEDSPECHAR => $speChars, PPOLICY_MINSPECHAR => $self->conf->{passwordPolicyMinSpeChar},
( PPOLICY_ALLOWEDSPECHAR => $speChars,
$speChars
? ( PPOLICY_MINSPECHAR => $self->conf->{passwordPolicyMinSpeChar} )
: ()
),
DISPLAY_GENERATE_PASSWORD => DISPLAY_GENERATE_PASSWORD =>
$self->conf->{portalDisplayGeneratePassword}, $self->conf->{portalDisplayGeneratePassword},
); );

View File

@ -156,7 +156,7 @@ sub setGroups {
my $groups = $req->sessionInfo->{groups} || ''; my $groups = $req->sessionInfo->{groups} || '';
my $hGroups = $req->sessionInfo->{hGroups} || {}; my $hGroups = $req->sessionInfo->{hGroups} || {};
for my $grp ( keys %demoGroups ) { for my $grp ( keys %demoGroups ) {
if ( grep { $_ eq $user } @{ $demoGroups{$grp} } ) { if ( grep { $user && $user eq $_ } @{ $demoGroups{$grp} } ) {
$hGroups->{$grp} = { 'name' => $grp }; $hGroups->{$grp} = { 'name' => $grp };
$groups = $groups =
($groups) ($groups)

View File

@ -32,24 +32,65 @@
<TMPL_IF NAME="HEADERS"> <TMPL_IF NAME="HEADERS">
<div class="row"> <div class="row">
<div class="card col border-secondary"> <div class ="col-12 col-sm-12 col-md-12 pb-3">
<div class="text-center bg-light text-dark"><b><span trspan="headers">HEADERS</span></b></div> <div class="card h-100 border-secondary">
<div class="font-weight-bold"> <div class="card-title text-center bg-light text-dark"><b><span trspan="headers">HEADERS</span></b></div>
<TMPL_LOOP NAME="HEADERS"> <div class="card-text font-weight-bold m-2">
<TMPL_VAR NAME="key">: <TMPL_VAR NAME="value"><br/> <TMPL_LOOP NAME="HEADERS">
</TMPL_LOOP> <TMPL_VAR NAME="key">: <TMPL_VAR NAME="value"><br/>
</TMPL_LOOP>
</div>
</div> </div>
</div> </div>
</div> </div>
</TMPL_IF> </TMPL_IF>
<div class="row">
<TMPL_IF NAME="HISTORY"> <div class= "row ">
<div class="card col border-secondary">
<div class="text-center bg-light text-dark"><b><span trspan="loginHistory">HISTORY</span></b></div> <!-- Groups Card 1 -->
<TMPL_IF NAME="SUCCESS"> <div class ="col-6 col-sm-12 col-md-6 p-0">
<div class="card h-100">
<div class="card-title text-center bg-light text-dark"><b><span trspan="groups_sso">SSO GROUPS</span></b></div>
<TMPL_LOOP NAME="GROUPS">
<div class="card-text text-left ml-2"><TMPL_VAR NAME="value"></div>
</TMPL_LOOP>
</div>
</div>
<!-- Macros Card 2 -->
<div class ="col-6 col-sm-12 col-md-6 p-0">
<div class="card h-100">
<TMPL_IF NAME="MACROS">
<div class="card-title text-center bg-light text-dark"><b><span trspan="macros">MACROS</span></b></div>
<table class="table table-sm table-hover">
<thead>
<tr>
<th scope="col"><span trspan="key">Key</span></th>
<th scope="col"><span trspan="value">Value</span></th>
</tr>
</thead>
<tbody>
<TMPL_LOOP NAME="MACROS">
<tr>
<td scope="row"><TMPL_VAR NAME="key"></td>
<td scope="row"><TMPL_VAR NAME="value"></td>
</tr>
</TMPL_LOOP>
</tbody>
</table>
</TMPL_IF>
</div>
</div>
<!-- History Card 3 -->
<div class ="col-6 col-sm-12 col-md-6 p-0">
<div class="card h-100">
<TMPL_IF NAME="HISTORY">
<div class="card-title text-center bg-light text-dark"><b><span trspan="loginHistory">HISTORY</span></b></div>
<TMPL_IF NAME="SUCCESS">
<table class="table table-sm table-hover"> <table class="table table-sm table-hover">
<thead> <thead>
<div class="text-center bg-light text-dark"><span trspan="lastLogins">Success</span></div> <div class="card-text text-center bg-light text-dark"><span trspan="lastLogins">Success</span></div>
<tr> <tr>
<th scope="col"><span trspan="date">Date</span></th> <th scope="col"><span trspan="date">Date</span></th>
<th scope="col"><span trspan="value">Value</span></th> <th scope="col"><span trspan="value">Value</span></th>
@ -57,18 +98,18 @@
</thead> </thead>
<tbody> <tbody>
<TMPL_LOOP NAME="SUCCESS"> <TMPL_LOOP NAME="SUCCESS">
<tr> <tr>
<td class="localeDate" scope="row" val="<TMPL_VAR NAME="utime">"></td> <td class="localeDate" scope="row" val="<TMPL_VAR NAME="utime">"></td>
<td scope="row"><TMPL_VAR NAME="values"></td> <td scope="row"><TMPL_VAR NAME="values"></td>
</tr> </tr>
</TMPL_LOOP> </TMPL_LOOP>
</tbody> </tbody>
</table> </table>
</TMPL_IF> </TMPL_IF>
<TMPL_IF NAME="FAILED"> <TMPL_IF NAME="FAILED">
<table class="table table-sm table-hover"> <table class="table table-sm table-hover">
<thead> <thead>
<div class="text-center bg-light text-dark"><span trspan="lastFailedLogins">Failed</span></div> <div class="card-text text-center bg-light text-dark"><span trspan="lastFailedLogins">Failed</span></div>
<tr> <tr>
<th scope="col"><span trspan="date">Date</span></th> <th scope="col"><span trspan="date">Date</span></th>
<th scope="col"><span trspan="value">Value</span></th> <th scope="col"><span trspan="value">Value</span></th>
@ -76,32 +117,23 @@
</thead> </thead>
<tbody> <tbody>
<TMPL_LOOP NAME="FAILED"> <TMPL_LOOP NAME="FAILED">
<tr> <tr>
<td class="localeDate" scope="row" val="<TMPL_VAR NAME="utime">"></td> <td class="localeDate" scope="row" val="<TMPL_VAR NAME="utime">"></td>
<td scope="row"><TMPL_VAR NAME="values"></td> <td scope="row"><TMPL_VAR NAME="values"></td>
</tr> </tr>
</TMPL_LOOP> </TMPL_LOOP>
</tbody> </tbody>
</table> </table>
</TMPL_IF>
</TMPL_IF> </TMPL_IF>
</div>
</TMPL_IF>
<TMPL_IF NAME="GROUPS">
<div class="card col border-secondary">
<div class="text-center bg-light text-dark"><b><span trspan="groups_sso">SSO GROUPS</span></b></div>
<div class="row">
<TMPL_LOOP NAME="GROUPS">
<div class="w-100"></div>
<div class="col"><TMPL_VAR NAME="value"></div>
</TMPL_LOOP>
</div> </div>
</div> </div>
</TMPL_IF>
<div class="col"> <!-- Attribute Card 4 -->
<div class="row"> <div class ="col-6 col-sm-12 col-md-6 p-0">
<div class="card h-100">
<TMPL_IF NAME="ATTRIBUTES"> <TMPL_IF NAME="ATTRIBUTES">
<div class="card col border-secondary"> <div class="card-title text-center bg-light text-dark"><b><span trspan="attributes">ATTRIBUTES</span></b></div>
<div class="text-center bg-light text-dark"><b><span trspan="attributes">ATTRIBUTES</span></b></div>
<table class="table table-sm table-hover"> <table class="table table-sm table-hover">
<thead> <thead>
<tr> <tr>
@ -118,32 +150,10 @@
</TMPL_LOOP> </TMPL_LOOP>
</tbody> </tbody>
</table> </table>
</div>
</TMPL_IF>
<TMPL_IF NAME="GROUPS"><div class="w-100"></div></TMPL_IF>
<TMPL_IF NAME="MACROS">
<div class="card col border-secondary">
<div class="text-center bg-light text-dark"><b><span trspan="macros">MACROS</span></b></div>
<table class="table table-sm table-hover">
<thead>
<tr>
<th scope="col"><span trspan="key">Key</span></th>
<th scope="col"><span trspan="value">Value</span></th>
</tr>
</thead>
<tbody>
<TMPL_LOOP NAME="MACROS">
<tr>
<td scope="row"><TMPL_VAR NAME="key"></td>
<td scope="row"><TMPL_VAR NAME="value"></td>
</tr>
</TMPL_LOOP>
</tbody>
</table>
</div>
</TMPL_IF> </TMPL_IF>
</div> </div>
</div> </div>
</div> </div>
<div class="buttons"> <div class="buttons">

View File

@ -7,7 +7,7 @@
<br /> <br />
<br /> <br />
<span trspan="yourLoginIs">Your login is</span> <span trspan="yourLoginIs">Your login is</span>
<span><img src="cid:key:../common/bullet_go.png" alt="go"/></span> <span><img src="cid:arrow:../common/bullet_go.png" alt="go"/></span>
<b><TMPL_VAR NAME="login" ESCAPE=HTML></b> <b><TMPL_VAR NAME="login" ESCAPE=HTML></b>
<br /> <br />
<span trspan="pwdIs">Your password is</span> <span trspan="pwdIs">Your password is</span>

View File

@ -13,7 +13,7 @@ BEGIN {
} }
my ( $res, $user, $pwd ); my ( $res, $user, $pwd );
my $maintests = 17; my $maintests = 19;
my $mailSend = 0; my $mailSend = 0;
my $mail2 = 0; my $mail2 = 0;
@ -54,7 +54,12 @@ SKIP: {
dbiAuthPasswordHash => '', dbiAuthPasswordHash => '',
dbiDynamicHashEnabled => 0, dbiDynamicHashEnabled => 0,
dbiMailCol => 'mail', dbiMailCol => 'mail',
portalDisplayPasswordPolicy => 1,
passwordPolicyActivation => 0,
passwordResetAllowedRetries => 4, passwordResetAllowedRetries => 4,
passwordPolicyMinDigit => 2,
passwordPolicyMinSpeChar => 1,
passwordPolicySpecialChar => '__ALL__'
} }
} }
); );
@ -141,8 +146,16 @@ SKIP: {
# Post new password # Post new password
( $host, $url, $query ) = expectForm( $res, '#', undef, 'token' ); ( $host, $url, $query ) = expectForm( $res, '#', undef, 'token' );
ok( $res->[2]->[0] =~ /newpassword/s, ' Ask for a new password #4' ); ok( $res->[2]->[0] =~ /newpassword/s, ' Ask for a new password #4' );
ok(
$query .= '&newpassword=zz&confirmpassword=zz'; $res->[2]->[0] !~ /passwordPolicySpecialChar/,
' Password special char list not found'
);
ok(
$res->[2]->[0] =~
/<span trspan="passwordPolicyMinDigit">Minimal digit characters:<\/span> 2/,
' Found password policy min digit == 2'
);
$query .= '&newpassword=zz11#&confirmpassword=zz11#';
ok( ok(
$res = $client->_post( $res = $client->_post(
'/resetpwd', IO::String->new($query), '/resetpwd', IO::String->new($query),
@ -157,8 +170,8 @@ SKIP: {
ok( ok(
$res = $client->_post( $res = $client->_post(
'/', '/',
IO::String->new('user=dwho&password=zz'), IO::String->new('user=dwho&password=zz11#'),
length => 21 length => 24
), ),
'Auth query' 'Auth query'
); );

View File

@ -10,7 +10,7 @@ BEGIN {
} }
my ( $res, $host, $url, $query ); my ( $res, $host, $url, $query );
my $maintests = 16; my $maintests = 18;
my $mailSend = 0; my $mailSend = 0;
my $mail2 = 0; my $mail2 = 0;
@ -33,6 +33,13 @@ SKIP: {
requireToken => 1, requireToken => 1,
portalDisplayResetPassword => 1, portalDisplayResetPassword => 1,
portalMainLogo => 'common/logos/logo_llng_old.png', portalMainLogo => 'common/logos/logo_llng_old.png',
passwordPolicyActivation => 1,
passwordPolicyMinUpper => 1,
passwordPolicyMinLower => 1,
passwordPolicyMinDigit => 2,
passwordPolicyMinSpeChar => 1,
randomPasswordRegexp => '',
passwordPolicySpecialChar => '*#@'
} }
} }
); );
@ -104,7 +111,7 @@ m#<img class="renewcaptchaclick" src="/static/common/icons/arrow_refresh.png"#,
( $host, $url, $query ) = expectForm( $res, '#', undef, 'token' ); ( $host, $url, $query ) = expectForm( $res, '#', undef, 'token' );
ok( $res->[2]->[0] =~ /newpassword/s, ' Ask for a new password' ); ok( $res->[2]->[0] =~ /newpassword/s, ' Ask for a new password' );
$query .= '&newpassword=zz&confirmpassword=zz'; $query .= '&reset=1';
# Post new password # Post new password
ok( ok(
@ -115,7 +122,9 @@ m#<img class="renewcaptchaclick" src="/static/common/icons/arrow_refresh.png"#,
), ),
'Post new password' 'Post new password'
); );
ok( mail() =~ /Your password was changed/, 'Password was changed' ); ok( mail() =~ /<span>Your new password is<\/span>/, 'New password sent' );
ok( mail() =~ /<b>(.+?)<\/b>/s, 'New generated password found' );
ok( $1 =~ /[A-Z]{1}[a-z]{1}\d{2}[*#@]{1}/, 'New generated password matches' );
#print STDERR Dumper($query); #print STDERR Dumper($query);
} }

View File

@ -10,7 +10,7 @@ BEGIN {
} }
my ( $res, $user, $pwd ); my ( $res, $user, $pwd );
my $maintests = 12; my $maintests = 18;
SKIP: { SKIP: {
eval eval
@ -21,15 +21,22 @@ SKIP: {
my $client = LLNG::Manager::Test->new( { my $client = LLNG::Manager::Test->new( {
ini => { ini => {
logLevel => 'error', logLevel => 'error',
useSafeJail => 1, useSafeJail => 1,
portalDisplayRegister => 1, portalDisplayRegister => 1,
authentication => 'Demo', authentication => 'Demo',
userDB => 'Same', userDB => 'Same',
passwordDB => 'Demo', passwordDB => 'Demo',
captcha_mail_enabled => 0, captcha_mail_enabled => 0,
portalDisplayResetPassword => 1, portalDisplayResetPassword => 1,
portalMainLogo => 'common/logos/logo_llng_old.png', portalMainLogo => 'common/logos/logo_llng_old.png',
portalDisplayPasswordPolicy => 1,
passwordPolicyActivation => 1,
passwordPolicyMinUpper => 1,
passwordPolicyMinLower => 1,
passwordPolicyMinDigit => 2,
passwordPolicyMinSpeChar => 1,
passwordPolicySpecialChar => '&%#'
} }
} }
); );
@ -87,8 +94,34 @@ SKIP: {
); );
( $host, $url, $query ) = expectForm( $res, '#', undef, 'token' ); ( $host, $url, $query ) = expectForm( $res, '#', undef, 'token' );
ok( $res->[2]->[0] =~ /newpassword/s, ' Ask for a new password' ); ok( $res->[2]->[0] =~ /newpassword/s, ' Ask for a new password' );
ok( $res->[2]->[0] =~ /<span trspan="passwordPolicy">/,
$query .= '&newpassword=zz&confirmpassword=zz'; ' Found password policy' );
ok(
$res->[2]->[0] =~
/<span trspan="passwordPolicyMinLower">Minimal lower characters:<\/span> 1/,
' Found password policy min lower == 1'
);
ok(
$res->[2]->[0] =~
/<span trspan="passwordPolicyMinUpper">Minimal upper characters:<\/span> 1/,
' Found password policy min upper == 1'
);
ok(
$res->[2]->[0] =~
/<span trspan="passwordPolicyMinDigit">Minimal digit characters:<\/span> 2/,
' Found password policy min digit == 2'
);
ok(
$res->[2]->[0] =~
/<span trspan="passwordPolicyMinSpeChar">Minimal special characters:<\/span> 1/,
' Found password policy min speChar == 1'
);
ok(
$res->[2]->[0] =~
/<span trspan="passwordPolicySpecialChar">Allowed special characters:<\/span> &%#/,
' Found password special char list'
);
$query .= '&newpassword=zZ11#&confirmpassword=zZ11#';
# Post new password # Post new password
ok( ok(

View File

@ -166,7 +166,8 @@ ok( $res->[2]->[0] =~ m%Auth-User: %, 'Found Auth-User' )
or explain( $res->[2]->[0], 'Header Key: Auth-User' ); or explain( $res->[2]->[0], 'Header Key: Auth-User' );
ok( $res->[2]->[0] =~ m%: rtyler<br/>%, 'Found rtyler' ) ok( $res->[2]->[0] =~ m%: rtyler<br/>%, 'Found rtyler' )
or explain( $res->[2]->[0], 'Header Value: rtyler' ); or explain( $res->[2]->[0], 'Header Value: rtyler' );
ok( $res->[2]->[0] =~ m%<div class="col">su</div>%, 'Found su' ) ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">su</div>%,
'Found su' )
or explain( $res->[2]->[0], 'SSO Groups: su' ); or explain( $res->[2]->[0], 'SSO Groups: su' );
ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found uid' ) ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found uid' )
or explain( $res->[2]->[0], 'Attribute Value uid' ); or explain( $res->[2]->[0], 'Attribute Value uid' );

View File

@ -186,10 +186,10 @@ ok( $res->[2]->[0] =~ m%<td scope="row">Macro_1</td>%, 'Found uid' )
ok( $nbr = ( $res->[2]->[0] =~ s%<td scope="row">Macro_1</td>%%g ), ok( $nbr = ( $res->[2]->[0] =~ s%<td scope="row">Macro_1</td>%%g ),
'Found two well computed macros' ) 'Found two well computed macros' )
or explain( $res->[2]->[0], 'Macros not well computed' ); or explain( $res->[2]->[0], 'Macros not well computed' );
ok( $res->[2]->[0] =~ m%<div class="col">authGroup</div>%, ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">authGroup</div>%,
'Found group "authGroup"' ) 'Found group "authGroup"' )
or explain( $res->[2]->[0], 'Group "authgroup"' ); or explain( $res->[2]->[0], 'Group "authgroup"' );
ok( $res->[2]->[0] =~ m%<div class="col">realAuthGroup</div>%, ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">realAuthGroup</div>%,
'Found group "realAuthGroup"' ) 'Found group "realAuthGroup"' )
or explain( $res->[2]->[0], 'Found group "realAuthGroup"' ); or explain( $res->[2]->[0], 'Found group "realAuthGroup"' );
count(7); count(7);

View File

@ -166,7 +166,8 @@ ok( $res->[2]->[0] !~ m%emptyHeader: %, 'emptyHeader not found' )
or explain( $res->[2]->[0], 'Header Key: emptyHeader' ); or explain( $res->[2]->[0], 'Header Key: emptyHeader' );
ok( $res->[2]->[0] =~ m%: rtyler%, 'Found rtyler' ) ok( $res->[2]->[0] =~ m%: rtyler%, 'Found rtyler' )
or explain( $res->[2]->[0], 'Header Value: rtyler' ); or explain( $res->[2]->[0], 'Header Value: rtyler' );
ok( $res->[2]->[0] =~ m%<div class="col">su</div>%, 'Found su' ) ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">su</div>%,
'Found su' )
or explain( $res->[2]->[0], 'SSO Groups: su' ); or explain( $res->[2]->[0], 'SSO Groups: su' );
ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found uid' ) ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found uid' )
or explain( $res->[2]->[0], 'Attribute Value uid' ); or explain( $res->[2]->[0], 'Attribute Value uid' );

View File

@ -429,7 +429,8 @@ ok( $res->[2]->[0] =~ m%Auth-User: %, 'Found Auth-User' )
or explain( $res->[2]->[0], 'Header Key: Auth-User' ); or explain( $res->[2]->[0], 'Header Key: Auth-User' );
ok( $res->[2]->[0] =~ m%: rtyler<br/>%, 'Found rtyler' ) ok( $res->[2]->[0] =~ m%: rtyler<br/>%, 'Found rtyler' )
or explain( $res->[2]->[0], 'Header Value: rtyler' ); or explain( $res->[2]->[0], 'Header Value: rtyler' );
ok( $res->[2]->[0] =~ m%<div class="col">su</div>%, 'Found su' ) ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">su</div>%,
'Found su' )
or explain( $res->[2]->[0], 'SSO Groups: su' ); or explain( $res->[2]->[0], 'SSO Groups: su' );
ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found uid' ) ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found uid' )
or explain( $res->[2]->[0], 'Attribute Value uid' ); or explain( $res->[2]->[0], 'Attribute Value uid' );
@ -489,7 +490,8 @@ ok( $res->[2]->[0] =~ m%Auth-User: %, 'Found Auth-User' )
or explain( $res->[2]->[0], 'Header Key: Auth-User' ); or explain( $res->[2]->[0], 'Header Key: Auth-User' );
ok( $res->[2]->[0] =~ m%: rtyler<br/>%, 'Found rtyler' ) ok( $res->[2]->[0] =~ m%: rtyler<br/>%, 'Found rtyler' )
or explain( $res->[2]->[0], 'Header Value: rtyler' ); or explain( $res->[2]->[0], 'Header Value: rtyler' );
ok( $res->[2]->[0] =~ m%<div class="col">su</div>%, 'Found su' ) ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">su</div>%,
'Found su' )
or explain( $res->[2]->[0], 'SSO Groups: su' ); or explain( $res->[2]->[0], 'SSO Groups: su' );
ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found uid' ) ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found uid' )
or explain( $res->[2]->[0], 'Attribute Value uid' ); or explain( $res->[2]->[0], 'Attribute Value uid' );

View File

@ -226,7 +226,7 @@ m%<div class="alert alert-success"><div class="text-center"><b><span trspan="all
or explain( $res->[2]->[0], 'Header Key: Auth-User' ); or explain( $res->[2]->[0], 'Header Key: Auth-User' );
ok( $res->[2]->[0] =~ m%: dwho<br/>%, 'Found dwho' ) ok( $res->[2]->[0] =~ m%: dwho<br/>%, 'Found dwho' )
or explain( $res->[2]->[0], 'Header Value: dwho' ); or explain( $res->[2]->[0], 'Header Value: dwho' );
ok( $res->[2]->[0] =~ m%<div class="col">su</div>%, 'Found su' ) ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">su</div>%, 'Found su' )
or explain( $res->[2]->[0], 'SSO Groups: su' ); or explain( $res->[2]->[0], 'SSO Groups: su' );
ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found uid' ) ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found uid' )
or explain( $res->[2]->[0], 'Attribute Value uid' ); or explain( $res->[2]->[0], 'Attribute Value uid' );

View File

@ -127,13 +127,13 @@ ok( $res->[2]->[0] =~ m%Auth-User: %, 'Found Auth-User' )
or explain( $res->[2]->[0], 'Header Key: Auth-User' ); or explain( $res->[2]->[0], 'Header Key: Auth-User' );
ok( $res->[2]->[0] =~ m%: dwho<br/>%, 'Found dwho' ) ok( $res->[2]->[0] =~ m%: dwho<br/>%, 'Found dwho' )
or explain( $res->[2]->[0], 'Header Value: dwho' ); or explain( $res->[2]->[0], 'Header Value: dwho' );
ok( $res->[2]->[0] =~ m%<div class="col">su</div>%, 'Found su' ) ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">su</div>%, 'Found su' )
or explain( $res->[2]->[0], 'SSO Groups: su' ); or explain( $res->[2]->[0], 'SSO Groups: su' );
ok( $res->[2]->[0] =~ m%<div class="col">su_test</div>%, 'Found su_test' ) ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">su_test</div>%, 'Found su_test' )
or explain( $res->[2]->[0], 'SSO Groups: su_test' ); or explain( $res->[2]->[0], 'SSO Groups: su_test' );
ok( $res->[2]->[0] !~ m%<div class="col">_test_</div>%, 'NOT found _test_' ) ok( $res->[2]->[0] !~ m%<div class="card-text text-left ml-2">_test_</div>%, 'NOT found _test_' )
or explain( $res->[2]->[0], 'SSO Groups: _test_' ); or explain( $res->[2]->[0], 'SSO Groups: _test_' );
ok( $res->[2]->[0] !~ m%<div class="col">test_su</td>%, 'NOT found test_su' ) ok( $res->[2]->[0] !~ m%<div class="card-text text-left ml-2">test_su</td>%, 'NOT found test_su' )
or explain( $res->[2]->[0], 'SSO Groups: test_su' ); or explain( $res->[2]->[0], 'SSO Groups: test_su' );
ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found uid' ) ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found uid' )
or explain( $res->[2]->[0], 'Attribute Value uid' ); or explain( $res->[2]->[0], 'Attribute Value uid' );

View File

@ -116,12 +116,12 @@ ok( $res->[2]->[0] =~ m%<span trspan="headers">%, 'Found trspan="headers"' )
ok( $res->[2]->[0] =~ m%<span trspan="groups_sso">%, ok( $res->[2]->[0] =~ m%<span trspan="groups_sso">%,
'Found trspan="groups_sso"' ) 'Found trspan="groups_sso"' )
or explain( $res->[2]->[0], 'trspan="groups_sso"' ); or explain( $res->[2]->[0], 'trspan="groups_sso"' );
ok( $res->[2]->[0] =~ m%<div class="col">su</div>%, 'Found SSO group "su"' ) ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">su</div>%, 'Found SSO group "su"' )
or explain( $res->[2]->[0], 'Found SSO group "su"' ); or explain( $res->[2]->[0], 'Found SSO group "su"' );
ok( $res->[2]->[0] =~ m%<div class="col">su_test</div>%, ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">su_test</div>%,
'Found SSO group "su_test"' ) 'Found SSO group "su_test"' )
or explain( $res->[2]->[0], 'Found SSO group "su_test"' ); or explain( $res->[2]->[0], 'Found SSO group "su_test"' );
ok( $res->[2]->[0] =~ m%<div class="col">test_su</div>%, ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">test_su</div>%,
'Found SSO group "test_su"' ) 'Found SSO group "test_su"' )
or explain( $res->[2]->[0], 'Found SSO group "test_su"' ); or explain( $res->[2]->[0], 'Found SSO group "test_su"' );
ok( $res->[2]->[0] =~ m%<span trspan="attributes">%, ok( $res->[2]->[0] =~ m%<span trspan="attributes">%,
@ -135,13 +135,13 @@ ok( $res->[2]->[0] =~ m%Auth-User: %, 'Found Auth-User' )
or explain( $res->[2]->[0], 'Header Key: Auth-User' ); or explain( $res->[2]->[0], 'Header Key: Auth-User' );
ok( $res->[2]->[0] =~ m%: dwho<br/>%, 'Found dwho' ) ok( $res->[2]->[0] =~ m%: dwho<br/>%, 'Found dwho' )
or explain( $res->[2]->[0], 'Header Value: dwho' ); or explain( $res->[2]->[0], 'Header Value: dwho' );
ok( $res->[2]->[0] =~ m%<div class="col">su</div>%, 'Found su' ) ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">su</div>%, 'Found su' )
or explain( $res->[2]->[0], 'SSO Groups: su' ); or explain( $res->[2]->[0], 'SSO Groups: su' );
ok( $res->[2]->[0] =~ m%<div class="col">su_test</div>%, 'Found su_test' ) ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">su_test</div>%, 'Found su_test' )
or explain( $res->[2]->[0], 'SSO Groups: su_test' ); or explain( $res->[2]->[0], 'SSO Groups: su_test' );
ok( $res->[2]->[0] !~ m%<div class="col">_test_</div>%, 'NOT found _test_' ) ok( $res->[2]->[0] !~ m%<div class="card-text text-left ml-2">_test_</div>%, 'NOT found _test_' )
or explain( $res->[2]->[0], 'SSO Groups: _test_' ); or explain( $res->[2]->[0], 'SSO Groups: _test_' );
ok( $res->[2]->[0] =~ m%<div class="col">test_su</div>%, 'Found test_su' ) ok( $res->[2]->[0] =~ m%<div class="card-text text-left ml-2">test_su</div>%, 'Found test_su' )
or explain( $res->[2]->[0], 'SSO Groups: test_su' ); or explain( $res->[2]->[0], 'SSO Groups: test_su' );
ok( $res->[2]->[0] =~ m%<td scope="row">_whatToTrace</td>%, ok( $res->[2]->[0] =~ m%<td scope="row">_whatToTrace</td>%,
'Found _whatToTrace' ) 'Found _whatToTrace' )

View File

@ -29,6 +29,7 @@ SKIP: {
u2fSelfRegistration => 1, u2fSelfRegistration => 1,
u2fActivation => 1, u2fActivation => 1,
u2fAuthnLevel => 5, u2fAuthnLevel => 5,
restSessionServer =>1,
skipUpgradeConfirmation => 1, skipUpgradeConfirmation => 1,
sfManagerRule => '$uid eq "dwho"', sfManagerRule => '$uid eq "dwho"',
portalMainLogo => 'common/logos/logo_llng_old.png', portalMainLogo => 'common/logos/logo_llng_old.png',
@ -151,6 +152,7 @@ SKIP: {
'Post code' 'Post code'
); );
$id = expectCookie($res); $id = expectCookie($res);
expectSessionAttributes($client, $id, _2f => "totp");
# Get 2F register form # Get 2F register form
ok( ok(
@ -440,6 +442,7 @@ JjTJecOOS+88fK8qL1TrYv5rapIdqUI7aQ==
'Push U2F signature' 'Push U2F signature'
); );
$id = expectCookie($res); $id = expectCookie($res);
expectSessionAttributes($client, $id, _2f => "u");
ok( ok(
$res = $client->_get( $res = $client->_get(
'/2fregisters', '/2fregisters',

View File

@ -84,6 +84,7 @@ ok(
); );
count(1); count(1);
my $id = expectCookie($res); my $id = expectCookie($res);
expectSessionAttributes($client, $id, _2f => "work");
$client->logout($id); $client->logout($id);
clean_sessions(); clean_sessions();
@ -167,6 +168,7 @@ ok(
); );
count(1); count(1);
$id = expectCookie($res); $id = expectCookie($res);
expectSessionAttributes($client, $id, _2f => "home");
# Verify Authn Level # Verify Authn Level
ok( $res = $client->_get("/sessions/global/$id"), 'Get session' ); ok( $res = $client->_get("/sessions/global/$id"), 'Get session' );

View File

@ -56,6 +56,7 @@ C<LLNG::Manager::Test::_post()> call I<(see below)>.
use strict; use strict;
use Data::Dumper; use Data::Dumper;
use File::Find; use File::Find;
use JSON;
use LWP::UserAgent; use LWP::UserAgent;
use Time::Fake; use Time::Fake;
use URI::Escape; use URI::Escape;
@ -376,6 +377,31 @@ sub expectAuthenticatedAs {
count(1); count(1);
} }
=head4 expectSessionAttributes($app,$id,%attributes)
Verify that the session contains attributes with these values
=cut
sub expectSessionAttributes {
my ( $app, $id, %attributes ) = @_;
my $res;
ok(
$res = $app->_get("/sessions/global/$id"),
"Get session using restSessionServer"
);
count(1);
expectOK($res);
ok( $res = eval { from_json( $res->[2]->[0] ) },
"Deserialize session content" );
count(1);
for my $attr ( keys %attributes ) {
is( $res->{$attr}, $attributes{$attr},
"Session has correct value for $attr" );
count(1);
}
}
=head4 expectOK($res) =head4 expectOK($res)
Verify that returned code is 200 Verify that returned code is 200

View File

@ -205,7 +205,7 @@ Requires: lemonldap-ng-test = %{version}-%{release}
# ! Not available in Centos7, you need to install lemonldap-ng-selinux manually # ! Not available in Centos7, you need to install lemonldap-ng-selinux manually
# This ensures that the *-selinux package and all its dependencies are not pulled # This ensures that the *-selinux package and all its dependencies are not pulled
# into containers and other systems that do not use SELinux # into containers and other systems that do not use SELinux
Requires: (%{name}-selinux if selinux-policy-%{selinuxtype}) Requires: (%{name}-selinux = %{version}-%{release} if selinux-policy-%{selinuxtype})
%endif %endif