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:
# Offline documentation install
@rm -rf $(RDEFDOCDIR)
# Install doc directories
@install -v -d -m 755 $(RDEFDOCDIR)
@cd doc && find * -type d |(cd $(RDEFDOCDIR); xargs install -v -d -m 755) && 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 -
@cd doc && find index.html pages/* -type f ! -path '*/.*' -exec install -v -m 644 -D '{}' $(RDEFDOCDIR)/'{}' \; && cd -
# Remove js
@cd $(RDEFDOCDIR) && if test "$(USEEXTERNALLIBS)" = "yes"; then \
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/test-apache2.X.conf lemonldap-ng-$(VERSION)/_example/etc/test-apache2.conf
@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)
@rm -rf lemonldap-ng-$(VERSION)

View File

@ -136,7 +136,7 @@ Application Configuration
.. image:: applications/simplesamlphp_logo.png :doc:`simpleSAMLphp<applications/simplesamlphp>`
.. image:: applications/spring_logo.png :doc:`Spring<applications/spring>`
.. 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/wekan-logo.png :doc:`Wekan<applications/wekan>`
.. 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.
To configure SSO with Sympa, use **Magic authentication**: a special SSO
URL is protected by LL::NG, Sympa will display a button for users who
wants to use this feature.
To configure SSO with Sympa, you have the choice between:
* CAS
* **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::
@ -19,9 +69,6 @@ wants to use this feature.
removed since it works only with Sympa-5 which has been deprecated
Configuration
-------------
Sympa configuration
~~~~~~~~~~~~~~~~~~~

View File

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

View File

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

View File

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

View File

@ -58,7 +58,7 @@ As *root:*
apt install aptitude
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

View File

@ -51,13 +51,20 @@ portal:
- macros are used to extend (or rewrite)
:doc:`exported variables<exportedvars>`. A macro is stored as
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}``
- groups are stored as a string with values separated by ''; ''
(default values separator) in the special attribute ``groups``: it
contains the names of groups whose rules were returned true for the
(default multivalues separator) in the special attribute ``groups``: it
contains names of groups whose rules were returned true for the
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
$groups = group3; admin

View File

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

View File

@ -361,7 +361,7 @@ Password Policy
- **Minimal upper 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
- **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:

View File

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

View File

