Merge branch 'v2.0'

This commit is contained in:
Christophe Maudoux 2020-08-28 22:05:52 +02:00
commit 5496d798a3
221 changed files with 13424 additions and 1768 deletions

View File

@ -55,9 +55,6 @@ build_centos_7:
script:
- rm -f /etc/yum.repos.d/CentOS-Sources.repo
- yum -y install epel-release
- yum -y install python-pip
- pip install -U pip
- pip install sphinx==1.8.5 sphinx-rtd_theme==0.4.3
- make rpm-dist
- ci-build-pkg
artifacts:
@ -65,19 +62,19 @@ build_centos_7:
paths:
- result/*
#build_centos_8:
# image: buildpkg/centos:8
# stage: build
# script:
# - yum-config-manager --enable PowerTools
# - yum-config-manager --enable AppStream
# - yum -y install epel-release
# - make rpm-dist
# - ci-build-pkg
# artifacts:
# expire_in: 1 day
# paths:
# - result/*
build_centos_8:
image: buildpkg/centos:8
stage: build
script:
- yum-config-manager --enable PowerTools
- yum-config-manager --enable AppStream
- yum -y install epel-release
- make rpm-dist
- ci-build-pkg
artifacts:
expire_in: 1 day
paths:
- result/*
sign:
image: buildpkg/debian:stretch

View File

@ -96,6 +96,9 @@ APACHEFILENOTIFDIR=$(DATADIR)/notifications
# LL::NG captcha dir
CAPTCHADIR=$(DATADIR)/captcha
# LL::NG cache dir
CACHEDIR=$(DATADIR)/cache
# Apache user/group
APACHEUSER=
APACHEGROUP=
@ -178,6 +181,7 @@ RAPACHEPSESSIONFILEDIR=$(DESTDIR)/$(APACHEPSESSIONFILEDIR)
RAPACHEPSESSIONFILELOCKDIR=$(DESTDIR)/$(APACHEPSESSIONFILELOCKDIR)
RFILENOTIFDIR=$(DESTDIR)/$(APACHEFILENOTIFDIR)
RCAPTCHADIR=$(DESTDIR)/$(CAPTCHADIR)
RCACHEDIR=$(DESTDIR)/$(CACHEDIR)
RFASTCGISOCKDIR=$(DESTDIR)/$(FASTCGISOCKDIR)
VERSION=`head -n1 changelog |sed -e 's/lemonldap-ng (//' -e 's/).*$$//'`
@ -747,7 +751,7 @@ install_site: install_manager_site install_portal_site install_handler_site inst
@echo "5 - Connect to Manager at http://manager.${DNSDOMAIN}/ to edit configuration"
@echo
@if [ ! "$(APACHEUSER)" ]; then \
echo;echo " Warning, since APACHEUSER was not set, $(APACHESESSIONFILEDIR), $(APACHEPSESSIONFILEDIR), $(CAPTCHADIR) and $(CONFDIR) have permissive permissions."; \
echo;echo " Warning, since APACHEUSER was not set, $(APACHESESSIONFILEDIR), $(APACHEPSESSIONFILEDIR), $(CAPTCHADIR), $(CACHEDIR) and $(CONFDIR) have permissive permissions."; \
echo " Fix them by yourself to restrict their view to apache process only"; \
fi
@echo
@ -904,7 +908,7 @@ install_doc_site:
rm -rvf $(DOCLIBSTOREMOVEFORDEBIAN); \
fi && cd -
install_conf_dir: install_sessions_dir install_notif_dir install_captcha_dir
install_conf_dir: install_sessions_dir install_notif_dir install_captcha_dir install_cache_dir
# Configuration files install
@install -v -d $(RCONFDIR) $(RFILECONFIGDIR) $(RTOOLSDIR)
@if [ "$(ERASECONFIG)" -eq "1" ]; then \
@ -915,7 +919,8 @@ install_conf_dir: install_sessions_dir install_notif_dir install_captcha_dir
@$(PERL) -i -pe 's/__DNSDOMAIN__/$(DNSDOMAIN)/g;\
s#__SESSIONDIR__#$(APACHESESSIONFILEDIR)#g;\
s#__PSESSIONDIR__#$(APACHEPSESSIONFILEDIR)#g;\
s#__NOTIFICATIONDIR__#$(APACHEFILENOTIFDIR)#g;' $(RFILECONFIGDIR)/lmConf-1.json
s#__NOTIFICATIONDIR__#$(APACHEFILENOTIFDIR)#g;\
s#__CACHEDIR__#$(CACHEDIR)#g;' $(RFILECONFIGDIR)/lmConf-1.json
@if [ "$(APACHEUSER)" != "" ]; then \
chown $(APACHEUSER) $(RFILECONFIGDIR) || exit 1; \
if [ "$(APACHEGROUP)" != "" ]; then \
@ -927,7 +932,8 @@ install_conf_dir: install_sessions_dir install_notif_dir install_captcha_dir
fi
@cp $(SRCCOMMONDIR)/tools/lmConfig.* $(SRCCOMMONDIR)/tools/apache-session-mysql.sql $(RTOOLSDIR)
@cp $(SRCCOMMONDIR)/tools/sso.schema $(RTOOLSDIR)
$(PERL) -i -pe 's/__DNSDOMAIN__/$(DNSDOMAIN)/g' $(RCONFDIR)/$(CONFFILENAME)
$(PERL) -i -pe 's/__DNSDOMAIN__/$(DNSDOMAIN)/g; s#__CACHEDIR__#$(CACHEDIR)#g;' \
$(RCONFDIR)/$(CONFFILENAME)
@rm -rf $$(find $(RCONFDIR) $(RFILECONFIGDIR) $(RTOOLSDIR) -type d -name .svn)
install_sessions_dir:
@ -969,6 +975,19 @@ install_captcha_dir:
chmod 777 $(RCAPTCHADIR); \
fi
install_cache_dir:
@install -m 777 -v -d $(RCACHEDIR)
# Fix captcha directory permissions
@if [ "$(APACHEUSER)" != "" ]; then \
chown $(APACHEUSER) $(RCACHEDIR) || exit 1; \
if [ "$(APACHEGROUP)" != "" ]; then \
chgrp $(APACHEGROUP) $(RCACHEDIR) || exit 1; \
fi; \
chmod 770 $(RCACHEDIR); \
else \
chmod 777 $(RCACHEDIR); \
fi
postconf_hosts:
@cat ${CONFDIR}/for_etc_hosts >> /etc/hosts
@echo "/etc/hosts was updated"
@ -1152,12 +1171,12 @@ documentation:
echo "Skipped documentation building" ;\
else \
sphinx-build -b html -d /tmp/doctrees \
-Dhtml_theme_path=. -Dhtml_theme=manager_doc_theme \
-Dhtml_theme=bootstrap\
doc/sources/admin/ doc/pages/documentation/current/ ;\
fi
website_documentation:
sphinx-build -b html -d /tmp/doctrees \
LLNGSPHINXWEBSITE=1 sphinx-build -b html -d /tmp/doctrees \
doc/sources/admin/ "$(WEBSITE_DIR)"
test-diff:

View File

@ -115,7 +115,7 @@
"localSessionStorage" : "Cache::FileCache",
"localSessionStorageOptions" : {
"cache_depth" : 3,
"cache_root" : "/tmp",
"cache_root" : "__CACHEDIR__",
"default_expires_in" : 600,
"directory_umask" : "007",
"namespace" : "lemonldap-ng-sessions"

View File

@ -30,7 +30,7 @@
auth_request_set $headervalue15 $upstream_http_headervalue15;
auth_request_set $lmcookie $upstream_http_cookie;
access_by_lua '
i = 1
local i = 1
ngx.req.set_header("Cookie",ngx.var.lmcookie)
while true do
if ngx.var["headername"..i] ~= nil then

2
debian/control vendored
View File

@ -53,7 +53,7 @@ Build-Depends-Indep: libapache-session-perl <!nocheck>,
libxml-libxml-perl <!nocheck>,
libxml-libxslt-perl <!nocheck>,
python3-sphinx,
python3-sphinx-rtd-theme,
python3-sphinx-bootstrap-theme,
perl
Standards-Version: 4.5.0
Vcs-Browser: https://salsa.debian.org/perl-team/modules/packages/lemonldap-ng

View File

@ -1,3 +1,4 @@
/var/cache/lemonldap-ng
/var/lib/lemonldap-ng/conf
/var/lib/lemonldap-ng/sessions/lock
/var/lib/lemonldap-ng/psessions/lock

7
debian/rules vendored
View File

@ -5,6 +5,7 @@
LMSHAREDIR=/usr/share/lemonldap-ng
LMVARDIR =/var/lib/lemonldap-ng
LMCACHEDIR =/var/cache/lemonldap-ng
TMP = $(CURDIR)/debian/tmp
CONFDIR=/etc/lemonldap-ng
@ -22,6 +23,7 @@ CAPTCHADIR=$(LMVARDIR)/captcha
override_dh_auto_configure:
$(MAKE) configure STORAGECONFFILE=/etc/lemonldap-ng/lemonldap-ng.ini \
DATADIR=$(LMVARDIR) \
CACHEDIR=$(LMCACHEDIR) \
PERLOPTIONS="INSTALLDIRS=vendor"
override_dh_auto_build:
@ -45,6 +47,7 @@ override_dh_auto_install:
CONFDIR=/etc/lemonldap-ng \
CRONDIR=/etc/cron.d \
DATADIR=$(LMVARDIR) \
CACHEDIR=$(LMCACHEDIR) \
APACHEUSER=www-data \
APACHEGROUP=www-data \
DEFDOCDIR=/usr/share/doc/lemonldap-ng-doc \
@ -70,6 +73,7 @@ override_dh_fixperms:
debian/*/$(PSESSIONSDIR)/lock \
debian/*/$(NOTIFICATIONSDIR) \
debian/liblemonldap-ng-common-perl/$(CONFSTORAGEDIR) \
debian/liblemonldap-ng-common-perl/$(LMCACHEDIR) \
debian/liblemonldap-ng-portal-perl/$(CAPTCHADIR)
chgrp www-data debian/liblemonldap-ng-common-perl/$(LMINIFILE) \
debian/liblemonldap-ng-common-perl/$(FIRSTCONFFILE)
@ -77,6 +81,7 @@ override_dh_fixperms:
debian/*/$(PSESSIONSDIR) debian/*/$(PSESSIONSDIR)/lock \
debian/*/$(NOTIFICATIONSDIR) \
debian/liblemonldap-ng-portal-perl/$(CAPTCHADIR)
chmod 750 debian/liblemonldap-ng-common-perl/$(CONFSTORAGEDIR)
chmod 750 debian/liblemonldap-ng-common-perl/$(CONFSTORAGEDIR) \
debian/liblemonldap-ng-common-perl/$(LMCACHEDIR)
chmod 640 debian/liblemonldap-ng-common-perl/$(FIRSTCONFFILE) \
debian/liblemonldap-ng-common-perl/$(LMINIFILE)

View File

@ -1 +1 @@
3.0.18
3.0.20

File diff suppressed because it is too large Load Diff

View File