@ -84,8 +84,6 @@ Then go in Manager, ``General Parameters`` » ``Plugins`` »
- **Display generate password box**: display a checkbox to allow
user to generate a new password instead of choosing one (default:
disabled)
::
* **Regexp for password generation**: Regular expression used to generate the password (default: [A-Z]{3}[a-z]{5}.\d{2})
- **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}

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:
Another solution is using the :doc:`REST auth/user/password backend<restauthuserpwdbackend>`.
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
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
everyone!!! **NOT RECOMMENDED**.
- the Bad (:doc:`Secure Token Handler<securetoken>`)
: **Deprecated**. Can be used in specific cases
- the Good (Service Token Handler): See below ! (Thanks Sergio...)
- the Bad (:doc:`Secure Token Handler<securetoken>`): **Deprecated**.
Should be used for specific cases
- the Good (Service Token Handler): See below! (Thanks Sergio...)
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
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 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
-----------------------------
Select **Main** handler type to protect WebApp1 and insert a header
named **X-Llng-Token** filled with this value:
Select **Main** handler type to protect WebApp1 and append a header containing:
.. code-block:: perl
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
``X-Llng-Token`` header. The token is built by using the session ID and
authorized virtualhosts list. By default, the Service Token is only
available during 30 seconds and for specified virtualhosts. The token
can be use to send service headers to webapp2 like origin host by
example.
``X-LLNG-TOKEN`` header. The token is built by using the ``token`` extended
with session ID and authorized virtualhosts list parameters. A Service Token is valide
for the specified virtual hosts only and during 30 seconds by default. It can also be
used for sending service headers (headerName1=headerValue1) to requested
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
each required virtualHost.
You can set Service Token TTL by editing virtualHost options in Manager
for each requested virtualHost.
You can also set ServiceToken default timeout (30 seconds) by editing
``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
user and server connections. And that's all !
user and server connections. And that's all!
.. |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
infrastructure, PaaS for platform,…). So for us, ``SSOaaS`` must provide
the ability for an application to manage authorizations and choose user
attributes to set. Authentication can not be really ``*aaS``: application
must just use it but not manage it.
attributes to receive. Authentication can not be really ``*aaS``: application
can just use it but not manage it.
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.
Docker or VM images (Nginx only) includes LL::NG Nginx configuration that
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:
``Auth-User``. If application defines a ``RULES_URL`` parameter that refers to
a JSON file, authorization server will read it, apply specified rules
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
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}```
Example of a central FastCGI architecture:
Example of a Central FastCGI architecture:
|image0|
@ -69,7 +69,8 @@ Nginx
Examples below are customized web server templates for
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
@ -93,12 +94,12 @@ You can use 'uwsgi_param' directive for requesting a Central uWSGI server (Nginx
fastcgi_pass_request_body off;
fastcgi_param CONTENT_LENGTH "";
# Keep original hostname
fastcgi_param HOST $http_host;
# Keep original request (LL::NG server will receive /lmauth)
fastcgi_param X_ORIGINAL_URI $original_uri;
# Keep original hostname
fastcgi_param HOST $http_host;
# Set redirection parameters
fastcgi_param HTTPS_REDIRECT "$https";
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;
}
# Example as ReverseProxy:
# Example as Reverse-Proxy:
location /api/ {
auth_request /lmauth;
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
^^^^^^
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),
FCGI::Client (Perl FastCGI dependency) and Mod_Perl2 (Apache module)
used for parsing HTTP headers.
Then, add this in your apache2.conf web applications or reverse-proxies.
FCGI::Client (Perl FastCGI dependency) and Mod_Perl2 (Apache module
used for parsing HTTP headers).
Then, add this in your apache2.conf web applications or Reverse-Proxies.
.. code-block:: apache
@ -182,25 +184,25 @@ Then, add this in your apache2.conf web applications or reverse-proxies.
# Keep original hostname
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
PerlSetVar PORT_REDIRECT SERVER_PORT
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>
Node.js
^^^^^^^
Using `express <https://github.com/expressjs/express#readme>`__ and
`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
@ -229,6 +231,7 @@ you can protect also an Express server. Example:
return console.log('Example app listening on port 3000!');
});
Plack application
^^^^^^^^^^^^^^^^^
@ -259,7 +262,7 @@ Simple example:
host => '127.0.0.1',
port => '9090',
fcgi_auth_params => {
RULES_URL => 'https://my-server/my.json',
RULES_URL => 'https://my-server/rules.json',
HTTPS_REDIRECT => 'ON',
PORT_REDIRECT => 443
},
@ -293,31 +296,40 @@ directory.
internal;
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
# Force handler type:
fastcgi_param VHOSTTYPE DevOps;
# Drop post datas
fastcgi_pass_request_body off;
fastcgi_param CONTENT_LENGTH "";
# Keep original hostname
fastcgi_param HOST $http_host;
# Keep original request (LL::NG server will received /lmauth)
fastcgi_param X_ORIGINAL_URI $original_uri;
# Set redirection params
fastcgi_param HTTPS_REDIRECT "$https";
fastcgi_param PORT_REDIRECT $server_port;
}
location /rules.json {
auth_request off;
allow 127.0.0.0/8;
deny all;
}
location / {
auth_request /lmauth;
set $original_uri $uri$is_args$args;
auth_request_set $lmremote_user $upstream_http_lm_remote_user;
auth_request_set $lmlocation $upstream_http_location;
error_page 401 $lmlocation;
include /etc/nginx/nginx-lua-headers.conf;
proxy_pass https://$vhost.internal.domain;
}
}

View File

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

View File

@ -115,7 +115,7 @@ sub new {
# Serialize $conf and call store().
# @param $conf Lemonldap::NG configuration hashRef
# @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 {
my ( $self, $conf, %args ) = @_;

View File

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

View File

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

View File

@ -50,7 +50,7 @@ $conf->{oidcServicePrivateKeySig} = $keys->{private};
$conf->{oidcServicePublicKeySig} = $keys->{public};
$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;

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

View File

@ -11,12 +11,23 @@ use Lemonldap::NG::Common::UserAgent;
use Lemonldap::NG::Common::FormEncode;
use Lemonldap::NG::Common::Session;
our $VERSION = '2.0.7';
our $VERSION = '2.0.15';
our @ISA = ('Exporter');
our @EXPORT = qw(fetchId retrieveSession createSession hideCookie goToPortal);
our @EXPORT_OK = @EXPORT;
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
# Get user session id from Authorization header
# 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;

View File

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

View File

@ -110,7 +110,7 @@ categories =
dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen']
connectionTitle: ['ipAddr', '_timezone', '_url']
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']
groups: ['groups', 'hGroups']
ldap: ['dn']

View File

@ -122,7 +122,7 @@
dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen'],
connectionTitle: ['ipAddr', '_timezone', '_url'],
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'],
groups: ['groups', 'hGroups'],
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-Metadata.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-Missing-SLO.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
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 );
$req->authResult($res);
return $res;
@ -437,6 +441,10 @@ sub _choice {
my $ch = $req->param('sf');
foreach my $m ( @{ $self->sfModules } ) {
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 );
$req->authResult($res);
return $self->p->do(

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ use Lemonldap::NG::Portal::Main::Constants qw(
extends 'Lemonldap::NG::Portal::Main::Plugin';
our $VERSION = '2.0.12';
our $VERSION = '2.0.14';
# INITIALIZATION
@ -59,7 +59,8 @@ sub _modifyPassword {
)
);
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(
@ -68,7 +69,8 @@ sub _modifyPassword {
)
);
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

View File

@ -32,7 +32,7 @@ use Lemonldap::NG::Portal::Main::Constants qw(
PE_PP_INSUFFICIENT_PASSWORD_QUALITY
);
our $VERSION = '2.0.12';
our $VERSION = '2.0.14';
extends qw(
Lemonldap::NG::Portal::Lib::SMTP
@ -58,6 +58,9 @@ has ott => (
# Captcha generator
has captcha => ( is => 'rw' );
# Password policy activation rule
has passwordPolicyActivationRule => ( is => 'rw', default => sub { 0 } );
# INITIALIZATION
sub init {
@ -70,6 +73,15 @@ sub init {
if ( $self->conf->{captcha_mail_enabled} ) {
$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;
}
@ -442,8 +454,32 @@ sub changePwd {
"Reset password request for $req->{sessionInfo}->{_user}");
# Generate a complex password
my $password =
$self->gen_password( $self->conf->{randomPasswordRegexp} );
my $pwdRegEx;
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");
$req->data->{newpassword} = $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;
my $cpq =
$self->Lemonldap::NG::Portal::Password::Base::checkPasswordQuality(
$req->data->{newpassword} );
$self->passwordPolicyActivationRule->( $req, $req->sessionInfo )
? $self->Lemonldap::NG::Portal::Password::Base::checkPasswordQuality(
$req->data->{newpassword} )
: PE_OK;
unless ( $cpq == PE_OK ) {
$self->ott->setToken( $req, $req->sessionInfo );
return $cpq;
@ -555,9 +593,19 @@ sub setSecurity {
sub display {
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|\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 );
my %tplPrm = (
@ -576,7 +624,8 @@ sub display {
STARTMAILTIME => $req->data->{startMailTime},
MAILALREADYSENT => $req->data->{mailAlreadySent},
MAIL => (
$self->p->checkXSSAttack( 'mail', $req->{user} ) ? ''
$self->p->checkXSSAttack( 'mail', $req->{user} )
? ''
: $req->{user}
),
DISPLAY_FORM => 0,
@ -584,17 +633,13 @@ sub display {
DISPLAY_CONFIRMMAILSENT => 0,
DISPLAY_MAILSENT => 0,
DISPLAY_PASSWORD_FORM => 0,
DISPLAY_PPOLICY => $self->conf->{portalDisplayPasswordPolicy},
PPOLICY_MINSIZE => $self->conf->{passwordPolicyMinSize},
PPOLICY_MINLOWER => $self->conf->{passwordPolicyMinLower},
PPOLICY_MINUPPER => $self->conf->{passwordPolicyMinUpper},
PPOLICY_MINDIGIT => $self->conf->{passwordPolicyMinDigit},
PPOLICY_ALLOWEDSPECHAR => $speChars,
(
$speChars
? ( PPOLICY_MINSPECHAR => $self->conf->{passwordPolicyMinSpeChar} )
: ()
),
DISPLAY_PPOLICY => $self->conf->{portalDisplayPasswordPolicy} && $isPP,
PPOLICY_MINSIZE => $self->conf->{passwordPolicyMinSize},
PPOLICY_MINLOWER => $self->conf->{passwordPolicyMinLower},
PPOLICY_MINUPPER => $self->conf->{passwordPolicyMinUpper},
PPOLICY_MINDIGIT => $self->conf->{passwordPolicyMinDigit},
PPOLICY_MINSPECHAR => $self->conf->{passwordPolicyMinSpeChar},
PPOLICY_ALLOWEDSPECHAR => $speChars,
DISPLAY_GENERATE_PASSWORD =>
$self->conf->{portalDisplayGeneratePassword},
);

View File

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

View File

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

View File

@ -7,7 +7,7 @@
<br />
<br />
<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>
<br />
<span trspan="pwdIs">Your password is</span>

View File

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

View File

@ -10,7 +10,7 @@ BEGIN {
}
my ( $res, $host, $url, $query );
my $maintests = 16;
my $maintests = 18;
my $mailSend = 0;
my $mail2 = 0;
@ -33,6 +33,13 @@ SKIP: {
requireToken => 1,
portalDisplayResetPassword => 1,
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' );
ok( $res->[2]->[0] =~ /newpassword/s, ' Ask for a new password' );
$query .= '&newpassword=zz&confirmpassword=zz';
$query .= '&reset=1';
# Post new password
ok(
@ -115,7 +122,9 @@ m#<img class="renewcaptchaclick" src="/static/common/icons/arrow_refresh.png"#,
),
'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);
}

View File

@ -10,7 +10,7 @@ BEGIN {
}
my ( $res, $user, $pwd );
my $maintests = 12;
my $maintests = 18;
SKIP: {
eval
@ -21,15 +21,22 @@ SKIP: {
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
useSafeJail => 1,
portalDisplayRegister => 1,
authentication => 'Demo',
userDB => 'Same',
passwordDB => 'Demo',
captcha_mail_enabled => 0,
portalDisplayResetPassword => 1,
portalMainLogo => 'common/logos/logo_llng_old.png',
logLevel => 'error',
useSafeJail => 1,
portalDisplayRegister => 1,
authentication => 'Demo',
userDB => 'Same',
passwordDB => 'Demo',
captcha_mail_enabled => 0,
portalDisplayResetPassword => 1,
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' );
ok( $res->[2]->[0] =~ /newpassword/s, ' Ask for a new password' );
$query .= '&newpassword=zz&confirmpassword=zz';
ok( $res->[2]->[0] =~ /<span trspan="passwordPolicy">/,
' 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
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' );
ok( $res->[2]->[0] =~ m%: rtyler<br/>%, 'Found 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' );
ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found 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 ),
'Found two well computed macros' )
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"' )
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"' )
or explain( $res->[2]->[0], 'Found group "realAuthGroup"' );
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' );
ok( $res->[2]->[0] =~ m%: rtyler%, 'Found 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' );
ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found 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' );
ok( $res->[2]->[0] =~ m%: rtyler<br/>%, 'Found 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' );
ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found 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' );
ok( $res->[2]->[0] =~ m%: rtyler<br/>%, 'Found 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' );
ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found 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' );
ok( $res->[2]->[0] =~ m%: dwho<br/>%, 'Found 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' );
ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found 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' );
ok( $res->[2]->[0] =~ m%: dwho<br/>%, 'Found 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' );
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' );
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_' );
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' );
ok( $res->[2]->[0] =~ m%<td scope="row">uid</td>%, 'Found 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">%,
'Found 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"' );
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"' )
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"' )
or explain( $res->[2]->[0], 'Found SSO group "test_su"' );
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' );
ok( $res->[2]->[0] =~ m%: dwho<br/>%, 'Found 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' );
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' );
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_' );
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' );
ok( $res->[2]->[0] =~ m%<td scope="row">_whatToTrace</td>%,
'Found _whatToTrace' )

View File

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

View File

@ -84,6 +84,7 @@ ok(
);
count(1);
my $id = expectCookie($res);
expectSessionAttributes($client, $id, _2f => "work");
$client->logout($id);
clean_sessions();
@ -167,6 +168,7 @@ ok(
);
count(1);
$id = expectCookie($res);
expectSessionAttributes($client, $id, _2f => "home");
# Verify Authn Level
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 Data::Dumper;
use File::Find;
use JSON;
use LWP::UserAgent;
use Time::Fake;
use URI::Escape;
@ -376,6 +377,31 @@ sub expectAuthenticatedAs {
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)
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
# This ensures that the *-selinux package and all its dependencies are not pulled
# 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