@ -26,14 +26,15 @@ Configuration
Pre-requisites
~~~~~~~~~~~~~~
In this guide, it is assumed that you have followed the `Jitsi Meet
This documentation assumes that you have already installed a :doc:`Nginx-based <../confignginx>`
LemonLDAP::NG Handler on your Jitsi server.
You need to install Nginx before Jitsi Meet. If you install Jitsi Meet first,
the Jitsi Meet installer will not generate a Nginx configuration file.
We assume that you have followed the `Jitsi Meet
quick
start <https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md>`__
and that **you have installed Nginx on your Jitsi Meet server first**
If you have not done that, the Jitsi Meet installer will not generate a
Nginx configuration file for you. This is not a problem is you are
already using your own reverse proxy.
Jitsi Meet configuration
~~~~~~~~~~~~~~~~~~~~~~~~
@ -59,26 +60,29 @@ configuration file:
::
location = /lmauth {
internal;
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
fastcgi_pass_request_body off;
fastcgi_param CONTENT_LENGTH "";
fastcgi_param HOST $http_host;
fastcgi_param X_ORIGINAL_URI $request_uri;
}
# This block lets Nginx know how to contact the local LLNG handler
# for authentication
location = /lmauth {
internal;
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
fastcgi_pass_request_body off;
fastcgi_param CONTENT_LENGTH "";
fastcgi_param HOST $http_host;
fastcgi_param X_ORIGINAL_URI $request_uri;
}
# Protect only the /login/ URL
# You may want to change this is your goal is to make the whole Jitsi Meet instance private
location /login/ {
# Protect the current path with LLNG
auth_request /lmauth;
auth_request_set $lmremote_user $upstream_http_lm_remote_user;
auth_request_set $lmlocation $upstream_http_location;
error_page 401 $lmlocation;
# Transmis user information to Jitsi through HTTP headers
auth_request_set $mail $upstream_http_mail;
proxy_set_header mail $mail;
auth_request_set $displayname $upstream_http_displayName;
@ -86,6 +90,7 @@ configuration file:
auth_request_set $lmcookie $upstream_http_cookie;
proxy_set_header Cookie: $lmcookie;
# Proxy requests to Jitsi Meet
proxy_pass http://127.0.0.1:8888/login;
}

View File

@ -10,8 +10,8 @@ Authentication Users Password
Presentation
------------
The Active Directory module is based on the
:doc:`LDAP module<authldap>`, with these features:
The Active Directory module is based on
:doc:`LDAP module<authldap>`, with the following features:
- Specific default values for filters to match AD schema
- Compatible password modification
@ -30,7 +30,7 @@ implemented its own policy. LemonLDAP::NG implements partially the
policy:
- when pwdLastSet = 0 in the user entry, it means that password has
been reset, and a form is presented to the user for him to change his
been reset, and a form is displayed to the user to change his
password.
- when computed virtual attribute 'msDS-User-Account-Control-Computed'
as 6th flag set to 8, the password is considered expired (support

View File

@ -18,7 +18,7 @@ example Kerberos, Radius, OTP, etc.
.. attention::
To authenticate users using Kerberos, you can now use
To authenticate users by using Kerberos, you can now use
the new :doc:`Kerberos authentication module<authkerberos>` which allow
one to chain Kerberos in a :doc:`combination<authcombination>`\

View File

@ -10,7 +10,7 @@ Authentication Users Password
Presentation
------------
LL::NG is able to transfer (trough REST or SOAP) authentication
LL::NG is able to send (through REST or SOAP) authentication
credentials to another LL::NG portal, like a proxy.
@ -39,10 +39,10 @@ Then, go in ``Proxy parameters``:
same as previous for SOAP, same with "/session/my" for REST)
- **Cookie name** (optional): name of the cookie of internal portal, if
different from external portal
- **Authentication level**: level given to this authentication
- **Authentication level**: authentication level for Proxy module
- **Use SOAP instead of REST**: use a deprecated SOAP server instead of
a REST one (you must set it if internal portal version is < 2.0). In
this case, "Portal URL" parameter must contains SOAP endpoint
this case, "Portal URL" parameter must contain SOAP endpoint
(generally http://auth.example.com/index.pl/sessions for 1.9 and
earlier, http://auth.example.com/sessions for 2.0)
@ -50,14 +50,14 @@ Internal portal
~~~~~~~~~~~~~~~
The portal must be configured to accept REST or SOAP authentication
requests if you've choose to use SOAP. See:
requests if you chose to use SOAP. See:
:doc:`REST server plugin<restservices>` or
:doc:`SOAP session backend<soapsessionbackend>` *(deprecated)*.
SOAP compatibility with 1.9 server
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you Proxy is a 2.0.x and your server is a 1.9.x, you should add this
If your Proxy is a 2.0.x and your server is a 1.9.x, you should add this
in your lemonldap-ng.ini:
.. code-block:: ini

View File

@ -34,7 +34,7 @@ Then, go in ``Slave parameters``:
- **Master's IP address**: the IP addresses of servers which are
accredited to authenticate user. This is a security point, to prevent
someone to create a session by sending custom headers. You can set
one or several IP addresses, separated by spaces, or let this
one or several IP addresses, spaces separated, or let this
parameter empty to disable the checking.
- **Control header name**: header that contains a value to control. Let
this parameter empty to disable the checking.

View File

@ -20,6 +20,15 @@ Go in Manager, ``General Parameters`` » ``Advanced Parameters`` »
``Security`` » ``Brute-force attack protection`` » ``Activation``\ and
set to ``On``.
- **Parameters**:
- **Activation**: Enable/disable brute force attack protection
- **Lock time**: Waiting time before another login attempt
- **Allowed failed login**: Number of failed login attempts allowed before account is locked
- **Incremental lock**: Enable/disable incremental lock times
- **Incremental lock times**: List of comma separated lock time values in seconds
Incremental lock time enabled
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -35,33 +44,29 @@ in ``lemonldap-ng.ini`` [portal] section:
[portal]
bruteForceProtectionIncrementalTempo = 1
Lock time increases between each failed login attempt. To modify lock
time values ('5 15 60 300 600' seconds by default) or max lock time
value (900 seconds by default) edit ``lemonldap-ng.ini`` in [portal]
section:
Lock time increases between each failed login attempt after allowed failed logins.
.. code-block:: ini
[portal]
bruteForceProtectionLockTimes = '5 15 60 300 600'
bruteForceProtectionLockTimes = 5, 15, 60, 300, 600
bruteForceProtectionMaxLockTime = 900
.. note::
Max lock time value is used by this plugin if a lock time is
missing (number of failed logins higher than listed lock time values).
Max lock time value is used if a lock time is missing
(number of failed logins higher than listed lock time values).
Lock time values can not be higher than max lock time.
Incremental lock time disabled
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After ``bruteForceProtectionMaxFailed`` failed login attempts, user must
wait ``bruteForceProtectionTempo`` seconds before trying to log in
again. To modify waiting time (30 seconds by default), MaxAge between
current and last stored failed login (300 seconds by default) or number
of allowed failed login attempts (3 by default) edit
``lemonldap-ng.ini`` in [portal] section:
After allowed failed login attempts, user must
wait the lock time before trying to log in again.
To modify delta (MaxAge) between current and last stored
failed login (300 seconds by default) edit ``lemonldap-ng.ini`` in [portal] section:
.. code-block:: ini
@ -72,7 +77,12 @@ of allowed failed login attempts (3 by default) edit
.. attention::
Number of failed login attempts history might be also higher than
number of incremental lock time value plus allowed failed login attempts.
Incremental lock time values list will be truncated if not.
.. danger::
Number of failed login attempts stored in history MUST
be higher than allowed failed logins for this plugin takes effect.
See :doc:`History plugin<loginhistory>`

View File

@ -19,7 +19,7 @@ Let's go
- Prepare your new lemonldap-ng.ini file
- Configure your new backend (create SQL database,...)
- Launch that:
- Launch the following command:
.. code-block:: shell
@ -29,6 +29,14 @@ Let's go
all LL::NG servers
- Restart all your Apache servers
.. note::
Since LemonLDAP 2.0.9, you don't need the ``--current`` and ``--new`` options
when migrating from the default file-based backend. Simply run
`convertConfig` to migrate from the default configuration backend to the
currently configured backend.
See also
--------

View File

@ -456,3 +456,52 @@ To update the master encryption key:
set \
key 'xxxxxxxxxxxxxxx'
Sessions Management
-------------------
.. versionadded:: 2.0.9
Get the content of a session ::
lemonldap-ng-sessions get 9684dd2a6489bf2be2fbdd799a8028e3
Get the content of a persistent session ::
lemonldap-ng-sessions get --persistent dwho
Search all sessions by username ::
lemonldap-ng-sessions search --where uid=dwho
Modify session ::
lemonldap-ng-sessions setKey 9684dd2a6489bf2be2fbdd799a8028e3 \
authenticationLevel 1
Second Factors management
-------------------------
.. versionadded:: 2.0.9
List second factors of a user ::
lemonldap-ng-sessions secondfactors get dwho
Deregister Yubikey of a user ::
lemonldap-ng-sessions secondfactors delType dwho UBK
OIDC Consents management
------------------------
.. versionadded:: 2.0.9
List consents of a user ::
lemonldap-ng-sessions consents get dwho
Revoke consents on OIDC provider 'test' for a user::
lemonldap-ng-sessions consents delete dwho test

View File

@ -19,6 +19,8 @@
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
import sphinx_bootstrap_theme
import os
# -- General configuration ------------------------------------------------
@ -48,7 +50,7 @@ master_doc = 'start'
# General information about the project.
project = u'LemonLDAP::NG'
copyright = u'2019, LemonLDAP::NG'
copyright = u'2020, LemonLDAP::NG'
author = u'LemonLDAP::NG'
# The version info for the project you're documenting, acts as replacement for
@ -118,20 +120,25 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
#html_theme_path = [ '.' ]
html_theme_path = sphinx_bootstrap_theme.get_html_theme_path()
# For website
html_theme = 'sphinx_rtd_theme'
html_theme = 'bootstrap'
# For embedded doc
#html_theme = 'my_theme'
html_theme_options = {
'globaltoc_includehidden': False,
'navbar_sidebarrel': False,
'navbar_pagenav': False,
'globaltoc_depth': 0,
'navbar_fixed_top': "false",
'navbar_class': "hidden",
}
if 'LLNGSPHINXWEBSITE' in os.environ:
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_options = {}
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# html_theme_options = {}

View File

@ -133,9 +133,10 @@ Install dependencies
aptitude install nginx nginx-extras # install Nginx
cpanm perltidy@20181120
For SAML:
::
SAML :
aptitude install liblasso-perl libglib-perl
Working Project
@ -188,3 +189,20 @@ Other commands
make manifest # to update manifest
make tidy # to magnify perl files (perl best pratices)
Documentation
^^^^^^^^^^^^^
Install dependencies:
::
apt install python3-sphinx python3-sphinx-bootstrap-theme
Then edit sources in doc/sources/admin.
You can check the result with:
::
make documentation
firefox doc/pages/documentation/current/start.html

View File

@ -1,397 +0,0 @@
Error at "source" (line 10, column 18):
unexpected "R"
expecting "{{"
| dwho | ''$env->{REMOTE_ADDR} eq '192.168.42.42' '' |
^
Error at "source" (line 7, column 21):
unexpected "}"
expecting "{{"
===== Available $ENV{} variables =====
^
/home/maxbes/proj/doc/authcustom.rst:46: WARNING: Explicit markup ends without a blank line; unexpected unindent.
/home/maxbes/proj/doc/authopenidconnect.rst:235: WARNING: image file not readable: section>openidconnectclaims&noheader
/home/maxbes/proj/doc/authopenidconnect.rst:235: WARNING: image file not readable: section>openidconnectclaims&noheader
/home/maxbes/proj/doc/aws.rst:45: WARNING: Cannot analyze code. No Pygments lexer found for "ldif".
/home/maxbes/proj/doc/behindproxyminihowto.rst:89: WARNING: image file not readable: documentation/reverseproxy.png
/home/maxbes/proj/doc/behindproxyminihowto.rst:89: WARNING: image file not readable: documentation/reverseproxy.png
/home/maxbes/proj/doc/browseablesessionbackend.rst:162: WARNING: Explicit markup ends without a blank line; unexpected unindent.
/home/maxbes/proj/doc/cda.rst:29: WARNING: image file not readable: section>..presentation#cross_domain_authentication_cda&noheader
/home/maxbes/proj/doc/cda.rst:29: WARNING: image file not readable: section>..presentation#cross_domain_authentication_cda&noheader
/home/maxbes/proj/doc/exportedvars.rst:49: WARNING: image file not readable: page>performances#macros_and_groups&noheader
/home/maxbes/proj/doc/exportedvars.rst:49: WARNING: image file not readable: page>performances#macros_and_groups&noheader
/home/maxbes/proj/doc/fusiondirectory.rst:34: WARNING: image file not readable: applications/fusiondirectory-logo.jpg
/home/maxbes/proj/doc/fusiondirectory.rst:34: WARNING: image file not readable: applications/fusiondirectory-logo.jpg
/home/maxbes/proj/doc/idpopenidconnect.rst:213: WARNING: image file not readable: section>openidconnectclaims&noheader
/home/maxbes/proj/doc/idpopenidconnect.rst:213: WARNING: image file not readable: section>openidconnectclaims&noheader
/home/maxbes/proj/doc/installdeb.rst:171: WARNING: image file not readable: rpm-gpg-key-ow2
/home/maxbes/proj/doc/installdeb.rst:171: WARNING: image file not readable: rpm-gpg-key-ow2
/home/maxbes/proj/doc/installrpm.rst:216: WARNING: image file not readable: rpm-gpg-key-ow2
/home/maxbes/proj/doc/installrpm.rst:216: WARNING: image file not readable: rpm-gpg-key-ow2
/home/maxbes/proj/doc/installsles.rst:247: WARNING: image file not readable: rpm-gpg-key-ow2
/home/maxbes/proj/doc/installsles.rst:247: WARNING: image file not readable: rpm-gpg-key-ow2
/home/maxbes/proj/doc/mediawiki.rst:96: WARNING: Explicit markup ends without a blank line; unexpected unindent.
/home/maxbes/proj/doc/mediawiki.rst:110: WARNING: Explicit markup ends without a blank line; unexpected unindent.
/home/maxbes/proj/doc/performances.rst:307: WARNING: Cannot analyze code. No Pygments lexer found for "ldif".
/home/maxbes/proj/doc/portalcustom.rst:22: WARNING: Bullet list ends without a blank line; unexpected unindent.
/home/maxbes/proj/doc/prereq.rst:212: WARNING: image file not readable: ../llng_deps.png
/home/maxbes/proj/doc/prereq.rst:212: WARNING: image file not readable: ../llng_deps.png
/home/maxbes/proj/doc/rbac.rst:71: WARNING: Cannot analyze code. No Pygments lexer found for "ldif".
/home/maxbes/proj/doc/renater.rst:174: WARNING: image file not readable: logos/1renater.png
/home/maxbes/proj/doc/renater.rst:174: WARNING: image file not readable: logos/1renater.png
/home/maxbes/proj/doc/salesforce.rst:122: WARNING: image file not readable: applications/salesforce-logo.jpg
/home/maxbes/proj/doc/salesforce.rst:122: WARNING: image file not readable: applications/salesforce-logo.jpg
/home/maxbes/proj/doc/sap.rst:18: WARNING: image file not readable: applications/SAPLogo.gif
/home/maxbes/proj/doc/sap.rst:18: WARNING: image file not readable: applications/SAPLogo.gif
/home/maxbes/proj/doc/servertoserver.rst:44: WARNING: image file not readable: ../server_to_server.png
/home/maxbes/proj/doc/servertoserver.rst:44: WARNING: image file not readable: ../server_to_server.png
/home/maxbes/proj/doc/sqlsessionbackend.rst:11: SEVERE: Unexpected section title or transition.
....
/home/maxbes/proj/doc/ssoaas.rst:22: WARNING: Inline emphasis start-string without end-string.
/home/maxbes/proj/doc/.applications2.swp.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/activedirectoryminihowto.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/adfs.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/alfresco.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/applications.1.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/applications2.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authad.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authapache.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authbasic.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authcas.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authchoice.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authcombination.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authcustom.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authdbi.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authdemo.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authfacebook.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authgpg.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authkerberos.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authldap.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authlinkedin.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authmulti.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authnull.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authopenid.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authopenidconnect.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authopenidconnect_franceconnect.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authopenidconnect_google.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authpam.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authproxy.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authradius.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authremote.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authrest.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authsaml.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authslave.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authssl.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authtwitter.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authwebid.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/authyubikey.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/autosignin.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/aws.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/behindproxyminihowto.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/browseablesessionbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/bruteforceprotection.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/bugzilla.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/captcha.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/cda.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/changeconfbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/checkstate.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/cli_examples.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/configapache.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/configlocation.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/confignginx.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/configplack.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/configvhost.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/cornerstone.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/customfunctions.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/customhandlers.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/devopshandler.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/devopssthandler.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/django.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/docker.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/dokuwiki.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/dos.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/drupal.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/error.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/exploit.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/exportedvars.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/extendedfunctions.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/external2f.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/fastcgi.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/fastcgiserver.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/federationproxy.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/fileconfbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/filesessionbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/forcereauthn.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/formreplay.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/fusiondirectory.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/gitlab.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/glpi.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/googleapps.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/grr.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/handlerarch.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/handlerauthbasic.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/header_remote_user_conversion.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/highavailability.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/idpcas.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/idpopenid.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/idpopenidconnect.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/idpsaml.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/installdeb.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/installrpm.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/installsles.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/installtarball.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/issuerdbget.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/kerberos.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/ldapconfbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/ldapminihowto.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/ldapsessionbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/liferay.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/limesurvey.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/localconfbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/loginhistory.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/logoutforward.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/logs.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/mail2f.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/managerprotection.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/managertests.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/mediawiki.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/memcachedsessionbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/mitm.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/mongodbconfbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/mongodbsessionbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/monitoring.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/mrtg.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/mysqlminihowto.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/nextcloud.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/nodehandler.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/nosqlsessionbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/notifications.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/obm.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/office365.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/openidconnectservice.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/parameterlist.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/passwordstore.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/performances.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/phpldapadmin.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/platformsoverview.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/plugincustom.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/portal.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/portalcustom.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/portalmenu.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/portalservers.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/prereq.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/psgi.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/public_pages.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/rbac.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/redirections.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/register.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/renater.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/resetpassword.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/rest2f.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/restconfbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/restminihowto.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/restservices.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/restsessionbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/roundcube.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/safejail.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/salesforce.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/samlservice.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/sap.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/secondfactor.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/securetoken.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/security.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/selfmadeapplication.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/selinux.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/servertoserver.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/sessions.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/simplesamlphp.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/smtp.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/soapconfbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/soapservices.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/soapsessionbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/spring.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/sqlconfbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/sqlsessionbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/ssoaas.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/ssocookie.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/status.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/stayconnected.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/symfony.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/sympa.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/tomcat.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/totp2f.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/u2f.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/upgrade.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/utotp2f.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/variables.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/wordpress.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/writingrulesand_headers.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/xwiki.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/yamlconfbackend.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/yubikey2f.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/zimbra.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/zimbra2.rst:: WARNING: document isn't included in any toctree
/home/maxbes/proj/doc/applications2.rst:28: WARNING: unknown document: applications/authbasic
/home/maxbes/proj/doc/applications2.rst:39: WARNING: unknown document: applications/adfs
/home/maxbes/proj/doc/applications2.rst:39: WARNING: unknown document: applications/adfs
/home/maxbes/proj/doc/applications2.rst:40: WARNING: unknown document: applications/alfresco
/home/maxbes/proj/doc/applications2.rst:40: WARNING: unknown document: applications/alfresco
/home/maxbes/proj/doc/applications2.rst:41: WARNING: unknown document: applications/aws
/home/maxbes/proj/doc/applications2.rst:41: WARNING: unknown document: applications/aws
/home/maxbes/proj/doc/applications2.rst:42: WARNING: unknown document: applications/bugzilla
/home/maxbes/proj/doc/applications2.rst:42: WARNING: unknown document: applications/bugzilla
/home/maxbes/proj/doc/applications2.rst:43: WARNING: unknown document: applications/cornerstone
/home/maxbes/proj/doc/applications2.rst:43: WARNING: unknown document: applications/cornerstone
/home/maxbes/proj/doc/applications2.rst:44: WARNING: unknown document: applications/django
/home/maxbes/proj/doc/applications2.rst:44: WARNING: unknown document: applications/django
/home/maxbes/proj/doc/applications2.rst:46: WARNING: unknown document: applications/dokuwiki
/home/maxbes/proj/doc/applications2.rst:46: WARNING: unknown document: applications/dokuwiki
/home/maxbes/proj/doc/applications2.rst:47: WARNING: unknown document: applications/drupal
/home/maxbes/proj/doc/applications2.rst:47: WARNING: unknown document: applications/drupal
/home/maxbes/proj/doc/applications2.rst:48: WARNING: unknown document: applications/fusiondirectory
/home/maxbes/proj/doc/applications2.rst:48: WARNING: unknown document: applications/fusiondirectory
/home/maxbes/proj/doc/applications2.rst:49: WARNING: unknown document: applications/gitlab
/home/maxbes/proj/doc/applications2.rst:49: WARNING: unknown document: applications/gitlab
/home/maxbes/proj/doc/applications2.rst:50: WARNING: unknown document: applications/glpi
/home/maxbes/proj/doc/applications2.rst:50: WARNING: unknown document: applications/glpi
/home/maxbes/proj/doc/applications2.rst:51: WARNING: unknown document: applications/googleapps
/home/maxbes/proj/doc/applications2.rst:51: WARNING: unknown document: applications/googleapps
/home/maxbes/proj/doc/applications2.rst:53: WARNING: unknown document: applications/grr
/home/maxbes/proj/doc/applications2.rst:53: WARNING: unknown document: applications/grr
/home/maxbes/proj/doc/applications2.rst:54: WARNING: unknown document: applications/liferay
/home/maxbes/proj/doc/applications2.rst:54: WARNING: unknown document: applications/liferay
/home/maxbes/proj/doc/applications2.rst:55: WARNING: unknown document: applications/limesurvey
/home/maxbes/proj/doc/applications2.rst:55: WARNING: unknown document: applications/limesurvey
/home/maxbes/proj/doc/applications2.rst:56: WARNING: unknown document: applications/mediawiki
/home/maxbes/proj/doc/applications2.rst:56: WARNING: unknown document: applications/mediawiki
/home/maxbes/proj/doc/applications2.rst:57: WARNING: unknown document: applications/nextcloud
/home/maxbes/proj/doc/applications2.rst:57: WARNING: unknown document: applications/nextcloud
/home/maxbes/proj/doc/applications2.rst:59: WARNING: unknown document: applications/obm
/home/maxbes/proj/doc/applications2.rst:59: WARNING: unknown document: applications/obm
/home/maxbes/proj/doc/applications2.rst:60: WARNING: unknown document: applications/office365
/home/maxbes/proj/doc/applications2.rst:60: WARNING: unknown document: applications/office365
/home/maxbes/proj/doc/applications2.rst:61: WARNING: unknown document: applications/phpldapadmin
/home/maxbes/proj/doc/applications2.rst:61: WARNING: unknown document: applications/phpldapadmin
/home/maxbes/proj/doc/applications2.rst:62: WARNING: unknown document: applications/roundcube
/home/maxbes/proj/doc/applications2.rst:62: WARNING: unknown document: applications/roundcube
/home/maxbes/proj/doc/applications2.rst:63: WARNING: unknown document: <applications/salesforce>
/home/maxbes/proj/doc/applications2.rst:63: WARNING: unknown document: applications/salesforce
/home/maxbes/proj/doc/applications2.rst:64: WARNING: unknown document: applications/sap
/home/maxbes/proj/doc/applications2.rst:64: WARNING: unknown document: applications/sap
/home/maxbes/proj/doc/applications2.rst:65: WARNING: unknown document: applications/simplesamlphp
/home/maxbes/proj/doc/applications2.rst:65: WARNING: unknown document: applications/simplesamlphp
/home/maxbes/proj/doc/applications2.rst:67: WARNING: unknown document: applications/spring
/home/maxbes/proj/doc/applications2.rst:67: WARNING: unknown document: applications/spring
/home/maxbes/proj/doc/applications2.rst:68: WARNING: unknown document: applications/symfony
/home/maxbes/proj/doc/applications2.rst:68: WARNING: unknown document: applications/symfony
/home/maxbes/proj/doc/applications2.rst:69: WARNING: unknown document: applications/sympa
/home/maxbes/proj/doc/applications2.rst:69: WARNING: unknown document: applications/sympa
/home/maxbes/proj/doc/applications2.rst:70: WARNING: unknown document: applications/tomcat
/home/maxbes/proj/doc/applications2.rst:70: WARNING: unknown document: applications/tomcat
/home/maxbes/proj/doc/applications2.rst:71: WARNING: unknown document: applications/wordpress
/home/maxbes/proj/doc/applications2.rst:71: WARNING: unknown document: applications/wordpress
/home/maxbes/proj/doc/applications2.rst:72: WARNING: unknown document: applications/xwiki
/home/maxbes/proj/doc/applications2.rst:72: WARNING: unknown document: applications/xwiki
/home/maxbes/proj/doc/applications2.rst:73: WARNING: unknown document: applications/zimbra
/home/maxbes/proj/doc/applications2.rst:73: WARNING: unknown document: applications/zimbra
/home/maxbes/proj/doc/authopenidconnect.rst:41: WARNING: unknown document: <authopenidconnect_google>
/home/maxbes/proj/doc/authopenidconnect.rst:41: WARNING: unknown document: <authopenidconnect_franceconnect>
/home/maxbes/proj/doc/authproxy.rst:47: WARNING: unknown document: /documentation/2.0/restservices
/home/maxbes/proj/doc/authssl.rst:24: WARNING: unknown document:
/home/maxbes/proj/doc/authssl.rst:268: WARNING: unknown document:
/home/maxbes/proj/doc/browseablesessionbackend.rst:7: WARNING: unknown document: /documentation/features
/home/maxbes/proj/doc/browseablesessionbackend.rst:7: WARNING: unknown document: /documentation/features
/home/maxbes/proj/doc/browseablesessionbackend.rst:35: WARNING: unknown document: session::browseable
/home/maxbes/proj/doc/browseablesessionbackend.rst:102: WARNING: unknown document: session::browseable
/home/maxbes/proj/doc/browseablesessionbackend.rst:128: WARNING: unknown document: /documentation/latest/sessions
/home/maxbes/proj/doc/browseablesessionbackend.rst:135: WARNING: unknown document: session::browseable
/home/maxbes/proj/doc/cli_examples.rst:99: WARNING: unknown document: ldap://ldap.example.com
/home/maxbes/proj/doc/configlocation.rst:27: WARNING: unknown document:
/home/maxbes/proj/doc/extendedfunctions.rst:165: WARNING: unknown document: applications/authbasic
/home/maxbes/proj/doc/extendedfunctions.rst:262: WARNING: unknown document: /documentation/2.0/servertoserver
/home/maxbes/proj/doc/formreplay.rst:18: WARNING: unknown document: applications
/home/maxbes/proj/doc/googleapps.rst:140: WARNING: unknown document:
/home/maxbes/proj/doc/handlerarch.rst:9: WARNING: unknown document: platform::main)//
/home/maxbes/proj/doc/handlerarch.rst:30: WARNING: unknown document: /documentation/latest/applications/zimbra
/home/maxbes/proj/doc/installdeb.rst:93: WARNING: unknown document: /download
/home/maxbes/proj/doc/installdeb.rst:162: WARNING: unknown document: /download
/home/maxbes/proj/doc/installrpm.rst:100: WARNING: unknown document: /download
/home/maxbes/proj/doc/installsles.rst:92: WARNING: unknown document: /download
/home/maxbes/proj/doc/installsles.rst:241: WARNING: unknown document: /download
/home/maxbes/proj/doc/installtarball.rst:7: WARNING: unknown document: /download
/home/maxbes/proj/doc/liferay.rst:85: WARNING: unknown document:
/home/maxbes/proj/doc/memcachedsessionbackend.rst:12: WARNING: unknown document: /documentation/features
/home/maxbes/proj/doc/memcachedsessionbackend.rst:13: WARNING: unknown document: /documentation/features
/home/maxbes/proj/doc/mongodbsessionbackend.rst:10: WARNING: unknown document: session::mongodb
/home/maxbes/proj/doc/parameterlist.rst:175: WARNING: unknown document: ldap::search
/home/maxbes/proj/doc/performances.rst:174: WARNING: unknown document: session::mysql
/home/maxbes/proj/doc/performances.rst:189: WARNING: unknown document: session::flex
/home/maxbes/proj/doc/performances.rst:209: WARNING: unknown document: session::mysql
/home/maxbes/proj/doc/performances.rst:214: WARNING: unknown document: session::browseable
/home/maxbes/proj/doc/performances.rst:231: WARNING: unknown document: session::browseable
/home/maxbes/proj/doc/performances.rst:241: WARNING: unknown document: session::memcached
/home/maxbes/proj/doc/performances.rst:253: WARNING: unknown document: session::browseable
/home/maxbes/proj/doc/platformsoverview.rst:9: WARNING: unknown document:
/home/maxbes/proj/doc/platformsoverview.rst:23: WARNING: unknown document:
/home/maxbes/proj/doc/platformsoverview.rst:24: WARNING: unknown document:
/home/maxbes/proj/doc/platformsoverview.rst:24: WARNING: unknown document:
/home/maxbes/proj/doc/platformsoverview.rst:55: WARNING: unknown document:
/home/maxbes/proj/doc/platformsoverview.rst:64: WARNING: unknown document:
/home/maxbes/proj/doc/portal.rst:91: WARNING: unknown document: /documentation/presentation
/home/maxbes/proj/doc/prereq.rst:52: WARNING: unknown document: http::headers
/home/maxbes/proj/doc/prereq.rst:53: WARNING: unknown document: http::request
/home/maxbes/proj/doc/restconfbackend.rst:19: WARNING: unknown document: /documentation/1.0/fileconfbackend
/home/maxbes/proj/doc/restconfbackend.rst:19: WARNING: unknown document: /documentation/1.0/sqlconfbackend
/home/maxbes/proj/doc/restconfbackend.rst:19: WARNING: unknown document: /documentation/1.0/ldapconfbackend
/home/maxbes/proj/doc/samlservice.rst:424: WARNING: unknown document: /documentation/features
/home/maxbes/proj/doc/selfmadeapplication.rst:19: WARNING: unknown document:
/home/maxbes/proj/doc/servertoserver.rst:11: WARNING: unknown document: /documentation/2.0/securetoken
/home/maxbes/proj/doc/sessions.rst:36: WARNING: unknown document: session::generate
/home/maxbes/proj/doc/soapconfbackend.rst:25: WARNING: unknown document: /documentation/1.0/fileconfbackend
/home/maxbes/proj/doc/soapconfbackend.rst:25: WARNING: unknown document: /documentation/1.0/sqlconfbackend
/home/maxbes/proj/doc/soapconfbackend.rst:25: WARNING: unknown document: /documentation/1.0/ldapconfbackend
/home/maxbes/proj/doc/sqlsessionbackend.rst:49: WARNING: unknown document: /documentation/latest/sessions
/home/maxbes/proj/doc/sqlsessionbackend.rst:106: WARNING: unknown document: /documentation/latest/sessions
/home/maxbes/proj/doc/ssocookie.rst:7: WARNING: unknown document: /documentation/presentation
/home/maxbes/proj/doc/ssocookie.rst:7: WARNING: unknown document: /documentation/presentation
/home/maxbes/proj/doc/start.rst:7: WARNING: unknown document: /documentation/presentation
/home/maxbes/proj/doc/start.rst:8: WARNING: unknown document: /documentation/features
/home/maxbes/proj/doc/start.rst:9: WARNING: unknown document: /documentation/quickstart
/home/maxbes/proj/doc/start.rst:221: WARNING: unknown document: applications/zimbra
/home/maxbes/proj/doc/start.rst:278: WARNING: unknown document: /documentation/features
/home/maxbes/proj/doc/start.rst:278: WARNING: unknown document: /documentation/features
/home/maxbes/proj/doc/start.rst:281: WARNING: unknown document: /documentation/features
/home/maxbes/proj/doc/start.rst:281: WARNING: unknown document: /documentation/features
/home/maxbes/proj/doc/start.rst:285: WARNING: unknown document: /documentation/features
/home/maxbes/proj/doc/start.rst:285: WARNING: unknown document: /documentation/features
/home/maxbes/proj/doc/start.rst:296: WARNING: unknown document: writingrulesand headers
/home/maxbes/proj/doc/start.rst:298: WARNING: unknown document: applications
/home/maxbes/proj/doc/start.rst:309: WARNING: unknown document: applications
/home/maxbes/proj/doc/start.rst:313: WARNING: unknown document: applications/adfs
/home/maxbes/proj/doc/start.rst:315: WARNING: unknown document: applications/alfresco
/home/maxbes/proj/doc/start.rst:317: WARNING: unknown document: applications/bugzilla
/home/maxbes/proj/doc/start.rst:319: WARNING: unknown document: applications/dokuwiki
/home/maxbes/proj/doc/start.rst:321: WARNING: unknown document: applications/drupal
/home/maxbes/proj/doc/start.rst:323: WARNING: unknown document: applications/fusiondirectory
/home/maxbes/proj/doc/start.rst:325: WARNING: unknown document: applications/gitlab
/home/maxbes/proj/doc/start.rst:327: WARNING: unknown document: applications/glpi
/home/maxbes/proj/doc/start.rst:329: WARNING: unknown document: applications/liferay
/home/maxbes/proj/doc/start.rst:331: WARNING: unknown document: applications/mediawiki
/home/maxbes/proj/doc/start.rst:333: WARNING: unknown document: applications/nextcloud
/home/maxbes/proj/doc/start.rst:335: WARNING: unknown document: applications/simplesamlphp
/home/maxbes/proj/doc/start.rst:337: WARNING: unknown document: applications/wordpress
/home/maxbes/proj/doc/start.rst:339: WARNING: unknown document: applications/xwiki
/home/maxbes/proj/doc/start.rst:341: WARNING: unknown document: applications/zimbra
/home/maxbes/proj/doc/start.rst:407: WARNING: unknown document: /bugreport
/home/maxbes/proj/doc/tomcat.rst:37: WARNING: unknown document: /download
/home/maxbes/proj/doc/tomcat.rst:53: WARNING: unknown document:
/home/maxbes/proj/doc/tomcat.rst:92: WARNING: unknown document: /download
/home/maxbes/proj/doc/u2f.rst:63: WARNING: unknown document: about:config
/home/maxbes/proj/doc/upgrade.rst:139: WARNING: unknown document: /documentation/2.0/behindproxyminihowto
/home/maxbes/proj/doc/upgrade.rst:170: WARNING: unknown document: /documentation/2.0/cda
/home/maxbes/proj/doc/upgrade.rst:170: WARNING: unknown document: /documentation/latest/applications/zimbra
/home/maxbes/proj/doc/upgrade.rst:170: WARNING: unknown document: /documentation/2.0/handlerarch
/home/maxbes/proj/doc/upgrade.rst:177: WARNING: unknown document: /documentation/2.0/ssocookie
/home/maxbes/proj/doc/variables.rst:64: WARNING: unknown document: /documentation/2.0/behindproxyminihowto

View File

@ -26,4 +26,11 @@ it in [configuration] section in your lemonldap-ng.ini file:
[configuration]
type = File
dirName = /var/lib/lemonldap-ng/conf
prettyPrint = 1
Parameters
----------
* `dirName`: directory under which the configuration files will be stored. It must be writable by your webserver account
* `prettyPrint`: store files in a readable. Set it to 0 to get a small performance increase when loading/saving configuration

View File

@ -17,7 +17,7 @@ Configuration
-------------
This feature can be enabled and configured in Manager, in
``General Parameters`` » ``Advanced Parameters`` » ``Login History``.
``General Parameters`` » ``Plugins`` » ``Login History``.
You can define how many logins and failed logins will be stored.
A login is considered as successful if user get authenticated and is

View File

@ -78,19 +78,37 @@ Logout:
[notice] User clement.oudot has been disconnected from LDAP (81.20.13.21)
Access to an SAML SP:
Access to a CAS application non registered in configuration (when CAS server is open):
::
[notice] User clement.oudot is authorized to access to sp-example-entityid
[notice] SAML authentication response sent to SAML SP sp-example for clement.oudot
[notice] User clement.oudot is redirected to https://cas.service.url
Access to an OIDC RP:
Access to a CAS application whose configuration key is ``app-example``:
::
[notice] User clement.oudot is authorized to access to app-example
Access to an SAML SP whose configuration key is ``sp-example``:
::
[notice] User clement.oudot is authorized to access to sp-example
Access to an OIDC RP whose configuration key is ``rp-example``:
::
[notice] User clement.oudot is authorized to access to rp-example
Access to a Get application whose vhost configuration key is ``host.example.com``:
::
[notice] User clement.oudot is authorized to access to host.example.com
Default loggers
---------------
@ -152,6 +170,18 @@ You can choose facility in lemonldap-ng.ini file. Default values:
syslogFacility = daemon
userSyslogFacility = auth
You can also override options. Default values:
.. code-block:: ini
syslogOptions = cons,pid,ndelay
userSyslogOptions = cons,pid,ndelay
.. tip:: You can find more information on Syslog options in
`Sys::Syslog <https://metacpan.org/pod/Sys::Syslog>`__ Perl
module.
Log4perl
~~~~~~~~

View File

@ -1,82 +0,0 @@
{# Support for Sphinx 1.3+ page_source_suffix, but don't break old builds. #}
{% if page_source_suffix %}
{% set suffix = page_source_suffix %}
{% else %}
{% set suffix = source_suffix %}
{% endif %}
{% if meta is defined and meta is not none %}
{% set check_meta = True %}
{% else %}
{% set check_meta = False %}
{% endif %}
{% if check_meta and 'github_url' in meta %}
{% set display_github = True %}
{% endif %}
{% if check_meta and 'bitbucket_url' in meta %}
{% set display_bitbucket = True %}
{% endif %}
{% if check_meta and 'gitlab_url' in meta %}
{% set display_gitlab = True %}
{% endif %}
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
{% block breadcrumbs %}
<li><a href="{{ pathto(master_doc) }}">{{ _('Docs') }}</a> &raquo;</li>
{% for doc in parents %}
<li><a href="{{ doc.link|e }}">{{ doc.title }}</a> &raquo;</li>
{% endfor %}
<li>{{ title }}</li>
{% endblock %}
{% block breadcrumbs_aside %}
<li class="wy-breadcrumbs-aside">
{% if hasdoc(pagename) %}
{% if display_github %}
{% if check_meta and 'github_url' in meta %}
<!-- User defined GitHub URL -->
<a href="{{ meta['github_url'] }}" class="fa fa-github"> {{ _('Edit on GitHub') }}</a>
{% else %}
<a href="https://{{ github_host|default("github.com") }}/{{ github_user }}/{{ github_repo }}/{{ theme_vcs_pageview_mode|default("blob") }}/{{ github_version }}{{ conf_py_path }}{{ pagename }}{{ suffix }}" class="fa fa-github"> {{ _('Edit on GitHub') }}</a>
{% endif %}
{% elif display_bitbucket %}
{% if check_meta and 'bitbucket_url' in meta %}
<!-- User defined Bitbucket URL -->
<a href="{{ meta['bitbucket_url'] }}" class="fa fa-bitbucket"> {{ _('Edit on Bitbucket') }}</a>
{% else %}
<a href="https://bitbucket.org/{{ bitbucket_user }}/{{ bitbucket_repo }}/src/{{ bitbucket_version}}{{ conf_py_path }}{{ pagename }}{{ suffix }}?mode={{ theme_vcs_pageview_mode|default("view") }}" class="fa fa-bitbucket"> {{ _('Edit on Bitbucket') }}</a>
{% endif %}
{% elif display_gitlab %}
{% if check_meta and 'gitlab_url' in meta %}
<!-- User defined GitLab URL -->
<a href="{{ meta['gitlab_url'] }}" class="fa fa-gitlab"> {{ _('Edit on GitLab') }}</a>
{% else %}
<a href="https://{{ gitlab_host|default("gitlab.com") }}/{{ gitlab_user }}/{{ gitlab_repo }}/{{ theme_vcs_pageview_mode|default("blob") }}/{{ gitlab_version }}{{ conf_py_path }}{{ pagename }}{{ suffix }}" class="fa fa-gitlab"> {{ _('Edit on GitLab') }}</a>
{% endif %}
{% elif show_source and source_url_prefix %}
<a href="{{ source_url_prefix }}{{ pagename }}{{ suffix }}">{{ _('View page source') }}</a>
{% elif show_source and has_source and sourcename %}
<a href="{{ pathto('_sources/' + sourcename, true)|e }}" rel="nofollow"> {{ _('View page source') }}</a>
{% endif %}
{% endif %}
</li>
{% endblock %}
</ul>
{% if (theme_prev_next_buttons_location == 'top' or theme_prev_next_buttons_location == 'both') and (next or prev) %}
<div class="rst-breadcrumbs-buttons" role="navigation" aria-label="breadcrumb navigation">
{% if next %}
<a href="{{ next.link|e }}" class="btn btn-neutral float-right" title="{{ next.title|striptags|e }}" accesskey="n">Next <span class="fa fa-arrow-circle-right"></span></a>
{% endif %}
{% if prev %}
<a href="{{ prev.link|e }}" class="btn btn-neutral float-left" title="{{ prev.title|striptags|e }}" accesskey="p"><span class="fa fa-arrow-circle-left"></span> Previous</a>
{% endif %}
</div>
{% endif %}
<hr/>
</div>

View File

@ -1,54 +0,0 @@
<footer>
{% if (theme_prev_next_buttons_location == 'bottom' or theme_prev_next_buttons_location == 'both') and (next or prev) %}
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
{% if next %}
<a href="{{ next.link|e }}" class="btn btn-neutral float-right" title="{{ next.title|striptags|e }}" accesskey="n" rel="next">{{ _('Next') }} <span class="fa fa-arrow-circle-right"></span></a>
{% endif %}
{% if prev %}
<a href="{{ prev.link|e }}" class="btn btn-neutral float-left" title="{{ prev.title|striptags|e }}" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left"></span> {{ _('Previous') }}</a>
{% endif %}
</div>
{% endif %}
<hr/>
<div role="contentinfo">
<p>
{%- if show_copyright %}
{%- if hasdoc('copyright') %}
{% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}{% endtrans %}
{%- else %}
{% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}{% endtrans %}
{%- endif %}
{%- endif %}
{%- if build_id and build_url %}
{% trans build_url=build_url, build_id=build_id %}
<span class="build">
Build
<a href="{{ build_url }}">{{ build_id }}</a>.
</span>
{% endtrans %}
{%- elif commit %}
{% trans commit=commit %}
<span class="commit">
Revision <code>{{ commit }}</code>.
</span>
{% endtrans %}
{%- elif last_updated %}
<span class="lastupdated">
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
</span>
{%- endif %}
</p>
</div>
{%- if show_sphinx %}
{% trans %}Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/rtfd/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>{% endtrans %}.
{%- endif %}
{%- block extrafooter %} {% endblock %}
</footer>

View File

@ -1,113 +0,0 @@
{# TEMPLATE VAR SETTINGS #}
{%- set url_root = pathto('', 1) %}
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
{%- if not embedded and docstitle %}
{%- set titlesuffix = " &mdash; "|safe + docstitle|e %}
{%- else %}
{%- set titlesuffix = "" %}
{%- endif %}
{%- set lang_attr = 'en' if language == None else (language | replace('_', '-')) %}
<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="{{ lang_attr }}" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="{{ lang_attr }}" > <!--<![endif]-->
<head>
<meta charset="utf-8">
{{ metatags }}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% block htmltitle %}
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
{% endblock %}
{# JAVASCRIPTS #}
<script type="text/javascript" src="{{ pathto('_static/js/modernizr.min.js', 1) }}"></script>
{%- if not embedded %}
{# XXX Sphinx 1.8.0 made this an external js-file, quick fix until we refactor the template to inherert more blocks directly from sphinx #}
{% if sphinx_version >= "1.8.0" %}
<script type="text/javascript" id="documentation_options" data-url_root="{{ pathto('', 1) }}" src="{{ pathto('_static/documentation_options.js', 1) }}"></script>
{%- for scriptfile in script_files %}
{{ js_tag(scriptfile) }}
{%- endfor %}
{% else %}
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT:'{{ url_root }}',
VERSION:'{{ release|e }}',
LANGUAGE:'{{ language }}',
COLLAPSE_INDEX:false,
FILE_SUFFIX:'{{ '' if no_search_suffix else file_suffix }}',
HAS_SOURCE: {{ has_source|lower }},
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'
};
</script>
{%- for scriptfile in script_files %}
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
{%- endfor %}
{% endif %}
<script type="text/javascript" src="{{ pathto('_static/js/theme.js', 1) }}"></script>
{%- endif %}
{# CSS #}
<link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
<link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
{%- for css in css_files %}
{%- if css|attr("rel") %}
<link rel="{{ css.rel }}" href="{{ pathto(css.filename, 1) }}" type="text/css"{% if css.title is not none %} title="{{ css.title }}"{% endif %} />
{%- else %}
<link rel="stylesheet" href="{{ pathto(css, 1) }}" type="text/css" />
{%- endif %}
{%- endfor %}
{%- for cssfile in extra_css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{%- endfor %}
{%- block linktags %}
{%- if hasdoc('about') %}
<link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
{%- endif %}
{%- if hasdoc('genindex') %}
<link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
{%- endif %}
{%- if hasdoc('search') %}
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
{%- endif %}
{%- if hasdoc('copyright') %}
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
{%- endif %}
{%- if next %}
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
{%- endif %}
{%- if prev %}
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
{%- endif %}
{%- endblock %}
{%- block extrahead %} {% endblock %}
</head>
<body class="wy-body-for-nav">
<div class="wy-nav-content">
{%- block content %}
<div class="rst-content">
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
{%- block document %}
<div itemprop="articleBody">
{% block body %}{% endblock %}
</div>
{% if self.comments()|trim %}
<div class="articleComments">
{% block comments %}{% endblock %}
</div>
{% endif%}
</div>
{%- endblock %}
{% include "footer.html" %}
</div>
{%- endblock %}
</div>
</body>
</html>

View File

@ -1,50 +0,0 @@
{#
basic/search.html
~~~~~~~~~~~~~~~~~
Template for the search page.
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
#}
{%- extends "layout.html" %}
{% set title = _('Search') %}
{% set script_files = script_files + ['_static/searchtools.js'] %}
{% block footer %}
<script type="text/javascript">
jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
</script>
{# this is used when loading the search index using $.ajax fails,
such as on Chrome for documents on localhost #}
<script type="text/javascript" id="searchindexloader"></script>
{{ super() }}
{% endblock %}
{% block body %}
<noscript>
<div id="fallback" class="admonition warning">
<p class="last">
{% trans %}Please activate JavaScript to enable the search
functionality.{% endtrans %}
</p>
</div>
</noscript>
{% if search_performed %}
<h2>{{ _('Search Results') }}</h2>
{% if not search_results %}
<p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p>
{% endif %}
{% endif %}
<div id="search-results">
{% if search_results %}
<ul>
{% for href, caption, context in search_results %}
<li>
<a href="{{ pathto(item.href) }}">{{ caption }}</a>
<p class="context">{{ context|e }}</p>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endblock %}

View File

@ -1,9 +0,0 @@
{%- if builder != 'singlehtml' %}
<div role="search">
<form id="rtd-search-form" class="wy-form" action="{{ pathto('search') }}" method="get">
<input type="text" name="q" placeholder="{{ _('Search docs') }}" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
{%- endif %}

View File

@ -1,2 +0,0 @@
[theme]
inherit = sphinx_rtd_theme

View File

@ -1,37 +0,0 @@
{% if READTHEDOCS %}
{# Add rst-badge after rst-versions for small badge style. #}
<div class="rst-versions" data-toggle="rst-versions" role="note" aria-label="versions">
<span class="rst-current-version" data-toggle="rst-current-version">
<span class="fa fa-book"> Read the Docs</span>
v: {{ current_version }}
<span class="fa fa-caret-down"></span>
</span>
<div class="rst-other-versions">
<dl>
<dt>{{ _('Versions') }}</dt>
{% for slug, url in versions %}
<dd><a href="{{ url }}">{{ slug }}</a></dd>
{% endfor %}
</dl>
<dl>
<dt>{{ _('Downloads') }}</dt>
{% for type, url in downloads %}
<dd><a href="{{ url }}">{{ type }}</a></dd>
{% endfor %}
</dl>
<dl>
<dt>{{ _('On Read the Docs') }}</dt>
<dd>
<a href="//{{ PRODUCTION_DOMAIN }}/projects/{{ slug }}/?fromdocs={{ slug }}">{{ _('Project Home') }}</a>
</dd>
<dd>
<a href="//{{ PRODUCTION_DOMAIN }}/builds/{{ slug }}/?fromdocs={{ slug }}">{{ _('Builds') }}</a>
</dd>
</dl>
<hr/>
{% trans %}Free document hosting provided by <a href="http://www.readthedocs.org">Read the Docs</a>.{% endtrans %}
</div>
</div>
{% endif %}

View File

@ -15,23 +15,23 @@ Perl module.
In the manager: set
`Apache::Session::Browseable::Redis <https://metacpan.org/pod/Apache::Session::Browseable::Redis>`__
in ``General parameters`` » ``Sessions`` » ``Session storage`` »
``Apache::Session module`` and add the following parameters (case
sensitive):
``Apache::Session module`` and add the connection parameters for your Redis server(s).
Parameters:
This backend uses the perl bindings for Redis database provided by the `Redis perl module <https://metacpan.org/pod/Redis>`__.
A complete list of supported constructor/connection options can be found in the `module documentation <https://metacpan.org/pod/Redis>`__.
============= ==================== ===============================================
Name Comment Example
============= ==================== ===============================================
**server** Redis server 127.0.0.1:6379
**sentinels** Redis sentinels list 127.0.0.1:26379,127.0.0.2:26379,127.0.0.3:26379
============= ==================== ===============================================
E.g., Parameters (case sensitive):
You can specify either a single Redis server or a list of Sentinel hosts
using the \*sentinels\* module parameter
============= =========================== ===============================================
Name Comment Example
============= =========================== ===============================================
**server** Redis server @ IP:PORT 127.0.0.1:6379
**sock** Redis server @ unix socket unix:/path/to/redis.sock
**sentinels** Redis sentinels list 127.0.0.1:26379,127.0.0.2:26379,127.0.0.3:26379
**password** password (== requirepass) ChangeMe
**select** Redis DB 1
============= =========================== ===============================================
You can find the complete list of supported options on the `Redis perl module
documentation <https://metacpan.org/pod/Redis#new>`__.
Security
--------

View File

@ -620,12 +620,11 @@ Configuration backend parameters
Full name Key name Configuration backend
============================================================================= ==================== ===========================================================
Configuration load timeout confTimeout all backends (default: 10)
Directory dirName :doc:`File<fileconfbackend>`
DBI connection string dbiChain :doc:`CDBI / RDBI<sqlconfbackend>`
DBI user dbiUser
DBI password dbiPassword
DBI table name dbiTable
Storage directory dirName :doc:`File<fileconfbackend>` / :doc:`YAML<yamlconfbackend>`
Directory dirName :doc:`File<fileconfbackend>` / :doc:`YAML<yamlconfbackend>`
LDAP server ldapServer :doc:`LDAP<ldapconfbackend>`
LDAP port ldapPort
LDAP base ldapConfBase
@ -638,6 +637,7 @@ Certificate authorities file ca
Certificate authorities directory caPath
MongoDB database dbName :doc:`MongoDB<mongodbconfbackend>`
MongoDB collection collectionName
Pretty print prettyPrint :doc:`File<fileconfbackend>`
REST base URL baseUrl :doc:`REST<restconfbackend>`
REST realm realm
REST user user

View File

@ -128,7 +128,7 @@ Then adapt your Nginx configuration to use this uWSGI app.
Configuration
^^^^^^^^^^^^^
To serve large requests with uWsgi, you could have to modify in uWsgi
To serve large requests with uWSGI, you could have to modify in uWSGI
and/or Nginx init files several options. Example:
.. code-block:: ini
@ -146,6 +146,31 @@ and/or Nginx init files several options. Example:
uwsgi_read_timeout 120;
uwsgi_send_timeout 120;
.. note::
Nginx natively includes support for upstream servers speaking the uwsgi protocol since version 0.8.40.
To improve performances, you can switch from a TCP socket to an Unix Domain Socket by editing
``llng-server.yaml``:
.. code-block:: ini
uwsgi:
plugins: psgi
socket: /tmp/uwsgi.sock
and adapting Nignx configuration files:
.. code-block:: nginx
# OR TO USE uWSGI
include /etc/nginx/uwsgi_params;
uwsgi_pass unix:///tmp/uwsgi.sock;
uwsgi_param LLTYPE psgi;
uwsgi_param SCRIPT_FILENAME $document_root$sc;
uwsgi_param SCRIPT_NAME $sc;
# Uncomment this if you use Auth SSL:
#uwsgi_param SSL_CLIENT_S_DN_CN $ssl_client_s_dn_cn;
Protect a PSGI application
--------------------------

View File

@ -7,22 +7,15 @@ Quick start tutorial
This tutorial will guide you into a minimal
installation and configuration procedure. You need some prerequisites:
- A computer with a GNU/Linux recent distribution (Debian, Ubuntu,
CentOS, RHEL, ...) with root privileges
- A web browser
- The possibility to update your local ``hosts`` file, or an easy
access to your DNS server
- A cup of coffee (or tea, we are open minded)
- Root access to a Debian, Ubuntu, CentOS or RHEL test system
- A web browser
- A cup of coffee (or tea, we are open minded)
Installation
------------
You should install Lemonldap::NG using packages, but you can also
install it from
:doc:`the tarball<installtarball>`.
Debian / Ubuntu
~~~~~~~~~~~~~~~
@ -40,27 +33,38 @@ CentOS / RHEL
::
curl https://lemonldap-ng.org/_media/rpm-gpg-key-ow2 > /etc/pki/rpm-gpg/RPM-GPG-KEY-OW2
echo "[lemonldap-ng]
echo '[lemonldap-ng]
name=LemonLDAP::NG packages
baseurl=https://lemonldap-ng.org/redhat/stable/$releasever/noarch
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-OW2" > /etc/yum.repos.d/lemonldap-ng.repo
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-OW2' > /etc/yum.repos.d/lemonldap-ng.repo
yum update
yum install epel-release
yum install lemonldap-ng
SSO domain configuration
------------------------
LemonLDAP::NG needs all its components to be served on the same DNS domain.
If you can edit your `/etc/hosts` file or have access to a DNS server, check :ref:`quickstart_own_domain`, if you have no way to modify your DNS configuration, check :ref:`quickstart_nipio`.
.. _quickstart_own_domain:
Using your own domain
~~~~~~~~~~~~~~~~~~~~~
The defaut SSO domain is ``example.com``. You can keep it for your tests
or change it, for example for ``mydomain.com``:
::
sed -i 's/example\.com/mydomain.com/g' /etc/lemonldap-ng/* /var/lib/lemonldap-ng/conf/lmConf-1.json
sed -i 's/example\.com/mydomain.com/g' /etc/nginx/conf.d/*
sed -i 's/example\.com/mydomain.com/g' /etc/httpd/conf.d/*
sed -i 's/example\.com/mydomain.com/g' /etc/apache2/sites-available/*
sed -i 's/example\.com/mydomain.com/g' \
/etc/lemonldap-ng/* /var/lib/lemonldap-ng/conf/lmConf-1.json \
/etc/nginx/conf.d/* \
/etc/httpd/conf.d/* \
/etc/apache2/sites-available/*
In order to be able to test, update your DNS or your local ``hosts``
file to map this names to the SSO server IP:
@ -70,20 +74,63 @@ file to map this names to the SSO server IP:
- test1.mydomain.com
- test2.mydomain.com
For example on your local computer:
For example on your local computer (adjust your server IP and test domain)
::
echo "192.168.1.30 auth.mydomain.com manager.mydomain.com test1.mydomain.com test2.mydomain.com" >> /etc/hosts
.. _quickstart_nipio:
Using nip.io (or other DNS wildcard services)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you cannot edit /etc/hosts or your DNS zone, don't give up yet, you can use services such as http://nip.io, http://xip.io, https://sslip.io/, or others.
For example, if your server IP is 192.168.12.13, you can use 192-168-12-13.nip.io as your SSO domain:
::
sed -i 's/example\.com/192-168-12-13.nip.io/g' \
/etc/lemonldap-ng/* /var/lib/lemonldap-ng/conf/lmConf-1.json \
/etc/nginx/conf.d/* \
/etc/httpd/conf.d/* \
/etc/apache2/sites-available/*
.. warning::
nip.io, xip.io or any DNS wildcard services mentionned in this section are not affiliated with the LemonLDAP::NG project in any way. These services will receive DNS requests that will allow them to know your test server's IP address. If this is an issue for you, do not use these services.
Run
---
Since LemonLDAP::NG 1.2, the
:doc:`demonstration backend<authdemo>` is
configured by default.
Starting services
~~~~~~~~~~~~~~~~~
Demonstration backend has hard coded user accounts:
Debian / Ubuntu
'''''''''''''''
Enable the Nginx virtualhosts and restart the web server and LemonLDAP::NG server to apply the configuration changes ::
cd /etc/nginx/sites-enabled
ln -s ../sites-available/*nginx* .
systemctl restart lemonldap-ng-fastcgi-server
systemctl restart nginx
CentOS / RHEL
'''''''''''''
Enable and start httpd ::
systemctl enable httpd
systemctl start httpd
Open SSO session
~~~~~~~~~~~~~~~~
Go on http://auth.mydomain.com and log with one of the demonstration
account.
====== ======== =============
Login Password Role
@ -93,12 +140,6 @@ msmith msmith user
dwho dwho administrator
====== ======== =============
Open SSO session
~~~~~~~~~~~~~~~~
Go on http://auth.mydomain.com and log with one of the demonstration
account.
Access protected application
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -9,13 +9,13 @@ Disk cache (sessions an configuration)
::
chcon -R -t httpd_sys_rw_content_t /tmp
chcon -R -t httpd_sys_rw_content_t /var/cache/lemonldap-ng
To persist the rule:
::
semanage fcontext -a -t http_sys_content_t /tmp
semanage fcontext -a -t http_sys_content_t /var/cache/lemonldap-ng
LDAP
----

View File

@ -29,13 +29,17 @@ To edit SSO cookie parameters, go in Manager, ``General Parameters`` >
only one session is created in session database
- **Javascript protection**: set httpOnly flag, to prevent cookie from
being caught by javascript code
being leaked by malicious javascript code
- **Cookie expiration time**: by default, SSO cookie is a session
cookie, which means it will be destroyed when browser is closed. You
can change this behavior by setting a cookie expiration time. It must
be an integer. **Cookie Expiration Time** value is a number of
seconds until the cookie expires. Set a zero value to disable
expiration time and use a session cookie.
- **Cookie SameSite value**: the value of the SameSite cookie attribute. By
default, LemonLDAP::NG will set it to "Lax" in most cases, and "None" if you
use SAML. Using "None" requres Secured Cookies, and accessing applications
over HTTPS on most web browsers.
.. danger::

View File

@ -22,6 +22,20 @@ backups and a rollback plan ready!
- | Bad default value to display OIDC Consents tab has been fixed.
| The default value is ``$_oidcConsents``
- Some user log messages have been modified, check :doc:`logs documentation <logs>`
(see also `#2244 <https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/issues/2244>`__)
- SAML SOAP calls are now using ``text/xml`` instead of ``application/xml`` as the MIME Content Type, as required by `the SOAP standard <https://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383526>`__
- The default config/session cache directory has been moved from ``/tmp`` to
``/var/cache/lemonldap-ng`` in order to avoid `issues with cache purges
<https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/issues/2254>`__ when
using Systemd. This change is only applied to new installations. If your
installation is experiencing cache purge issues, you
need to manually change your existing
``localSessionStorageOptions/cache_root`` parameter from ``/tmp`` to
``/var/cache/lemonldap-ng``.
- This release fixes several issues when using ``SameSite=None``. The new
default value of the SameSite configuration parameter will set SameSite to
``Lax`` unless you are using SAML, which requires ``None``
2.0.8
-----

View File

@ -36,6 +36,8 @@ In the manager (second factors), you just have to enable it:
what you are doing
- **Allow users to remove Yubikey**: If enabled, users can unregister
Yubikey device.
- **Get Yubikey ID from session attribute**: If non-empty, the Yubikey ID will
be read from this session attribute. This allows external provisionning of Yubikeys.
- **Lifetime**: Unlimited by default. Set a Time To Live in seconds.
TTL is checked at each login process if set. If TTL is expired,
relative Yubikey is removed.

File diff suppressed because it is too large Load Diff

View File

@ -66,6 +66,7 @@ lib/Lemonldap/NG/Common/Session.pm
lib/Lemonldap/NG/Common/Session/REST.pm
lib/Lemonldap/NG/Common/TOTP.pm
lib/Lemonldap/NG/Common/UserAgent.pm
lib/Lemonldap/NG/Common/Util.pm
Makefile.PL
MANIFEST This list of files
META.json

View File

@ -69,7 +69,9 @@ logLevel = warn
; For Syslog logging, you can also overwrite facilities. Default values:
;logger = Lemonldap::NG::Common::Logger::Syslog
;syslogFacility = daemon
;syslogOptions = cons,pid,ndelay
;userSyslogFacility = auth
;userSyslogOptions = cons,pid,ndelay
;
; 2.2 - Using Log4perl
;
@ -109,6 +111,8 @@ checkTime = 1
;
; type = File ; or type = YAMLFile
; dirName = /var/lib/lemonldap-ng/conf
; ; Optimize JSON for readability instead of performance
; prettyPrint = 1
;
; * RDBI/CDBI : you have to set 'dbiChain' (required) and 'dbiUser' and 'dbiPassword'
; if needed. Example:
@ -154,6 +158,8 @@ checkTime = 1
type=File
dirName=/var/lib/lemonldap-ng/conf
; Optimize for readability instead of performance
prettyPrint = 1
; LOCAL CACHE CONFIGURATION
;
@ -165,7 +171,7 @@ dirName=/var/lib/lemonldap-ng/conf
; 'namespace' => 'lemonldap-ng-config',\
; 'default_expires_in' => 600, \
; 'directory_umask' => '007', \
; 'cache_root' => '/tmp', \
; 'cache_root' => '__CACHEDIR__', \
; 'cache_depth' => 3, \
; }
localStorage=Cache::FileCache
@ -173,7 +179,7 @@ localStorageOptions={ \
'namespace' => 'lemonldap-ng-config',\
'default_expires_in' => 600, \
'directory_umask' => '007', \
'cache_root' => '/tmp', \
'cache_root' => '__CACHEDIR__', \
'cache_depth' => 3, \
}
@ -323,7 +329,7 @@ languages = en, fr, vi, it, ar, de, fi, tr, pl
; 'namespace' => 'lemonldap-ng-sessions', \
; 'default_expires_in' => 600, \
; 'directory_umask' => '007', \
; 'cache_root' => '/tmp', \
; 'cache_root' => '__CACHEDIR__', \
; 'cache_depth' => 3, \
; }

View File

@ -3,12 +3,11 @@ package Lemonldap::NG::Common::CliSessions;
use strict;
use Mouse;
use JSON;
use Digest::MD5 qw(md5_hex);
use MIME::Base64;
use Lemonldap::NG::Common::Conf;
use Lemonldap::NG::Common::Logger::Std;
use Lemonldap::NG::Common::Apache::Session;
use Lemonldap::NG::Common::Session;
use Lemonldap::NG::Common::Util qw/getPSessionID genId2F/;
our $VERSION = '2.0.8';
@ -118,7 +117,7 @@ sub _get_one_session {
# Handle --persistent
elsif ( $self->opts->{persistent} ) {
$backendStorage = "persistentStorage";
$id = $self->_md5hash($id);
$id = getPSessionID($id);
}
# In any case, fall back to global storage if we couldn't find the backend
@ -195,23 +194,9 @@ sub get {
return 0;
}
# Return md5(s)
# TODO factor with portal function
sub _md5hash {
my ( $self, $s ) = @_;
return substr( Digest::MD5::md5_hex($s), 0, 32 );
}
# TODO factor with manager API function
sub _genId2F {
my ( $self, $device ) = @_;
return encode_base64( "$device->{epoch}::$device->{type}::$device->{name}",
"" );
}
sub _get_psession {
my ( $self, $uid ) = @_;
my $psession_id = $self->_md5hash($uid);
my $psession_id = getPSessionID($uid);
my $res = $self->_get_one_session( $psession_id, 'persistent' );
die "Could not get psession for user $uid" unless $res;
return $res;
@ -287,7 +272,7 @@ sub secondfactors_get {
my $target = shift;
my $o = $self->stdout;
my $consents = $self->_get_psession_special( $target, '_2fDevices',
sub { $self->_genId2F( $_[0] ) } );
sub { genId2F( $_[0] ) } );
print $o $self->_to_json($consents);
return 0;
}
@ -308,7 +293,7 @@ sub secondfactors_delete {
my @ids = @_;
return unless @ids;
$self->_del_psession_special( $target, '_2fDevices',
sub { $self->_genId2F( $_[0] ) }, @ids );
sub { genId2F( $_[0] ) }, @ids );
return 0;
}

View File

@ -87,7 +87,18 @@ sub store {
return UNKNOWN_ERROR;
}
binmode(FILE);
my $f = to_json( $fields, { allow_nonref => 1 } );
my $json_options = {
allow_nonref => 1,
(
$self->{prettyPrint}
? (
pretty => 1,
canonical => 1
)
: ()
)
};
my $f = to_json( $fields, $json_options );
print FILE $f;
close FILE;
umask($mask);

View File

@ -23,6 +23,7 @@ use constant HANDLERSECTION => "handler";
use constant MANAGERSECTION => "manager";
use constant SESSIONSEXPLORERSECTION => "sessionsExplorer";
use constant APPLYSECTION => "apply";
our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|macro)s|o(?:idc(?:S(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar|Macro)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option|Macro)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|c(?:as(?:A(?:ppMetaData(?:(?:ExportedVar|Option|Macro)s|Node)|ttributes)|S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions))|(?:ustom(?:Plugins|Add)Param|ombModule)s)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/;
our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|kipRenewConfirmation|fRemovedUseNotif|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:Allow(?:PasswordGrant|Offline)|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|o(?:ntextSwitchingStopWithLogout|mpactConf|rsEnabled)|heck(?:State|User|XSS)|da)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|re(?:st(?:(?:Password|Session|Config|Auth)Server|ExportSecretKeys)|freshSessions)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|g(?:roupsBeforeMacros|lobalLogoutTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs))$/;

View File

@ -19,7 +19,7 @@ sub defaultValues {
'authentication' => 'Demo',
'available2F' => 'UTOTP,TOTP,U2F,REST,Mail2F,Ext2F,Yubikey,Radius',
'available2FSelfRegistration' => 'TOTP,U2F,Yubikey',
'bruteForceProtectionLockTimes' => '5 15 60 300 600',
'bruteForceProtectionLockTimes' => '5, 15, 60, 300, 600',
'bruteForceProtectionMaxAge' => 300,
'bruteForceProtectionMaxFailed' => 3,
'bruteForceProtectionMaxLockTime' => 900,
@ -140,7 +140,6 @@ sub defaultValues {
'ldapGroupObjectClass' => 'groupOfNames',
'ldapPasswordResetAttribute' => 'pwdReset',
'ldapPasswordResetAttributeValue' => 'TRUE',
'ldapPort' => 389,
'ldapPwdEnc' => 'utf-8',
'ldapSearchDeref' => 'find',
'ldapServer' => 'ldap://localhost',
@ -153,7 +152,7 @@ sub defaultValues {
'localSessionStorage' => 'Cache::FileCache',
'localSessionStorageOptions' => {
'cache_depth' => 3,
'cache_root' => '/tmp',
'cache_root' => '/var/cache/lemonldap-ng',
'default_expires_in' => 600,
'directory_umask' => '007',
'namespace' => 'lemonldap-ng-sessions'
@ -246,26 +245,27 @@ sub defaultValues {
'portalDisplayGeneratePassword' => 1,
'portalDisplayLoginHistory' => 1,
'portalDisplayLogout' => 1,
'portalDisplayOidcConsents' => '$_oidcConsents',
'portalDisplayRefreshMyRights' => 1,
'portalDisplayRegister' => 1,
'portalErrorOnExpiredSession' => 1,
'portalForceAuthnInterval' => 5,
'portalMainLogo' => 'common/logos/logo_llng_400px.png',
'portalPingInterval' => 60000,
'portalRequireOldPassword' => 1,
'portalSkin' => 'bootstrap',
'portalUserAttr' => '_user',
'proxyAuthnLevel' => 2,
'radius2fActivation' => 0,
'radius2fTimeout' => 20,
'radiusAuthnLevel' => 3,
'randomPasswordRegexp' => '[A-Z]{3}[a-z]{5}.\\d{2}',
'redirectFormMethod' => 'get',
'registerDB' => 'Null',
'registerTimeout' => 0,
'registerUrl' => 'http://auth.example.com/register',
'reloadTimeout' => 5,
'portalDisplayOidcConsents' =>
'$_oidcConsents && $_oidcConsents =~ /\\w+/',
'portalDisplayRefreshMyRights' => 1,
'portalDisplayRegister' => 1,
'portalErrorOnExpiredSession' => 1,
'portalForceAuthnInterval' => 5,
'portalMainLogo' => 'common/logos/logo_llng_400px.png',
'portalPingInterval' => 60000,
'portalRequireOldPassword' => 1,
'portalSkin' => 'bootstrap',
'portalUserAttr' => '_user',
'proxyAuthnLevel' => 2,
'radius2fActivation' => 0,
'radius2fTimeout' => 20,
'radiusAuthnLevel' => 3,
'randomPasswordRegexp' => '[A-Z]{3}[a-z]{5}.\\d{2}',
'redirectFormMethod' => 'get',
'registerDB' => 'Null',
'registerTimeout' => 0,
'registerUrl' => 'http://auth.example.com/register',
'reloadTimeout' => 5,
'remoteGlobalStorage' => 'Lemonldap::NG::Common::Apache::Session::SOAP',
'remoteGlobalStorageOptions' => {
'ns' =>
@ -276,7 +276,7 @@ sub defaultValues {
'rest2fActivation' => 0,
'restAuthnLevel' => 2,
'restClockTolerance' => 15,
'sameSite' => 'None',
'sameSite' => '',
'samlAttributeAuthorityDescriptorAttributeServiceSOAP' =>
'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;#PORTAL#/saml/AA/SOAP;',
'samlAuthnContextMapKerberos' => 4,

View File

@ -769,8 +769,8 @@ sub metadata {
return $self->sendJSONresponse(
$req,
$self->currentConf,
forceJSON => 1,
headers => [
pretty => 1,
headers => [
'Content-Disposition' => "Attachment; filename=lmConf-$c.json"
],
);

View File

@ -11,11 +11,13 @@ sub new {
my $self = bless {}, $class;
if ( $args{user} ) {
$self->{facility} = $conf->{userSyslogFacility} || 'auth';
$self->{options} = $conf->{userSyslogOptions} || 'cons,pid,ndelay';
}
else {
$self->{facility} = $conf->{syslogFacility} || 'daemon';
$self->{options} = $conf->{syslogOptions} || 'cons,pid,ndelay';
}
eval { openlog( 'LLNG', 'cons,pid,ndelay', $self->{facility} ) };
eval { openlog( 'LLNG', $self->{options}, $self->{facility} ) };
no warnings 'redefine';
my $show = 1;
foreach (qw(error warn notice info debug)) {

View File

@ -126,7 +126,23 @@ sub sendJSONresponse {
$args{headers} ||= [ $req->spliceHdrs ];
my $type = 'application/json; charset=utf-8';
if ( ref $j ) {
eval { $j = $_json->encode($j); };
eval {
if ( $args{pretty} ) {
# This avoids changing the settings of the $_json reference
$j = to_json(
$j,
{
allow_nonref => 1,
pretty => 1,
canonical => 1
}
);
}
else {
$j = $_json->encode($j);
}
};
return $self->sendError( $req, $@ ) if ($@);
}
return [ $args{code}, [ 'Content-Type' => $type, @{ $args{headers} } ],

View File

@ -132,6 +132,7 @@ sub BUILD {
if ( $self->{info} ) {
foreach ( keys %{ $self->{info} } ) {
next if ( $_ eq "_session_id" and $data->{_session_id} );
next if ( $_ eq "_session_kind" and $data->{_session_kind});
if ( defined $self->{info}->{$_} ) {
$data->{$_} = $self->{info}->{$_};
}

View File

@ -0,0 +1,115 @@
package Lemonldap::NG::Common::Util;
require Exporter;
use Digest::MD5;
use MIME::Base64 qw/encode_base64/;
use 5.10.0;
our $VERSION = '2.0.9';
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(getSameSite getPSessionID genId2F);
sub getPSessionID {
my ($uid) = @_;
return substr( Digest::MD5::md5_hex($uid), 0, 32 );
}
sub genId2F {
my ( $device ) = @_;
return encode_base64( "$device->{epoch}::$device->{type}::$device->{name}",
"" );
}
sub getSameSite {
my ($conf) = @_;
# Initialize cookie SameSite value
unless ( $conf->{sameSite} ) {
# SAML requires SameSite=None for POST bindings
if ( $conf->{issuerDBSAMLActivation}
or keys %{ $conf->{samlIDPMetaDataXML} } )
{
return "None";
}
else {
return "Lax";
}
# if CDA, OIDC, CAS: Lax
# TODO: try to detect when we can use 'Strict'?
# Any scenario that uses pdata to save state during login,
# Issuers, and CDA all require at least Lax
}
else {
return $conf->{sameSite};
}
}
1;
__END__
=head1 NAME
=encoding utf8
Lemonldap::NG::Common::Util - Utility class for LemonLDAP::NG
=head1 DESCRIPTION
This package contains various functions that need to be shared between
modules.
=head1 METHODS
=head3 getPSessionID($uid)
This method computes the psession ID from the user login
=head3 genId2F($device)
This method computes the unique ID of each 2F device, for use with the API and CLI
=head3 getSameSite($conf)
Try to find a sensible value for the SameSite cookie attribute.
If the user has overriden it, return the forced value
=head1 AUTHORS
=over
=item LemonLDAP::NG team L<http://lemonldap-ng.org/team>
=back
=head1 BUG REPORT
Use OW2 system to report bug or ask for features:
L<https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/issues>
=head1 DOWNLOAD
Lemonldap::NG is available at
L<http://forge.objectweb.org/project/showfiles.php?group_id=274>
=head1 COPYRIGHT AND LICENSE
See COPYING file for details.
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see L<http://www.gnu.org/licenses/>.
=cut

View File

@ -72,6 +72,16 @@ unless ($new) {
exit 5;
}
my $oldtype = $old->{type} =~ s/^Lemonldap::NG::Common::Conf::Backends:://r;
my $newtype = $new->{type} =~ s/^Lemonldap::NG::Common::Conf::Backends:://r;
print STDERR "Converting from " . $oldtype . " to " . $newtype . "\n";
if ( $oldtype eq $newtype ) {
print STDERR "\nWARNING: "
. "converting configuration without changing backend type.\n"
. "Make sure you know what you are doing.\n\n";
}
my @available;
if ( $opts{latest} ) {
@available = $old->lastCfg();
@ -88,7 +98,7 @@ foreach my $e ( @available, @dstavailable ) { $tmp{$e}++ }
my @isect = grep { $tmp{$_} > 1 } keys %tmp;
if ( @isect and not $opts{overwrite} ) {
print STDERR "Warning, " . @isect
print STDERR "WARNING: " . @isect
. " existing configurations found in destination \n";
print STDERR " use --overwrite to overwrite existing configurations\n";
exit 8;

View File

@ -55,6 +55,7 @@ t/64-Lemonldap-NG-Handler-PSGI-DevOps.t
t/65-Lemonldap-NG-Handler-Nginx-ServiceToken.t
t/65-Lemonldap-NG-Handler-PSGI-ServiceToken.t
t/66-Lemonldap-NG-Handler-PSGI-wildcard.t
t/67-Lemonldap-NG-Handler-PSGI-vhostoptions-with-reload.t
t/67-Lemonldap-NG-Handler-PSGI-vhostoptions.t
t/68-Lemonldap-NG-Handler-PSGI-Zimbra.t
t/69-Lemonldap-NG-Handler-PSGI-SecureToken.t

View File

@ -7,8 +7,8 @@ our $VERSION = '2.1.0';
sub fetchId {
my ( $class, $req ) = @_;
my $token = $req->{env}->{HTTP_X_LLNG_TOKEN};
return $class->Lemonldap::NG::Handler::Main::fetchId($req) unless ($token);
$class->logger->debug('Found token header');
return $class->Lemonldap::NG::Handler::Main::fetchId($req) unless ($token =~ /\w+/);
$class->logger->debug("Found token: $token");
# Decrypt token
my $s = $class->tsv->{cipher}->decrypt($token);
@ -16,6 +16,8 @@ sub fetchId {
# Token format:
# time:_session_id:vhost1:vhost2:serviceHeader1=value1:serviceHeader2=value2,...
my ( $t, $_session_id, @vhosts ) = split /:/, $s;
$class->logger->debug("Found epoch: $t");
$class->logger->debug("Found _session_id: $_session_id");
# Looking for service headers
my $vhost = $class->resolveAlias($req);
@ -43,6 +45,7 @@ sub fetchId {
"$vhost not authorized in token (" . join( ', ', @vhosts ) . ')' );
return 0;
}
$class->logger->debug( 'Found VHosts: ' . join ', ', @vhosts );
# Is token in good interval ?
my $ttl =

View File

@ -259,7 +259,7 @@ sub run {
}
}
else {
print STDERR "Status: Unknown command line : $_";
print STDERR "Status: Unknown command line -> $_";
}
}
}

View File

@ -553,9 +553,8 @@ sub conditionSub {
eval 'use Apache2::Filter' unless ( $INC{"Apache2/Filter.pm"} );
return (
sub {
my ($req) = @_;
$class->localUnlog;
$class->localUnlog( $req, @_ );
$req->{env}->{'psgi.r'}->add_output_filter(
sub {
my $r = $_[0]->r;

View File

@ -452,7 +452,7 @@ sub fetchId {
my $value =
$lookForHttpCookie
? ( $t =~ /${cn}http=([^,; ]+)/o ? $1 : 0 )
: ( $t =~ /$cn=([^,; ]+)/o ? $1 : 0 );
: ( $t =~ /$cn=([^,; ]+)/o ? $1 : 0 );
if ( $value && $lookForHttpCookie && $class->tsv->{securedCookie} == 3 ) {
$value = $class->tsv->{cipher}->decryptHex( $value, "http" );
@ -621,7 +621,7 @@ sub _isHttps {
return $class->tsv->{https}->{_};
}
else {
return ( ( uc( $req->{env}->{HTTPS} ) || "OFF" ) eq "ON" );
return ( uc( $req->{env}->{HTTPS} || "OFF" ) eq "ON" );
}
}
}

View File

@ -0,0 +1,59 @@
use Test::More;
use JSON;
use MIME::Base64;
use Data::Dumper;
use URI::Escape;
require 't/test-psgi-lib.pm';
init(
'Lemonldap::NG::Handler::PSGI',
{
locationRules => {},
exportedHeaders => {},
https => undef,
port => undef,
maintenance => undef,
}
);
my $res;
ok( $res = $client->_get('/'), 'Unauthentified query' );
ok( ref($res) eq 'ARRAY', 'Response is an array' ) or explain( $res, 'array' );
ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 );
my $conf;
eval {
local $/ = undef;
open my $file, 't/lmConf-1.json' or die $!;
$conf = JSON::from_json(<$file>);
close $file;
};
fail $@ if $@;
$conf->{vhostOptions} = {
'test1.example.com' => {
vhostHttps => 1,
vhostPort => 443,
},
};
Lemonldap::NG::Handler::Main->configReload($conf);
fail $@ if $@;
ok( $res = $client->_get('/'), 'Unauthentified query' );
ok( ref($res) eq 'ARRAY', 'Response is an array' ) or explain( $res, 'array' );
ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 );
my %h = @{ $res->[1] };
ok(
$h{Location} eq 'http://auth.example.com/?url='
. uri_escape( encode_base64( 'https://test1.example.com/', '' ) ),
'Redirection points to portal and site is https'
)
or explain(
\%h,
'Location => http://auth.example.com/?url='
. uri_escape( encode_base64( 'https://test1.example.com/', '' ) )
);
count(7);
done_testing( count() );
clean();

View File

@ -10,6 +10,7 @@ lib/Lemonldap/NG/Manager/2ndFA.pm
lib/Lemonldap/NG/Manager/Api.pm
lib/Lemonldap/NG/Manager/Api/2F.pm
lib/Lemonldap/NG/Manager/Api/Common.pm
lib/Lemonldap/NG/Manager/Api/Misc.pm
lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm
lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm
lib/Lemonldap/NG/Manager/Attributes.pm

View File

@ -4,7 +4,6 @@ use 5.10.0;
use utf8;
use strict;
use Mouse;
use MIME::Base64 qw(encode_base64 decode_base64);
use Lemonldap::NG::Common::Session;
use Lemonldap::NG::Common::Conf::Constants;

View File

@ -10,10 +10,14 @@ extends 'Lemonldap::NG::Manager::Plugin',
'Lemonldap::NG::Common::Session::REST';
use Lemonldap::NG::Manager::Api::2F;
use Lemonldap::NG::Manager::Api::Misc;
use Lemonldap::NG::Manager::Api::Providers::OidcRp;
use Lemonldap::NG::Manager::Api::Providers::SamlSp;
use Lemonldap::NG::Manager::Api::Providers::CasApp;
use Lemonldap::NG::Manager::Api::Menu::Cat;
use Lemonldap::NG::Manager::Api::Menu::App;
our $VERSION = '2.0.8';
our $VERSION = '2.0.9';
#############################
# I. INITIALIZATION METHODS #
@ -30,6 +34,7 @@ sub init {
->addRoute(
api => {
v1 => {
status => 'status',
providers => {
oidc => {
rp => {
@ -53,6 +58,17 @@ sub init {
':confKey' => 'getSamlSpByConfKey'
},
},
cas => {
app => {
findByConfKey => {
':uPattern' => 'findCasAppByConfKey'
},
findByServiceUrl => {
':uServiceUrl' => 'findCasAppsByServiceUrl'
},
':confKey' => 'getCasAppByConfKey'
},
},
},
secondFactor => {
':uid' => {
@ -65,6 +81,24 @@ sub init {
'*' => 'getSecondFactors'
},
},
menu => {
cat => {
findByConfKey => {
':uPattern' => 'findMenuCatByConfKey'
},
':confKey' => {
'*' => 'getMenuCatByConfKey'
}
},
app => {
':confKey' => {
findByConfKey => {
':uPattern' => 'findMenuAppByConfKey'
},
':appConfKey' => 'getMenuApp'
}
},
},
},
},
['GET']
@ -80,6 +114,15 @@ sub init {
saml => {
sp => 'addSamlSp'
},
cas => {
app => 'addCasApp'
},
},
menu => {
cat => 'addMenuCat',
app => {
':confKey' => 'addMenuApp'
}
},
},
},
@ -96,6 +139,17 @@ sub init {
saml => {
sp => { ':confKey' => 'replaceSamlSp' }
},
cas => {
app => { ':confKey' => 'replaceCasApp' }
},
},
menu => {
cat => { ':confKey' => 'replaceMenuCat' },
app => {
':confKey' => {
':appConfKey' => 'replaceMenuApp'
}
}
},
},
},
@ -112,6 +166,17 @@ sub init {
saml => {
sp => { ':confKey' => 'updateSamlSp' }
},
cas => {
app => { ':confKey' => 'updateCasApp' }
},
},
menu => {
cat => { ':confKey' => 'updateMenuCat' },
app => {
':confKey' => {
':appConfKey' => 'updateMenuApp'
}
}
},
},
},
@ -128,6 +193,9 @@ sub init {
saml => {
sp => { ':confKey' => 'deleteSamlSp' }
},
cas => {
app => { ':confKey' => 'deleteCasApp' }
},
},
secondFactor => {
':uid' => {
@ -140,6 +208,14 @@ sub init {
'*' => 'deleteSecondFactors'
},
},
menu => {
cat => { ':confKey' => 'deleteMenuCat' },
app => {
':confKey' => {
':appConfKey' => 'deleteMenuApp'
}
}
},
},
},
['DELETE']

View File

@ -8,9 +8,9 @@ use 5.10.0;
use utf8;
use Mouse;
use JSON;
use MIME::Base64;
use Lemonldap::NG::Common::Session;
use Lemonldap::NG::Common::Util qw/genId2F/;
sub getSecondFactors {
my ( $self, $req ) = @_;
@ -155,12 +155,12 @@ sub _get2F {
);
push @secondFactors,
{
id => $self->_genId2F($device),
id => genId2F($device),
type => $device->{type},
name => $device->{name}
}
unless ( ( defined $type and $type ne $device->{type} )
or ( defined $id and $id ne $self->_genId2F($device) ) );
or ( defined $id and $id ne genId2F($device) ) );
}
}
$self->logger->debug(
@ -168,26 +168,6 @@ sub _get2F {
return { res => 'ok', secondFactors => [@secondFactors] };
}
sub _genId2F {
my ( $self, $device ) = @_;
return encode_base64( "$device->{epoch}::$device->{type}::$device->{name}",
"" );
}
sub _getPersistentMod {
my ($self) = @_;
my $mod = $self->sessionTypes->{persistent};
$mod->{options}->{backend} = $mod->{module};
return $mod;
}
sub _getSSOMod {
my ($self) = @_;
my $mod = $self->sessionTypes->{global};
$mod->{options}->{backend} = $mod->{module};
return $mod;
}
sub _getSessions2F {
my ( $self, $mod, $kind, $key, $uid ) = @_;
$self->logger->debug("Looking for sessions for uid $uid ...");
@ -245,14 +225,13 @@ sub _delete2FFromSessions {
if (
( defined $type or defined $id )
and ( ( defined $type and $type ne $element->{type} )
or
( defined $id and $id ne $self->_genId2F($element) ) )
or ( defined $id and $id ne genId2F($element) ) )
)
{
push @keep, $element;
}
else {
$removed->{ $self->_genId2F($element) } = "removed";
$removed->{ genId2F($element) } = "removed";
}
}
if ( ( $total - scalar @keep ) > 0 ) {

View File

@ -112,4 +112,18 @@ sub _getRegexpFromPattern {
return qr/$pattern/;
}
sub _getPersistentMod {
my ($self) = @_;
my $mod = $self->sessionTypes->{persistent};
$mod->{options}->{backend} = $mod->{module};
return $mod;
}
sub _getSSOMod {
my ($self) = @_;
my $mod = $self->sessionTypes->{global};
$mod->{options}->{backend} = $mod->{module};
return $mod;
}
1;

View File

@ -0,0 +1,408 @@
package Lemonldap::NG::Manager::Api::Menu::App;
our $VERSION = '2.0.9';
package Lemonldap::NG::Manager::Api;
use 5.10.0;
use utf8;
use Mouse;
use Lemonldap::NG::Manager::Conf::Parser;
use Data::Dumper;
extends 'Lemonldap::NG::Manager::Api::Common';
sub getMenuApp {
my ( $self, $req ) = @_;
my $catConfKey = $req->params('confKey')
or return $self->sendError( $req, 'Category confKey is missing', 400 );
my $appConfKey = $req->params('appConfKey');
# Get latest configuration
my $conf = $self->_confAcc->getConf;
# Check if catConfKey is defined
return $self->sendError( $req,
"Menu category '$catConfKey' not found", 404 )
unless ( defined $conf->{applicationList}->{$catConfKey} );
if ( defined $appConfKey ) {
# Return one application referenced with this appConfKey
$self->logger->debug(
"[API] Menu application $appConfKey from category $catConfKey configuration requested"
);
my $menuApp =
$self->_getMenuAppByConfKey( $conf, $catConfKey, $appConfKey );
# Return 404 if not found
return $self->sendError(
$req,
"Menu application '$appConfKey' from category '$catConfKey' not found",
404
) unless ( defined $menuApp );
return $self->sendJSONresponse( $req, $menuApp );
}
else {
# Return all applications for this category
$self->logger->debug(
"[API] Menu applications from category $catConfKey configuration requested"
);
my $cat = $conf->{applicationList}->{$catConfKey};
my @menuApps =
map {
$self->_isCatApp( $cat->{$_} )
? $self->_getMenuAppByConfKey( $conf, $catConfKey, $_ )
: ()
}
keys %{$cat};
return $self->sendJSONresponse( $req, [@menuApps] );
}
}
sub findMenuAppByConfKey {
my ( $self, $req ) = @_;
my $catConfKey = $req->params('confKey')
or return $self->sendError( $req, 'Category confKey is missing', 400 );
my $pattern = (
defined $req->params('uPattern')
? $req->params('uPattern')
: ( defined $req->params('pattern') ? $req->params('pattern') : undef )
);
return $self->sendError( $req, 'Invalid input: pattern is missing', 400 )
unless ( defined $pattern );
unless ( $pattern = $self->_getRegexpFromPattern($pattern) ) {
return $self->sendError( $req, 'Invalid input: pattern is invalid',
400 );
}
$self->logger->debug(
"[API] Find Menu Applications from category $catConfKey by confKey regexp $pattern requested"
);
# Get latest configuration
my $conf = $self->_confAcc->getConf;
# Check if catConfKey is defined
return $self->sendError( $req,
"Menu category '$catConfKey' not found", 404 )
unless ( defined $conf->{applicationList}->{$catConfKey} );
my $cat = $conf->{applicationList}->{$catConfKey};
my @menuApps =
map {
$self->_isCatApp( $cat->{$_} )
&& $_ =~ $pattern
? $self->_getMenuAppByConfKey( $conf, $catConfKey, $_ )
: ()
}
keys %{$cat};
return $self->sendJSONresponse( $req, [@menuApps] );
}
sub addMenuApp {
my ( $self, $req ) = @_;
my $add = $req->jsonBodyToObj;
my $catConfKey = $req->params('confKey')
or return $self->sendError( $req, 'Category confKey is missing', 400 );
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
unless ($add);
return $self->sendError( $req, 'Invalid input: confKey is missing', 400 )
unless ( defined $add->{confKey} );
return $self->sendError( $req, 'Invalid input: confKey is not a string',
400 )
if ( ref $add->{confKey} );
return $self->sendError( $req, 'Invalid input: confKey contains invalid characters',
400 )
unless ( $add->{confKey} =~ '^\w[\w\.\-]*$' );
return $self->sendError( $req, 'Invalid input: name is missing', 400 )
unless ( defined $add->{options} && defined $add->{options}{name} );
return $self->sendError( $req, 'Invalid input: name is not a string', 400 )
if ( ref $add->{options}{name} );
$self->logger->debug(
"[API] Add Menu Application from category $catConfKey with confKey $add->{confKey} requested"
);
# Get latest configuration
my $conf = $self->_confAcc->getConf( { noCache => 1 } );
# Check if catConfKey is defined
return $self->sendError( $req,
"Menu category '$catConfKey' not found", 404 )
unless ( defined $conf->{applicationList}->{$catConfKey} );
return $self->sendError(
$req,
"Invalid input: A Menu Application with confKey $add->{confKey} already exists in category $catConfKey",
409
)
if (
defined $self->_getMenuAppByConfKey( $conf, $catConfKey,
$add->{confKey} ) );
my $res =
$self->_pushMenuApp( $conf, $catConfKey, $add->{confKey}, $add, 1 );
return $self->sendError( $req, $res->{msg}, 400 )
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse(
$req,
{ message => "Successful operation" },
code => 201
);
}
sub updateMenuApp {
my ( $self, $req ) = @_;
my $catConfKey = $req->params('confKey')
or return $self->sendError( $req, 'Category confKey is missing', 400 );
my $appConfKey = $req->params('appConfKey')
or return $self->sendError( $req, 'Application confKey is missing', 400 );
my $update = $req->jsonBodyToObj;
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
unless ($update);
$self->logger->debug(
"[API] Menu application $appConfKey from category $catConfKey configuration update requested"
);
# Get latest configuration
my $conf = $self->_confAcc->getConf( { noCache => 1 } );
# Return 404 if not found
return $self->sendError( $req,
"Menu category '$catConfKey' not found", 404 )
unless ( defined $self->_getMenuCatByConfKey( $conf, $catConfKey ) );
return $self->sendError(
$req,
"Menu application '$appConfKey' from category '$catConfKey' not found",
404
)
unless (
defined $self->_getMenuAppByConfKey( $conf, $catConfKey, $appConfKey )
);
my $res =
$self->_pushMenuApp( $conf, $catConfKey, $appConfKey, $update, 0 );
return $self->sendError( $req, $res->{msg}, 400 )
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse( $req, undef, code => 204 );
}
sub replaceMenuApp {
my ( $self, $req ) = @_;
my $catConfKey = $req->params('confKey')
or return $self->sendError( $req, 'Category confKey is missing', 400 );
my $appConfKey = $req->params('appConfKey')
or return $self->sendError( $req, 'Application confKey is missing', 400 );
my $replace = $req->jsonBodyToObj;
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
unless ($replace);
return $self->sendError( $req, 'Invalid input: confKey is missing', 400 )
unless ( defined $replace->{confKey} );
return $self->sendError( $req, 'Invalid input: confKey is not a string',
400 )
if ( ref $replace->{confKey} );
return $self->sendError( $req, 'Invalid input: confKey contains invalid characters',
400 )
unless ( $replace->{confKey} =~ '^\w[\w\.\-]*$' );
return $self->sendError( $req, 'Invalid input: name is missing', 400 )
unless ( defined $replace->{options}
&& defined $replace->{options}{name} );
return $self->sendError( $req, 'Invalid input: name is not a string', 400 )
if ( ref $replace->{options}{name} );
$self->logger->debug(
"[API] Menu application $appConfKey from category $catConfKey configuration replace requested"
);
# Get latest configuration
my $conf = $self->_confAcc->getConf( { noCache => 1 } );
# Return 404 if not found
return $self->sendError( $req,
"Menu category '$catConfKey' not found", 404 )
unless ( defined $self->_getMenuCatByConfKey( $conf, $catConfKey ) );
return $self->sendError(
$req,
"Menu application '$appConfKey' from category '$catConfKey' not found",
404
)
unless (
defined $self->_getMenuAppByConfKey( $conf, $catConfKey, $appConfKey )
);
my $res =
$self->_pushMenuApp( $conf, $catConfKey, $appConfKey, $replace, 1 );
return $self->sendError( $req, $res->{msg}, 400 )
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse( $req, undef, code => 204 );
}
sub deleteMenuApp {
my ( $self, $req ) = @_;
my $catConfKey = $req->params('confKey')
or return $self->sendError( $req, 'Category confKey is missing', 400 );
my $appConfKey = $req->params('appConfKey')
or return $self->sendError( $req, 'Application confKey is missing', 400 );
$self->logger->debug(
"[API] Menu Application $appConfKey from category $catConfKey configuration delete requested"
);
# Get latest configuration
my $conf = $self->_confAcc->getConf( { noCache => 1 } );
return $self->sendError( $req,
"Menu category '$catConfKey' not found", 404 )
unless ( defined $self->_getMenuCatByConfKey( $conf, $catConfKey ) );
my $delete = $self->_getMenuAppByConfKey( $conf, $catConfKey, $appConfKey );
# Return 404 if not found
return $self->sendError( $req,
"Menu category '$appConfKey' not found", 404 )
unless ( defined $delete );
delete $conf->{applicationList}->{$catConfKey}->{$appConfKey};
# Save configuration
$self->_confAcc->saveConf($conf);
return $self->sendJSONresponse( $req, undef, code => 204 );
}
sub _isCatApp {
my ( $self, $candidate ) = @_;
# Check if candidate is a hash, has "type" defined and if "type" equals "application".
return
ref $candidate eq ref {}
&& defined $candidate->{type}
&& $candidate->{type} eq 'application';
}
sub _getMenuAppByConfKey {
my ( $self, $conf, $catConfKey, $appConfKey ) = @_;
# Check if catConfKey is defined
return undef unless ( defined $conf->{applicationList}->{$catConfKey} );
# Check if appConfKey is defined
return undef
unless ( defined $conf->{applicationList}->{$catConfKey}->{$appConfKey} );
my $cat = $conf->{applicationList}->{$catConfKey};
my $menuApp = { confKey => $appConfKey };
$menuApp->{order} = $cat->{$appConfKey}->{order}
if ( defined $cat->{$appConfKey}->{order} );
# Get options
my $options = {};
for my $configOption ( keys %{ $cat->{$appConfKey}->{options} } ) {
$options->{ $self->_translateOptionConfToApi($configOption) } =
$cat->{$appConfKey}->{options}->{$configOption};
}
$menuApp->{options} = $options;
return $menuApp;
}
sub _pushMenuApp {
my ( $self, $conf, $catConfKey, $appConfKey, $push, $replace ) = @_;
if ($replace) {
$conf->{applicationList}->{$catConfKey}->{$appConfKey} = {};
$conf->{applicationList}->{$catConfKey}->{$appConfKey}->{type} =
"application";
$conf->{applicationList}->{$catConfKey}->{$appConfKey}->{options} = {};
$conf->{applicationList}->{$catConfKey}->{$appConfKey}->{options}
->{display} = "auto";
$conf->{applicationList}->{$catConfKey}->{$appConfKey}->{options}
->{logo} = "network.png";
}
$conf->{applicationList}->{$catConfKey}->{$appConfKey}->{order} =
$push->{order}
if ( defined $push->{order} );
if ( defined $push->{options} ) {
foreach ( keys %{ $push->{options} } ) {
$conf->{applicationList}->{$catConfKey}->{$appConfKey}->{options}
->{$_} = $push->{options}->{$_};
}
}
# Test new configuration
my $parser = Lemonldap::NG::Manager::Conf::Parser->new( {
refConf => $self->_confAcc->getConf,
newConf => $conf,
req => {},
}
);
unless ( $parser->testNewConf( $self->p ) ) {
return {
res => 'ko',
code => 400,
msg => "Configuration error: "
. join( ". ", map { $_->{message} } @{ $parser->errors } ),
};
}
# Save configuration
$self->_confAcc->saveConf($conf);
return { res => 'ok' };
}
1;

View File

@ -0,0 +1,270 @@
package Lemonldap::NG::Manager::Api::Menu::Cat;
our $VERSION = '2.0.9';
package Lemonldap::NG::Manager::Api;
use 5.10.0;
use utf8;
use Mouse;
use Lemonldap::NG::Manager::Conf::Parser;
extends 'Lemonldap::NG::Manager::Api::Common';
sub getMenuCatByConfKey {
my ( $self, $req ) = @_;
my $confKey = $req->params('confKey')
or return $self->sendError( $req, 'confKey is missing', 400 );
$self->logger->debug(
"[API] Menu Category $confKey configuration requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf;
my $menuCat = $self->_getMenuCatByConfKey( $conf, $confKey );
# Return 404 if not found
return $self->sendError( $req, "Menu category '$confKey' not found", 404 )
unless ( defined $menuCat );
return $self->sendJSONresponse( $req, $menuCat );
}
sub findMenuCatByConfKey {
my ( $self, $req ) = @_;
my $pattern = (
defined $req->params('uPattern')
? $req->params('uPattern')
: ( defined $req->params('pattern') ? $req->params('pattern') : undef )
);
return $self->sendError( $req, 'Invalid input: pattern is missing', 400 )
unless ( defined $pattern );
unless ( $pattern = $self->_getRegexpFromPattern($pattern) ) {
return $self->sendError( $req, 'Invalid input: pattern is invalid',
400 );
}
$self->logger->debug(
"[API] Find Menu Categories by confKey regexp $pattern requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf;
my @menuCats =
map { $_ =~ $pattern ? $self->_getMenuCatByConfKey( $conf, $_ ) : () }
keys %{ $conf->{applicationList} };
return $self->sendJSONresponse( $req, [@menuCats] );
}
sub addMenuCat {
my ( $self, $req ) = @_;
my $add = $req->jsonBodyToObj;
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
unless ($add);
return $self->sendError( $req, 'Invalid input: confKey is missing', 400 )
unless ( defined $add->{confKey} );
return $self->sendError( $req, 'Invalid input: confKey is not a string',
400 )
if ( ref $add->{confKey} );
return $self->sendError( $req, 'Invalid input: confKey contains invalid characters',
400 )
unless ( $add->{confKey} =~ '^\w[\w\.\-]*$' );
return $self->sendError( $req, 'Invalid input: catname is missing', 400 )
unless ( defined $add->{catname} );
return $self->sendError( $req, 'Invalid input: catname is not a string',
400 )
if ( ref $add->{catname} );
$self->logger->debug(
"[API] Add Menu Category with confKey $add->{confKey} requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf( { noCache => 1 } );
return $self->sendError(
$req,
"Invalid input: A Menu Category with confKey $add->{confKey} already exists",
409
) if ( defined $self->_getMenuCatByConfKey( $conf, $add->{confKey} ) );
my $res = $self->_pushMenuCat( $conf, $add->{confKey}, $add, 1 );
return $self->sendError( $req, $res->{msg}, 400 )
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse(
$req,
{ message => "Successful operation" },
code => 201
);
}
sub updateMenuCat {
my ( $self, $req ) = @_;
my $confKey = $req->params('confKey')
or return $self->sendError( $req, 'confKey is missing', 400 );
my $update = $req->jsonBodyToObj;
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
unless ($update);
$self->logger->debug(
"[API] Menu Category $confKey configuration update requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf( { noCache => 1 } );
my $current = $self->_getMenuCatByConfKey( $conf, $confKey );
# Return 404 if not found
return $self->sendError( $req, "Menu category '$confKey' not found", 404 )
unless ( defined $current );
my $res = $self->_pushMenuCat( $conf, $confKey, $update, 0 );
return $self->sendError( $req, $res->{msg}, 400 )
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse( $req, undef, code => 204 );
}
sub replaceMenuCat {
my ( $self, $req ) = @_;
my $confKey = $req->params('confKey')
or return $self->sendError( $req, 'confKey is missing', 400 );
my $replace = $req->jsonBodyToObj;
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
unless ($replace);
return $self->sendError( $req, 'Invalid input: confKey is missing', 400 )
unless ( defined $replace->{confKey} );
return $self->sendError( $req, 'Invalid input: confKey is not a string',
400 )
if ( ref $replace->{confKey} );
return $self->sendError( $req, 'Invalid input: confKey contains invalid characters',
400 )
unless ( $replace->{confKey} =~ '^\w[\w\.\-]*$' );
return $self->sendError( $req, 'Invalid input: catname is missing', 400 )
unless ( defined $replace->{catname} );
return $self->sendError( $req, 'Invalid input: catname is not a string',
400 )
if ( ref $replace->{catname} );
$self->logger->debug(
"[API] Menu Category $confKey configuration replace requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf( { noCache => 1 } );
# Return 404 if not found
return $self->sendError( $req, "Menu category '$confKey' not found", 404 )
unless ( defined $self->_getMenuCatByConfKey( $conf, $confKey ) );
my $res = $self->_pushMenuCat( $conf, $confKey, $replace, 1 );
return $self->sendError( $req, $res->{msg}, 400 )
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse( $req, undef, code => 204 );
}
sub deleteMenuCat {
my ( $self, $req ) = @_;
my $confKey = $req->params('confKey')
or return $self->sendError( $req, 'confKey is missing', 400 );
$self->logger->debug(
"[API] Menu Category $confKey configuration delete requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf( { noCache => 1 } );
my $delete = $self->_getMenuCatByConfKey( $conf, $confKey );
# Return 404 if not found
return $self->sendError( $req, "Menu category '$confKey' not found", 404 )
unless ( defined $delete );
delete $conf->{applicationList}->{$confKey};
# Save configuration
$self->_confAcc->saveConf($conf);
return $self->sendJSONresponse( $req, undef, code => 204 );
}
sub _getMenuCatByConfKey {
my ( $self, $conf, $confKey ) = @_;
# Check if confKey is defined
return undef unless ( defined $conf->{applicationList}->{$confKey} );
my $menuCat = {
confKey => $confKey,
catname => $conf->{applicationList}->{$confKey}->{catname}
};
$menuCat->{order} = $conf->{applicationList}->{$confKey}->{order}
if ( defined $conf->{applicationList}->{$confKey}->{order} );
return $menuCat;
}
sub _pushMenuCat {
my ( $self, $conf, $confKey, $push, $replace ) = @_;
if ($replace) {
$conf->{applicationList}->{$confKey} = {};
$conf->{applicationList}->{$confKey}->{type} = "category";
}
$conf->{applicationList}->{$confKey}->{order} = $push->{order}
if ( defined $push->{order} );
$conf->{applicationList}->{$confKey}->{catname} = $push->{catname}
if ( defined $push->{catname} );
# Test new configuration
my $parser = Lemonldap::NG::Manager::Conf::Parser->new( {
refConf => $self->_confAcc->getConf,
newConf => $conf,
req => {},
}
);
unless ( $parser->testNewConf( $self->p ) ) {
return {
res => 'ko',
code => 400,
msg => "Configuration error: "
. join( ". ", map { $_->{message} } @{ $parser->errors } ),
};
}
# Save configuration
$self->_confAcc->saveConf($conf);
return { res => 'ok' };
}
1;

View File

@ -0,0 +1,130 @@
# Miscenalleous endpoints
package Lemonldap::NG::Manager::Api::Misc;
our $VERSION = '2.0.8';
package Lemonldap::NG::Manager::Api;
extends 'Lemonldap::NG::Manager::Api::Common';
# Health-check endpoint
sub status {
my ( $self, $req ) = @_;
my $code = 200;
my $response = {
name => "LemonLDAP::NG Manager API",
version => $Lemonldap::NG::Manager::VERSION,
status => "ok",
status_config => "ok",
status_sessions => "ok",
status_psessions => "ok",
};
# Test configuration backend
my $conf =
$self->_confAcc->getDBConf( { cfgNum => $self->_confAcc->lastCfg } );
unless ( $conf->{cfgNum} ) {
$code = 503;
$response->{status} = "ko";
$response->{status_config} = "ko";
}
# Test session backend
my $status = $self->_getSessionDBState( $self->_getSSOMod );
if ( $status == 0 ) {
$code = 503;
$response->{status} = "ko";
$response->{status_sessions} = "ko";
}
elsif ( $status == 2 ) {
$response->{status_sessions} = "unknown";
}
# Test psession backend
$status = $self->_getSessionDBState( $self->_getPersistentMod );
if ( $status == 0 ) {
$code = 503;
$response->{status} = "ko";
$response->{status_psessions} = "ko";
}
elsif ( $status == 2 ) {
$response->{status_psessions} = "unknown";
}
return $self->sendJSONresponse(
$req, $response,
code => $code,
pretty => 1
);
}
# Apache::Session has no API for healthchecking yet. Until it does, we have to
# break the encapsulation model to get the info. This needs to be reworked.
# So far, we only implement the check for:
# * Apache::Session::DBI
# * Apache::Session::Browseable::DBI (Postgresq, MySQL)
# * Apache::Session::MongoDB
#
# Returns 0 for ko, 1 for ok and 2 for unknown
sub _getSessionDBState {
my ( $self, $mod ) = @_;
my $fakeobj;
eval { $fakeobj = $self->_getObjectSessionModule($mod); };
# If we could not instanciate the session module directly, bail
return 2 unless $fakeobj;
my $fakeobj_store = $fakeobj->{object_store};
# If the Apache::Session object does not have an object store, bail
return 2 unless $fakeobj_store;
# Handle DBI-type session stores
if ( $fakeobj->{object_store}->isa("Apache::Session::Store::DBI") ) {
#
# The 'connection' method will fail if the DB is unreachable
# this is good enough a test for now
eval { $fakeobj->{object_store}->connection($fakeobj) };
if ($@) {
return 0;
}
else {
return 1;
}
}
# Handle MongoDB
if ( $fakeobj->{object_store}->isa("Apache::Session::Store::MongoDB") ) {
# Try to find collection
eval {
$fakeobj->{object_store}->connection($fakeobj);
$fakeobj->{object_store}->{collection}->estimated_document_count;
};
if ($@) {
return 0;
}
else {
return 1;
}
}
# We don't know
return 2;
}
sub _getObjectSessionModule {
my ( $self, $mod ) = @_;
my $class = $mod->{module};
eval "require $class;";
my $fakeSession = { args => $mod->{options}, };
bless $fakeSession, $class;
$fakeSession->populate;
return $fakeSession;
}
1;

View File

@ -0,0 +1,407 @@
package Lemonldap::NG::Manager::Api::Providers::CasApp;
our $VERSION = '2.0.9';
package Lemonldap::NG::Manager::Api;
use 5.10.0;
use utf8;
use Mouse;
use Lemonldap::NG::Manager::Conf::Parser;
extends 'Lemonldap::NG::Manager::Api::Common';
sub getCasAppByConfKey {
my ( $self, $req ) = @_;
my $confKey = $req->params('confKey')
or return $self->sendError( $req, 'confKey is missing', 400 );
$self->logger->debug("[API] CAS App $confKey configuration requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf;
my $casApp = $self->_getCasAppByConfKey( $conf, $confKey );
# Return 404 if not found
return $self->sendError( $req,
"CAS application '$confKey' not found", 404 )
unless ( defined $casApp );
return $self->sendJSONresponse( $req, $casApp );
}
sub findCasAppByConfKey {
my ( $self, $req ) = @_;
my $pattern = (
defined $req->params('uPattern')
? $req->params('uPattern')
: ( defined $req->params('pattern') ? $req->params('pattern') : undef )
);
return $self->sendError( $req, 'Invalid input: pattern is missing', 400 )
unless ( defined $pattern );
unless ( $pattern = $self->_getRegexpFromPattern($pattern) ) {
return $self->sendError( $req, 'Invalid input: pattern is invalid',
400 );
}
$self->logger->debug(
"[API] Find CAS Apps by confKey regexp $pattern requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf;
my @casApps =
map { $_ =~ $pattern ? $self->_getCasAppByConfKey( $conf, $_ ) : () }
keys %{ $conf->{casAppMetaDataOptions} };
return $self->sendJSONresponse( $req, [@casApps] );
}
sub findCasAppsByServiceUrl {
my ( $self, $req ) = @_;
my $serviceUrl = (
defined $req->params('uServiceUrl') ? $req->params('uServiceUrl')
: (
defined $req->params('serviceUrl') ? $req->params('serviceUrl')
: undef
)
);
return $self->sendError( $req, 'Invalid input: serviceUrl is missing', 400 )
unless ( defined $serviceUrl );
$self->logger->debug("[API] Find CAS Apps by service URL $serviceUrl requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf;
my $casApp = $self->_getCasAppByServiceUrl( $conf, $serviceUrl );
return $self->sendError( $req,
"CAS application with service '$serviceUrl' not found", 404 )
unless ( defined $casApp );
return $self->sendJSONresponse( $req, $casApp );
}
sub addCasApp {
my ( $self, $req ) = @_;
my $add = $req->jsonBodyToObj;
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
unless ($add);
return $self->sendError( $req, 'Invalid input: confKey is missing', 400 )
unless ( defined $add->{confKey} );
return $self->sendError( $req, 'Invalid input: confKey is not a string',
400 )
if ( ref $add->{confKey} );
return $self->sendError( $req, 'Invalid input: service is missing', 400 )
unless ( defined $add->{options}->{service} );
return $self->sendError( $req, 'Invalid input: service is not a string',
400 )
if ( ref $add->{options}->{service} );
$self->logger->debug(
"[API] Add CAS App with confKey $add->{confKey} requested"
);
# Get latest configuration
my $conf = $self->_confAcc->getConf( { noCache => 1 } );
return $self->sendError(
$req,
"Invalid input: A CAS App with confKey $add->{confKey} already exists",
409
) if ( defined $self->_getCasAppByConfKey( $conf, $add->{confKey} ) );
my $res =
$self->_getCasAppByServiceUrl( $conf, $add->{options}->{service} );
if ( defined $res ) {
my $conflict = $res->{options}->{service};
return $self->sendError(
$req,
"Invalid input: A CAS application with service URL $conflict already exists",
409
);
}
$res = $self->_pushCasApp( $conf, $add->{confKey}, $add, 1 );
return $self->sendError( $req, $res->{msg}, 400 )
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse(
$req,
{ message => "Successful operation" },
code => 201
);
}
sub updateCasApp {
my ( $self, $req ) = @_;
my $confKey = $req->params('confKey')
or return $self->sendError( $req, 'confKey is missing', 400 );
my $update = $req->jsonBodyToObj;
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
unless ($update);
$self->logger->debug(
"[API] CAS App $confKey configuration update requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf( { noCache => 1 } );
my $current = $self->_getCasAppByConfKey( $conf, $confKey );
# Return 404 if not found
return $self->sendError( $req,
"CAS application '$confKey' not found", 404 )
unless ( defined $current );
# check if new clientID exists already
my $res = $self->_isNewCasAppServiceUrlUnique( $conf, $confKey, $update );
return $self->sendError( $req, $res->{msg}, 409 )
unless ( $res->{res} eq 'ok' );
$res = $self->_pushCasApp( $conf, $confKey, $update, 0 );
return $self->sendError( $req, $res->{msg}, 400 )
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse( $req, undef, code => 204 );
}
sub replaceCasApp {
my ( $self, $req ) = @_;
my $confKey = $req->params('confKey')
or return $self->sendError( $req, 'confKey is missing', 400 );
my $replace = $req->jsonBodyToObj;
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
unless ($replace);
return $self->sendError( $req, 'Invalid input: confKey is missing', 400 )
unless ( defined $replace->{confKey} );
return $self->sendError( $req, 'Invalid input: confKey is not a string',
400 )
if ( ref $replace->{confKey} );
$self->logger->debug(
"[API] CAS App $confKey configuration replace requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf( { noCache => 1 } );
# Return 404 if not found
return $self->sendError( $req,
"CAS application '$confKey' not found", 404 )
unless ( defined $self->_getCasAppByConfKey( $conf, $confKey ) );
# check if new clientID exists already
my $res = $self->_isNewCasAppServiceUrlUnique( $conf, $confKey, $replace );
return $self->sendError( $req, $res->{msg}, 409 )
unless ( $res->{res} eq 'ok' );
$res = $self->_pushCasApp( $conf, $confKey, $replace, 1 );
return $self->sendError( $req, $res->{msg}, 400 )
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse( $req, undef, code => 204 );
}
sub deleteCasApp {
my ( $self, $req ) = @_;
my $confKey = $req->params('confKey')
or return $self->sendError( $req, 'confKey is missing', 400 );
$self->logger->debug(
"[API] CAS App $confKey configuration delete requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf( { noCache => 1 } );
my $delete = $self->_getCasAppByConfKey( $conf, $confKey );
# Return 404 if not found
return $self->sendError( $req,
"CAS application '$confKey' not found", 404 )
unless ( defined $delete );
delete $conf->{casAppMetaDataOptions}->{$confKey};
delete $conf->{casAppMetaDataExportedVars}->{$confKey};
delete $conf->{casAppMetaDataMacros}->{$confKey};
# Save configuration
$self->_confAcc->saveConf($conf);
return $self->sendJSONresponse( $req, undef, code => 204 );
}
sub _getCasAppByConfKey {
my ( $self, $conf, $confKey ) = @_;
# Check if confKey is defined
return undef unless ( defined $conf->{casAppMetaDataOptions}->{$confKey} );
# Get exported vars
my $exportedVars = $conf->{casAppMetaDataExportedVars}->{$confKey};
# # Get extra claim
# my $extraClaims = $conf->{casAppMetaDataOptionsExtraClaims}->{$confKey};
# Get macros
my $macros = $conf->{casAppMetaDataMacros}->{$confKey} || {};
# Get options
my $options = {};
for
my $configOption ( keys %{ $conf->{casAppMetaDataOptions}->{$confKey} } )
{
$options->{ $self->_translateOptionConfToApi($configOption) } =
$conf->{casAppMetaDataOptions}->{$confKey}->{$configOption};
}
return {
confKey => $confKey,
exportedVars => $exportedVars,
macros => $macros,
options => $options
};
}
sub _getCasAppByServiceUrl {
my ( $self, $conf, $serviceUrl ) = @_;
my ($serviceHost) = $serviceUrl =~ m#^(https?://[^/]+)(?:/.*)?$#;
return undef unless $serviceHost;
foreach ( keys %{ $conf->{casAppMetaDataOptions} } ) {
my $url =
$conf->{casAppMetaDataOptions}->{$_}->{casAppMetaDataOptionsService};
my ($curHost) = $url =~ m#^(https?://[^/]+)(?:/.*)?$#;
if ( $serviceHost eq $curHost ) {
return $self->_getCasAppByConfKey( $conf, $_ );
}
}
return undef;
}
sub _isNewCasAppServiceUrlUnique {
my ( $self, $conf, $confKey, $casApp ) = @_;
my $curServiceUrl = $self->_getCasAppByConfKey( $conf, $confKey )->{options}->{service};
my $newServiceUrl = $casApp->{options}->{service} || "";
if ( $newServiceUrl ne '' && $newServiceUrl ne $curServiceUrl ) {
return {
res => 'ko',
msg =>
"A CAS application with service URL '$newServiceUrl' already exists"
}
if ( defined $self->_getCasAppByServiceUrl( $conf, $newServiceUrl ) );
}
return { res => 'ok' };
}
sub _pushCasApp {
my ( $self, $conf, $confKey, $push, $replace ) = @_;
my $translatedOptions = {};
if ($replace) {
$conf->{casAppMetaDataOptions}->{$confKey} = {};
$conf->{casAppMetaDataExportedVars}->{$confKey} = {};
$conf->{casAppMetaDataMacros}->{$confKey} = {};
$translatedOptions = $self->_getDefaultValues('casAppMetaDataNodes');
}
if ( defined $push->{options} ) {
foreach ( keys %{ $push->{options} } ) {
$translatedOptions->{ $self->_translateOptionApiToConf( $_,
'casApp' ) } = $push->{options}->{$_};
}
my $res = $self->_hasAllowedAttributes( $translatedOptions,
'casAppMetaDataNode' );
return $res unless ( $res->{res} eq 'ok' );
foreach ( keys %{$translatedOptions} ) {
$conf->{casAppMetaDataOptions}->{$confKey}->{$_} =
$translatedOptions->{$_};
}
}
if ( defined $push->{exportedVars} ) {
if ( $self->_isSimpleKeyValueHash( $push->{exportedVars} ) ) {
foreach ( keys %{ $push->{exportedVars} } ) {
$conf->{casAppMetaDataExportedVars}->{$confKey}->{$_} =
$push->{exportedVars}->{$_};
}
}
else {
return {
res => 'ko',
msg =>
"Invalid input: exportedVars is not a hash object with \"key\":\"value\" attributes"
};
}
}
if ( defined $push->{macros} ) {
if ( $self->_isSimpleKeyValueHash( $push->{macros} ) ) {
foreach ( keys %{ $push->{macros} } ) {
$conf->{casAppMetaDataMacros}->{$confKey}->{$_} =
$push->{macros}->{$_};
}
}
else {
return {
res => 'ko',
msg =>
"Invalid input: macros is not a hash object with \"key\":\"value\" attributes"
};
}
}
# Test new configuration
my $parser = Lemonldap::NG::Manager::Conf::Parser->new( {
refConf => $self->_confAcc->getConf,
newConf => $conf,
req => {},
}
);
unless ( $parser->testNewConf( $self->p ) ) {
return {
res => 'ko',
code => 400,
msg => "Configuration error: "
. join( ". ", map { $_->{message} } @{ $parser->errors } ),
};
}
# Save configuration
$self->_confAcc->saveConf($conf);
return { res => 'ok' };
}
1;

View File

@ -252,7 +252,7 @@ m[^(?:(?:\-+\s*BEGIN\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+\r?\n)?[a-zA-Z0-9/\+\r\
'form' => 'text',
'msgFail' => '__badUrl__',
'test' =>
qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)))(?::(?:(?:[0-9]*)))?(?:\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*)(?:\/(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*))*))(?:[?](?:(?:(?:[;\/?:@&=+\$,a-zA-Z0-9\-_.!~*'()]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)))?))?)/
qr/(?:^$|(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)))(?::(?:(?:[0-9]*)))?(?:\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*)(?:\/(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*))*))(?:[?](?:(?:(?:[;\/?:@&=+\$,a-zA-Z0-9\-_.!~*'()]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)))?))?))/
}
};
}
@ -636,7 +636,7 @@ sub attributes {
'type' => 'bool'
},
'bruteForceProtectionLockTimes' => {
'default' => '5 15 60 300 600',
'default' => '5, 15, 60, 300, 600',
'type' => 'text'
},
'bruteForceProtectionMaxAge' => {
@ -1651,8 +1651,7 @@ qr/^(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-
'type' => 'text'
},
'ldapPort' => {
'default' => 389,
'type' => 'int'
'type' => 'int'
},
'ldapPpolicyControl' => {
'default' => 0,
@ -1743,7 +1742,7 @@ m[^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
'localSessionStorageOptions' => {
'default' => {
'cache_depth' => 3,
'cache_root' => '/tmp',
'cache_root' => '/var/cache/lemonldap-ng',
'default_expires_in' => 600,
'directory_umask' => '007',
'namespace' => 'lemonldap-ng-sessions'
@ -2581,7 +2580,7 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'type' => 'boolOrExpr'
},
'portalDisplayOidcConsents' => {
'default' => '$_oidcConsents',
'default' => '$_oidcConsents && $_oidcConsents =~ /\\w+/',
'type' => 'boolOrExpr'
},
'portalDisplayPasswordPolicy' => {
@ -2922,8 +2921,12 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'type' => 'url'
},
'sameSite' => {
'default' => 'None',
'default' => '',
'select' => [ {
'k' => '',
'v' => ''
},
{
'k' => 'Strict',
'v' => 'Strict'
},

View File

@ -326,6 +326,12 @@ use constant HANDLERSECTION => "handler";
use constant MANAGERSECTION => "manager";
use constant SESSIONSEXPLORERSECTION => "sessionsExplorer";
use constant APPLYSECTION => "apply";
# Default configuration backend
use constant DEFAULTCONFBACKEND => "File";
use constant DEFAULTCONFBACKENDOPTIONS => (
dirName => '/usr/local/lemonldap-ng/data/conf',
);
$confConstants
our \@sessionTypes = ( '$sessionTypes' );
@ -348,6 +354,8 @@ our %EXPORT_TAGS = (
MANAGERSECTION
SESSIONSEXPLORERSECTION
APPLYSECTION
DEFAULTCONFBACKEND
DEFAULTCONFBACKENDOPTIONS
NO
\$hashParameters
\@sessionTypes
@ -425,7 +433,7 @@ sub buildZeroConf {
open( F, '>', $self->firstLmConfFile ) or die($!);
my $tmp = Lemonldap::NG::Manager::Conf::Zero::zeroConf(
'__DNSDOMAIN__', '__SESSIONDIR__',
'__PSESSIONDIR__', '__NOTIFICATIONDIR__'
'__PSESSIONDIR__', '__NOTIFICATIONDIR__', '__CACHEDIR__'
);
$tmp->{cfgNum} = 1;
print F $jsonEnc->encode($tmp);

View File

@ -30,9 +30,10 @@ sub perlExpr {
return $err ? ( -1, "__badExpression__: $err" ) : (1);
}
my $url = $RE{URI}{HTTP}{ -scheme => "https?" };
$url =~ s/(?<=[^\\])\$/\\\$/g;
$url = qr/$url/;
my $url_re = $RE{URI}{HTTP}{ -scheme => "https?" };
$url_re =~ s/(?<=[^\\])\$/\\\$/g;
my $url = qr/$url_re/;
my $urlOrEmpty = qr/(?:^$|$url_re)/;
sub types {
return {
@ -51,7 +52,7 @@ sub types {
},
url => {
form => 'text',
test => $url,
test => $urlOrEmpty,
msgFail => '__badUrl__',
},
PerlModule => {
@ -844,7 +845,7 @@ sub attributes {
},
bruteForceProtectionLockTimes => {
type => 'text',
default => '5 15 60 300 600',
default => '5, 15, 60, 300, 600',
documentation =>
'Incremental lock time values for brute force attack protection',
},
@ -1099,7 +1100,7 @@ sub attributes {
},
portalDisplayOidcConsents => {
type => 'boolOrExpr',
default => '$_oidcConsents',
default => '$_oidcConsents && $_oidcConsents =~ /\w+/',
documentation => 'Display OIDC consent tab in portal',
},
portalDisplayGeneratePassword => {
@ -1165,11 +1166,12 @@ sub attributes {
sameSite => {
type => 'select',
select => [
{ k => '', v => '' },
{ k => 'Strict', v => 'Strict' },
{ k => 'Lax', v => 'Lax' },
{ k => 'None', v => 'None' },
],
default => 'None',
default => '',
documentation => 'Cookie SameSite value',
flags => 'hp',
},
@ -1346,7 +1348,7 @@ sub attributes {
'namespace' => 'lemonldap-ng-sessions',
'default_expires_in' => 600,
'directory_umask' => '007',
'cache_root' => '/tmp',
'cache_root' => '/var/cache/lemonldap-ng',
'cache_depth' => 3,
},
documentation => 'Sessions cache module options',
@ -3130,7 +3132,6 @@ sub attributes {
},
ldapPort => {
type => 'int',
default => 389,
documentation => 'LDAP port',
},
ldapServer => {

View File

@ -642,7 +642,8 @@ sub tree {
'notificationStorageOptions',
{
title => 'serverNotification',
help => 'notifications.html#notification-server',
help =>
'notifications.html#notification-server',
nodes => [
'notificationServer',
'notificationDefaultCond',
@ -971,7 +972,10 @@ sub tree {
form => 'simpleInputContainer',
nodes => [
'bruteForceProtection',
'bruteForceProtectionTempo',
'bruteForceProtectionMaxFailed',
'bruteForceProtectionIncrementalTempo',
'bruteForceProtectionLockTimes',
]
},
'lwpOpts',

View File

@ -159,7 +159,7 @@ sub prx {
# - getConfByNum: override SUPER method to be able to use Zero
# - newConf()
# - newRawConf(): restore a saved conf
# - applyConf(): called by the 2 previous to prevent other servers that a new
# - applyConf(): called by the 2 previous to inform other servers that a new
# configuration is available
sub getConfByNum {
@ -229,7 +229,7 @@ sub newConf {
if ( $cfgNum ne $req->params('cfgNum') ) { $parser->confChanged(1); }
my $res = { result => $parser->check($self) };
my $res = { result => $parser->check($self->p) };
# "message" fields: note that words enclosed by "__" (__word__) will be
# translated
@ -328,7 +328,7 @@ sub newRawConf {
}
## @method private applyConf()
# Try to prevent other servers declared in `reloadUrls` that a new
# Try to inform other servers declared in `reloadUrls` that a new
# configuration is available.
#
#@return reload status as boolean
@ -352,6 +352,7 @@ sub applyConf {
# Parse apply values
while ( my ( $host, $request ) = each %reloadUrls ) {
my $r = HTTP::Request->new( 'GET', "http://$host$request" );
$self->logger->debug("Sending reload request to $host");
if ( $request =~ /^https?:\/\/[^\/]+.*$/ ) {
my $url = URI::URL->new($request);
my $targetUrl = $url->scheme . "://" . $host;

View File

@ -3,6 +3,7 @@ package Lemonldap::NG::Manager::Conf::Tests;
use utf8;
use Lemonldap::NG::Common::Regexp;
use Lemonldap::NG::Handler::Main;
use Lemonldap::NG::Common::Util qw(getSameSite);
our $VERSION = '2.1.0';
@ -253,8 +254,6 @@ sub tests {
return ( 1, "Cookie TTL should be higher or equal than one hour" )
unless ( $conf->{cookieExpiration} >= 3600
|| $conf->{cookieExpiration} == 0 );
# Return
return 1;
},
@ -264,8 +263,6 @@ sub tests {
return ( -1, "Session timeout should be higher than ten minutes" )
unless ( $conf->{timeout} > 600
|| $conf->{timeout} == 0 );
# Return
return 1;
},
@ -277,8 +274,6 @@ sub tests {
)
unless ( $conf->{timeoutActivity} > 59
|| $conf->{timeoutActivity} == 0 );
# Return
return 1;
},
@ -291,8 +286,6 @@ sub tests {
if ( $conf->{timeoutActivity}
and $conf->{timeoutActivity} <=
$conf->{timeoutActivityInterval} );
# Return
return 1;
},
@ -337,12 +330,10 @@ sub tests {
return ( 1, "SMTP authentication failed" )
unless $smtp->auth( $conf->{SMTPAuthUser},
$conf->{SMTPAuthPass} );
# Return
return 1;
},
# SAML entity ID must be uniq
# SAML entity ID must be unique
samlIDPEntityIdUniqueness => sub {
return 1
unless ( $conf->{samlIDPMetaDataXML}
@ -440,8 +431,6 @@ sub tests {
unless ( $conf->{combination} );
return ( 0, 'userDB must be set to "Same" to enable Combination' )
unless ( $conf->{userDB} eq "Same" );
# Return
return 1;
},
@ -481,8 +470,6 @@ sub tests {
"Auth::Yubikey_WebClient module is required to enable Yubikey"
) if ($@);
}
# Return
return 1;
},
@ -520,8 +507,6 @@ sub tests {
unless ( $conf->{totp2fRange} );
return ( 1, "TOTP interval should be higher than 10s" )
unless ( $conf->{totp2fInterval} > 10 );
# Return
return 1;
},
@ -569,7 +554,6 @@ sub tests {
|| $conf->{'totp2fSelfRegistration'} );
$msg = "A self registrable module should be enabled to require 2FA"
unless ($ok);
return ( 1, $msg );
},
@ -582,8 +566,6 @@ sub tests {
return ( 0, "External 2F Validate command must be set" )
unless ( defined $conf->{ext2FValidateCommand} );
}
# Return
return 1;
},
@ -594,8 +576,6 @@ sub tests {
unless ( $conf->{formTimeout} > 30 );
return ( 1, "XSRF form token TTL should not be higher than 2mn" )
if ( $conf->{formTimeout} > 120 );
# Return
return 1;
},
@ -606,8 +586,6 @@ sub tests {
unless ( $conf->{issuersTimeout} > 30 );
return ( 1, "Issuers token TTL should not be higher than 2mn" )
if ( $conf->{issuersTimeout} > 120 );
# Return
return 1;
},
@ -616,21 +594,16 @@ sub tests {
return 1 unless ( $conf->{portalDisplayResetPassword} );
return ( 1, "Number of reset password retries should not be null" )
unless ( $conf->{passwordResetAllowedRetries} );
# Return
return 1;
},
# Warn if ldapPpolicyControl is used with AD (#2007)
ppolicyAd => sub {
if ( $conf->{ldapPpolicyControl}
and $conf->{authentication} eq "AD" )
{
return ( 1,
return ( 1,
"LDAP password policy control should be disabled when using AD authentication"
);
}
)
if ( $conf->{ldapPpolicyControl}
and $conf->{authentication} eq "AD" );
return 1;
},
@ -643,8 +616,18 @@ sub tests {
return ( 1,
'Number of failed logins must be higher than 2 to enable "BruteForceProtection" plugin'
) unless ( $conf->{failedLoginNumber} > 2 );
# Return
return ( 1,
'Number of failed logins history must be higher than allowed failed logins plus lock time values'
)
if ( $conf->{bruteForceProtectionIncrementalTempo}
&& $conf->{failedLoginNumber} <=
$conf->{bruteForceProtectionMaxFailed} +
$conf->{bruteForceProtectionLockTimes} );
return ( 1,
'Number of failed logins history must be higher than allowed failed logins'
)
unless ( $conf->{failedLoginNumber} >
$conf->{bruteForceProtectionMaxFailed} );
return 1;
},
@ -656,8 +639,6 @@ sub tests {
)
unless ( $conf->{requireToken}
or $conf->{captcha_mail_enabled} );
# Return
return 1;
},
@ -668,8 +649,6 @@ sub tests {
)
if ( $conf->{impersonationRule}
&& $conf->{contextSwitchingRule} );
# Return
return 1;
},
@ -693,8 +672,6 @@ sub tests {
return ( 1,
"BruteForceProtection plugin enabled WITHOUT persistent session storage"
) if ( $conf->{bruteForceProtection} );
# Return
return 1;
},
@ -709,8 +686,6 @@ sub tests {
return ( 1,
"XML::LibXSLT module is required to enable old format notifications"
) if ($@);
# Return
return 1;
},
@ -724,8 +699,6 @@ sub tests {
return ( 1,
"DateTime::Format::RFC3339 module is required to enable CertificateResetByMail plugin"
) if ($@);
# Return
return 1;
},
@ -770,6 +743,71 @@ sub tests {
return 1;
},
# OIDC RP Client ID must exist and be unique
oidcRPClientIdUniqueness => sub {
return 1
unless ( $conf->{oidcRPMetaDataOptions}
and %{ $conf->{oidcRPMetaDataOptions} } );
my @msg;
my $res = 1;
my %clientIds;
foreach
my $clientConfKey ( keys %{ $conf->{oidcRPMetaDataOptions} } )
{
my $clientId = $conf->{oidcRPMetaDataOptions}->{$clientConfKey}
->{oidcRPMetaDataOptionsClientID};
unless ($clientId) {
push @msg,
"$clientConfKey OIDC Relying Party has no Client ID";
$res = 0;
next;
}
if ( defined $clientIds{$clientId} ) {
push @msg,
"$clientConfKey and $clientIds{$clientId} have the same Client ID";
$res = 0;
next;
}
$clientIds{$clientId} = $clientConfKey;
}
return ( $res, join( ', ', @msg ) );
},
# CAS APP URL must be unique
casAppHostnameUniqueness => sub {
return 1
unless ( $conf->{casAppMetaDataOptions}
and %{ $conf->{casAppMetaDataOptions} } );
my @msg;
my $res = 1;
my %casHosts;
foreach my $casConfKey ( keys %{ $conf->{casAppMetaDataOptions} } )
{
my $appUrl =
$conf->{casAppMetaDataOptions}->{$casConfKey}
->{casAppMetaDataOptionsService}
|| "";
$appUrl =~ m#^(https?://[^/]+)(/.*)?$#;
my $appHost = $1;
unless ($appHost) {
push @msg,
"$clientConfKey CAS Application has no Service URL";
$res = 0;
next;
}
if ( defined $casHosts{$appHost} ) {
push @msg,
"$casConfKey and $casHosts{$appHost} have the same Service hostname";
$res = 0;
next;
}
$casHosts{$appHost} = $casConfKey;
}
return ( $res, join( ', ', @msg ) );
},
# Notification system required with removed SF notification
sfRemovedNotification => sub {
return 1 unless ( $conf->{sfRemovedMsgRule} );
@ -778,6 +816,32 @@ sub tests {
) if ( $conf->{sfRemovedUseNotif} and not $conf->{notification} );
return 1;
},
# noAjaxHook and krbByJs are incompatible (#2237)
noAjaxHookwithKrb => sub {
return ( 1,
'noAjaxHook is not compatible with'
. ' AJAX Kerberos authentication' )
if ( $conf->{noAjaxHook} and $conf->{krbByJs} );
return 1;
},
# Cookie SameSite=None requires Secure flag
# Same with SameSite=(auto) and SAML issuer in use
SameSiteNoneWithSecure => sub {
return ( 1, 'SameSite value = None requires the secured flag' )
if ( getSameSite($conf) eq 'None' and !$conf->{securedCookie} );
return 1;
},
# Secure cookies require HTTPS
SecureCookiesRequireHttps => sub {
return ( 1, 'Secure cookies require a HTTPS portal URL' )
if ( $conf->{securedCookie} == 1
and $conf->{portal}
and $conf->{portal} !~ /^https:/ );
return 1;
},
};
}

View File

@ -3,11 +3,12 @@ package Lemonldap::NG::Manager::Conf::Zero;
our $VERSION = '2.1.0';
sub zeroConf {
my ( $domain, $sessionDir, $persistentSessionDir, $notificationDir ) = @_;
my ( $domain, $sessionDir, $persistentSessionDir, $notificationDir, $cacheDir ) = @_;
$domain ||= 'example.com';
$sessionDir ||= '/var/lib/lemonldap-ng/sessions';
$persistentSessionDir ||= '/var/lib/lemonldap-ng/psessions';
$notificationDir ||= '/var/lib/lemonldap-ng/notifications';
$cacheDir ||= '/var/cache/lemonldap-ng/';
return {
'timeout' => 72000,
'loginHistoryEnabled' => 1,
@ -187,7 +188,7 @@ sub zeroConf {
'namespace' => 'lemonldap-ng-sessions',
'default_expires_in' => 600,
'directory_umask' => '007',
'cache_root' => '/tmp',
'cache_root' => "$cacheDir",
'cache_depth' => 3,
},
};

View File

@ -154,7 +154,11 @@ sub notifications {
or die "Unknown type $type";
# Case 1: a notification is required
return $self->notification( $req, $notif, $type ) if ($notif);
if ($notif) {
my $params = $req->parameters();
return $self->notification( $req, $notif, $type, $params->{uid},
$params->{reference} );
}
# Case 2: list
my $params = $req->parameters();
@ -175,7 +179,8 @@ sub notifications {
$value = qr/^$value$/;
foreach my $k ( keys %$notifs ) {
delete $notifs->{$k}
unless ( $notifs->{$k}->{$field} =~ $value );
unless ( $notifs->{$k}->{$field}
&& $notifs->{$k}->{$field} =~ $value );
}
}
}
@ -238,10 +243,10 @@ sub notifications {
}
sub notification {
my ( $self, $req, $id, $type ) = @_;
my ( $self, $req, $id, $type, $uid, $ref ) = @_;
if ( $type eq 'actives' ) {
my ( $uid, $ref ) = ( $id =~ /([^_]+?)_(.+)/ );
( $uid, $ref ) = ( $id =~ /([^_]+?)_(.+)/ );
my $n = $self->notifAccess->get( $uid, $ref );
unless ($n) {
$self->userLogger->notice(
@ -258,8 +263,20 @@ sub notification {
{ result => 1, count => 1, notifications => [ values %$n ] } );
}
else {
my $n = $self->notifAccess->getAccepted( $uid, $ref );
unless ($n) {
$self->userLogger->notice(
"Notification $ref not found for user $uid");
return $self->sendJSONresponse(
$req,
{
result => 0,
error => "Notification $ref not found for user $uid"
}
);
}
return $self->sendJSONresponse( $req,
{ result => 1, count => 1, done => $id } );
{ result => 1, count => 1, done => $id, notifications => [ values %$n ] } );
}
}

View File

@ -4,6 +4,7 @@ use Lemonldap::NG::Common::Conf;
use Lemonldap::NG::Common::Conf::Constants;
use Lemonldap::NG::Manager::Conf::Parser;
use Lemonldap::NG::Handler::Main::Jail;
use Lemonldap::NG::Manager::Cli::Lib;
use Data::Dumper;
use English qw(-no_match_vars);
use File::Temp;
@ -11,6 +12,8 @@ use POSIX qw(setuid setgid);
use Safe;
use strict;
my $cli = Lemonldap::NG::Manager::Cli::Lib->new;
eval {
setgid( ( getgrnam('__APACHEGROUP__') )[2] );
setuid( ( getpwnam('__APACHEUSER__') )[2] );
@ -73,7 +76,7 @@ if (`diff $refFile $editFile`) {
req => 1,
}
);
unless ( $parser->testNewConf(undef) ) {
unless ( $parser->testNewConf( $cli->mgr ) ) {
print STDERR "Configuration seems to have some errors:\n ";
print STDERR Dumper(
{ errors => $parser->errors, warnings => $parser->warnings } );

View File

@ -108,8 +108,10 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
# Delete 2FA device
$scope.delete2FA = (type, epoch) ->
item = angular.element(".data-#{epoch}")
item.remove()
#item = angular.element(".data-#{epoch}")
items = document.querySelectorAll(".data-#{epoch}")
for e in items
e.remove()
$scope.waiting = true
$http['delete']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?type=#{type}&epoch=#{epoch}").then (response) ->
$scope.waiting = false

View File

@ -206,26 +206,29 @@ llapp.controller 'NotificationsExplorerCtrl', [ '$scope', '$translator', '$locat
$scope.currentScope = scope
node = scope.$modelValue
notificationId = node.notification
query = ''
if $scope.type == 'actives'
notificationId = "#{node.uid}_#{node.reference}"
$http.get("#{scriptname}notifications/#{$scope.type}/#{notificationId}").then (response) ->
if $scope.type == 'done'
query = "?uid=#{node.uid}&reference=#{node.reference}"
$http.get("#{scriptname}notifications/#{$scope.type}/#{notificationId}#{query}").then (response) ->
$scope.currentNotification =
uid: node.uid
reference: node.reference
condition: node.condition
if $scope.type == 'actives'
try
console.log "Try to parse a JSON formated notification..."
notif = JSON.parse response.data.notifications
$scope.currentNotification.date = $scope.notifDate(notif.date)
$scope.currentNotification.text = notif.text
$scope.currentNotification.title = notif.title
$scope.currentNotification.subtitle = notif.subtitle
catch e
console.log "Unable to parse JSON"
$scope.currentNotification.notifications = response.data.notifications
else
if $scope.type == 'done'
$scope.currentNotification.done = response.data.done
try
console.log "Try to parse a JSON formated notification..."
notif = JSON.parse response.data.notifications
$scope.currentNotification.date = $scope.notifDate(notif.date)
$scope.currentNotification.text = notif.text
$scope.currentNotification.title = notif.title
$scope.currentNotification.subtitle = notif.subtitle
$scope.currentNotification.check = notif.check
catch e
console.log "Unable to parse JSON"
$scope.currentNotification.notifications = response.data.notifications
$scope.waiting = false
, (resp) ->
$scope.waiting = false

View File

@ -42,9 +42,9 @@
<input class="form-control" ng-model="t[1]" />
</td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.data[5],$index)"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.data[5],$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign"
ng-click="menuClick({title:'newCmbOver', action:'newChoiceOver'})" />
ng-click="menuClick({title:'newCmbOver', action:'newChoiceOver'})"></span>
</td>
</tr>
</table>

View File

@ -30,8 +30,8 @@
<input class="form-control" ng-model="c.data[4]" aria-describedby="condlabel">
</td>
<td width="5%">
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newChain',action: 'newAuthChoice'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newChain',action: 'newAuthChoice'})"></span>
</td>
</tr>
</tbody>

View File

@ -10,8 +10,8 @@
<tr ng-repeat="s in currentNode.nodes">
<td><input class="form-control" ng-model="s.title"/></td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addAppCasPartner',action:'addCasApp'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addAppCasPartner',action:'addCasApp'})"></span>
</td>
</tr>
</tbody>

View File

@ -10,8 +10,8 @@
<tr ng-repeat="s in currentNode.nodes">
<td><input class="form-control" ng-model="s.title"/></td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addSrvCasPartner',action:'addCasSrv'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addSrvCasPartner',action:'addCasSrv'})"></span>
</td>
</tr>
</tbody>

View File

@ -8,7 +8,7 @@
<th width="25%" trspan="name"></th>
<th width="25%" trspan="type"></th>
<th width="35%" trspan="use"></th>
<th />
<th></th>
</tr>
</thead>
<tbody>
@ -23,9 +23,9 @@
</td>
<td>
<select class="form-control" ng-model="currentNode.data.for">
<option value="0" ng-selected="currentNode.data.for==0" trspan="authAndUserdb">
<option value="1" ng-selected="currentNode.data.for==1" trspan="authOnly">
<option value="2" ng-selected="currentNode.data.for==2" trspan="userdbOnly">
<option value="0" ng-selected="currentNode.data.for==0" trspan="authAndUserdb"></option>
<option value="1" ng-selected="currentNode.data.for==1" trspan="authOnly"></option>
<option value="2" ng-selected="currentNode.data.for==2" trspan="userdbOnly"></option>
</select>
</td>
</tr>
@ -41,8 +41,8 @@
<input class="form-control" ng-model="t[1]" />
</td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.data.over,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newCmbOver'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.data.over,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newCmbOver'})"></span>
</td>
</tr>
</table>

View File

@ -8,7 +8,7 @@
<th width="25%" trspan="name"></th>
<th width="25%" trspan="type"></th>
<th width="35%" trspan="use"></th>
<th />
<th></th>
</tr>
</thead>
<tbody>
@ -23,14 +23,14 @@
</td>
<td>
<select class="form-control" ng-model="s.data.for">
<option value="0" ng-selected="s.data.for==0" trspan="authAndUserdb">
<option value="1" ng-selected="s.data.for==1" trspan="authOnly">
<option value="2" ng-selected="s.data.for==2" trspan="userdbOnly">
<option value="0" ng-selected="s.data.for==0" trspan="authAndUserdb"></option>
<option value="1" ng-selected="s.data.for==1" trspan="authOnly"></option>
<option value="2" ng-selected="s.data.for==2" trspan="userdbOnly"></option>
</select>
</td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newCmbMod'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newCmbMod'})"></span>
</td>
</tr>
</tbody>

View File

@ -16,8 +16,8 @@ Special container to show hash in hash
<th width="40%" trspan="keys"></th>
<th width="40%" trspan="values"></th>
<th>
<span class="link text-success glyphicon glyphicon-plus-sign" ng-click="n.h.push({'k':'key','v':'uid'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.data,$index)"/>
<span class="link text-success glyphicon glyphicon-plus-sign" ng-click="n.h.push({'k':'key','v':'uid'})"></span>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.data,$index)"></span>
</th>
</tr>
</thead>
@ -26,7 +26,7 @@ Special container to show hash in hash
<td><input class="form-control" ng-model="get.k"/></td>
<td><input class="form-control" ng-model="get.v"/></td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="n.h.splice($index,1)"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="n.h.splice($index,1)"></span>
</td>
</tr>
</tbody>

View File

@ -8,7 +8,7 @@
<th width="20%" trspan="comments"></th>
<th width="30%" trspan="rules"></th>
<th width="50%" trspan="messages"></th>
<th />
<th></th>
</tr>
</thead>
<tbody>
@ -23,8 +23,8 @@
<input class="form-control" ng-model="s.re" ng-change="changeRuleTitle(s)"/>
</td>
<td>
<span ng-model="s.title" class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newGrantRule',action:'newGrantRule'})"/>
<span ng-model="s.title" class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newGrantRule',action:'newGrantRule'})"></span>
</td>
</tr>
</tbody>

View File

@ -4,7 +4,7 @@
</div>
<table class="table">
<tr>
<th trspan="value" />
<th trspan="value"></th>
<td><input id="intinput" type="number" class="form-control" ng-model="currentNode.data"/></td>
</tr>
</table>

View File

@ -5,11 +5,11 @@
<table class="table">
<tr>
<th><span trspan="hashkey"></span></th>
<td><input id="hashkeyinput" class="form-control" ng-model="currentNode.title"/>
<td><input id="hashkeyinput" class="form-control" ng-model="currentNode.title"/></td>
</tr>
<tr>
<th><span trspan="value"></span></th>
<td><input id="hashvalueinput" class="form-control" ng-model="currentNode.data"/>
<td><input id="hashvalueinput" class="form-control" ng-model="currentNode.data"/></td>
</tr>
</table>
</div>

View File

@ -15,8 +15,8 @@
<td><input class="form-control" ng-model="s.title"/></td>
<td><input id-"hv-{{s.title}}" class="form-control" ng-model="s.data"/></td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newEntry',action:'newHashEntry'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newEntry',action:'newHashEntry'})"></span>
</td>
</tr>
</tbody>

View File

@ -29,28 +29,28 @@
</button>
</div>
<div class="col-md-11">
<input id="ilogo" type="search" list="logos" class="form-control" ng-model="currentNode.data.logo"/></td>
<input id="ilogo" type="search" list="logos" class="form-control" ng-model="currentNode.data.logo"/>
<datalist id="logos">
<option value="attach.png">
<option value="bell.png">
<option value="bookmark.png">
<option value="configure.png">
<option value="database.png">
<option value="demo.png">
<option value="docs.png">
<option value="folder.png">
<option value="gear.png">
<option value="help.png">
<option value="llng.png">
<option value="mailappt.png">
<option value="money.png">
<option value="network.png">
<option value="terminal.png">
<option value="thumbnail.png">
<option value="tools.png">
<option value="tux.png">
<option value="web.png">
<option value="wheels.png">
<option value="attach.png"></option>
<option value="bell.png"></option>
<option value="bookmark.png"></option>
<option value="configure.png"></option>
<option value="database.png"></option>
<option value="demo.png"></option>
<option value="docs.png"></option>
<option value="folder.png"></option>
<option value="gear.png"></option>
<option value="help.png"></option>
<option value="llng.png"></option>
<option value="mailappt.png"></option>
<option value="money.png"></option>
<option value="network.png"></option>
<option value="terminal.png"></option>
<option value="thumbnail.png"></option>
<option value="tools.png"></option>
<option value="tux.png"></option>
<option value="web.png"></option>
<option value="wheels.png"></option>
</datalist>
</div>
</div>

View File

@ -10,8 +10,8 @@
<tr ng-repeat="s in currentNode.nodes">
<td><input class="form-control" ng-model="s.title"/></td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addOidcOp'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addOidcOp'})"></span>
</td>
</tr>
</tbody>

View File

@ -10,8 +10,8 @@
<tr ng-repeat="s in currentNode.nodes">
<td><input class="form-control" ng-model="s.title"/></td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addOidcRp'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addOidcRp'})"></span>
</td>
</tr>
</tbody>

View File

@ -26,15 +26,15 @@
<tr>
<th colspan="3">
<span trspan="postedVars"></span>
<span ng-if="!currentNode.data.vars.length" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newPostVar'})"/>
<span ng-if="!currentNode.data.vars.length" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newPostVar'})"></span>
</th>
</tr>
<tr ng-repeat="v in currentNode.data.vars">
<td><input class="form-control" ng-model="v[0]"/></td>
<td><input class="form-control" ng-model="v[1]"/></td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.data.vars,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newPostVar'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.data.vars,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newPostVar'})"></span>
</td>
</tr>
</table>

View File

@ -30,8 +30,8 @@
<input class="form-control" ng-model="s.data.buttonSelector"/>
</td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newPost'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newPost'})"></span>
</td>
</tr>
</tbody>

View File

@ -4,11 +4,11 @@
</div>
<table class="table">
<tr>
<th trspan="fileToUpload" />
<th trspan="fileToUpload"></th>
<td>
<input id="fileinput" type="file" class="form-control" on-read-file="saveRawConf($fileContent)" />
</td>
<tr>
</tr>
</table>
</div>
<script type="text/menu">

View File

@ -9,7 +9,7 @@
<th trspan="regexps"></th>
<th trspan="rules"></th>
<th class="th_rulesAuthnLevel" trspan="rulesAuthnLevel"></th>
<th class="th_rulesAuthnLevel" />
<th class="th_rulesAuthnLevel"></th>
</tr>
</thead>
<tbody>
@ -36,8 +36,8 @@
<input class="form-control" placeholder="default" readonly/>
</td>
<td>
<span ng-if="s.re!='default'" class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newRule'})"/>
<span ng-if="s.re!='default'" class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newRule'})"></span>
</td>
</tr>
</tbody>

View File

@ -40,8 +40,8 @@
</select>
</td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addSamlAttribute'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addSamlAttribute'})"></span>
</td>
</tr>
</tbody>

View File

@ -10,8 +10,8 @@
<tr ng-repeat="s in currentNode.nodes">
<td><input class="form-control" ng-model="s.title"/></td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addSamlIDPSamlPartner',action:'addSamlIDP'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addSamlIDPSamlPartner',action:'addSamlIDP'})"></span>
</td>
</tr>
</tbody>

View File

@ -10,8 +10,8 @@
<tr ng-repeat="s in currentNode.nodes">
<td><input class="form-control" ng-model="s.title"/></td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addSPSamlPartner',action:'addSamlSP'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.nodes,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'addSPSamlPartner',action:'addSamlSP'})"></span>
</td>
</tr>
</tbody>

View File

@ -17,7 +17,7 @@
</tr>
<!-- URL -->
<tr>
<th><span trspan="url"></th>
<th><span trspan="url"></span></th>
<td><input id="saninput" class="form-control" ng-model="currentNode.data[1]"/></td>
</tr>
<!-- Return URL -->

View File

@ -11,7 +11,7 @@
<th trspan="logo"></th>
<th trspan="level"></th>
<th trspan="rule"></th>
<th />
<th></th>
</tr>
</thead>
<tbody>
@ -49,8 +49,8 @@
<input class="form-control" ng-model="t[1]" />
</td>
<td>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.data.over,$index)"/>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newSfOver'})"/>
<span class="link text-danger glyphicon glyphicon-minus-sign" ng-click="del(currentNode.data.over,$index)"></span>
<span ng-if="$last" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newSfOver'})"></span>
</td>
</tr>
</table>

Some files were not shown because too many files have changed in this diff Show More