diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 894f6158acd37e948bee17a562e3340e04c95253..d76a8d006fe6c363173e6b09d22aa3ffdeb98acd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,6 @@ .build_job: stage: build + retry: 1 artifacts: expire_in: 1 hour paths: @@ -52,7 +53,7 @@ build_centos_7: script: - rm -f /etc/yum.repos.d/CentOS-Sources.repo - yum -y install epel-release - - make rpm-dist + - make dist - ci-build-pkg build_centos_8: @@ -62,7 +63,7 @@ build_centos_8: - yum-config-manager --enable PowerTools - yum-config-manager --enable AppStream - yum -y install epel-release - - make rpm-dist + - make dist - ci-build-pkg sign: @@ -80,7 +81,7 @@ sign: # - build_xenial - build_bionic - build_centos_7 -# - build_centos_8 + - build_centos_8 artifacts: expire_in: 1 day paths: @@ -130,7 +131,7 @@ sonar-upload: stage: deploy image: sonarsource/sonar-scanner-cli script: - - sonar-scanner + - /usr/bin/entrypoint.sh only: variables: - $SONARJOB == "1" diff --git a/Makefile b/Makefile index 98b9a43fe4e53ca074fbc1289808fb51b89f2ca3..7d0a07db19891ddf513472578e8e98c2fc97f3ae 100644 --- a/Makefile +++ b/Makefile @@ -1085,23 +1085,13 @@ manager_uninstall: manager dist: clean @mkdir -p lemonldap-ng-$(VERSION) - @cp -pRH $$(find * -maxdepth 0|grep -v -e "\(lemonldap-ng-$(VERSION)\|debian\|rpm\)") lemonldap-ng-$(VERSION) + @cp -pRH $$(find * -maxdepth 0|grep -v -e "lemonldap-ng-$(VERSION)") lemonldap-ng-$(VERSION) @find $$dir -name '*.bak' -delete @rm -rf lemonldap-ng-$(VERSION)/lemonldap-ng-$(VERSION) @rm -rf lemonldap-ng-$(VERSION)/node_modules @$(COMPRESS) lemonldap-ng-$(VERSION).$(COMPRESSSUFFIX) lemonldap-ng-$(VERSION) @rm -rf lemonldap-ng-$(VERSION) -rpm-dist: clean - @mkdir -p lemonldap-ng-$(VERSION) - @cp -pRH $$(find * -maxdepth 0|grep -v -e "\(lemonldap-ng-$(VERSION)\|debian\)") lemonldap-ng-$(VERSION) - @find $$dir -name '*.bak' -delete - @rm -rf lemonldap-ng-$(VERSION)/lemonldap-ng-$(VERSION) - @rm -rf lemonldap-ng-$(VERSION)/node_modules - @$(COMPRESS) lemonldap-ng-$(VERSION).$(COMPRESSSUFFIX) lemonldap-ng-$(VERSION) - @rm -rf lemonldap-ng-$(VERSION) - - debian-dist: clean @mkdir -p lemonldap-ng-$(VERSION) @cp -pRH $$(find * -maxdepth 0|grep -v -e "\(lemonldap-ng-$(VERSION)\|rpm\)") lemonldap-ng-$(VERSION) diff --git a/RELEASE b/RELEASE index dab1c808eadfd289af50c5b2e648f36c94278910..40d7946246a2186c51458cbe331722aa1f447c77 100644 --- a/RELEASE +++ b/RELEASE @@ -60,10 +60,7 @@ $ make clean && make dist - RedHat packaging: -Create the RPM specific tarball: -$ make clean && make rpm-dist - -Next steps: see rpm/README +See rpm/README - Debian packaging: @@ -87,7 +84,7 @@ Upload modules tarballs (generated by make cpan) - OW2 Release: Upload dist and bundles on sftp://release-up.ow2.org/projects/lemonldap -- RPM: see rpm/REDAME +- RPM: see rpm/README - DEB: The DEB repository is hosted on https://lemonldap-ng.org/deb diff --git a/_example/conf/lmConf-1.json b/_example/conf/lmConf-1.json index 17dffe05b0a03fe6372c3b428d4978b47c7bf178..71b58e1a0033a10a74f20639a405dd501b07a502 100644 --- a/_example/conf/lmConf-1.json +++ b/_example/conf/lmConf-1.json @@ -86,7 +86,7 @@ "authentication" : "Demo", "cfgAuthor" : "The LemonLDAP::NG team", "cfgNum" : 1, - "cfgVersion" : "2.0.9", + "cfgVersion" : "2.0.12", "cookieName" : "lemonldap", "demoExportedVars" : { "cn" : "cn", @@ -127,7 +127,7 @@ "default" : "accept" }, "manager.__DNSDOMAIN__" : { - "(?#Configuration)^/(.*?\\.(fcgi|psgi)/)?(manager\\.html|confs/|$)" : "inGroup(\"timelords\")", + "(?#Configuration)^/(.*?\\.(fcgi|psgi)/)?(manager\\.html|confs|prx/|$)" : "inGroup(\"timelords\")", "(?#Notifications)/(.*?\\.(fcgi|psgi)/)?notifications" : "inGroup(\"timelords\") or $uid eq \"rtyler\"", "(?#Sessions)/(.*?\\.(fcgi|psgi)/)?sessions" : "inGroup(\"timelords\") or $uid eq \"rtyler\"", "default" : "inGroup(\"timelords\") or $uid eq \"rtyler\"" diff --git a/debian/control b/debian/control index 5de22feeb8dc727a6ffa4a5b16e4082e31e06306..49738d1d5021535dfedc19a6a79a24d35423c956 100644 --- a/debian/control +++ b/debian/control @@ -163,8 +163,8 @@ Recommends: lemonldap-ng-fastcgi-server (= ${binary:Version}) | lemonldap-ng-uws Suggests: libcache-memcached-perl, libdigest-hmac-perl, libsoap-lite-perl -Breaks: liblemonldap-ng-handler-perl (<< 1.9.1-2~) -Replaces: liblemonldap-ng-handler-perl (<< 1.9.1-2~) +Breaks: liblemonldap-ng-handler-perl (<< 1.9.1-2~), lemonldap-ng-fastcgi-server (<< 2.0.5~) +Replaces: liblemonldap-ng-handler-perl (<< 1.9.1-2~), lemonldap-ng-fastcgi-server (<< 2.0.5~) Description: Lemonldap::NG handler part Lemonldap::NG is a complete Web-SSO system that can run with reverse-proxies or directly on application webservers. It can be used in conjunction with diff --git a/doc/pages/manager-api/index.html b/doc/pages/manager-api/index.html index 81e7823c5188b54d46f43506171484f1c7dd8b19..31cfce4da7f7be4d828854048c9da9905059f464 100644 --- a/doc/pages/manager-api/index.html +++ b/doc/pages/manager-api/index.html @@ -895,6 +895,11 @@ "default" : "HS512", "enum" : [ "none", "RS256", "RS384", "RS512" ] }, + "userInfoSignAlg" : { + "type" : "string", + "default" : "", + "enum" : [ "", "none", "HS256", "HS384", "HS512", "RS256", "RS384", "RS512" ] + }, "accessTokenJWT" : { "type" : "bool" }, diff --git a/doc/sources/admin/applications.rst b/doc/sources/admin/applications.rst index 26299a3ae943dee4ef62cb10519b94242e2a04fb..2268ab8967e90da56fb52bf01edc5d55a692d649 100644 --- a/doc/sources/admin/applications.rst +++ b/doc/sources/admin/applications.rst @@ -24,15 +24,19 @@ Applications applications/grr applications/guacamole applications/humhub + applications/iparapheur applications/jitsimeet applications/liferay applications/limesurvey applications/mattermost applications/mediawiki + applications/mobilizon applications/nextcloud applications/obm applications/office365 + applications/publik applications/phpldapadmin + applications/redmine applications/roundcube applications/salesforce applications/sap @@ -42,6 +46,7 @@ Applications applications/sympa applications/tomcat applications/wekan + applications/wikijs applications/wordpress applications/xwiki applications/zimbra @@ -53,7 +58,7 @@ To integrate a Web application in LL::NG, you have the following possibilities: - Protect the application with the Handler, and push user identity - trough HTTP headers. This is how main Access Manager products, like + through HTTP headers. This is how main Access Manager products, like CA SiteMinder, are working. This also how Apache authentication modules are working, so if your application is compatible with Apache authentication (often called "external authentifcation"), then you @@ -100,15 +105,19 @@ Application Configuration .. image:: applications/grr_logo.png :doc:`GRR` ✔ .. image:: applications/guacamole.png :doc:`Apache Guacamole` ✔ ✔ ✔ .. image:: applications/humhub_logo.png :doc:`HumHub` ✔ +.. image:: applications/iparapheur_logo.png :doc:`i-Parapheur` ✔ .. image:: applications/logo-jitsimeet.png :doc:`Jitsi Meet` ✔ .. image:: applications/liferay_logo.png :doc:`Liferay` ✔ .. image:: applications/limesurvey_logo.png :doc:`LimeSurvey` ✔ .. image:: applications/mattermost_logo.png :doc:`Mattermost` ✔ .. image:: applications/mediawiki_logo.png :doc:`Mediawiki` ✔ +.. image:: applications/mobilizon_logo.jpg :doc:`Mobilizon` ✔ .. image:: applications/nextcloud-logo.png :doc:`NextCloud` ✔ .. image:: applications/obm_logo.png :doc:`OBM` ✔ .. image:: applications/logo_office_365.png :doc:`Office 365` ✔ +.. image:: applications/logo-publik.png :doc:`Publik` ✔ .. image:: applications/phpldapadmin_logo.png :doc:`phpLDAPAdmin` ✔ +.. image:: applications/redmine_logo.png :doc:`Redmine` ✔ .. image:: applications/roundcube_logo.png :doc:`Roundcube` ✔ .. image:: applications/salesforce-logo.jpg :doc:`SalesForce` ✔ .. image:: applications/SAPLogo.gif :doc:`SAP` ✔ ✔ @@ -118,6 +127,7 @@ Application Configuration .. image:: applications/sympa_logo.png :doc:`Sympa` ✔ .. image:: applications/tomcat_logo.png :doc:`Tomcat` ✔ .. image:: applications/wekan-logo.png :doc:`Wekan` ✔ +.. image:: applications/wiki.js.svg :doc:`Wiki.js` ✔ .. image:: applications/wordpress_logo.png :doc:`Wordpress` ✔ .. image:: applications/xwiki.png :doc:`XWiki` ✔ .. image:: applications/zimbra_logo.png :doc:`Zimbra` ✔ diff --git a/doc/sources/admin/applications/.tomcat.rst +92.swp b/doc/sources/admin/applications/.tomcat.rst +92.swp deleted file mode 100644 index 09c6e784f801432461fddfa5e911dffad29291d3..0000000000000000000000000000000000000000 Binary files a/doc/sources/admin/applications/.tomcat.rst +92.swp and /dev/null differ diff --git a/doc/sources/admin/applications/alfresco.rst b/doc/sources/admin/applications/alfresco.rst index bb5cc03af81b6ce0be9ea899b4aa42078941e830..dc7a943f6d22da04522af6f63e2f4e088c1086d9 100644 --- a/doc/sources/admin/applications/alfresco.rst +++ b/doc/sources/admin/applications/alfresco.rst @@ -11,7 +11,7 @@ Presentation Since 4.0 release, it offers an easy way to configure SSO thanks to authentication subsystems. -Authentication against LL::NG can be done trough: +Authentication against LL::NG can be done through: - HTTP headers (LL::NG Handler) - SAML 2 (LL::NG as SAML2 IDP) diff --git a/doc/sources/admin/applications/authbasic.rst b/doc/sources/admin/applications/authbasic.rst index 740728ca3809ce17955eddc95fa3a9d4a9dd1402..aa7693063edcffd04b6b65c85611d7c4da648acc 100644 --- a/doc/sources/admin/applications/authbasic.rst +++ b/doc/sources/admin/applications/authbasic.rst @@ -28,7 +28,7 @@ Little effort is required to translate the encoded string back into the user name and password, and many popular security tools will decode the strings "on the fly". -So HTTP Basic Authentication is managed trough an HTTP header +So HTTP Basic Authentication is managed through an HTTP header (``Authorization``), that can be forged by LL::NG, with this precautions: diff --git a/doc/sources/admin/applications/bigbluebutton.rst b/doc/sources/admin/applications/bigbluebutton.rst index 9d5ce2a5270267a86c126107fd2e11d4dfeb0496..2db551a01053a9c9e8fbc1e81bc1301c24b27848 100644 --- a/doc/sources/admin/applications/bigbluebutton.rst +++ b/doc/sources/admin/applications/bigbluebutton.rst @@ -56,7 +56,7 @@ Notes * Your ID Token Signature Algorithm has to be RSxxx, symmetric algorithms seem broken as of Greenlight 2.7.17 * ``OAUTH2_REDIRECT`` must match the URL you use to access Greenlight. the ``auth/openid_connect/callback`` suffix must be omitted -* Greenlight will not work if your LemonLDAP::NG server is not served over HTTPS using a publically recognized certificate authority (such as Let's Encrypt) +* Greenlight requires your LemonLDAP::NG server to be served over HTTPS using a publically recognized certificate authority (such as Let's Encrypt) .. |logo| image:: /applications/bigbluebutton-logo.png :class: align-center diff --git a/doc/sources/admin/applications/gerrit.rst b/doc/sources/admin/applications/gerrit.rst index 0a9816221469a6b16b1ebc36de0b2f20784eeaef..f95cf624b6422dd86cde3abb955cd25e47cd132d 100644 --- a/doc/sources/admin/applications/gerrit.rst +++ b/doc/sources/admin/applications/gerrit.rst @@ -16,12 +16,7 @@ Configuration Gerrit ------ -`Install `__ the OAuth Provider plugin. - -.. tip:: - - The LemonLDAP::NG support was added on February 23, 2020. - If you can't find a prebuilt package, you can use this `dockerfile `__ to build your own. +`Install `__ the OAuth Provider plugin. A prebuilt package of the plugin can be found on the `Gerrit CI `__. Then, configure Gerrit: diff --git a/doc/sources/admin/applications/grafana.rst b/doc/sources/admin/applications/grafana.rst index a26b22027ec97916f01cf869580260d10fc0e803..779ce41d152df14619ff4a7b89d4174c2e02fc42 100644 --- a/doc/sources/admin/applications/grafana.rst +++ b/doc/sources/admin/applications/grafana.rst @@ -46,14 +46,13 @@ Make sure you have already :doc:`enabled OpenID Connect<../idpopenidconnect>` on your LemonLDAP::NG server -Then, add a Relaying Party with the following configuration +Then, add a Relaying Party with the following configuration: - Options » Authentification » Client ID : same as ``client_id`` above -- Options » Allowed redirection address : same as ''client_secret '' - above +- Options » Authentification » Client Secret : same as ``client_secret`` above +- Options » Allowed redirection address : ``https:///login/generic_oauth`` -If you want to transmit user attributes to Grafana, you also need to -configure +If you want to transmit extra user attributes to Grafana, you also need to configure: - Extra Claims » @@ -72,6 +71,11 @@ configure - map them to your corresponding LemonLDAP::NG session attribute +.. tip:: + + To trigger OIDC authentication directly, you can register grafana in application menu and + set as URL: ``https:///login/generic_oauth`` + .. |image0| image:: /applications/grafana_logo.png :class: align-center diff --git a/doc/sources/admin/applications/iparapheur.rst b/doc/sources/admin/applications/iparapheur.rst new file mode 100644 index 0000000000000000000000000000000000000000..fae28136891077669af123b2e7b01f6a01b63c15 --- /dev/null +++ b/doc/sources/admin/applications/iparapheur.rst @@ -0,0 +1,45 @@ +i-Parapheur +=========== + +|image0| + +Presentation +------------ + +`i-Parapheur `__ is a web application +allowing digital signature on documents. It was built around Alfresco. + +It can use external authentication based on HTTP header. + +Configuration +------------- + +On i-Parapheur +~~~~~~~~~~~~~~ + +Edit ``/opt/iParapheur/tomcat/shared/classes/alfresco-global.properties`` and add: + +.. code-block:: ini + + parapheur.auth.external.header.authorize=true + +Edit ``/opt/iParapheur/tomcat/shared/classes/iparapheur-global.properties`` and add: + +.. code-block:: ini + + parapheur.auth.external.header.name=Auth-User + parapheur.auth.external.header.regexp=.* + +On LemonLDAP::NG +~~~~~~~~~~~~~~~~ + +Go to the Manager and :doc:`create a new virtual host<../configvhost>` for iParapheur. + +Just configure the :ref:`access rules`. + +Create the ``Auth-User`` :ref:`header` to send the user login to iParapheur. + + +.. |image0| image:: /applications/iparapheur_logo.png + :class: align-center + diff --git a/doc/sources/admin/applications/iparapheur_logo.png b/doc/sources/admin/applications/iparapheur_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3a8d88e707b8e88d9c259774a1ea73613af79e00 Binary files /dev/null and b/doc/sources/admin/applications/iparapheur_logo.png differ diff --git a/doc/sources/admin/applications/logo-publik.png b/doc/sources/admin/applications/logo-publik.png new file mode 100644 index 0000000000000000000000000000000000000000..50ee7ec1123af1ea34ecf937bb47375357c231df Binary files /dev/null and b/doc/sources/admin/applications/logo-publik.png differ diff --git a/doc/sources/admin/applications/mobilizon.rst b/doc/sources/admin/applications/mobilizon.rst new file mode 100644 index 0000000000000000000000000000000000000000..ac4d0f779ecf4d7a7a013269c69ae17f19e0f423 --- /dev/null +++ b/doc/sources/admin/applications/mobilizon.rst @@ -0,0 +1,51 @@ +Mobilizon +========= + +|mobilizon_logo.jpg| + +Presentation +------------ + +`Mobilizon `__ is an online tool to help manage your events, your profiles and your groups. + +Mobilizon lets users `authenticate with OpenID Connect `__ through the same plugin used by Keycloak. + +First, make sure you have set up LemonLDAP::NG 's +:doc:`OpenID Connect service<..//openidconnectservice>` and added +:doc:`a Relaying Party for your Mobilizon instance<..//idpopenidconnect>` + +The only options you need to configure are: + +* *Client ID*: choose one +* *Client Secret*: choose one +* *Allowed redirection addresses for login*: ``https://mobilizon.example.com/auth/keycloak/callback`` + +Mobilizon configuration +----------------------- + +Edit ``/etc/mobilizon/config.exs``, and adjust the Client ID, Client Secret and URLs to match your domain :: + + config :ueberauth, + Ueberauth, + providers: [ + keycloak: {Ueberauth.Strategy.Keycloak, [default_scope: "openid profile email"]} + ] + + config :mobilizon, :auth, + oauth_consumer_strategies: [ + {:keycloak, "LemonLDAP::NG"} + ] + + config :ueberauth, Ueberauth.Strategy.Keycloak.OAuth, + client_id: "CHANGEME", + client_secret: "CHANGEME", + site: "https://auth.example.com", + authorize_url: "https://auth.example.com/oauth2/authorize", + token_url: "https://auth.example.com/oauth2/token", + userinfo_url: "https://auth.example.com/oauth2/userinfo", + token_method: :post + + +.. |mobilizon_logo.jpg| image:: /applications/mobilizon_logo.jpg + :class: align-center + diff --git a/doc/sources/admin/applications/mobilizon_logo.jpg b/doc/sources/admin/applications/mobilizon_logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a4778a9caf54d51182ed71d356726ef8888bcc5f Binary files /dev/null and b/doc/sources/admin/applications/mobilizon_logo.jpg differ diff --git a/doc/sources/admin/applications/nextcloud.rst b/doc/sources/admin/applications/nextcloud.rst index 4603f9ae5fb77b02f22dc18751e70b56946de205..e0b7cfe39090ea406d1015123747fbb9f752d9df 100644 --- a/doc/sources/admin/applications/nextcloud.rst +++ b/doc/sources/admin/applications/nextcloud.rst @@ -30,14 +30,13 @@ software 'nextcloud.example.com', - - + 'overwriteprotocol' => 'https', You also need to enable the "SAML authentication" plugin in your NextCloud. + Apps -> Not enabled -> SAML authentication diff --git a/doc/sources/admin/applications/publik.rst b/doc/sources/admin/applications/publik.rst new file mode 100644 index 0000000000000000000000000000000000000000..3206e0f24b17a555ecc5cebe46718235e9ecfbdf --- /dev/null +++ b/doc/sources/admin/applications/publik.rst @@ -0,0 +1,57 @@ +Publik +======= + +|image0| + +Presentation +------------ + +Publik is an open-source citizen relationship management tool. + +See `the official Publik website `__ for a +complete presentation. + +It feature an OpenID Connect login that work with LemonLDAP::NG. + +Configuring Publik +------------------- + +Connect to your publik instance authentic2 webui with an Admin user, in the admin panel, go to "Authentic2_Auth_Oidc" › "Oidc providers". + +Click on "Add Oidc Provider". + +* Name : LemonLDAP SSO +* Short id : lemonldap +* Provider : https://auth.example.com/ +* Client id : clientid +* Client secret : secret +* Authorization endpoint : https://auth.example.com/oauth2/authorize +* Token endpoint : https://auth.example.com/oauth2/token +* Userinfo endpoint : https://auth.example.com/oauth2/userinfo +* End session endpont : https://auth.example.com/oauth2/logout +* WebKey JSON : Copy/Paste the content of https://auth.example.com/oauth2/jwks +* Claims Enabled : yes +* Show on connection page : yes + +Strategy and Collectivity can be configured based to your needs. + +OIDC Claim mappings can be configured based on your needs. + +Configuring LemonLDAP +~~~~~~~~~~~~~~~~~~~~~ + +We now have to configure LemonLDAP::NG to recognize publik as a valid OIDC relying party. + +Add a :doc:`new OpenID Connect relying party<..//idpopenidconnect>` +with the following parameters (Options -> Basic) : + +* **Client ID**: the same you set in Publik configuration. +* **Client Secret**: the same you set in Publik configuration. +* **Allowed redirection addresses for login**: The "Callback URL" for authentic2 : https://authentic2-instance/accounts/oidc/callback/ + +And in Options -> Logout + +* **Allowed redirection addresses for logout**: The "Logout URL" for authentic2 : https://authentic2-instance/logout/ + +.. |image0| image:: /applications/logo-publik.png + :class: align-center diff --git a/doc/sources/admin/applications/redmine.rst b/doc/sources/admin/applications/redmine.rst new file mode 100644 index 0000000000000000000000000000000000000000..32abd09df7a6f267c9305e717387b6486ff3f50f --- /dev/null +++ b/doc/sources/admin/applications/redmine.rst @@ -0,0 +1,85 @@ +Redmine +======= + +|logo| + +Presentation +------------ + +`Redmine `__ is is a flexible project management web application. +Written using the Ruby on Rails framework, it is cross-platform and cross-database. + +It can be configured to authenticate users with :doc:`OpenID Connect <../idpopenidconnect>` with a plugin. + +Configuration +-------------- + +LL:NG +~~~~~ + +Make sure you have already +:doc:`enabled OpenID Connect<../idpopenidconnect>` on your LemonLDAP::NG +server. + +Make sure you have generated a set of signing keys in +``OpenID Connect Service`` » ``Security`` » ``Keys`` + +You also need to set a Signing key ID to a non-empty value of your choice. + +Then, add a Relaying Party with the following configuration: + +- Options » Basic » Client ID : choose a client ID, such as ``my_client_id`` +- Options » Basic » Client Secret : choose a client secret, such as ``my_client_secret`` +- Options » Basic » Allowed redirection address : ``https://my_redmine_server/oic/local_login`` +- Options » Advanced » Force claims to be returned in ID Token : ``On`` +- Options » Security » ID Token Signature Algorithm : ``RS512`` +- Options » Logou( » Allowed redirection address for logout : ``https://my_redmine_server/oic/local_logout`` + +Define exported attributes: + +- ``email`` +- ``family_name`` +- ``given_name`` +- ``name`` +- ``nickname``: the user login + +To transfer groups: + +- Declare ``member_of`` exported attribute as an array +- Declare a new scope named ``groups`` whith value ``member_of`` +- Create a local macro ``member_of`` which will return ``["admin"]`` is user is administrator and ``["user"]`` else. + +Redmine +~~~~~~~ + +Install `OpenID Connect plugin `__. + + +Go in Redmine admin console and configure the OpenID Connect plugin: + +- Enabled: check the box +- Client ID: ``my_client_id`` +- OpenID Connect server url: ``https://auth.example.com/`` +- Client Secret: ``my_client_secret`` +- OpenID Connect scopes: ``openid profile email groups`` +- Authorized group: leave blank +- Admins group: ``admin`` +- How often to retrieve openid configuration: leave blank +- Disable Ssl Validation: uncheck the box +- Login Selector: uncheck the box +- Create user if not exists: check the box +- Users from the following auth sources will be required to login with SSO: do not select anythin + +.. attention:: + + A `bug `__ has been reported, you must apply a patch + if you transfer groups. + +.. note:: + + To bypass SSO, you can connect to ``__ + +.. |logo| image:: /applications/redmine_logo.png + :class: align-center + + diff --git a/doc/sources/admin/applications/redmine_logo.png b/doc/sources/admin/applications/redmine_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..094a642e8b8d8176bec74b367ba7ead97c7bfefe Binary files /dev/null and b/doc/sources/admin/applications/redmine_logo.png differ diff --git a/doc/sources/admin/applications/simplesamlphp.rst b/doc/sources/admin/applications/simplesamlphp.rst index f8e52a307b5fbb3801335d8d57afb9dbc1f9eb0f..bcad6435c4c7dd03ece667446449c12c7241f15c 100644 --- a/doc/sources/admin/applications/simplesamlphp.rst +++ b/doc/sources/admin/applications/simplesamlphp.rst @@ -80,7 +80,7 @@ We suppose you configured LemonLDAP::NG as as Service Provider. In LL::NG Manager, create an new SP and load simpleSAMLphp metadata -trough URL (by default: +through URL (by default: http://localhost/simplesamlphp/module.php/saml/sp/metadata.php/default-sp): |image1| diff --git a/doc/sources/admin/applications/spring.rst b/doc/sources/admin/applications/spring.rst index 3749184b80d26cf07c1f6880d50ec9a9d3f130e2..ac186a1c31bb5a6ecdbceb0fa49aad0968b9e6ec 100644 --- a/doc/sources/admin/applications/spring.rst +++ b/doc/sources/admin/applications/spring.rst @@ -20,7 +20,7 @@ Configuration You can find all suitable information here: http://static.springsource.org/spring-security/site/docs/3.0.x/reference/preauth.html -To summarize, to get the user connected trough the ``Auth-User`` HTTP +To summarize, to get the user connected through the ``Auth-User`` HTTP Header, use this Sping Security configuration: .. code-block:: xml diff --git a/doc/sources/admin/applications/wiki.js.svg b/doc/sources/admin/applications/wiki.js.svg new file mode 100644 index 0000000000000000000000000000000000000000..8a0bfad4bd34e8a3979e1d818e1a520060385710 --- /dev/null +++ b/doc/sources/admin/applications/wiki.js.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/sources/admin/applications/wikijs.rst b/doc/sources/admin/applications/wikijs.rst new file mode 100644 index 0000000000000000000000000000000000000000..15f93898afd9f18f18df3f9fdc20a41a4118883b --- /dev/null +++ b/doc/sources/admin/applications/wikijs.rst @@ -0,0 +1,56 @@ +Wiki.js +======= + +|image0| + +Presentation +------------ + +Wiki.js is an open-source wiki. + +See `the official Wiki.js website `__ for a +complete presentation. + +It feature an OpenID Connect login that work with LemonLDAP::NG. + +Configuring Wiki.js +------------------- + +Connect to your wiki.js instance with an Admin user, in the admin panel, go to "Authentication". + +Click on "Add Strategy" and Choose "Generic OpenID Connect / OAuth2". + +Choose a Display Name. + +Define a Client ID and a Client Secret. + +* Authorization Endpoint URL : https://auth.example.com/oauth2/authorize +* Token Endpoint URL : https://auth.example.com/oauth2/token +* User info Endpoint URL : https://auth.example.com/oauth2/userinfo +* Issuer : https://auth.example.com +* Logout URL : https://auth.example.com/oauth2/logout + +Make a note of the "Callback URL" and the bottom of the screen and save the configuration. + +Configuring LemonLDAP +~~~~~~~~~~~~~~~~~~~~~ + +We now have to configure LemonLDAP::NG to recognize wiki.js as a valid OIDC relying party. + +Add a :doc:`new OpenID Connect relying party<..//idpopenidconnect>` +with the following parameters (Options -> Basic) : + +* **Client ID**: the same you set in Wiki.js configuration. +* **Client Secret**: the same you set in Wiki.js configuration. +* **Allowed redirection addresses for login**: The "Callback URL" defined in wiki.js. + +Portal with internal CA +^^^^^^^^^^^^^^^^^^^^^^^ + +.. danger:: + + OIDC login fails when your LemonLDAP portal doesn't use a publicaly recognized certificate (Internal Corporate CA), this is because nodejs use it's own keystore. + You'll need to pass "NODE_EXTRA_CA_CERTS=" to your wiki installation. If done in docker you will have to create a new image from the official one, add your CA certificates into it, and use the env variable to use it in your wiki.js container. + +.. |image0| image:: /applications/wiki.js.svg + :class: align-center diff --git a/doc/sources/admin/applications/wordpress.rst b/doc/sources/admin/applications/wordpress.rst index 0b66213bf51beca622d31bab80358f32bc600035..3b010b3f21a19d04cd7141dd84172375edf0febe 100644 --- a/doc/sources/admin/applications/wordpress.rst +++ b/doc/sources/admin/applications/wordpress.rst @@ -43,7 +43,7 @@ User Roles Settings You can assign WP Roles depending on values sent by CAS. The rules syntax is quite special, you can use it or you can just define -macros on LL::NG side and send them trough CAS to keep simple rules on +macros on LL::NG side and send them through CAS to keep simple rules on WP side. For example create a macro ``role_wordpress_admin`` which contains ``1`` diff --git a/doc/sources/admin/authcas.rst b/doc/sources/admin/authcas.rst index 8350e1ab3b11b014f9a5e96f802ab13fd2958438..cb567ff6ac0da99cb45341c034fc7ca000691703 100644 --- a/doc/sources/admin/authcas.rst +++ b/doc/sources/admin/authcas.rst @@ -25,7 +25,7 @@ session under the form: ``_casPT`` = **Proxy ticket value** -They can then be forwarded to applications trough +They can then be forwarded to applications through :ref:`HTTP headers`. .. tip:: diff --git a/doc/sources/admin/authchoice.rst b/doc/sources/admin/authchoice.rst index 608b9a0f3abd5dbebc9f1a8d92f27dda7eff7511..e45ff951a57726597b23f0f223b6c7511c57e429 100644 --- a/doc/sources/admin/authchoice.rst +++ b/doc/sources/admin/authchoice.rst @@ -50,8 +50,8 @@ Then, go in ``Choice Parameters``: - **URL parameter**: parameter name used to set choice value (default: ``lmAuth``) - **Allowed modules**: click on ``New chain`` to add a choice. -- **AuthBasic handler parameter**: authentication module called by - AuthBasic handler (:doc:`AuthBasic handler`) +- **Choice used for password authentication**: authentication module used by + :doc:`AuthBasic handler` and :ref:`OAuth2.0 Password Grant ` - **FindUser plugin parameter**: authentication module called by Find user plugin (:doc:`Find user plugin`) diff --git a/doc/sources/admin/authopenidconnect.rst b/doc/sources/admin/authopenidconnect.rst index ae8d03e829cda19138664c42d0ea6738dc933f3b..726c4252fb8bd50d57964e6a94fdb80c8c139b43 100644 --- a/doc/sources/admin/authopenidconnect.rst +++ b/doc/sources/admin/authopenidconnect.rst @@ -17,8 +17,8 @@ Presentation stacks. It is described here: http://openid.net/connect/. LL::NG can act as an OpenID Connect Relying Party (RP) towards multiple -OpenID Connect Providers (OP). It will get the user identity trough an -ID Token, and grab user attributes trough UserInfo endpoint. +OpenID Connect Providers (OP). It will get the user identity through an +ID Token, and grab user attributes through UserInfo endpoint. As an RP, LL::NG supports a lot of OpenID Connect features: @@ -113,22 +113,19 @@ Register LL::NG to an OpenID Connect Provider ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To register LL::NG, you will need to give some information like -application name or logo. One of mandatory information is the redirect -URL (one or many). +application name or logo. -To know this information, just take the portal URL and the Callback GET -parameter, for example: +You will be asked to provide a *Redirect URI* for LemonLDAP::NG, which is constructed by appending the ``openidcallback=1`` parameter to the Portal URL. -- http://auth.example.com/?openidcallback=1 -- http://auth.example.com/index.pl?openidcallback=1 -- http://auth.example.com/?lmAuth=oidc&openidcallback=1 +For example: + +- https://auth.example.com/?openidcallback=1 .. attention:: - If you use the :doc:`choice backend`, you - need to add the choice parameter in redirect URL or - set SameSite cookie value to "Lax" or "None". + If you use the :doc:`choice backend`, + you need to set SameSite cookie value to "Lax" or "None". See :doc:`SSO cookie parameters` After registration, the OP must give you a client ID and a client diff --git a/doc/sources/admin/authopenidconnect_google.rst b/doc/sources/admin/authopenidconnect_google.rst index 25325c7868cbdbb3619abbd0b97ea5036b567f8b..e5f62851fdc8e5c48d1b4434e9ba47dc6833d258 100644 --- a/doc/sources/admin/authopenidconnect_google.rst +++ b/doc/sources/admin/authopenidconnect_google.rst @@ -14,7 +14,7 @@ https://developers.google.com/identity/protocols/OpenIDConnect .. attention:: - Google does not support logout trough OpenID Connect. If + Google does not support logout through OpenID Connect. If you close your session on LL::NG side, your Google session will still be open. diff --git a/doc/sources/admin/authremote.rst b/doc/sources/admin/authremote.rst index dd7a7c351c78b023085265fa3a225e2050e4c1b3..7459f2302df013acaf7471007652449a1d835d4f 100644 --- a/doc/sources/admin/authremote.rst +++ b/doc/sources/admin/authremote.rst @@ -23,7 +23,7 @@ Presentation be rejected). - The portal of the secondary LL::NG structure is configured to delegate authentication to a remote portal. A request to the main - session database is done (trough + session database is done (through :doc:`SOAP session backend`) to be sure that the session exists. - If ``exportedAttr`` is set, only those attributes are copied in the diff --git a/doc/sources/admin/browseableldapsessionbackend.rst b/doc/sources/admin/browseableldapsessionbackend.rst index 057bb2471064e3f003256caf9762bc6dadddb6ba..d4c10a81c88843286ae4013697542642ab814611 100644 --- a/doc/sources/admin/browseableldapsessionbackend.rst +++ b/doc/sources/admin/browseableldapsessionbackend.rst @@ -22,6 +22,7 @@ Name Comment Example **ldapConfBase** DN of sessions branch ou=sessions,dc=example,dc=com **ldapBindDN** Connection login cn=admin,dc=example,dc=password **ldapBindPassword** Connection password secret +**ldapRaw** Binary attributes (?i:^jpegPhoto|;binary) **Index** Fields to index refer to :ref:`fieldstoindex` Optional parameters Name Comment Default value @@ -33,3 +34,9 @@ Name Comment Default value **ldapCAFile** Path of CA file bundle (system CA bundle) **ldapCAPath** Perform CA directory (system CA bundle) ======================== ================================= =============================== + +.. note:: + + In order to properly handle UTF-8 encoded values, you may need to set the + ldapRaw parameter to a non-null value. This requires + Apache::Session::Browseable >= 1.3.3 diff --git a/doc/sources/admin/bruteforceprotection.rst b/doc/sources/admin/bruteforceprotection.rst index 5e4288d478af65736c0084b81490de0cca7448ca..8136b4f33c4887303acf0c77a2f7be7b875095d0 100644 --- a/doc/sources/admin/bruteforceprotection.rst +++ b/doc/sources/admin/bruteforceprotection.rst @@ -11,6 +11,13 @@ repeatedly trying to guess the password of an user. If disabled, automated tools may submit thousands of password attempts in a matter of seconds. +.. attention:: + This plugin relies on the Login History, stored in users' persistent sessions. + This means that the authentication and persistent session backends will be + accessed for every login attempt, even fraudulent ones. This plugin is not + meant to protect against denial of service attacks. + + Configuration ------------- diff --git a/doc/sources/admin/changesessionbackend.rst b/doc/sources/admin/changesessionbackend.rst index 4a073b5b1968dbb0e123ef2fc8421453afebe5b6..90c1e4e9079308b1d33793c8ffd19e041f2f6986 100644 --- a/doc/sources/admin/changesessionbackend.rst +++ b/doc/sources/admin/changesessionbackend.rst @@ -19,22 +19,22 @@ configuration file with the following content: # This example migrates psessions from the default File backend to a PostgreSQL database [sessions_from] storageModule = Apache::Session::File - storageModuleOptions = { \\ - 'Directory' => '/var/lib/lemonldap-ng/psessions', \\ - 'LockDirectory' => '/var/lib/lemonldap-ng/psessions/lock', \\ + storageModuleOptions = { \ + 'Directory' => '/var/lib/lemonldap-ng/psessions', \ + 'LockDirectory' => '/var/lib/lemonldap-ng/psessions/lock', \ } # Only convert some session types # sessionKind = Persistent, SSO [sessions_to] storageModule = Apache::Session::Browseable::Postgres - storageModuleOptions = { \\ - 'DataSource' => 'DBI:Pg:database=lemonldapdb;host=pg.example.com', \\ - 'UserName' => 'lemonldaplogin', \\ - 'Password' => 'lemonldappw', \\ - 'Commit' => 1, \\ - 'Index' => 'ipAddr _whatToTrace user', \\ - 'TableName' => 'psessions', \\ + storageModuleOptions = { \ + 'DataSource' => 'DBI:Pg:database=lemonldapdb;host=pg.example.com', \ + 'UserName' => 'lemonldaplogin', \ + 'Password' => 'lemonldappw', \ + 'Commit' => 1, \ + 'Index' => 'ipAddr _whatToTrace user', \ + 'TableName' => 'psessions', \ } Invocation @@ -45,6 +45,8 @@ Invocation Options: - ``-c``: job configuration file (mandatory) +- ``-r oldkey=newkey``: rename session keys during conversion (optional, can be given multiple times) +- ``-x key``: remove session keys during conversion (optional, can be given multiple times) - ``-i``: ignore errors. By default errors will stop the script execution - ``-d``: print debugging output diff --git a/doc/sources/admin/checkdevops.rst b/doc/sources/admin/checkdevops.rst index 1d6c620aff684c94e178d5959eca86f5bf87b51a..668bc81747bdaa0902fa3a7ef06f607f8bf6789f 100644 --- a/doc/sources/admin/checkdevops.rst +++ b/doc/sources/admin/checkdevops.rst @@ -12,8 +12,9 @@ Just enable it in the manager (section “plugins”). - **Activation**: Enable / Disable this plugin - **Download file**: Allow users to download DevOps file from a remote server by - ptroviding an URL (By example: http://myapp.example.com:8080). Plugin will try to - retrieve remote file by sending a request (i.e. http://myapp.example.com:8080/rules.json) + providing an URL (By example: http://myapp.example.com:8080). Plugin will + try to retrieve remote file by sending a request (i.e. + http://myapp.example.com:8080/rules.json) Usage ----- @@ -51,4 +52,4 @@ access rules and headers: Be careful to not display secret attributes. - checkDevOps plugin uses hidden attributes option. \ No newline at end of file + checkDevOps plugin uses hidden attributes option. diff --git a/doc/sources/admin/checkuser.rst b/doc/sources/admin/checkuser.rst index 65c10208bb8e2d180a6aecf1c902fadb6b4a5ba3..1b206ad70c8c46ce5cc58f5dd45b612f1f5797c7 100644 --- a/doc/sources/admin/checkuser.rst +++ b/doc/sources/admin/checkuser.rst @@ -23,21 +23,23 @@ Just enable it in the manager (section “plugins”). for searching sessions in backend if ``whatToTrace`` fails. Useful to look for sessions by mail or givenName. Let it blank to search by ``whatToTrace`` only - - **Display computed sessions**: Rule to define which users can display a - computed session if no SSO session is found - - **Display empty headers**: Rule to define which users can display ALL headers - appended by LemonLDAP::NG including empty ones - - **Display normalized headers**: Rule to define which users see headers name sent by - the web server (see RFC3875) - - **Display empty values**: Rule to define which users can display ALL attributes - even empty ones - - **Display persistent session data**: Rule to define which users can display - persistent session data - **Hidden headers**: Sent headers whose value is masked except for unrestricted users. Key is a Virtualhost name and value represents a space-separated headers list. A blank value obfuscates ALL relative Virtualhost sent headers. Note that just valued hearders are masked. +- **Display**: + + - **Computed sessions**: Rule to define which users can display a + computed session if no SSO session is found + - **Empty headers**: Rule to define which users can display ALL headers + appended by LemonLDAP::NG including empty ones + - **Normalized headers**: Rule to define which users can see headers name sent by + the web server (see RFC3875) + - **Empty values**: Rule to define which users can display ALL attributes + even empty ones + - **Persistent session data**: Rule to define which users can display + persistent session data .. note:: diff --git a/doc/sources/admin/cli_examples.rst b/doc/sources/admin/cli_examples.rst index 64ff78c5933ca632a1d53023b653e2ca5754ab65..e936080afd46fcaf75abeab54ab133990033d676 100644 --- a/doc/sources/admin/cli_examples.rst +++ b/doc/sources/admin/cli_examples.rst @@ -386,7 +386,7 @@ Import them: set \ oidcServicePrivateKeySig "`cat oidc.key`" \ oidcServicePublicKeySig "`cat oidc_pub.key`" \ - oidcServiceKeyIdSig "`genpasswd`" + oidcServiceKeyIdSig "randomstring" If needed you can allow implicit and hybrid flows: diff --git a/doc/sources/admin/configlocation.rst b/doc/sources/admin/configlocation.rst index fb15ea66b9263046dfa45453e2d580dd68e0fa42..da9abf78ad83557e4c538d2135dc9491f370cdca 100644 --- a/doc/sources/admin/configlocation.rst +++ b/doc/sources/admin/configlocation.rst @@ -19,7 +19,7 @@ modules to access it. :ref:`here`. By default, configuration is stored in :doc:`files`, so -access trough network is not possible. To allow this, use +access through network is not possible. To allow this, use :doc:`SOAP` for configuration access, or use a network service like :doc:`SQL database` or :doc:`LDAP directory`. @@ -46,7 +46,7 @@ For example, to configure the ``File`` configuration backend: Manager ------- -Most of configuration can be done trough LemonLDAP::NG Manager (by +Most of configuration can be done through LemonLDAP::NG Manager (by default http://manager.example.com). By default, Manager is protected to allow only the demonstration user @@ -174,6 +174,11 @@ and is stored in the LemonLDAP::NG bin/ directory, for example This script must be run as root, it will then use the Apache user and group to access configuration. +.. tip:: + + You can change the user and group by setting ``--user`` and + ``--group`` options in the command line. + The script uses the ``editor`` system command, that links to your favorite editor. To change it: @@ -276,6 +281,11 @@ You can use accessors (options) to change the behavior: configuration. - -force: set it to 1 to save a configuration earlier than latest. +Additional options: + +- --user=: change user running the script +- --group=: change group running the script + Some examples: :: @@ -283,6 +293,7 @@ Some examples: /usr/share/lemonldap-ng/bin/lemonldap-ng-cli -cfgNum 10 get exportedHeaders/test1.example.com /usr/share/lemonldap-ng/bin/lemonldap-ng-cli -yes 1 set notification 1 /usr/share/lemonldap-ng/bin/lemonldap-ng-cli -sep ',' get macros,_whatToTrace + /usr/share/lemonldap-ng/bin/lemonldap-ng-cli get portal --user=nginx --group=nginx .. tip:: diff --git a/doc/sources/admin/configvhost.rst b/doc/sources/admin/configvhost.rst index 90e80ecf56f0e96186a13ff4b2b975b9a5961bba..3d1f3d019f3f66cb4c21790a744fea6543a71609 100644 --- a/doc/sources/admin/configvhost.rst +++ b/doc/sources/admin/configvhost.rst @@ -467,8 +467,8 @@ application by LL::NG. With **Nginx**-based ReverseProxy, header directives can be appended by a LUA script. - To send more than **TEN** headers to protected applications, you have to - edit and modify : + To send more than **15** headers to protected applications, + you have to edit and modify : ``/etc/nginx/nginx-lua-headers.conf`` @@ -505,7 +505,8 @@ Some options are available: rules,...)* - **Access to trace**: can be used for overwriting REMOTE_CUSTOM with a custom function. Provide a comma separated parameters list with custom function path and args. - By example: My::accessToTrace, Doctor, Who + Args can be vars or session attributes, macros, ... + By example: My::accessToTrace, Doctor, Who, _whatToTrace - **Type**: handler type (normal, :doc:`ServiceToken Handler`, :doc:`DevOps Handler`,...) @@ -536,7 +537,7 @@ Some options are available: my $params = $hash->{params}; my $session = $hash->{session}; - return "$custom alias $params->[0]_$params->[1]:$session->{groups}"; + return "$custom alias $params->[0]_$params->[1]:$session->{groups}:$session->{$params->[2]}"; } 1; diff --git a/doc/sources/admin/decryptvalue.rst b/doc/sources/admin/decryptvalue.rst index b9a02534e80fa5654c53dbfcb34f72ee8061da23..799bcaf29819e4716794f6eac783bceadf7233ff 100644 --- a/doc/sources/admin/decryptvalue.rst +++ b/doc/sources/admin/decryptvalue.rst @@ -1,5 +1,3 @@ -|image0| - Decrypt value plugin ==================== diff --git a/doc/sources/admin/documentation.rst b/doc/sources/admin/documentation.rst index be9171ea52e160ddfe391e53d79ae0025acd3498..5d7388589d721fafec931e35847271d05c752103 100644 --- a/doc/sources/admin/documentation.rst +++ b/doc/sources/admin/documentation.rst @@ -48,9 +48,9 @@ their policy. Debian ^^^^^^ -Following Debian Policy, LLNG packages are never upgraded in -published distributions. However, security patches are backported by -maintenance teams *(except minor ones)*. +.. tip:: + + Following Debian Policy, LLNG packages are never upgraded in published distributions. However, security patches are backported by maintenance teams *(except some inor ones)*. =========== ======================== ======================================== ===================================================== ============================================================ =============================== ============================================================= Debian dist LLNG version Secured Maintenance LTS Limit `Extended LTS `__ Limit @@ -60,9 +60,10 @@ Debian dist LLNG version Se **8** Jessie `1.3.3 `__ |clean| CVE-2019-19791 tagged as minor **None** [1]_ June 2020 Probably 2023 **9** Stretch `1.9.7 `__ |clean| CVE-2019-19791 tagged as minor `Debian LTS Team `__ June 2022 \ *Stretch-backports* `2.0.2 `__ |bad| CVE-2019-12046, CVE-2019-13031, CVE-2019-15941 *None* *June 2019* -\ Stretch-backports-sloppy `2.0.11 `__ |clean| `LLNG Team `__, "best effort" [3]_ Until Debian 11 release [4]_ +\ Stretch-backports-sloppy `2.0.11 `__ |maybe| *Maybe none*, "best effort" [3]_ Until Debian 11 release [4]_ **10** Buster `2.0.2 `__ |clean| CVE-2019-19791 tagged as minor `Debian Security Team `__ Probably July 2024 -\ Buster-backports `2.0.11 `__ |clean| `LLNG Team `__ Until Debian 11 release [4]_ +\ Buster-backports `2.0.11 `__ |clean| `LLNG Team `, "best effort" [3]_ Until Debian 11 release [4]_ +\ Bullseye `2.0.11 `__ |clean| `Debian Security Team `__ Probably July 2026 **Next** Testing Latest [5]_ |clean| `LLNG Team `__ =========== ======================== ======================================== ===================================================== ============================================================ =============================== ============================================================= @@ -74,9 +75,9 @@ Tracker `__ for more. Ubuntu ^^^^^^ -Ubuntu version are included in "universe" branch [8]_, so -not really security maintained. Prefer to use our repositories or Debian -ones +.. attention:: + + Ubuntu version are included in "universe" branch [8]_, so not really security maintained. Prefer to use our repositories or Debian ones =========== ============= ================================ ==================================================================== =========== Ubuntu dist LLNG version Secured Maintenance @@ -85,11 +86,9 @@ Ubuntu dist LLNG version Secured 14.04 Trusty `1.2.5 `__ |maybe| No known vulnerability None 16.04 Xenial [9]_ `1.4.6 `__ |bad| CVE-2019-12046, CVE-2019-13031 None 18.04 Bionic [9]_ `1.9.16 `__ |bad| CVE-2019-12046, CVE-2019-13031, CVE-2020-24660 None -18.10 Cosmic `1.9.17 `__ |bad| CVE-2019-12046, CVE-2019-13031, CVE-2020-24660 None -19.04 Disco `2.0.2 `__ |bad| CVE-2019-12046, CVE-2019-13031, CVE-2019-15941, CVE-2020-24660 None -19.10 Eoan `2.0.5 `__ |bad| CVE-2019-15941, CVE-2020-24660 None 20.04 Focal [9]_ `2.0.7 `__ |bad| CVE-2020-24660 None 20.10 Groovy `2.0.8 `__ |bad| CVE-2020-24660 None +21.04 Hirsute `2.0.11 `__ |clean| None =========== ============= ================================ ==================================================================== =========== Bug report @@ -137,10 +136,12 @@ Other Possible `Extended LTS `__ .. [3] - updated by `LLNG Team `__ until dependencies are compatible + updated by `LLNG Team `__ until dependencies are compatible. + Don't use backports unless you plan to update your system because + backports are not covered by Debian Security Policy .. [4] - around June 2021 + around September 2021 .. [5] few days after release diff --git a/doc/sources/admin/finduser.rst b/doc/sources/admin/finduser.rst index 320b0600709a38327af731162ae408e51e3016a7..7612fe3c1f1cb062504287361a35c0fcf0647297 100644 --- a/doc/sources/admin/finduser.rst +++ b/doc/sources/admin/finduser.rst @@ -1,5 +1,3 @@ -|image0| - Find user plugin ================ @@ -20,21 +18,27 @@ Just enable it in the Manager (section “plugins”). Then, set searching attri - **Activation**: Enable / Disable this plugin - **Character used as wildcard**: Character that can be used by users as wildcard. An empty value disable wildcarded search requests - **Parameters control**: Regular expression used for checking searching values syntax + - **User accounts URL**: User database URL to search on if REST backend is used. Let it blank to use default user data URL. - **Searching attributes**: For each attribute, you have to set a key (attribute as defined in UserBD) and a value that will be display in login form (placeholder). A value can be a multivalued list separated by multiValuesSeparator parameter (General Parameters > Advanced parameters > Separator). See note below. - **Excluding attributes**: You can defined here attributes used for excluding accounts. Set keys corresponding to UserBD attributes and values to exclude. A value can be a multivalued list separated by multiValuesSeparator parameter (General Parameters > Advanced parameters > Separator) .. note:: - You can provide a 'multiValuesSeparator' separated list of allowed searching values that will be displayed as an HTML list :: + + attribute#placeholder[#empty] => value1; placeholder1; value2; placeholder2 + + For example :: - attribute#placeholder[#empty] => value1; placeholder1; value2; placeholder2 + uid#Identity => dwho; Dr Who; rtyler; Rose Tyler; msmith; Mr Smith - - By example: - uid#Identity => dwho; Dr Who; rtyler; Rose Tyler; msmith; Mr Smith + uid#Identity#1 => dwho; Dr Who; rtyler; Rose Tyler (allow empty value) - uid#Identity#1 => dwho; Dr Who; rtyler; Rose Tyler (allow empty value) + Entries are sorted by alphabetical order. + +.. attention:: - Entries are sorted by alphabetical order. + LDAP filter works only if an objectClass is set. .. attention:: @@ -44,7 +48,7 @@ Just enable it in the Manager (section “plugins”). Then, set searching attri .. danger:: - This plugin works only with a users backend and the searching or excluding attributes must exist. + This plugin works only with a users backend and of course if the searching or excluding attributes are existing. .. danger:: diff --git a/doc/sources/admin/handlerauthbasic.rst b/doc/sources/admin/handlerauthbasic.rst index d139768cc14712afee6811f8317f1eec06666481..36036be9ca5a7dde846c605e398d392769ecacc1 100644 --- a/doc/sources/admin/handlerauthbasic.rst +++ b/doc/sources/admin/handlerauthbasic.rst @@ -53,7 +53,7 @@ to access required locations in Portal Virtual Host. requireToken => $env->{REMOTE_ADDR} !~ /^127\.0\.[1-3]\.1$/ - With AutChoice, you have to declare which authentication module is + With :doc:`authchoice`, you have to declare which authentication module is requested by handler to create global session. Go to: @@ -61,7 +61,7 @@ to access required locations in Portal Virtual Host. and set authentication module's name : - **AuthBasic handler parameter** => 2_LDAP (by example) + **Choice used for password authentication** => 2_LDAP (by example) diff --git a/doc/sources/admin/hooks.rst b/doc/sources/admin/hooks.rst index a9ef27ba6a1945c824478d9fcd7087a79fff5a69..92f00e93ae1782e946fa2e173ddecbb69c92ec91 100644 --- a/doc/sources/admin/hooks.rst +++ b/doc/sources/admin/hooks.rst @@ -26,6 +26,57 @@ Sample code:: return PE_OK; } +oidcGotClientCredentialsGrant +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.0.12 + +This hook is triggered when LemonLDAP::NG successfully authorized a :ref:`Client Credentials Grant `. + +The hook's parameters are: + +* A hash of the current session info +* the configuration key of the relying party which is being identified + +Sample code:: + + use constant hook => { + oidcGotClientCredentialsGrant => 'addSessionVariable', + }; + + sub addSessionVariable { + my ( $self, $req, $info, $rp ) = @_; + $info->{is_client_credentials} = 1; + + return PE_OK; + } + + +oidcGenerateCode +~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.0.12 + +This hook is triggered when LemonLDAP::NG is about to generate an Authorization Code for a Relying Party. + +The hook's parameters are: + +* A hash of the parameters for the OIDC Authorize request, which you can modify +* the configuration key of the relying party which will receive the token +* A hash of the session keys for the (internal) Authorization Code session + +Sample code:: + + use constant hook => { + oidcGenerateCode => 'modifyRedirectUri', + }; + + sub modifyRedirectUri { + my ( $self, $req, $oidc_request, $rp, $code_payload ) = @_; + my $original_uri = $oidc_request->{redirect_uri}; + $oidc_request->{redirect_uri} = "$original_uri?hooked=1"; + return PE_OK; + } oidcGenerateUserInfoResponse ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -58,7 +109,7 @@ This hook is triggered when LemonLDAP::NG is generating an ID Token. The hook's parameters are: * A hash of the claims to be contained in the ID Token -* the configuration key of the relying party which will receive the token +* the configuration key of the relying party which will receive the token Sample code:: @@ -136,7 +187,7 @@ The hook's parameter is the Lasso::Login object Sample code:: - use constant hook => { + use constant hook => { samlGotAuthnRequest => 'gotRequest', }; @@ -157,7 +208,7 @@ The hook's parameter is the Lasso::Login object Sample code:: - use constant hook => { + use constant hook => { samlBuildAuthnResponse => 'buildResponse', }; @@ -178,7 +229,7 @@ The hook's parameter is the Lasso::Logout object Sample code:: - use constant hook => { + use constant hook => { samlGotLogoutRequest => 'gotLogout', }; @@ -199,7 +250,7 @@ The hook's parameter is the Lasso::Logout object Sample code:: - use constant hook => { + use constant hook => { samlGotLogoutResponse => 'gotLogoutResponse', }; @@ -220,7 +271,7 @@ The hook's parameter is the Lasso::Logout object Sample code:: - use constant hook => { + use constant hook => { samlBuildLogoutResponse => 'buildLogoutResponse', }; @@ -229,3 +280,142 @@ Sample code:: # Your code here } + +CAS Issuer hooks +----------------- + +casGotRequest +~~~~~~~~~~~~~ + +.. versionadded:: 2.0.12 + +This hook is triggered when LemonLDAP::NG received an CAS authentication request on the `/cas/login` endpoint. + +The hook's parameter is a hash containing the CAS request parameters. + +Sample code:: + + use constant hook => { + casGotRequest => 'filterService' + }; + + sub filterService { + my ( $self, $req, $cas_request ) = @_; + if ( $cas_request->{service} eq "http://auth.sp.com/" ) { + return PE_OK; + } + else { + return 999; + } + } + + +casGenerateServiceTicket +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.0.12 + +This hook is triggered when LemonLDAP::NG is about to generate a Service Ticket for a CAS application + +The hook's parameters are: + +* A hash of the parameters for the CAS request, which you can modify +* the configuration key of the cas application which will receive the ticket +* A hash of the session keys for the (internal) CAS session + +Sample code:: + + use constant hook => { + 'casGenerateServiceTicket' => 'changeRedirectUrl', + }; + + sub changeRedirectUrl { + my ( $self, $req, $cas_request, $app, $Sinfos ) = @_; + $cas_request->{service} .= "?hooked=1"; + return PE_OK; + } + + +casGenerateValidateResponse +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.0.12 + +This hook is triggered when LemonLDAP::NG is about to send a CAS response to an application on the `/cas/serviceValidate` endpoint. + +The hook's parameters are: + +* The username (CAS principal) +* A hash of modifiable attributes to be sent + +Sample code:: + + use constant hook => { + casGenerateValidateResponse => 'addAttributes', + }; + + sub addAttributes { + my ( $self, $req, $username, $attributes ) = @_; + $attributes->{hooked} = 1; + return PE_OK; + } + + +Password change hooks +--------------------- + + +passwordBeforeChange +~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.0.12 + +This hook is triggered when LemonLDAP::NG is about to change or reset a user's password. Returning an error will cancel the password change operation + +The hook's parameters are: + +* The main user identifier +* The new password +* The old password, if relevant + +Sample code:: + + use constant hook => { + passwordBeforeChange => 'blacklistPassword', + }; + + sub blacklistPassword { + my ( $self, $req, $user, $password, $old ) = @_; + if ( $password eq "12345" ) { + $self->logger->error("I've got the same combination on my luggage"); + return PE_PP_INSUFFICIENT_PASSWORD_QUALITY; + } + return PE_OK; + } + + +passwordAfterChange +~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.0.12 + +This hook is triggered after LemonLDAP::NG has changed the user's password successfully in the underlying password database + +The hook's parameters are: + +* The main user identifier +* The new password +* The old password, if relevant + +Sample code:: + + use constant hook => { + passwordAfterChange => 'logPasswordChange', + }; + + sub logPasswordChange { + my ( $self, $req, $user, $password, $old ) = @_; + $old ||= ""; + $self->userLogger->info("Password changed for $user: $old -> $password") + return PE_OK; + } diff --git a/doc/sources/admin/idpcas.rst b/doc/sources/admin/idpcas.rst index ad1db51dea96e9e0fcf3951147618907d4b9aa14..636e712a38735383496bc16efd6940009dde6737 100644 --- a/doc/sources/admin/idpcas.rst +++ b/doc/sources/admin/idpcas.rst @@ -49,9 +49,6 @@ Then go in ``CAS Service`` to define: - **CAS login**: the session key transmitted to CAS client as the main identifier (CAS Principal). This setting can be overriden per-application. -- **CAS attributes**: list of attributes that will be transmitted by - default in the validate response. Keys are the name of attribute in - the CAS response, values are the name of session key. - **Access control policy**: define if access control should be done on CAS service. Three options: @@ -67,6 +64,13 @@ Then go in ``CAS Service`` to define: - **CAS session module name and options**: choose a specific module if you do not want to mix CAS sessions and normal sessions (see :ref:`why`). +- **CAS attributes**: list of attributes that will be transmitted by + default in the validate response. Keys are the name of attribute in + the CAS response, values are the name of session key. +- **Use strict URL matching**: (since *2.0.12*) enforces a stricter URL + matching. By default, LemonLDAP::NG will try to find a declared CAS + Application matching the hostname of the requested application if it cannot + find a match using the full path. See :ref:`idpcas-url-matching` for details .. tip:: @@ -143,3 +147,13 @@ For example, if you declared two applications in LemonLDAP::NG with the followin * https://cas.example.com/applications/ An application located at https://cas.example.com/applications/zone1/myapp will match the first CAS service definition + +An application located at https://cas.example.com/undeclared/ will also be accepted in order to preserve the previous behavior of matching on hostnames only. + +.. versionchanged:: 2.0.12 + + The *Strict URL matching* option now lets you decide if LemonLDAP::NG should + fall back to legacy host-based matching if it cannot find a declared service + matching an incoming service URL. In the previous example, + https://cas.example.com/undeclared/ will no longer match if strict URL + matching is enabled diff --git a/doc/sources/admin/idpopenidconnect.rst b/doc/sources/admin/idpopenidconnect.rst index f7085e296e71fdc729aa41f5ccafa2ee3f478c99..740ccbd2adc71f82560cc678c3aeddfccd094d7e 100644 --- a/doc/sources/admin/idpopenidconnect.rst +++ b/doc/sources/admin/idpopenidconnect.rst @@ -11,8 +11,8 @@ Presentation stacks. It is described here: http://openid.net/connect/. LL::NG can act as an OpenID Connect Provider (OP). It will answer to -OpenID Connect requests to give user identity (trough ID Token) and -information (trough User Info end point). +OpenID Connect requests to give user identity (through ID Token) and +information (through User Info end point). As an OP, LL::NG supports a lot of OpenID Connect features: @@ -136,7 +136,6 @@ An example of its content: "hybrid" ], "authorization_endpoint" : "http://auth.example.com/oauth2/authorize", - "check_session_iframe" : "http://auth.example.com/oauth2/checksession", "scopes_supported" : [ "openid", "profile", @@ -186,6 +185,8 @@ For each OpenID Connect claim you want to release to applications, you can defin in User attribute parameter (see below). +.. _oidcextraclaims: + Extra Claims ^^^^^^^^^^^^ @@ -216,6 +217,8 @@ Userinfo endpoint. LemonLDAP::NG session attribute in the **Exported Attributes** section +.. _oidcscoperules: + Scope Rules ^^^^^^^^^^^ @@ -297,7 +300,7 @@ Options default value is one minute. - **ID Token expiration**: Expiration time of ID Tokens. The default value is one hour. - - **Access token expiration** (since version ``2.0.12``): Expiration time + - **Access token expiration**: Expiration time of Access Tokens. The default value is one hour. - **Offline session expiration**: This sets the lifetime of the refresh token obtained with the **offline_access** scope. The @@ -308,8 +311,11 @@ Options - **ID Token signature algorithm**: Select one of the available public key (RSXXX) or HMAC (HSXXX) based signature algorithms - - **Access Token signature algorithm**: Select one of the available public - key signature algorithms + - **Access Token signature algorithm** (since version ``2.0.12``): Select + one of the available public key signature algorithms + - **Userinfo response format** (since version ``2.0.12``): By default, + UserInfo is returned as a simple JSON object. You can also choose to + return it as a JWT, using one of the available signature algorithms. - **Require PKCE** (since version ``2.0.4``): a code challenge is required at token endpoint (see `RFC7636 `__) @@ -321,7 +327,7 @@ Options for details. These offline sessions can be administered through the Session Browser. - **Allow OAuth2.0 Password Grant** (since version ``2.0.8``): Allow the use of the :ref:`Resource Owner Password Credentials Grant ` by this client. This feature only works if you have configured a form-based authentication module. - - **Allow OAuth2.0 Client Credentials Grant** (since version ``2.0.11``): Allow the use of the :ref:`Resource Owner Password Credentials Grant ` by this client. + - **Allow OAuth2.0 Client Credentials Grant** (since version ``2.0.11``): Allow the use of the :ref:`Client Credentials Grant ` by this client. - **Authentication Level**: required authentication level to access this application - **Access Rule**: lets you specify a :doc:`Perl rule` to restrict access to this client @@ -339,6 +345,10 @@ Resource Owner Password Credentials Grant The Resource Owner Password Credentials Grant allows you to exchange a user's login and password for an access token. This must be considered a legacy form of authentication, since the Authorization Code web-based flow is prefered for all applications that support it. It can however be useful in some scenarios involving technical accounts that cannot implement a web-based authentication flow. +.. versionchanged:: 2.0.12 + + when using the :doc:`Choice ` authentication module, the *Choice used for password authentication* setting can be used to select which authentication choice is used by the Resource Owner Password Credentials Grant. Naturally, the selected choice must be a password-based authentication method (LDAP, DBI, REST, etc.) + .. seealso:: `Specification for the Resource Owner Password Credentials Grant `__ diff --git a/doc/sources/admin/idpsaml.rst b/doc/sources/admin/idpsaml.rst index 88ea1876d1eb8be3a25e671f8928498f1d6729b2..c0cd86057978de6010fa889e831b827f4d970aad 100644 --- a/doc/sources/admin/idpsaml.rst +++ b/doc/sources/admin/idpsaml.rst @@ -95,7 +95,7 @@ For each attribute, you can set: - **Mandatory**: if set to "On", then this attribute is required to build the SAML response, an error will displayed if there is no value for it. Optional attribute will be sent only if there is a value - associated. Else it just will be sent trough an attribute response, + associated. Else it just will be sent through an attribute response, if explicitly requested in an attribute request. - **Format**: optional, SAML attribute format. @@ -190,15 +190,21 @@ IDP Initiated mode The IDP Initiated URL is the SSO SAML URL with GET parameters: -- IDPInitiated: 1 -- One of: +- ``IDPInitiated``: ``1`` +- One of: - - sp: SP entity ID - - spConfKey: SP configuration key + - ``sp``: Service Provider entity ID + - ``spConfKey``: Service Provider configuration key For example: http://auth.example.com/saml/singleSignOn?IDPInitiated=1&spConfKey=simplesamlphp +- Optionally, if you may also specify, in addition to ``sp`` or ``spConfKey``: + + - ``spDest``: URL of Service Provider's AssertionConsumerService + +The URL specified in ``spDest`` *must* be present in the Service Provider metadata registered in LemonLDAP::NG. This is only useful if your Service Provider is reachable over multiple URLs. + Macros ^^^^^^ diff --git a/doc/sources/admin/impersonation.rst b/doc/sources/admin/impersonation.rst index 71175691b2d38a984377d94c32f91c3198f9381a..6758de8fe0b4cf2d9685f4c7c6a2d0ddaf967dc4 100644 --- a/doc/sources/admin/impersonation.rst +++ b/doc/sources/admin/impersonation.rst @@ -51,17 +51,19 @@ protected from being impersonated. - .. attention:: Both spoofed and real session attributes can be used to set access rules, groups or macros. - By example : ``$real_uid eq 'dwho'`` or ``$real_groups =~ /\bsu\b/`` + By example : ``$real_uid && $real_uid eq 'dwho'`` or ``$real_groups && $real_groups =~ /\bsu\b/`` Keep in mind that real session is computed first. Afterward, if access is granted, impersonated session is computed with real and spoofed session attributes if Impersonation is allowed. + So, ``real_`` attributes are computed by second authentication process. + To avoid Perl warnings, you have to prefix regex with ``$real_var &&``. + .. attention:: diff --git a/doc/sources/admin/index_minihowtos.rst b/doc/sources/admin/index_minihowtos.rst index acd2cc1a5474c10e333e21db089c3a47f704f406..724294db8279d70cf0a10781a72165e82b072c0f 100644 --- a/doc/sources/admin/index_minihowtos.rst +++ b/doc/sources/admin/index_minihowtos.rst @@ -17,3 +17,4 @@ Mini Howtos renater behindproxyminihowto useoutgoingproxy + testopenidconnect diff --git a/doc/sources/admin/kerberos.rst b/doc/sources/admin/kerberos.rst index 7fa8e8e74f408a25743ec9ee232128b3016f0ce7..a9624714a55f3ee1a259baab7c5f7999b00960be 100644 --- a/doc/sources/admin/kerberos.rst +++ b/doc/sources/admin/kerberos.rst @@ -38,16 +38,24 @@ It is recommended to use NTP to do this. DNS ~~~ -The auth.example.com must be registered in the DNS server (which is -Active Directory). The reverse DNS of auth.example.com **must** return -the portal IP. +In our experience, we have observed the following limitations when using Kerberos for web applications in an Active Directory environment +* ``auth.example.com`` must be registered in the DNS server as a ``A`` record. ``CNAME`` usually do not work +* The reverse DNS (``PTR``) for ``auth.example.com``'s IP address MUST point back to ``auth.example.com`` .. tip:: If you have a SSO cluster, you must setup a Virtual IP in cluster and register this IP in DNS. +.. tip:: + + If you cannot configure the PTR record to point to the portal's hostname, it + may help to run the following command. Assuming that ``proxy.example.com`` is + the PTR record of the portal's IP address :: + + setspn -s HTTP/proxy.example.com keytab-account + SSL ~~~ @@ -194,7 +202,7 @@ You can close the Kerberos session: kdestroy -Now you can compare the above result with the same request done trough +Now you can compare the above result with the same request done through the keytab file: :: diff --git a/doc/sources/admin/ldapsessionbackend.rst b/doc/sources/admin/ldapsessionbackend.rst index 9102c0663e32efad8b5d016a4fd3428887e97a30..c642e015334bd04d654e16471f25feb2e7ed518c 100644 --- a/doc/sources/admin/ldapsessionbackend.rst +++ b/doc/sources/admin/ldapsessionbackend.rst @@ -42,7 +42,7 @@ Name Comment Example ======================== ================================= =============================== **ldapServer** URI of the server ldap://localhost **ldapConfBase** DN of sessions branch ou=sessions,dc=example,dc=com -**ldapBindDN** Connection login cn=admin,dc=example,dc=password +**ldapBindDN** Connection login cn=admin,dc=example,dc=dom **ldapBindPassword** Connection password secret ======================== ================================= =============================== diff --git a/doc/sources/admin/logoutforward.rst b/doc/sources/admin/logoutforward.rst index d10be6e74a3f7740bd3d38d1446ce65b51fab9a9..88b1fc89e90a96f581b76081bcdfc7e28b2822e9 100644 --- a/doc/sources/admin/logoutforward.rst +++ b/doc/sources/admin/logoutforward.rst @@ -4,7 +4,7 @@ Logout forward Presentation ------------ -Even if LL:NG can catch logout URL trough +Even if LL:NG can catch logout URL through :ref:`virtual host rules`, you can have the need to forward a logout to other applications, to close their local sessions. diff --git a/doc/sources/admin/managerprotection.rst b/doc/sources/admin/managerprotection.rst index a886d7c966585fffe086219fa69f213a258d4977..951d6e237ee00af83f94716fbf0263b2a50e93df 100644 --- a/doc/sources/admin/managerprotection.rst +++ b/doc/sources/admin/managerprotection.rst @@ -89,7 +89,7 @@ Save the configuration and exit the Manager. .. tip:: - The next time you will access Manager, it will be trough + The next time you will access Manager, it will be through LL::NG. Enable protection on Manager, by editing ``lemonldap-ng.ini``: diff --git a/doc/sources/admin/mongodbconfbackend.rst b/doc/sources/admin/mongodbconfbackend.rst index ef572c819e9c52b12cad36f8f718cdfd1d1d2f35..f8c7062e653c2546a3a6d27698b0684f73a005c5 100644 --- a/doc/sources/admin/mongodbconfbackend.rst +++ b/doc/sources/admin/mongodbconfbackend.rst @@ -6,6 +6,18 @@ used both for storing configuration and :doc:`sessions`. You need to install Perl MongoDB module to be able to use this backend. +For Debian, you can install mongodb module with: + +:: + + apt install libmongodb-perl + +For CentOS: + +:: + + yum install perl-MongoDB + See :doc:`how to change configuration backend` to change your configuration database. diff --git a/doc/sources/admin/mongodbsessionbackend.rst b/doc/sources/admin/mongodbsessionbackend.rst index cb13ec340f444e3832cbd3fa6a30fe9c76bf7f82..a0dd794e8bff01cae6f56ef24dba5fac09e48bff 100644 --- a/doc/sources/admin/mongodbsessionbackend.rst +++ b/doc/sources/admin/mongodbsessionbackend.rst @@ -20,6 +20,21 @@ Perl module (version ⩾ 0.15 required). You also need a recent version of client `__ (version ⩾ 1.00 required). +For Debian, you can install mongodb module and Apache::Session module with: + +:: + + apt install libmongodb-perl + cpan Apache::Session::MongoDB + +For CentOS: + +:: + + yum install perl-MongoDB + cpan Apache::Session::MongoDB + + In the manager: set `Apache::Session::MongoDB `__ in ``General parameters`` » ``Sessions`` » ``Session storage`` » diff --git a/doc/sources/admin/nosqlsessionbackend.rst b/doc/sources/admin/nosqlsessionbackend.rst index 76b7de4a184b6b286f387a7b993785581923ea6c..d108bc7f44bd944c830c439287cbd9504ee55dac 100644 --- a/doc/sources/admin/nosqlsessionbackend.rst +++ b/doc/sources/admin/nosqlsessionbackend.rst @@ -7,11 +7,13 @@ is the faster shareable session backend Setup ----- -Install and launch a `Redis server `__. +Install and launch a `Redis server `__. Install -`Apache::Session::Browseable::Redis `__ +`Apache::Session::Browseable::Redis `__ Perl module. +With Sentinel, make sure you are using at least version 1.3.8 of ``Apache::Session::Browseable``, this might require installing it from Debian Backports or CPAN. + In the manager: set `Apache::Session::Browseable::Redis `__ in ``General parameters`` » ``Sessions`` » ``Session storage`` » @@ -28,6 +30,7 @@ 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 +**service** Sentinel service name mymaster **password** password (== requirepass) ChangeMe **select** Redis DB 1 **Index** Fields to index refer to :ref:`fieldstoindex` diff --git a/doc/sources/admin/notifications.rst b/doc/sources/admin/notifications.rst index eb1361f86c3f6e48140f6ee46261b230ec17ea91..87217ea77f795fa1e4fd72419519f22c83f30a12 100644 --- a/doc/sources/admin/notifications.rst +++ b/doc/sources/admin/notifications.rst @@ -20,7 +20,7 @@ Activation To activate notifications system: -Go to Manager ``General Parameters`` » ``Advanced Parameters`` » ``Notifications`` » ``Activation`` +Go to Manager ``General Parameters`` » ``Plugins`` » ``Notifications`` » ``Activation`` or in ``lemonldap-ng.ini`` [portal] section: @@ -34,7 +34,7 @@ Explorer Notifications explorer allows users to see and display theirs accepted notifications. Disable by default, you just have to activate it in the -Manager (``General Parameters`` » ``Advanced Parameters`` » ``Notifications`` » +Manager (``General Parameters`` » ``Plugins`` » ``Notifications`` » ``Explorer``) or in ``lemonldap-ng.ini`` [portal] section: @@ -91,7 +91,7 @@ configuration: You can change default parameters using the "notificationStorage" and "notificationStorageOptions" parameters with the same syntax as configuration storage parameters. To do this in Manager, go in General -Parameters > Advanced Parameters > Notifications. +Parameters > Plugins > Notifications. File ^^^^ @@ -200,7 +200,7 @@ The notifications module uses a wildcard to manage notifications for all users. The default value of this wildcard is ``allusers``, but you can change it if ``allusers`` is a known identifier in your system. -To change it, go in General Parameters > Advanced Parameters > +To change it, go in General Parameters > Plugins > Notifications > Wildcard for all users, and set for example ``alluserscustom``. diff --git a/doc/sources/admin/oauth2handler.rst b/doc/sources/admin/oauth2handler.rst index 68071f3866a78221876fb130aeada3a6cfd38ac5..89e49725e1d3cb8fdad45f7ac48ebe74845ef160 100644 --- a/doc/sources/admin/oauth2handler.rst +++ b/doc/sources/admin/oauth2handler.rst @@ -10,7 +10,7 @@ This Handler is able to check an OAuth2 access token to retrieve the user real session and protect a virtual host like a standard Handler (access control and HTTP headers transmission). -This requires to get an OAuth2 access token trough LL::NG Portal (OpenID +This requires to get an OAuth2 access token through LL::NG Portal (OpenID Connect server). This access token can then be used in the ``Authorization`` header to authenticate to the Web Service / API protected by the OAuth2 Handler. diff --git a/doc/sources/admin/openidconnectservice.rst b/doc/sources/admin/openidconnectservice.rst index 57e75e3806b6cf7c519672e5ef5598676b7c709e..f9e20b58127484b310b644a2e61805168c97611d 100644 --- a/doc/sources/admin/openidconnectservice.rst +++ b/doc/sources/admin/openidconnectservice.rst @@ -48,6 +48,12 @@ Security configuration in the backend per registration request. You can limit this by protecting in the WebServer the registration end point with an authentication module, and give the credentials to clients. +- **Only allow declared scopes**: By default, LemonLDAP::NG will grant all requested scopes. When this option is in use, LemonLDAP will only grant: + + - Standard OIDC scopes (``openid`` ``profile`` ``email`` ``address`` ``phone``) + - Scopes declared in :ref:`Extra Claims ` + - Scopes declared in :ref:`Scope Rules ` (if they match the rule) + - **Authorization Code flow**: Set to 1 to allow Authorization Code flow - **Implicit flow**: Set to 1 to allow Implicit flow @@ -64,7 +70,7 @@ Dynamic Registration If dynamic registration is enabled, you can configure the following options to define attributes and extra claims when a new relying party -is registered trough the ``/oauth2/register`` endpoint: +is registered through the ``/oauth2/register`` endpoint: - Exported vars for dynamic registration - Extra claims for dynamic registration @@ -92,7 +98,7 @@ run for example each week: Session management ------------------ -LL::NG implements the `OpenID Connect Chance Notification specification `__ +LL::NG implements the `OpenID Connect Change Notification specification `__ A ``changed`` state will be sent if the user is disconnected from LL::NG portal (or has destroyed its SSO cookie). Else the ``unchanged`` state diff --git a/doc/sources/admin/parameterlist.rst b/doc/sources/admin/parameterlist.rst index b24abaf486b2f9967efe0e84bc1440e6aee57bac..569b2bd2fc66474593b935825563c90d71e7b68a 100644 --- a/doc/sources/admin/parameterlist.rst +++ b/doc/sources/admin/parameterlist.rst @@ -274,7 +274,7 @@ log4perlConfFile Log4Perl logger configur logLevel Log level, must be set in .ini ✔ ✔ ✔ ✔ logger technical logger ✔ ✔ ✔ ✔ loginHistoryEnabled Enable login history ✔ -logoutServices Send logout trough GET request to these services ✔ +logoutServices Send logout through GET request to these services ✔ lwpOpts Options given to LWP::UserAgent ✔ lwpSslOpts SSL options given to LWP::UserAgent ✔ macros Macros ✔ @@ -400,6 +400,7 @@ portalDisplayGeneratePassword Display password generat portalDisplayLoginHistory Display login history tab in portal ✔ portalDisplayLogout Display logout tab in portal ✔ portalDisplayOidcConsents Display OIDC consent tab in portal ✔ +portalDisplayManageSessions Display manage sessions tab in portal portalDisplayPasswordPolicy Display policy in password form ✔ portalDisplayRefreshMyRights Display link to refresh the user session ✔ portalDisplayRegister Display register button in portal ✔ @@ -574,7 +575,6 @@ tokenUseGlobalStorage Enable global token stor totp2fActivation TOTP activation ✔ totp2fAuthnLevel Authentication level for users authentified by password+TOTP ✔ totp2fDigits Number of digits for TOTP code ✔ -totp2fDisplayExistingSecret Display existing TOTP secret in registration form ✔ totp2fInterval TOTP interval ✔ totp2fIssuer TOTP Issuer ✔ totp2fLabel Portal label for TOTP 2F ✔ @@ -582,7 +582,6 @@ totp2fLogo Custom logo for TOTP 2F totp2fRange TOTP range (number of interval to test) ✔ totp2fSelfRegistration TOTP self registration activation ✔ totp2fTTL TOTP device time to live ✔ -totp2fUserCanChangeKey Authorize users to change existing TOTP secret ✔ totp2fUserCanRemoveKey Authorize users to remove existing TOTP secret ✔ trustedDomains Trusted domains ✔ twitterAppName ✔ diff --git a/doc/sources/admin/portalcustom.rst b/doc/sources/admin/portalcustom.rst index 3f3cb3dc1a1e9ab6ab45f44daf2e15723c38a495..c41b5894eacae6887f4d487ed4ce1fc5105b616d 100644 --- a/doc/sources/admin/portalcustom.rst +++ b/doc/sources/admin/portalcustom.rst @@ -46,7 +46,7 @@ Custom CSS file You can define a custom CSS file, for example ``custom.css``, which will be loaded after default CSS files. This file needs to be created in the static repository -(``/usr/share/lemonldap-ng/portal/htdocs/static/boostrap/css``). +(``/usr/share/lemonldap-ng/portal/htdocs/static/bootstrap/css``). Then set this value in Custom CSS parameter : ``bootstrap/css/custom.css``. @@ -89,7 +89,7 @@ configuration. |image0| To set your own background, copy your file in -``/usr/share/lemonldap-ng/portal/htdocs/skins/common/backgrounds/`` and +``/usr/share/lemonldap-ng/portal/htdocs/static/common/backgrounds/`` and register it in ``/etc/lemonldap-ng/lemonldap-ng.ini``: .. code-block:: ini @@ -332,6 +332,9 @@ General - **Send mail on password change**: send a mail if the password is changed from the Menu, or from forced password reset (LDAP password policy) +- **Allow to display password**: if enabled, a small icon in the password + field is added and when users click on it, the password value is + revealed. Disabled by default. Password Policy ~~~~~~~~~~~~~~~ @@ -346,8 +349,7 @@ Password Policy - **Minimal upper characters**: leave 0 to bypass the check - **Minimal digit characters**: leave 0 to bypass the check - **Minimal special characters**: leave 0 to bypass the check -- **Allowed special characters**: set blanck to forbid special - characters (``_`` is not a special character) +- **Allowed special characters**: set '__ALL__' value to allow ALL special characters. A blanck value forbids ALL special characters (Note that ``_`` is not a special character) - **Display policy in password form**: enable this to display an information message about password policy constraints diff --git a/doc/sources/admin/portalmenu.rst b/doc/sources/admin/portalmenu.rst index 69931494f95553d95848d4692a181815aa25fd46..9908aa08d0c174b79c830ac5aa2acccb95033ac8 100644 --- a/doc/sources/admin/portalmenu.rst +++ b/doc/sources/admin/portalmenu.rst @@ -11,22 +11,23 @@ Portal menu Menu modules ------------ -LemonLDAP::NG portal menu has 4 modules: +LemonLDAP::NG portal menu has 6 modules: - **Application list**: display categories and applications allowed for the user - **Password change**: form to change the password - **Login history**: display user's last logins and last failed logins - **OIDC Consents**: display user's OpenId Connect consents +- **Sessions Manager**: display user's sessions and associated OIDC tokens - **Logout**: logout button -Each module can be activated trough a rule, using user session -information. These rules can be set trough Manager: +Each module can be activated through a rule, using user session +information. These rules can be set through Manager: ``General Parameters`` > ``Portal`` > ``Menu`` > ``Modules activation``. You can use ``0`` or ``1`` to disable/enable the module, or use a more complex rule. For example, to display the password change form only for -user authenticated trough LDAP or DBI: +user authenticated through LDAP or DBI: .. code-block:: perl @@ -38,7 +39,7 @@ Categories and applications --------------------------- :doc:`Configuring the virtual hosts` is not sufficient to -display an application in the menu. Indeed, a virtual host can contain +display an application in the menu. Indeed, a virtual host can serve several applications (http://vhost.example.com/appli1, http://vhost.example.com/appli2). diff --git a/doc/sources/admin/portalservers.rst b/doc/sources/admin/portalservers.rst index 5de9cde13fdbcf64e4677d484f737875c1059a7a..687332fdf7665b8f381b41a2eafe371fcaf3f670 100644 --- a/doc/sources/admin/portalservers.rst +++ b/doc/sources/admin/portalservers.rst @@ -16,9 +16,9 @@ Configuration ------------- - **SOAP/REST exported attributes**: list session attributes shared - trough SOAP/REST + through SOAP/REST - - Use ``+`` to append to the default list of technical attributes, + - Use ``+`` to append the default list of technical attributes, example: ``+ uid mail`` REST diff --git a/doc/sources/admin/radius2f-inwebo.rst b/doc/sources/admin/radius2f-inwebo.rst index 8abcb862960053bb7b85b3c931d2b7cefe46c0a1..3492f709469ed2a94522e445dfe261d3a29fde76 100644 --- a/doc/sources/admin/radius2f-inwebo.rst +++ b/doc/sources/admin/radius2f-inwebo.rst @@ -2,7 +2,7 @@ InWebo Second Factor ==================== `InWebo `_ is a proprietary MFA solution. -You can use is as second factor trough :doc:`Radius 2FA module`. +You can use is as second factor through :doc:`Radius 2FA module`. Configuration ~~~~~~~~~~~~~ diff --git a/doc/sources/admin/redirections.rst b/doc/sources/admin/redirections.rst index 8e2be6e5079c4df26f4685b216a3ebb866ca7b63..d954debeeb9cc70d8cae476341f1bb6773b93893 100644 --- a/doc/sources/admin/redirections.rst +++ b/doc/sources/admin/redirections.rst @@ -39,7 +39,7 @@ Handler use the default Apache error code for the following cases: - An error occurs on server side: SERVER_ERROR (500) - The application is in maintenance: HTTP_SERVICE_UNAVAILABLE (503) -These errors can be catch trough Apache ``ErrorDocument`` directive or +These errors can be catch through Apache ``ErrorDocument`` directive or Nginx ``error_page`` directive, to redirect user on a specific page: .. code-block:: apache diff --git a/doc/sources/admin/renater.rst b/doc/sources/admin/renater.rst index 6ca8e0889fdd38580531ad2d5bf156488c4dae7c..b6235c351edad3d15c4e8e5cec43b86ec9faa4f1 100644 --- a/doc/sources/admin/renater.rst +++ b/doc/sources/admin/renater.rst @@ -92,6 +92,53 @@ Then run the script: /usr/share/lemonldap-ng/bin/importMetadataRenater -m https://metadata.federation.renater.fr/renater/main/main-idps-renater-metadata.xml -r -i "idp-renater-" -s "sp-renater-" +The script provide the following options + + * -i (--idpconfprefix): Prefix used to set IDP configuration key + * -h (--help): print this message + * -m (--metadata): URL of metadata document + * -s (--spconfprefix): Prefix used to set SP configuration key + * --ignore-sp: ignore SP maching this entityID (can be specified multiple times) + * --ignore-idp: ignore IdP matching this entityID (can be specified multiple times) + * -a (--nagios): output statistics in Nagios format + * -n (--dry-run): print statistics but do not apply changes + * -v (--verbose): increase verbosity of output + * -r (--remove): remove provider from LemonLDAP::NG if it does not appear in metadata + + +Example : +:: + + /usr/libexec/lemonldap-ng/bin/importMetadata -m https://pub.federation.renater.fr/metadata/renater/main/main-sps-renater-metadata.xml -s "sp-fed-prd" -c https://pub.federation.renater.fr/metadata/certs/renater-metadata-signing-cert-2016.pem -bs https://test-sp.federation.renater.fr -r -v -d + +This command will + * fetch all SPs metadata from renater + * set a prefix to entity stored inside LemonLdap::NG + * disable local modification of SP https://test-sp.federation.renater.fr + * remove local SPs wich didn't exist anymore in Federation metadata + * show only all modifications to apply + +The output is the following : +:: + + ... + Update SP https://www-iuem.univ-brest.fr/sp in configuration + Attribute mail (urn:oid:0.9.2342.19200300.100.1.3) requested by SP https://gesper.ad.bnu.fr/shibboleth + Attribute eduPersonPrimaryAffiliation (urn:oid:1.3.6.1.4.1.5923.1.1.1.5) requested by SP https://gesper.ad.bnu.fr/shibboleth + Attribute eduPersonPrincipalName (urn:oid:1.3.6.1.4.1.5923.1.1.1.6) requested by SP https://gesper.ad.bnu.fr/shibboleth + Attribute displayName (urn:oid:2.16.840.1.113730.3.1.241) requested by SP https://gesper.ad.bnu.fr/shibboleth + Update SP https://gesper.ad.bnu.fr/shibboleth in configuration + [INFO] Dry-run mod no EntityID inserted + [IDP] Found: 0 Updated: 0 Created: 0 Removed: 0 Rejected: 0 Ignored: 0 + [SP] Found: 1248 Updated: 1240 Created: 0 Removed: 0 Rejected: 7 Ignored: 1 + + +With "-n" options you could get a "nagios like" output with metrics : +:: + + /usr/libexec/lemonldap-ng/bin/importMetadataFedRenater -m https://pub.federation.renater.fr/metadata/renater/main/main-sps-renater-metadata.xml -s "sp-fed-prd" -c https://pub.federation.renater.fr/metadata/certs/renater-metadata-signing-cert-2016.pem -bs https://test-sp.federation.renater.fr -r -d -n + Metadata loaded inside Conf: [DRY-RUN]|idp_found=0, idp_updated=0, idp_created=0, idp_removed=0, idp_rejected=0, idp_ignored=0, sp_found=1248, sp_updated=1240, sp_created=0, sp_removed=0, sp_rejected=7, sp_ignored=1 + .. attention:: diff --git a/doc/sources/admin/requirements.txt b/doc/sources/admin/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..59aa86ccce8d86a6db81782ff7a334c2f1eae8da --- /dev/null +++ b/doc/sources/admin/requirements.txt @@ -0,0 +1 @@ +sphinx_bootstrap_theme diff --git a/doc/sources/admin/resetcertificate.rst b/doc/sources/admin/resetcertificate.rst index 809118f37b2555e2feb26e9691df94719b9ecf81..9ff6fdd4a2b0ca49a01db5d7d47a67fa7a0123b0 100644 --- a/doc/sources/admin/resetcertificate.rst +++ b/doc/sources/admin/resetcertificate.rst @@ -1,5 +1,3 @@ -|image0| - Certificate reset ================= diff --git a/doc/sources/admin/restconfbackend.rst b/doc/sources/admin/restconfbackend.rst index d1224c3fec7884d3ae25ec857cbca715f1526e24..17864c45db9dd73122e5ced381cef9dd8c7b8019 100644 --- a/doc/sources/admin/restconfbackend.rst +++ b/doc/sources/admin/restconfbackend.rst @@ -77,3 +77,7 @@ You can also add some other parameters # LWP::UserAgent parameters proxyOptions = { timeout => 5 } +`User` and `Password` parameters are only used if the entry point `index.fcgi/config` +is protected by a basic authentication. Thus, handlers will make requests to the portal +using these parameters. + diff --git a/doc/sources/admin/restsessionbackend.rst b/doc/sources/admin/restsessionbackend.rst index 6d88e1bc683acfb96b30c5313d09e93bf9ecb017..29e2a497c3d251fcde0eeb45bcf57d6ffc595944 100644 --- a/doc/sources/admin/restsessionbackend.rst +++ b/doc/sources/admin/restsessionbackend.rst @@ -33,7 +33,7 @@ protected with a firewall that only accepts HTTP flows. Most of the time, REST session backend is used by Handlers installed on external servers. -To configure it, REST session backend will be set trough Manager in +To configure it, REST session backend will be set through Manager in global configuration (used by all Handlers), and the real session backend will be configured for local components in lemonldap-ng.ini. @@ -68,6 +68,10 @@ Name Comment Example **password** Password to use for auth basic mechanism =================== ======================================== ================================================== +`user` and `password` parameters are only used if the entry point `index.fcgi/sessions/global` +is protected by a basic authentication. Thus, handlers will make requests to the portal +using these parameters. + .. attention:: @@ -86,7 +90,7 @@ configuration (for example, access by IP range): # REST/SOAP functions for sessions access (disabled by default) - Require 192.168.2.0/24 + Require ip 192.168.2.0/24 Real session backend diff --git a/doc/sources/admin/security.rst b/doc/sources/admin/security.rst index f1f515b7baaf2169c1893bbe8e50c7b3993853c3..dd28a4697f70c3beaa2605560eba8035085d9a2f 100644 --- a/doc/sources/admin/security.rst +++ b/doc/sources/admin/security.rst @@ -354,7 +354,7 @@ Go in Manager, ``General parameters`` » ``Advanced parameters`` » to disable CSRF token by setting a special rule based on callers IP address like this : - requireToken => $env->{REMOTE_ADDR} !~ /^127\.0\.[1-3]\.1$/ + requireToken => $env->{REMOTE_ADDR} && $env->{REMOTE_ADDR} !~ /^127\.0\.[1-3]\.1$/ .. danger:: diff --git a/doc/sources/admin/soapsessionbackend.rst b/doc/sources/admin/soapsessionbackend.rst index 52530d0dbae1d1683c971e185fbd468f19161826..1af8066afbaeb8ed8a6339cb7338c48e87b72132 100644 --- a/doc/sources/admin/soapsessionbackend.rst +++ b/doc/sources/admin/soapsessionbackend.rst @@ -17,7 +17,7 @@ protected with a firewall that only accepts HTTP flows. Most of the time, SOAP session backend is used by Handlers installed on external servers. -To configure it, SOAP session backend will be set trough Manager in +To configure it, SOAP session backend will be set through Manager in global configuration (used by all Hanlders), and the real session backend will be configured for local components in lemonldap-ng.ini. @@ -78,12 +78,12 @@ configuration (for example, access by IP range): # SOAP functions for sessions management (disabled by default) - Require 192.168.2.0/24 + Require ip 192.168.2.0/24 # SOAP functions for sessions access (disabled by default) - Require 192.168.2.0/24 + Require ip 192.168.2.0/24 Real session backend diff --git a/doc/sources/admin/sqlconfbackend.rst b/doc/sources/admin/sqlconfbackend.rst index 6042e18f0c31169fee5eaa5c23d7d97bdd87eed4..f115d812283dc2d568ff8282e42255f949201f7f 100644 --- a/doc/sources/admin/sqlconfbackend.rst +++ b/doc/sources/admin/sqlconfbackend.rst @@ -4,7 +4,7 @@ SQL configuration backends There is 2 types of SQL configuration backends for LemonLDAP::NG: - **CDBI**: very simple storage (recommended) -- **RDBI**: triple store storage +- **RDBI**: triple store storage (not recommended) .. tip:: @@ -50,27 +50,27 @@ Use database to create table: use lemonldap-ng -RDBI +CDBI ^^^^ .. code-block:: sql CREATE TABLE lmConfig ( - cfgNum int(11) NOT NULL, - field varchar(255) NOT NULL DEFAULT '', - value longtext, - PRIMARY KEY (cfgNum,field) - ); + cfgNum int not null primary key, + data longtext + ); -CDBI +RDBI ^^^^ .. code-block:: sql CREATE TABLE lmConfig ( - cfgNum int not null primary key, - data longtext - ); + cfgNum int(11) NOT NULL, + field varchar(255) NOT NULL DEFAULT '', + value longtext, + PRIMARY KEY (cfgNum,field) + ); Grant access ~~~~~~~~~~~~ @@ -107,7 +107,7 @@ file (section configuration): .. code-block:: ini [configuration] - type = RDBI + type = CDBI dbiChain = DBI:mysql:database=lemonldap-ng;host=1.2.3.4 dbiUser = lemonldaprw dbiPassword = mypassword @@ -155,6 +155,18 @@ Use database to create table: .. _rdbi-1: +CDBI +^^^^ + +.. code-block:: sql + + CREATE TABLE lmConfig ( + cfgnum integer not null primary key, + data text + ); + +.. _connection-settings-1: + RDBI ^^^^ @@ -169,18 +181,6 @@ RDBI .. _cdbi-1: -CDBI -^^^^ - -.. code-block:: sql - - CREATE TABLE lmConfig ( - cfgnum integer not null primary key, - data text - ); - -.. _connection-settings-1: - Connection settings ------------------- @@ -190,7 +190,7 @@ file (section configuration): .. code-block:: ini [configuration] - type = RDBI + type = CDBI dbiChain = DBI:Pg:database=lemonldap-ng;host=1.2.3.4 dbiUser = lemonldaprw dbiPassword = mypassword diff --git a/doc/sources/admin/start.rst b/doc/sources/admin/start.rst index ab60bb60550b747deaba2468d28b2bf75df26920..9cba3e80c4362cd54237febb1e5a4df443a9599f 100644 --- a/doc/sources/admin/start.rst +++ b/doc/sources/admin/start.rst @@ -284,13 +284,13 @@ Name Description :doc:`Global Logout` [10]_ Suggest to close all opened sessions at logout :doc:`Grant Sessions` Rules to apply before allowing a user to open a session :doc:`Impersonation` [11]_\ |new| Allow users to use another identity -:doc:`Find user` [12]_\ |beta| Search for user account +:doc:`Find user` [12]_\ |new| Search for user account :doc:`Notifications system` DIsplay a message during log in process :doc:`Portal Status` Experimental portal status page :doc:`Public pages` Enable public pages system :doc:`Refresh session API` [13]_ Plugin that provides an API to refresh a user session :doc:`Reset password by mail` Send a mail to reset its password -:doc:`Reset certificate by mail` [14]_\ |beta| Allow users to reset their certificate +:doc:`Reset certificate by mail` [14]_\ |new| Allow users to reset their certificate :doc:`REST services` |new| REST server for :doc:`Proxy` :doc:`SOAP services` |deprecated| SOAP server for :doc:`Proxy` :doc:`Stay connected` |new| Enable persistent connection on same browser @@ -341,7 +341,7 @@ Backend Shareable Comment Selected by default during installation. :doc:`YAML` |new| Same as :doc:`File` but in YAML format instead of JSON -:doc:`SQL (RDBI/CDBI)` ✔ Recommended for large-scale systems. Prefer CDBI. +:doc:`SQL (CDBI/RDBI)` ✔ **Recommended for large-scale systems**. Prefer CDBI. :doc:`LDAP` ✔ :doc:`MongoDB` ✔ :doc:`SOAP` |deprecated| ✔ Proxy backend to be used in conjunction with another @@ -386,7 +386,7 @@ style modules are usable except for some features. Backend Shareable :ref:`Session explorer` :ref:`Session restrictions` Session expiration Comment ================================================================ ========= ================================================ ==================================================== ================== ================================================================================================================================================================================================= :doc:`File` ✔ ✔ ✔ Not shareable between servers except if used in conjunction with :doc:`REST session backend` or with a shared file system (NFS,...). Selected by default during installation. -:doc:`PgJSON` ✔ ✔ ✔ ✔ Recommended backend for production installations +:doc:`PgJSON` ✔ ✔ ✔ ✔ **Recommended backend for production installations** :doc:`Browseable MySQL` ✔ ✔ ✔ ✔ Recommended for those who prefer MySQL :doc:`Browseable LDAP` ✔ ✔ ✔ ✔ :doc:`Redis` ✔ ✔ ✔ ✔ The fastest. Must be secured by network access control. diff --git a/doc/sources/admin/testopenidconnect.rst b/doc/sources/admin/testopenidconnect.rst new file mode 100644 index 0000000000000000000000000000000000000000..89d662ac8e9168f801cc8e97c7bbdd5066800935 --- /dev/null +++ b/doc/sources/admin/testopenidconnect.rst @@ -0,0 +1,132 @@ +Test OpenID Connect with command line tools +=========================================== + +We present here how to test the OpenID Connect protocol (authorization code flow) with commande line tools, like `curl`. + +We use in this example a public OIDC provider based on LL::NG: ``_ + +Authentication +-------------- + +The first step is to obtain a valid SSO session on the portal. Several solutions: + * Use a web browser and log into the portal, then get the value of the SSO cookie + * Use portal REST API, and adapt the `requireToken` configuration to get cookie value in JSON response (see :doc:`REST services`) + +Example of REST service usage, with credentials `dwho`/`dwho`: + +.. code-block:: shell + + curl -X POST -d user=dwho -d password=dwho -H 'Accept: application/json' 'https://oidctest.wsweet.org/oauth2/' + +The session id is displayed in JSON response: + +.. code-block:: javascript + + { + "error" : "0", + "id" : "0640f95827111f00ba7ad5863ba819fe46cfbcecdb18ce525836369fb4c8350b", + "result" : 1 + } + +Authorization code +------------------ + +In the first step of authorization code flow, we request a temporary code, ont the `authorize` end point. + +Parameters needed: + * SSO session id (will be passed in `lemonldap` cookie, adapt the name if needed) + * Client ID: given by your OIDC provider, we use here `private` + * Scope: depends on which information you need, we will use here `openid profile email` + * Redirect URI: shoud match the value registered in your OIDC provider, we will use here `http://localhost` + +The OIDC provide will return the code in the location header, so we just output this reponse header: + +.. code-block:: shell + + curl -s -D - -o /dev/null -b lemonldap=0640f95827111f00ba7ad5863ba819fe46cfbcecdb18ce525836369fb4c8350b 'https://oidctest.wsweet.org/oauth2/authorize?response_type=code&client_id=private&scope=openid+profile+email&redirect_uri=http://localhost' | grep '^location' + +The value of the location header is: + +.. code-block:: shell + + location: http://localhost?code=294b0facd91a0fa92762edc48d18369e99c330ba2b8fb05ab2c45999fcef6e17&session_state=BpB8KRMBEDUs%2B7lAjsz4DRk3E0RJImxgUbMsCFFAUa8%3D.N3dVOFg3a2RpNXVJK3ltSldrYXZjUjhtU0tvd29sWkpuWWJJbll5ZGs5NzhZMnh5bmQwd0IxRmJVWUxJSTlkWDBnSWZ2SWFVZmU0UnRaMkVJVjNUY3c9PQ + + +So we get the code value: `94b0facd91a0fa92762edc48d18369e99c330ba2b8fb05ab2c45999fcef6e17` + +This code has a short lifetime, we will use it to get access token and ID token in the next step + +Tokens +------ + +In this step, we exchange the authorization code against tokens: + * Access token + * ID token + * Refresh token (optional) + +Parameters needed: + * Authorization code: see previous step + * Grant type: we use here `authorization_code` + * Redirect URI: same value as the one used in the previous step + * Client ID and Client Secret: given by your OIDC provider, we use here `private`/`tardis` + +.. code-block:: shell + + curl -X POST -d grant_type=authorization_code -d 'redirect_uri=http://localhost' -d code=94b0facd91a0fa92762edc48d18369e99c330ba2b8fb05ab2c45999fcef6e17 -u 'private:tardis' 'https://oidctest.wsweet.org/oauth2/token' | json_pp + +The JSON response looks like this: + +.. code-block:: javascript + + { + "access_token" : "a88b8dde538719e55c3cb8fbd14d06ed77853c685a62abf6ecb88d86228a9c64", + "expires_in" : 3600, + "id_token" : "eyJhbGciOiJSUzI1NiIsImtpZCI6Im9pZGN0ZXN0IiwidHlwIjoiSldUIn0.eyJhdXRoX3RpbWUiOjE2MTQxNjAwMDYsImlhdCI6MTYxNDE2MzIxOCwiaXNzIjoiaHR0cHM6Ly9vaWRjdGVzdC53c3dlZXQub3JnLyIsImF0X2hhc2giOiJIVGswOVNjSjRObEFua3k5SGFFX2VRIiwiYWNyIjoibG9hLTIiLCJleHAiOjE2MTQxNjY4MTgsInN1YiI6ImR3aG8iLCJhenAiOiJwcml2YXRlIiwiYXVkIjpbInByaXZhdGUiXX0.N3TNufjKLzKM3qiIitA7JHUei4L572XjF6AcVl7UAFB6efdGUCiAL7amlUl0FgjZfzW9bzvulBVDidoYSicIaysIdI4KkjmjpVN0Z3gOSu0ecuk5p8fD1KbX6-tmA3txeR18nzfhdckq-S-6Lx7wrWpPNyrzGx-FImbOaUPN2yeVhKPXhdyHJbzI0RqJETxnBkyW-CLEzAJyq3rCUVX-D8kHADvg6a42QQyPdxvBuGrdBfyDDDb_Py13H1qhn40NnuFknR1wSahsY6U97uUooyk-0_U4J3XJAHySjCtivtSeP0fM_5eblMuh6WdVjrfnUF0xnCTbCa2gYRlTS38BkqcsWY26PXoRAOo31a1cmB5sMSZyPtRF9UZcmGiNBIymMMdFgVAJONb6uliiTS5j9-nkmHOqVC-XJ6tuiU3ZSBQ8nCRyNW2LaCzpJ5c3ytP9yYQtyT8HmhN0VnXob3K1uJEA_Xcu4sADjtrm-LbrGiwaVMkfu-C6YIrbuC9riOW6TneV2gAzAjXPOW_UZeXrCrx66GHIJPsJIq29UfbTN5Pxo9SH2yKw6PSfxevkZhBIhEXCOMaIUHrlWz2jDBBzPIWeiSRbK_MRtejQmdRUs8nqdq-McVwnFiUMDt1KZXxqScTtMDF_Lo9oK2RaCijEJ7MSPEscr_YOyp3KIq2FLVg", + "refresh_token" : "19434440ed4da2803e8ba9d91cb2eabd5b8bd12af2609429bda03ed487e6ef57", + "token_type" : "Bearer" + } + +The access token will be used for the last step, to get information about the user. + +The ID Token is a JWT (JSON Web Token) and can be parsed easily, as this is the concatenation of 3 JSON strings encoded in base 64: `base64(header).base64(payload).base64(signature)`. + +Decoding the payload gives: + +.. code-block:: javascript + + { + "acr" : "loa-2", + "at_hash" : "HTk09ScJ4NlAnky9HaE_eQ", + "aud" : [ + "private" + ], + "auth_time" : 1614160006, + "azp" : "private", + "exp" : 1614166818, + "iat" : 1614163218, + "iss" : "https://oidctest.wsweet.org/", + "sub" : "dwho" + } + +User info +--------- + +This step is optional and allows to fetch user information linked to scopes requested in the first step. + +Parameters needed: + * Access token, used as bearer authorization + +.. code-block:: shell + + curl -H 'Authorization: Bearer a88b8dde538719e55c3cb8fbd14d06ed77853c685a62abf6ecb88d86228a9c64' 'https://oidctest.wsweet.org/oauth2/userinfo' | json_pp + +JSON response: + +.. code-block:: javascript + + { + "email" : "dwho@badwolf.org", + "name" : "Doctor Who", + "preferred_username" : "dwho", + "sub" : "dwho" + } diff --git a/doc/sources/admin/totp2f.rst b/doc/sources/admin/totp2f.rst index 8baea97f546c34e34a0b0ac0fdcce3e241fd4536..817b98eed66c73243f3f9a6270409bc5051e06ea 100644 --- a/doc/sources/admin/totp2f.rst +++ b/doc/sources/admin/totp2f.rst @@ -52,10 +52,6 @@ In the manager (advanced parameters), you just have to enable it: - **Interval**: interval for TOTP algorithm (default: 30) - **Range**: number of additional intervals to test (default: 1) - **Digits**: number of digit by codes (default: 6) -- **Display existing secret**: display an already registered secret - (default: disabled) -- **Change existing secret**: authorize a user to change its previoulsy - registered TOTP secret - **Allow users to remove TOTP**: If enabled, users can unregister TOTP. - **Lifetime**: Unlimited by default. Set a Time To Live in seconds. diff --git a/doc/sources/admin/upgrade_2_0_x.rst b/doc/sources/admin/upgrade_2_0_x.rst index 7132aa4f65465694fc292e0e20b142d3ae272712..156aa1bffe72865bacbf42ed312cea5d43787923 100644 --- a/doc/sources/admin/upgrade_2_0_x.rst +++ b/doc/sources/admin/upgrade_2_0_x.rst @@ -26,6 +26,78 @@ Known regressions in the latest released version None + +2.0.12 +------ + +Portal templates changes +~~~~~~~~~~~~~~~~~~~~~~~~ + +If you customized the HTML mail content, you must update them to use HTML::Template variables (this was changed to fix XSS injections). + +For session variables, replace for example ``$cn`` by ````, and for other variables, replace for example ``$url`` by ````. + +Client Credential sessions missing expiration time +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you started using Client Credential grants in 2.0.11, you may have encountered +`issue 2481 `__. + +Because of this bug, the created sessions may never be purged by the `purgeCentralCache` script. + +In order to detect these sessions, you can run the following command: + +:: + + lemonldap-ng-sessions search --where _session_kind=SSO --select _session_id --select _utime | \ + jq -r '. | map(select(._utime == null)) | map(._session_id) | join ("\n")' + +This will output a list of SSO sessions with no expiration time. + +Review them manually using :: + + lemonldap-ng-sessions get + +You can then remove them with :: + + lemonldap-ng-sessions delete + +Brute-force protection plugin may cause duplicate persistent sessions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Because of `bug #2482 `__ , some users may notice that the persistent session database is filling with duplicate sessions. Some examples include: + +* An uppercase version of the regular persistent session (dwho vs DWHO) +* An unqualified version (dwho vs dwho@idp.com) + +This bug was fixed in 2.0.12, but administrators are advised to clean up their persistent session database to remove any duplicate persistent sessions remaining after the upgrade. + +OpenID Connect check session iframe +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The OIDC check session iframe is not working, it has been removed from OIDC configuration metadata. It should not impact any installation as this feature was already broken. + +Simplification of TOTP options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following options have been removed from TOTP configuration: + +* Display existing secret (``totp2fDisplayExistingSecret``) +* Change existing secret (``totp2fUserCanChangeKey``) + +As a consequence, users who are *not* using the default `bootstrap` skin may need to ajust their ``totp2fregister.tpl`` template: + +* Move ``#divToHide`` from the ``.col-md-6`` div to the ``.card`` div +* Change:: + +

+
+* to::
+
+  
+ +* Remove the ``#changekey`` button + 2.0.11 ------ diff --git a/doc/sources/admin/variables.rst b/doc/sources/admin/variables.rst index 41a2abead0bba950302d2c5c4911eb51ce2c62f3..4ae85136070c33ee0470c0210f50d90d691c657f 100644 --- a/doc/sources/admin/variables.rst +++ b/doc/sources/admin/variables.rst @@ -141,7 +141,7 @@ OpenID ================ ============================================= Key Description ================ ============================================= -\_openid\_\ *id* Consent to share attribute *id* trough OpenID +\_openid\_\ *id* Consent to share attribute *id* through OpenID ================ ============================================= OpenID Connect diff --git a/doc/sources/manager-api/openapi-spec.yaml b/doc/sources/manager-api/openapi-spec.yaml index 911e36bc418697e40e5055759b78a48630d67c54..f2414964c0cea73de8e96889b3acf3d5919a2699 100644 --- a/doc/sources/manager-api/openapi-spec.yaml +++ b/doc/sources/manager-api/openapi-spec.yaml @@ -1213,6 +1213,18 @@ components: - RS384 - RS512 default: HS512 + userInfoSignAlg: + type: string + enum: + - "" + - none + - HS256 + - HS384 + - HS512 + - RS256 + - RS384 + - RS512 + default: "" accessTokenJWT: type: bool accessTokenClaims: diff --git a/e2e-tests/custom.pm b/e2e-tests/custom.pm index a58794f0e8622dc16de51e33e02d31c22d8f1624..6174730aa18ac1ded80e654c71c797dc25338c41 100644 --- a/e2e-tests/custom.pm +++ b/e2e-tests/custom.pm @@ -17,7 +17,7 @@ sub accessToTrace { my $params = $hash->{params}; my $session = $hash->{session}; - return "$custom alias $params->[0]_$params->[1]:$session->{groups} with $session->{$params->[2]}"; + return "$custom alias $params->[0]_$params->[1]:$session->{groups} ($session->{$params->[2]})"; } 1; diff --git a/e2e-tests/lmConf-1.json b/e2e-tests/lmConf-1.json index cc40fcc936a2636075b9480a49807d026a625b36..56dab1a9fb6057378a3d72e99b6069e6b5dd2e1d 100644 --- a/e2e-tests/lmConf-1.json +++ b/e2e-tests/lmConf-1.json @@ -171,7 +171,7 @@ "vhostHttps": -1, "vhostAliases": "", "vhostServiceTokenTTL": -1, - "vhostAccessToTrace": "My::accessToTrace, Doctor, Who","vhostType":"Main" + "vhostAccessToTrace": "My::accessToTrace, Doctor, Who, _whatToTrace","vhostType":"Main" } }, "loginHistoryEnabled": 1, diff --git a/fastcgi-server/man/llng-fastcgi-server.8p b/fastcgi-server/man/llng-fastcgi-server.8p index 08bfa38085732fca5056c79fc7d9db491a2e10e0..3fbbb1b80ee28297b93d58c5a718812397f2e1c1 100644 --- a/fastcgi-server/man/llng-fastcgi-server.8p +++ b/fastcgi-server/man/llng-fastcgi-server.8p @@ -1,4 +1,4 @@ -.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) +.\" Automatically generated by Pod::Man 4.11 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "llng-fastcgi-server 8" -.TH llng-fastcgi-server 8 "2021-01-18" "perl v5.28.1" "User Contributed Perl Documentation" +.TH llng-fastcgi-server 8 "2021-07-03" "perl v5.30.0" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/fastcgi-server/sbin/llng-fastcgi-server b/fastcgi-server/sbin/llng-fastcgi-server index c7329085f15b83e792d0d27a12e491a879c36068..a9e96cbc1f22c4127a367e01fae8c157200f7b54 100644 --- a/fastcgi-server/sbin/llng-fastcgi-server +++ b/fastcgi-server/sbin/llng-fastcgi-server @@ -104,6 +104,9 @@ require Lemonldap::NG::Handler::Server::Nginx; $_apps{handler} = Lemonldap::NG::Handler::Server::Nginx->run( {} ); my $app = sub { + $SIG{'PIPE'} = sub { + print STDERR "Got a PIPE signal"; + }; my $type = $_[0]->{LLTYPE} || 'handler'; return $_apps{$type}->(@_) if ( defined $_apps{$type} ); if ( defined $builder{$type} ) { diff --git a/lemonldap-ng-common/META.json b/lemonldap-ng-common/META.json index 5deab80e4de48892c9ba8eaf4815ce5cd0d602f1..b1956bd9f9b7c0762a1e009d082a5bd4bff7dcdc 100644 --- a/lemonldap-ng-common/META.json +++ b/lemonldap-ng-common/META.json @@ -4,7 +4,7 @@ "Xavier Guimard , Clément Oudot " ], "dynamic_config" : 1, - "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010", + "generated_by" : "ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010", "license" : [ "open_source" ], diff --git a/lemonldap-ng-common/META.yml b/lemonldap-ng-common/META.yml index 05fd9c428e20a9a519e13909f5e9c5891606497f..d2f811330f021ec66724a14fd490553c8ce6dda5 100644 --- a/lemonldap-ng-common/META.yml +++ b/lemonldap-ng-common/META.yml @@ -10,7 +10,7 @@ build_requires: configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 -generated_by: 'ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010' +generated_by: 'ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010' license: open_source meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html diff --git a/lemonldap-ng-common/lemonldap-ng.ini b/lemonldap-ng-common/lemonldap-ng.ini index 05e170e877ed82fb575b81f454288a1d7d42cc38..abad54bb9e3ba9180ae93d795e307f5c70661b7f 100644 --- a/lemonldap-ng-common/lemonldap-ng.ini +++ b/lemonldap-ng-common/lemonldap-ng.ini @@ -103,7 +103,7 @@ checkTime = 1 ;confTimeout = 5 ; GLOBAL CONFIGURATION ACCESS TYPE -; (File, REST, SOAP, RDBI/CDBI, LDAP, YAMLFile) +; (File, REST, SOAP, CDBI/RDBI, LDAP, YAMLFile) ; Set here the parameters needed to access to LemonLDAP::NG configuration. ; You have to set "type" to one of the followings : ; @@ -114,11 +114,11 @@ checkTime = 1 ; ; Optimize JSON for readability instead of performance ; prettyPrint = 1 ; -; * RDBI/CDBI : you have to set 'dbiChain' (required) and 'dbiUser' and 'dbiPassword' +; * CDBI/RDBI : you have to set 'dbiChain' (required) and 'dbiUser' and 'dbiPassword' ; if needed. Example: ; -; type = RDBI -; ;type = CDBI +; type = CDBI +; ;type = RDBI ; dbiChain = DBI:MariaDB:database=lemonldap-ng;host=1.2.3.4 ; dbiUser = lemonldap ; dbiPassword = password @@ -218,7 +218,7 @@ languages = en, fr, vi, it, ar, de, fi, tr, pl, zh_TW, es ; Override error codes ;error_0 = You are well authenticated! ; Custom template parameters -; For example to use +; For example to use ;tpl_myparam = test ; COMBINATION FORMS diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf.pm index 5480537859afc8a326bf040eccc1cbbe32fd6414..6fd0bba4fa0d8ea091b1000617c411087557ebae 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf.pm @@ -27,7 +27,7 @@ use Config::IniFiles; #inherits Lemonldap::NG::Common::Conf::Backends::SOAP #inherits Lemonldap::NG::Common::Conf::Backends::LDAP -our $VERSION = '2.0.9'; +our $VERSION = '2.0.12'; our $msg = ''; our $iniObj; @@ -119,10 +119,13 @@ sub saveConf { my ( $self, $conf, %args ) = @_; my $last = $self->lastCfg; + return UNKNOWN_ERROR if $last < 1; # If configuration was modified, return an error if ( not $args{force} ) { - return CONFIG_WAS_CHANGED if ( $conf->{cfgNum} != $last ); + return CONFIG_WAS_CHANGED + if ( $conf->{cfgNum} ne $last + || $args{cfgDate} && $args{cfgDate} ne $args{currentCfgDate} ); return DATABASE_LOCKED if ( $self->isLocked() or not $self->lock() ); } $conf->{cfgNum} = $last + 1 unless ( $args{cfgNumFixed} ); @@ -393,6 +396,7 @@ sub getDBConf { : $a[0]; } my $conf = $self->load( $args->{cfgNum} ); + return undef if $conf == "-1"; $msg .= "Get configuration $conf->{cfgNum}.\n" if ( defined $conf->{cfgNum} ); return $conf; @@ -411,7 +415,11 @@ sub _launch { alarm 0; die $@ if $@; }; - $msg .= $@ if $@; + if($@) { + $msg .= $@; + print STDERR "MSG $msg\n"; + return undef; + } return wantarray ? (@res) : $res[0]; } diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Backends/RDBI.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Backends/RDBI.pm index 64ea593caeb127706d99974515332aa885acc7f6..08010c5536f0b5a45a96ade1894e23161aa64a95 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Backends/RDBI.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Backends/RDBI.pm @@ -5,7 +5,7 @@ use utf8; use Lemonldap::NG::Common::Conf::Serializer; use Lemonldap::NG::Common::Conf::Backends::_DBI; -our $VERSION = '2.0.0'; +our $VERSION = '2.0.12'; our @ISA = qw(Lemonldap::NG::Common::Conf::Backends::_DBI); sub store { @@ -16,32 +16,22 @@ sub store { my $req; my $lastCfg = $self->lastCfg; + $req = $self->_dbh->prepare( + "INSERT INTO $self->{dbiTable} (cfgNum,field,value) VALUES (?,?,?)"); - if ( $lastCfg == $cfgNum ) { - $req = $self->_dbh->prepare( -"UPDATE $self->{dbiTable} SET field=?, value=? WHERE cfgNum=? AND field=?" - ); - - } - else { - $req = $self->_dbh->prepare( - "INSERT INTO $self->{dbiTable} (cfgNum,field,value) VALUES (?,?,?)" - ); - } + _delete($self,$cfgNum) if $lastCfg == $cfgNum; unless ($req) { $self->logError; return UNKNOWN_ERROR; } while ( my ( $k, $v ) = each %$fields ) { - my @execValues; - if ( $lastCfg == $cfgNum ) { - @execValues = ( $k, $v, $cfgNum, $k ); - } - else { @execValues = ( $cfgNum, $k, $v ); } + my @execValues = ( $cfgNum, $k, $v ); my $execute; eval { $execute = $req->execute(@execValues); }; + print STDERR $@ if $@; unless ($execute) { $self->logError; + _delete( $self, $cfgNum ) if $lastCfg != $cfgNum; $self->_dbh->do("ROLLBACK"); return UNKNOWN_ERROR; } @@ -54,8 +44,9 @@ sub load { $fields = $fields ? join( ",", @$fields ) : '*'; my $sth = $self->_dbh->prepare( - "SELECT field,value from " . $self->{dbiTable} . " WHERE cfgNum=?" ); - $sth->execute($cfgNum); + "SELECT field,value from " . $self->{dbiTable} . " WHERE cfgNum=?" ) + or $self->logError; + $sth->execute($cfgNum) or $self->logError; my ( $res, @row ); while ( @row = $sth->fetchrow_array ) { $res->{ $row[0] } = $row[1]; @@ -69,5 +60,11 @@ sub load { return $self->unserialize($res); } +sub _delete { + my ( $self, $cfgNum ) = @_; + my $r = + $self->_dbh->prepare("DELETE FROM $self->{dbiTable} where cfgNum=?"); + $r->execute($cfgNum); +} + 1; -__END__ diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Backends/_DBI.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Backends/_DBI.pm index 1d7562ec266f44f613f259d715a38a065cb4d10a..180dd172925f13cc528257fb77206583948be4be 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Backends/_DBI.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Backends/_DBI.pm @@ -5,7 +5,7 @@ use utf8; use DBI; use Lemonldap::NG::Common::Conf::Constants; #inherits -our $VERSION = '2.0.0'; +our $VERSION = '2.0.12'; our @ISA = qw(Lemonldap::NG::Common::Conf::Constants); our ( @EXPORT, %EXPORT_TAGS ); @@ -36,8 +36,8 @@ sub available { my $sth = $self->_dbh->prepare( "SELECT DISTINCT cfgNum from " . $self->{dbiTable} - . " order by cfgNum" ); - $sth->execute(); + . " order by cfgNum" ) or $self->logError; + $sth->execute() or $self->logError; my @conf; while ( my @row = $sth->fetchrow_array ) { push @conf, $row[0]; @@ -105,8 +105,9 @@ sub unlock { sub delete { my ( $self, $cfgNum ) = @_; my $req = - $self->_dbh->prepare("DELETE FROM $self->{dbiTable} WHERE cfgNum=?"); - my $res = $req->execute($cfgNum); + $self->_dbh->prepare("DELETE FROM $self->{dbiTable} WHERE cfgNum=?") + or $self->logError; + my $res = $req->execute($cfgNum) or $self->logError; $Lemonldap::NG::Common::Conf::msg .= "Unable to find conf $cfgNum (" . $self->_dbh->errstr . ")" unless ($res); @@ -116,8 +117,7 @@ sub delete { sub logError { my $self = shift; $Lemonldap::NG::Common::Conf::msg .= - "Database error: " . $self->_dbh->errstr . "\n"; + "Database error: " . $DBI::errstr . "\n"; } 1; -__END__ diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Constants.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Constants.pm index b3049c6427d2603f73cb0fdc6e5a7d3ab3f243a2..bc71f74bbb07a4ebbebe187a912bd3bb37ab47c6 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Constants.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Constants.pm @@ -31,7 +31,7 @@ use constant DEFAULTCONFBACKENDOPTIONS => ( ); our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|f(?:indUser(?:Exclud|Search)ingAttribute|acebookExportedVar)|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|macro)s|o(?:idc(?:S(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar|ScopeRule|Macro)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node))|penIdExportedVars)|c(?:as(?:A(?:ppMetaData(?:(?:ExportedVar|Option|Macro)s|Node)|ttributes)|S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions))|(?:ustom(?:Plugins|Add)Param|heckUserHiddenHeader|ombModule)s)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option|Macro)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|a(?:(?:daptativeAuthenticationLevelR|ut(?:hChoiceMod|oSigninR))ules|pplicationList)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/; our $arrayParameters = qr/^mySessionAuthorizedRWKeys$/; -our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|f(?:RemovedUseNotif|OnlyUpgrade)|kip(?:Upgrade|Renew)Confirmation|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:A(?:llow(?:(?:ClientCredentials|Password)Grant|Offline)|ccessToken(?:Claims|JWT))|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|CertificateResetByMail|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|ForceAuthn|AntiFrame)|roxyUseSoap)|c(?:o(?:ntextSwitching(?:Allowed2fModifications|StopWithLogout)|mpactConf|rsEnabled)|a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:DevOps(?:Download)?|State|User|XSS)|rowdsec|da)|l(?:dap(?:(?:G(?:roup(?:DecodeSearchedValu|Recursiv)|etUserBeforePasswordChang)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|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)|d(?:is(?:ablePersistentStorage|playSessionId)|biDynamicHashEnabled)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|g(?:roupsBeforeMacros|lobalLogoutTimer)|a(?:voidAssignment|ctiveTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|krb(?:RemoveDomain|ByJs)|(?:wsdlServ|findUs)er)$/; +our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|f(?:RemovedUseNotif|OnlyUpgrade)|kip(?:Upgrade|Renew)Confirmation|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:A(?:llow(?:(?:ClientCredentials|Password)Grant|Offline)|ccessToken(?:Claims|JWT))|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration|OnlyDeclaredScopes)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|CertificateResetByMail|GeneratePassword|PasswordPolicy)|E(?:rrorOn(?:ExpiredSession|MailNotFound)|nablePasswordDisplay)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|ForceAuthn|AntiFrame)|roxyUseSoap)|c(?:a(?:sS(?:rvMetaDataOptions(?:Gateway|Renew)|trictMatching)|ptcha_(?:register|login|mail)_enabled)|o(?:ntextSwitching(?:Allowed2fModifications|StopWithLogout)|mpactConf|rsEnabled)|heck(?:DevOps(?:Download)?|State|User|XSS)|rowdsec|da)|l(?:dap(?:(?:G(?:roup(?:DecodeSearchedValu|Recursiv)|etUserBeforePasswordChang)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|re(?:st(?:(?:Password|Session|Config|Auth)Server|ExportSecretKeys)|freshSessions)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|d(?:is(?:ablePersistentStorage|playSessionId)|biDynamicHashEnabled)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|to(?:tp2fUserCanRemoveKey|kenUseGlobalStorage)|g(?:roupsBeforeMacros|lobalLogoutTimer)|a(?:voidAssignment|ctiveTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|krb(?:RemoveDomain|ByJs)|(?:wsdlServ|findUs)er)$/; our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' ); diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/DefaultValues.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/DefaultValues.pm index 810e6a2d5170e8ceb7305be3b81628479271b56d..980fc9900dc32a72a645fb36b7806f2d1bf09d3c 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/DefaultValues.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/DefaultValues.pm @@ -252,6 +252,7 @@ sub defaultValues { 'portalDisplayGeneratePassword' => 1, 'portalDisplayLoginHistory' => 1, 'portalDisplayLogout' => 1, + 'portalDisplayManageSessions' => 1, 'portalDisplayOidcConsents' => '$_oidcConsents && $_oidcConsents =~ /\\w+/', 'portalDisplayRefreshMyRights' => 1, @@ -336,7 +337,7 @@ sub defaultValues { 'sfManagerRule' => 1, 'sfRemovedMsgRule' => 0, 'sfRemovedNotifMsg' => - '_removedSF_ expired second factor(s) has/have been removed!', +'_removedSF_ expired second factor(s) has/have been removed (_nameSF_)!', 'sfRemovedNotifRef' => 'RemoveSF', 'sfRemovedNotifTitle' => 'Second factor notification', 'sfRequired' => 0, diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/RESTServer.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/RESTServer.pm index 4a5bc4d073700b15b733968cdcee37e334f2d350..413be34a9d965c073e02952edf1630c304b798d9 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/RESTServer.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/RESTServer.pm @@ -6,7 +6,7 @@ use Mouse; use Lemonldap::NG::Common::Conf::Constants; use Lemonldap::NG::Common::Conf::ReConstants; -our $VERSION = '2.0.9'; +our $VERSION = '2.0.12'; extends 'Lemonldap::NG::Common::Conf::AccessLib'; diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/ReConstants.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/ReConstants.pm index f512756d879046d3b55f6ea71a1517b156f5f106..23e55cde5cbfad036125dd4eeed68612881e11fb 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/ReConstants.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/ReConstants.pm @@ -22,12 +22,12 @@ our $specialNodeHash = { }; our $doubleHashKeys = 'issuerDBGetParameters'; -our $simpleHashKeys = '(?:(?:c(?:as(?:StorageOption|Attribute)|ustom(?:Plugins|Add)Param|heckUserHiddenHeader|ombModule)|l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|f(?:indUser(?:Exclud|Search)ingAttribute|acebookExportedVar)|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|p(?:ersistentStorageOption|ortalSkinRule)|(?:(?:d(?:emo|bi)|webID)E|e)xportedVar|macro)s|o(?:idcS(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|penIdExportedVars)|a(?:(?:daptativeAuthenticationLevelR|ut(?:hChoiceMod|oSigninR))ules|pplicationList)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember|fExtra)|S(?:MTPTLSOpts|SLVarIf))'; +our $simpleHashKeys = '(?:(?:c(?:as(?:StorageOption|Attribute)|ustom(?:Plugins|Add)Param|heckUserHiddenHeader|ombModule)|l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|f(?:indUser(?:Exclud|Search)ingAttribute|acebookExportedVar)|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|p(?:ersistentStorageOption|ortalSkinRule)|(?:(?:d(?:emo|bi)|webID)E|e)xportedVar|macro)s|o(?:idc(?:S(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|OPMetaDataJ(?:SON|WKS))|penIdExportedVars)|a(?:(?:daptativeAuthenticationLevelR|ut(?:hChoiceMod|oSigninR))ules|pplicationList)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember|fExtra)|S(?:MTPTLSOpts|SLVarIf))'; our $specialNodeKeys = '(?:(?:(?:saml(?:ID|S)|oidc[OR])P|cas(?:App|Srv))MetaDataNode|virtualHost)s'; our $casAppMetaDataNodeKeys = 'casAppMetaData(?:Options(?:(?:UserAttribut|Servic|Rul)e|AuthnLevel)|(?:ExportedVar|Macro)s)'; our $casSrvMetaDataNodeKeys = 'casSrvMetaData(?:Options(?:ProxiedServices|DisplayName|SortNumber|Gateway|Renew|Icon|Url)|ExportedVars)'; our $oidcOPMetaDataNodeKeys = 'oidcOPMetaData(?:Options(?:C(?:lient(?:Secret|ID)|heckJWTSignature|onfigurationURI)|S(?:toreIDToken|ortNumber|cope)|TokenEndpointAuthMethod|(?:JWKSTimeou|Promp)t|I(?:DTokenMaxAge|con)|U(?:iLocales|seNonce)|Display(?:Name)?|AcrValues|MaxAge)|ExportedVars|J(?:SON|WKS))'; -our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:A(?:llow(?:(?:ClientCredentials|Password)Grant|Offline)|ccessToken(?:Expiration|SignAlg|Claims|JWT)|uth(?:orizationCodeExpiration|nLevel)|dditionalAudiences)|I(?:DToken(?:ForceClaims|Expiration|SignAlg)|con)|R(?:e(?:directUris|freshToken|quirePKCE)|ule)|Logout(?:SessionRequired|Type|Url)|P(?:ostLogoutRedirectUris|ublic)|OfflineSessionExpiration|Client(?:Secret|ID)|BypassConsent|DisplayName|ExtraClaims|UserIDAttr)|(?:ExportedVar|ScopeRule|Macro)s)'; +our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:A(?:llow(?:(?:ClientCredentials|Password)Grant|Offline)|ccessToken(?:Expiration|SignAlg|Claims|JWT)|uth(?:orizationCodeExpiration|nLevel)|dditionalAudiences)|I(?:DToken(?:ForceClaims|Expiration|SignAlg)|con)|R(?:e(?:directUris|freshToken|quirePKCE)|ule)|Logout(?:SessionRequired|Type|Url)|P(?:ostLogoutRedirectUris|ublic)|UserI(?:nfoSignAlg|DAttr)|OfflineSessionExpiration|Client(?:Secret|ID)|BypassConsent|DisplayName|ExtraClaims)|(?:ExportedVar|ScopeRule|Macro)s)'; our $samlIDPMetaDataNodeKeys = 'samlIDPMetaData(?:Options(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|EncryptionMod|UserAttribut|DisplayNam)e|S(?:ign(?:S[LS]OMessage|atureMethod)|toreSAMLToken|[LS]OBinding|ortNumber)|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Re(?:questedAuthnContext|solutionRule|layStateURL)|Force(?:Authn|UTF8)|I(?:sPassive|con)|NameIDFormat)|ExportedAttributes|XML)'; our $samlSPMetaDataNodeKeys = 'samlSPMetaData(?:Options(?:S(?:ign(?:S[LS]OMessage|atureMethod)|essionNotOnOrAfterTimeout)|N(?:ameID(?:SessionKey|Format)|otOnOrAfterTimeout)|(?:CheckS[LS]OMessageSignatur|OneTimeUs|Rul)e|En(?:ableIDPInitiatedURL|cryptionMode)|AuthnLevel|ForceUTF8)|(?:ExportedAttribute|Macro)s|XML)'; our $virtualHostKeys = '(?:vhost(?:A(?:ccessToTrace|uthnLevel|liases)|(?:Maintenanc|Typ)e|ServiceTokenTTL|Https|Port)|(?:exportedHeader|locationRule)s|post)'; @@ -54,7 +54,7 @@ our $authParameters = { proxyParams => [qw(proxyAuthnLevel proxyAuthService proxySessionService remoteCookieName proxyUseSoap)], radiusParams => [qw(radiusAuthnLevel radiusSecret radiusServer)], remoteParams => [qw(remotePortal remoteCookieName remoteGlobalStorage remoteGlobalStorageOptions)], - restParams => [qw(restAuthnLevel restAuthUrl restUserDBUrl restPwdConfirmUrl restPwdModifyUrl restFindUserDBUrl)], + restParams => [qw(restAuthnLevel restAuthUrl restUserDBUrl restPwdConfirmUrl restPwdModifyUrl)], slaveParams => [qw(slaveAuthnLevel slaveUserHeader slaveMasterIP slaveHeaderName slaveHeaderContent slaveDisplayLogo slaveExportedVars)], sslParams => [qw(SSLAuthnLevel SSLVar SSLVarIf sslByAjax sslHost)], twitterParams => [qw(twitterAuthnLevel twitterKey twitterSecret twitterAppName twitterUserField)], @@ -69,6 +69,6 @@ our $issuerParameters = { issuerOptions => [qw(issuersTimeout)], }; our $samlServiceParameters = [qw(samlEntityID samlServicePrivateKeySig samlServicePrivateKeySigPwd samlServicePublicKeySig samlServicePrivateKeyEnc samlServicePrivateKeyEncPwd samlServicePublicKeyEnc samlServiceUseCertificateInResponse samlServiceSignatureMethod samlNameIDFormatMapEmail samlNameIDFormatMapX509 samlNameIDFormatMapWindows samlNameIDFormatMapKerberos samlAuthnContextMapPassword samlAuthnContextMapPasswordProtectedTransport samlAuthnContextMapTLSClient samlAuthnContextMapKerberos samlOrganizationDisplayName samlOrganizationName samlOrganizationURL samlSPSSODescriptorAuthnRequestsSigned samlSPSSODescriptorWantAssertionsSigned samlSPSSODescriptorSingleLogoutServiceHTTPRedirect samlSPSSODescriptorSingleLogoutServiceHTTPPost samlSPSSODescriptorSingleLogoutServiceSOAP samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact samlSPSSODescriptorAssertionConsumerServiceHTTPPost samlSPSSODescriptorArtifactResolutionServiceArtifact samlIDPSSODescriptorWantAuthnRequestsSigned samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect samlIDPSSODescriptorSingleSignOnServiceHTTPPost samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect samlIDPSSODescriptorSingleLogoutServiceHTTPPost samlIDPSSODescriptorSingleLogoutServiceSOAP samlIDPSSODescriptorArtifactResolutionServiceArtifact samlAttributeAuthorityDescriptorAttributeServiceSOAP samlMetadataForceUTF8 samlRelayStateTimeout samlUseQueryStringSpecific samlOverrideIDPEntityID samlStorage samlStorageOptions samlCommonDomainCookieActivation samlCommonDomainCookieDomain samlCommonDomainCookieReader samlCommonDomainCookieWriter samlDiscoveryProtocolActivation samlDiscoveryProtocolURL samlDiscoveryProtocolPolicy samlDiscoveryProtocolIsPassive)]; -our $oidcServiceParameters = [qw(oidcServiceMetaDataIssuer oidcServiceMetaDataAuthorizeURI oidcServiceMetaDataTokenURI oidcServiceMetaDataUserInfoURI oidcServiceMetaDataJWKSURI oidcServiceMetaDataRegistrationURI oidcServiceMetaDataIntrospectionURI oidcServiceMetaDataEndSessionURI oidcServiceMetaDataCheckSessionURI oidcServiceMetaDataFrontChannelURI oidcServiceMetaDataBackChannelURI oidcServiceMetaDataAuthnContext oidcServicePrivateKeySig oidcServicePublicKeySig oidcServiceKeyIdSig oidcServiceAllowDynamicRegistration oidcServiceAllowAuthorizationCodeFlow oidcServiceAllowImplicitFlow oidcServiceAllowHybridFlow oidcServiceAuthorizationCodeExpiration oidcServiceAccessTokenExpiration oidcServiceIDTokenExpiration oidcServiceOfflineSessionExpiration oidcStorage oidcStorageOptions oidcServiceDynamicRegistrationExportedVars oidcServiceDynamicRegistrationExtraClaims)]; +our $oidcServiceParameters = [qw(oidcServiceMetaDataIssuer oidcServiceMetaDataAuthorizeURI oidcServiceMetaDataTokenURI oidcServiceMetaDataUserInfoURI oidcServiceMetaDataJWKSURI oidcServiceMetaDataRegistrationURI oidcServiceMetaDataIntrospectionURI oidcServiceMetaDataEndSessionURI oidcServiceMetaDataCheckSessionURI oidcServiceMetaDataFrontChannelURI oidcServiceMetaDataBackChannelURI oidcServiceMetaDataAuthnContext oidcServicePrivateKeySig oidcServicePublicKeySig oidcServiceKeyIdSig oidcServiceAllowDynamicRegistration oidcServiceAllowOnlyDeclaredScopes oidcServiceAllowAuthorizationCodeFlow oidcServiceAllowImplicitFlow oidcServiceAllowHybridFlow oidcServiceAuthorizationCodeExpiration oidcServiceAccessTokenExpiration oidcServiceIDTokenExpiration oidcServiceOfflineSessionExpiration oidcStorage oidcStorageOptions oidcServiceDynamicRegistrationExportedVars oidcServiceDynamicRegistrationExtraClaims)]; 1; diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Serializer.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Serializer.pm index 8ca3e942267d688be701ba2805acdfbb5d03c2d0..989f586a1dca4db0fb6522b5cd3394dedc28573c 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Serializer.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Serializer.pm @@ -6,7 +6,7 @@ use Encode; use JSON; use Lemonldap::NG::Common::Conf::Constants; -our $VERSION = '2.0.0'; +our $VERSION = '2.0.12'; BEGIN { *Lemonldap::NG::Common::Conf::normalize = \&normalize; diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/JWT.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/JWT.pm index 24719415aa333d43dce4750ce2e7bfc555da4da0..ae497ff08c527f0e3ba05e368be52271cbfa3326 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/JWT.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/JWT.pm @@ -8,6 +8,8 @@ our @EXPORT_OK = use JSON; use MIME::Base64 qw/encode_base64 decode_base64/; +our $VERSION = '2.0.12'; + # Gets the Access Token session ID embedded in a LLNG-emitted JWT sub getAccessTokenSessionId { my ($access_token) = @_; diff --git a/lemonldap-ng-common/scripts/convertSessions b/lemonldap-ng-common/scripts/convertSessions index cb5c8bfd1a927b839d6397fc1db3e2568eab205f..66e1465a48ce7460c87d60743984db8c80ec4b83 100755 --- a/lemonldap-ng-common/scripts/convertSessions +++ b/lemonldap-ng-common/scripts/convertSessions @@ -13,62 +13,48 @@ use Lemonldap::NG::Common::Apache::Session; use Lemonldap::NG::Common::Session; use Config::IniFiles; use strict; -use Getopt::Std; -$Getopt::Std::STANDARD_HELP_VERSION = 1; +use Getopt::Long; +use Pod::Usage; -our $VERSION = "2.0.6"; +our $VERSION = "2.0.12"; # Options # -d: debug mode # -c: configuration file +# -r: rename attributes # -i: ignore errors -my $opts = {}; -getopts( 'dic:', $opts ); - -my $debug = $opts->{d}; -my $config_file = $opts->{c}; -my $ignore_errors = $opts->{i}; -my $nb_converted = 0; -my $nb_error = 0; - -sub HELP_MESSAGE { - my $OUT = shift; - print $OUT < '/var/lib/lemonldap-ng/sessions', \\ - 'LockDirectory' => '/var/lib/lemonldap-ng/sessions/lock', \\ -} -# Only convert some session types -# sessionKind = Persistent, SSO - -[sessions_to] -storageModule = Apache::Session::Browseable::Postgres -storageModuleOptions = { \\ - 'DataSource' => 'DBI:Pg:database=lemonldapdb;host=pg.example.com', \\ - 'UserName' => 'lemonldaplogin', \\ - 'Password' => 'lemonldappw', \\ - 'Commit' => 1, \\ - 'Index' => 'ipAddr _whatToTrace user', \\ - 'TableName' => 'sessions', \\ -} - -END_MESSAGE -} +# -x: exclude attributes + +my $debug; +my $config_file; +my $ignore_errors; +my %rename; +my @exclude; +my $help; +my $nb_converted = 0; +my $nb_error = 0; + +GetOptions( + 'help|?' => \$help, + 'debug|d' => \$debug, + 'config|c=s' => \$config_file, + 'ignore-errors|i' => \$ignore_errors, + 'rename|r=s' => \%rename, + 'exclude|x=s' => \@exclude, +) or pod2usage(2); +pod2usage( + -exitval => 1, + -verbose => 99, + -sections => "SYNOPSIS|OPTIONS|CONFIGURATION FILE FORMAT" +) if $help; unless ($config_file) { - HELP_MESSAGE( \*STDERR ); - die "You must provide the -c option"; + pod2usage( + -exitval => 2, + -verbose => 99, + -message => "You must provide the -c option\n", + -sections => "SYNOPSIS|OPTIONS|CONFIGURATION FILE FORMAT" + ); } my $inicfg = @@ -91,8 +77,7 @@ for my $section (qw/sessions_from sessions_to/) { if ( $r->{$_} =~ /^[{\[].*[}\]]$/ || $r->{$_} =~ /^sub\s*{.*}$/ ) { eval "\$r->{$_} = $r->{$_}"; if ($@) { - print $@; - return $r; + die "Error evaluating $section/$_: $@"; } } } @@ -129,6 +114,38 @@ Lemonldap::NG::Common::Apache::Session->get_key_from_all_sessions( my $entry = shift; my $id = shift; + # If filtering sessionKind + if (@sessionKindOnly) { + + unless ( grep { $_ eq $entry->{_session_kind} } @sessionKindOnly ) { + print "Ignoring session $id with type " + . $entry->{_session_kind} . "\n" + if $debug; + return undef; + } + } + + if (%rename) { + for my $oldkey ( keys %rename ) { + my $newkey = $rename{$oldkey}; + if ( $newkey and $entry->{$oldkey} ) { + print "Renaming $oldkey to $newkey in session $id\n" + if $debug; + $entry->{$newkey} = delete $entry->{$oldkey}; + } + } + } + + if (@exclude) { + for my $excludekey (@exclude) { + if ( $entry->{$excludekey} ) { + print "Exclude $excludekey in session $id\n" + if $debug; + delete $entry->{$excludekey}; + } + } + } + print "Processing session $id\n" if $debug; my $s = Lemonldap::NG::Common::Session->new( { storageModule => $backendTo->{backend}, @@ -139,14 +156,6 @@ Lemonldap::NG::Common::Apache::Session->get_key_from_all_sessions( } ); - # If filtering sessionKind - if (@sessionKindOnly) { - - unless ( grep { $_ eq $entry->{_session_kind} } @sessionKindOnly ) { - return undef; - } - } - if ( $s->error ) { die "Error encountered on session $id" unless $ignore_errors; $nb_error += 1; @@ -176,7 +185,7 @@ convertSessions - A tool to convert Lemonldap::NG sessions between storage backe =head1 SYNOPSIS - convertSession [-di] -c parameters.ini + convertSession [-di] [-r oldkey=newkey ] -c parameters.ini =head1 DESCRIPTION @@ -192,6 +201,29 @@ destination backend will be kept, unless they have the same session ID as a session in the source backend. In that case, the source will overwrite the destination. +=head1 OPTIONS + +=over + +=item B<--config>,B<-c> + +Specify configuration file + +=item B<--debug>,B<-d> + +Turns on debugging information + +=item B<--ignore-errors>,B<-i> + +Skip to the next session if converting a session fails + +=item B<--rename oldkey=newkey>,B<-r oldkey=newkey> + +Rename key names when migrating from one backend to the next. + +This option can be specified multiple times + +=back =head1 CONFIGURATION FILE FORMAT diff --git a/lemonldap-ng-common/scripts/importMetadata b/lemonldap-ng-common/scripts/importMetadata old mode 100755 new mode 100644 index 1a2d8ca310199f29dc795464614c33c158ca7b56..c9c57952b0b98f573e65d3bc203cc9aa7532786d --- a/lemonldap-ng-common/scripts/importMetadata +++ b/lemonldap-ng-common/scripts/importMetadata @@ -7,16 +7,27 @@ use LWP::UserAgent; use MIME::Base64; use XML::LibXML; +sub toEntityIDkey { + my ( $prefix, $entityID ) = @_; + + my $entityIDKey = $entityID; + $entityIDKey =~ s/^https?:\/\///; + $entityIDKey =~ s/[^a-zA-Z0-9]/-/g; + $entityIDKey =~ s/-+$//g; + return ( $prefix . $entityIDKey ); +} + #============================================================================== # Get command line options #============================================================================== my %opts; my $result = GetOptions( - \%opts, 'metadata|m=s', - 'certificate|c=s', 'verbose|v', - 'help|h', 'spconfprefix|s=s', - 'idpconfprefix|i=s', 'warning|w', - 'remove|r' + \%opts, 'metadata|m=s', + 'verbose|v', 'help|h', + 'spconfprefix|s=s', 'idpconfprefix|i=s', + 'remove|r', 'nagios|a', + 'ignore-sp=s@', 'ignore-idp=s@', + 'dry-run|n' ); #============================================================================== @@ -28,14 +39,20 @@ if ( $opts{help} or !$opts{metadata} ) { print STDERR "Usage: $0 -m \n\n"; print STDERR "Options:\n"; print STDERR -"\t-c (--certificate): URL of certificate, to check metadata document signature\n"; - print STDERR "\t-i (--idpconfprefix): Prefix used to set IDP configuration key\n"; print STDERR "\t-h (--help): print this message\n"; print STDERR "\t-m (--metadata): URL of metadata document\n"; print STDERR "\t-s (--spconfprefix): Prefix used to set SP configuration key\n"; - print STDERR "\t-w (--warning): print debug messages\n"; + print STDERR +"\t--ignore-sp: ignore SP maching this entityID (can be specified multiple times)\n"; + print STDERR +"\t--ignore-idp: ignore IdP matching this entityID (can be specified multiple times)\n"; + print STDERR "\t-a (--nagios) : output statistics in Nagios format\n"; + print STDERR "\t-n (--dry-run): print statistics but do not apply changes\n"; + print STDERR "\t-v (--verbose): increase verbosity of output\n"; + print STDERR +"\t-r (--remove): remove provider from LemonLDAP::NG if it does not appear in metadata\n"; exit 1; } @@ -48,6 +65,7 @@ my $idpConfKeyPrefix = $opts{idpconfprefix} || "idp-"; # Set here attributes that are declared for your SP in the federation # They will be set as exported attributes for all IDP +# my $exportedAttributes = { 'cn' => '0;cn', 'eduPersonPrincipalName' => '0;eduPersonAffiliation', @@ -101,16 +119,22 @@ my $idpCounter = { 'updated' => 0, 'created' => 0, 'rejected' => 0, - 'removed' => 0 + 'removed' => 0, + 'ignored' => 0 }; my $spCounter = { 'found' => 0, 'updated' => 0, 'created' => 0, 'rejected' => 0, - 'removed' => 0 + 'removed' => 0, + 'ignored' => 0, }; +# BlockList initialisation +my @spIgnorelist = @{ $opts{'ignore-sp'} || [] }; +my @idpIgnorelist = @{ $opts{'ignore-idp'} || [] }; + #============================================================================== # Main #============================================================================== @@ -173,33 +197,6 @@ else { my $dom = XML::LibXML->load_xml( string => $response->decoded_content ); -# Check file signature -if ( $opts{certificate} ) { - my $certificate_file = $opts{certificate}; - if ( $opts{verbose} ) { - print "Try to download certificate file at $certificate_file\n"; - } - my $cert_response = $ua->get($certificate_file); - - if ( $cert_response->is_success ) { - if ( $opts{verbose} ) { - print "Certificate file found:\n" - . $cert_response->decoded_content . "\n"; - } - } - else { - die $cert_response->status_line; - } - - if ( $opts{verbose} ) { - print "Check metadata signature with certificate"; - } - - # TODO - print STDERR "[WARN] Signature verification not yet implemented\n" - if $opts{warning}; -} - # Remove extensions foreach ( $dom->findnodes('//md:Extensions') ) { $_->unbindNode; } @@ -232,57 +229,64 @@ foreach my $partner_metadata = $partner->toString; $partner_metadata =~ s/\n//g; - # Check if entityID already in configuration - if ( defined $idpList->{$entityID} ) { - - # Update metadata - $lastConf->{samlIDPMetaDataXML}->{ $idpList->{$entityID} } - ->{samlIDPMetaDataXML} = $partner_metadata; - - # Update attributes - $lastConf->{samlIDPMetaDataExportedAttributes} - ->{ $idpList->{$entityID} } = $exportedAttributes; - - # Update options - $lastConf->{samlIDPMetaDataOptions}->{ $idpList->{$entityID} } - = $idpOptions; + # test if IDP entityID is inside the block list + if ( grep { $entityID eq $_ } @idpIgnorelist ) { if ( $opts{verbose} ) { - print "Update IDP $entityID in configuration\n"; + print "IDP $entityID won't be update/added \n"; } - $idpCounter->{updated}++; + $idpCounter->{ignored}++; } else { - # Create a new partner - my $entityIDKey = $entityID; - $entityIDKey =~ s/^https?:\/\///; - $entityIDKey =~ s/[^a-zA-Z0-9]/-/g; - $entityIDKey =~ s/-+$//g; - my $confKey = $idpConfKeyPrefix . $entityIDKey; + # Check if entityID already in configuration + if ( defined $idpList->{$entityID} ) { - # Metadata - $lastConf->{samlIDPMetaDataXML}->{$confKey} - ->{samlIDPMetaDataXML} = $partner_metadata; + # Update metadata + $lastConf->{samlIDPMetaDataXML}->{ $idpList->{$entityID} } + ->{samlIDPMetaDataXML} = $partner_metadata; - # Attributes - $lastConf->{samlIDPMetaDataExportedAttributes}->{$confKey} = - $exportedAttributes; + # Update attributes + $lastConf->{samlIDPMetaDataExportedAttributes} + ->{ $idpList->{$entityID} } = $exportedAttributes; - # Options - $lastConf->{samlIDPMetaDataOptions}->{$confKey} = $idpOptions; + # Update options + $lastConf->{samlIDPMetaDataOptions} + ->{ $idpList->{$entityID} } = $idpOptions; - if ( $opts{verbose} ) { - print + if ( $opts{verbose} ) { + print "Update IDP $entityID in configuration\n"; + } + $idpCounter->{updated}++; + } + else { + # Create a new partner + my $confKey = toEntityIDkey( $idpConfKeyPrefix, $entityID ); + + # Metadata + $lastConf->{samlIDPMetaDataXML}->{$confKey} + ->{samlIDPMetaDataXML} = $partner_metadata; + + # Attributes + $lastConf->{samlIDPMetaDataExportedAttributes}->{$confKey} + = $exportedAttributes; + + # Options + $lastConf->{samlIDPMetaDataOptions}->{$confKey} = + $idpOptions; + + if ( $opts{verbose} ) { + print "Declare new IDP $entityID (configuration key $confKey)\n"; + } + $idpCounter->{created}++; } - $idpCounter->{created}++; } } else { print STDERR "[WARN] IDP $entityID is not compatible with SAML 2.0, it will not be imported.\n" - if $opts{warning}; + if $opts{verbose}; $idpCounter->{rejected}++; } } @@ -346,57 +350,78 @@ foreach my $partner_metadata = $partner->toString; $partner_metadata =~ s/\n//g; - # Check if entityID already in configuration - if ( defined $spList->{$entityID} ) { - - # Update metadata - $lastConf->{samlSPMetaDataXML}->{ $spList->{$entityID} } - ->{samlSPMetaDataXML} = $partner_metadata; - - # Update attributes - $lastConf->{samlSPMetaDataExportedAttributes} - ->{ $spList->{$entityID} } = $requestedAttributes; - - # Update options - $lastConf->{samlSPMetaDataOptions}->{ $spList->{$entityID} } = - $spOptions; + # test if IDP entityID is inside the block list + if ( grep { $entityID eq $_ } @spIgnorelist ) { if ( $opts{verbose} ) { - print "Update SP $entityID in configuration\n"; + print "SP $entityID won't be update/added \n"; } - $spCounter->{updated}++; + $spCounter->{ignored}++; } else { - # Create a new partner - my $entityIDKey = $entityID; - $entityIDKey =~ s/^https?:\/\///; - $entityIDKey =~ s/[^a-zA-Z0-9]/-/g; - $entityIDKey =~ s/-+$//g; - my $confKey = $spConfKeyPrefix . $entityIDKey; + # Check if entityID already in configuration + if ( defined $spList->{$entityID} ) { - # Metadata - $lastConf->{samlSPMetaDataXML}->{$confKey}->{samlSPMetaDataXML} - = $partner_metadata; + # Update metadata + $lastConf->{samlSPMetaDataXML}->{ $spList->{$entityID} } + ->{samlSPMetaDataXML} = $partner_metadata; - # Attributes - $lastConf->{samlSPMetaDataExportedAttributes}->{$confKey} = - $requestedAttributes; + # Update attributes + $lastConf->{samlSPMetaDataExportedAttributes} + ->{ $spList->{$entityID} } = $requestedAttributes; - # Options - $lastConf->{samlSPMetaDataOptions}->{$confKey} = $spOptions; +# Update options +# $lastConf->{samlSPMetaDataOptions}->{ $spList->{$entityID} } = +# $spOptions; +# FIX AGA + $lastConf->{samlSPMetaDataOptions}->{ $spList->{$entityID} } + = { %{$spOptions} }; - if ( $opts{verbose} ) { - print - "Declare new SP $entityID (configuration key $confKey)\n"; + if ( $opts{verbose} ) { + print "Update SP $entityID in configuration\n"; + } + $spCounter->{updated}++; } - $spCounter->{created}++; + else { + # Create a new partner + my $confKey = toEntityIDkey( $spConfKeyPrefix, $entityID ); + + # Metadata + $lastConf->{samlSPMetaDataXML}->{$confKey} + ->{samlSPMetaDataXML} = $partner_metadata; + + # Attributes + $lastConf->{samlSPMetaDataExportedAttributes}->{$confKey} = + $requestedAttributes; + + # Options + # $lastConf->{samlSPMetaDataOptions}->{$confKey} = $spOptions; + + # FIX AGA + $lastConf->{samlSPMetaDataOptions}->{$confKey} = + { %{$spOptions} }; + + if ( $opts{verbose} ) { + print +"Declare new SP $entityID (configuration key $confKey)\n"; + } + $spCounter->{created}++; + } + + # handle eduPersonTargetedID + if ( $requestedAttributes->{eduPersonTargetedID} ) { + delete $requestedAttributes->{eduPersonTargetedID}; + $lastConf->{samlSPMetaDataOptions}->{ $spList->{$entityID} } + ->{samlSPMetaDataOptionsNameIDFormat} = 'persistent'; + } + } } else { print STDERR "[WARN] SP $entityID is not compatible with SAML 2.0, it will not be imported.\n" - if $opts{warning}; + if $opts{verbose}; $spCounter->{rejected}++; } @@ -406,61 +431,130 @@ foreach # Remove partners if ( $opts{remove} ) { - foreach ( keys %$idpList ) { - my $idpConfKey = $idpList->{$_}; - unless ( defined $mdIdpList->{$_} ) { - delete $lastConf->{samlIDPMetaDataXML}->{$idpConfKey}; - delete $lastConf->{samlIDPMetaDataExportedAttributes} - ->{$idpConfKey}; - delete $lastConf->{samlIDPMetaDataOptions}->{$idpConfKey}; - $idpCounter->{removed}++; - if ( $opts{verbose} ) { - print "Remove IDP $idpConfKey\n"; + foreach my $entityID ( keys %$idpList ) { + my $idpConfKey = $idpList->{$entityID}; + unless ( defined $mdIdpList->{$entityID} ) { + if ( grep { $entityID eq $_ } @idpIgnorelist ) { + $idpCounter->{ignored}++; + if ( $opts{verbose} ) { + print "IDP $idpConfKey won't be deleted \n"; + } + } + else { + delete $lastConf->{samlIDPMetaDataXML}->{$idpConfKey}; + delete $lastConf->{samlIDPMetaDataExportedAttributes} + ->{$idpConfKey}; + delete $lastConf->{samlIDPMetaDataOptions}->{$idpConfKey}; + $idpCounter->{removed}++; + if ( $opts{verbose} ) { + print "Remove IDP $idpConfKey\n"; + } } } } - foreach ( keys %$spList ) { - my $spConfKey = $spList->{$_}; - unless ( defined $mdSpList->{$_} ) { - delete $lastConf->{samlSPMetaDataXML}->{$spConfKey}; - delete $lastConf->{samlSPMetaDataExportedAttributes}->{$spConfKey}; - delete $lastConf->{samlSPMetaDataOptions}->{$spConfKey}; - $spCounter->{removed}++; - if ( $opts{verbose} ) { - print "Remove SP $spConfKey\n"; + foreach my $entityID ( keys %$spList ) { + my $spConfKey = $spList->{$entityID}; + unless ( defined $mdSpList->{$entityID} ) { + if ( grep { $entityID eq $_ } @spIgnorelist ) { + $spCounter->{ignored}++; + if ( $opts{verbose} ) { + print "SP $spConfKey won't be deleted \n"; + } + } + else { + delete $lastConf->{samlSPMetaDataXML}->{$spConfKey}; + delete $lastConf->{samlSPMetaDataExportedAttributes} + ->{$spConfKey}; + delete $lastConf->{samlSPMetaDataOptions}->{$spConfKey}; + $spCounter->{removed}++; + if ( $opts{verbose} ) { + print "Remove SP $spConfKey\n"; + } } } } } -# Register configuration -my $numConf = $conf->saveConf( $lastConf, ( cfgNumFixed => 1 ) ); +my $numConf = "DRY-RUN"; +my $exitCode = 0; -unless ($numConf) { - print "[ERROR] Unable to save configuration\n"; - exit 1; +if ( !$opts{'dry-run'} ) { + + # Register configuration + if ( $opts{verbose} ) { + print "[INFO] run mod EntityID will be inserted\n"; + } + $numConf = $conf->saveConf( $lastConf, ( cfgNumFixed => 1 ) ); + if ( $opts{verbose} ) { + print "[OK] Configuration $numConf saved\n"; + $exitCode = 0; + } + unless ($numConf) { + print "[ERROR] Unable to save configuration\n"; + $exitCode = 1; + } +} +else { + if ( $opts{verbose} ) { + print "[INFO] Dry-run mod no EntityID inserted\n"; + } } -print "[IDP]\tFound: " - . $idpCounter->{found} - . "\tUpdated: " - . $idpCounter->{updated} - . "\tCreated: " - . $idpCounter->{created} - . "\tRemoved: " - . $idpCounter->{removed} - . "\tRejected: " - . $idpCounter->{rejected} . "\n"; -print "[SP]\tFound: " - . $spCounter->{found} - . "\tUpdated: " - . $spCounter->{updated} - . "\tCreated: " - . $spCounter->{created} - . "\tRemoved: " - . $spCounter->{removed} - . "\tRejected: " - . $spCounter->{rejected} . "\n"; -print "[OK] Configuration $numConf saved\n"; -exit 0; +if ( $opts{nagios} ) { + print "Metadata loaded inside Conf: [" + . $numConf + . "]|idp_found=" + . $idpCounter->{found} + . ", idp_updated=" + . $idpCounter->{updated} + . ", idp_created=" + . $idpCounter->{created} + . ", idp_removed=" + . $idpCounter->{removed} + . ", idp_rejected=" + . $idpCounter->{rejected} + . ", idp_ignored=" + . $idpCounter->{ignored} + . ", sp_found=" + . $spCounter->{found} + . ", sp_updated=" + . $spCounter->{updated} + . ", sp_created=" + . $spCounter->{created} + . ", sp_removed=" + . $spCounter->{removed} + . ", sp_rejected=" + . $spCounter->{rejected} + . ", sp_ignored=" + . $spCounter->{ignored} . "\n"; +} +else { + print "[IDP]\tFound: " + . $idpCounter->{found} + . "\tUpdated: " + . $idpCounter->{updated} + . "\tCreated: " + . $idpCounter->{created} + . "\tRemoved: " + . $idpCounter->{removed} + . "\tRejected: " + . $idpCounter->{rejected} + . "\tIgnored: " + . $idpCounter->{ignored} . "\n"; + print "[SP]\tFound: " + . $spCounter->{found} + . "\tUpdated: " + . $spCounter->{updated} + . "\tCreated: " + . $spCounter->{created} + . "\tRemoved: " + . $spCounter->{removed} + . "\tRejected: " + . $spCounter->{rejected} + . "\tIgnored: " + . $spCounter->{ignored} . "\n"; +} + +exit $exitCode; + diff --git a/lemonldap-ng-common/scripts/lemonldap-ng-cli b/lemonldap-ng-common/scripts/lemonldap-ng-cli index e8fc87e8a08e50942fc8c97f4cc1f0c1d6724744..51fc2a81e6fa52ee16bc1eb43c02e4d380ac462f 100755 --- a/lemonldap-ng-common/scripts/lemonldap-ng-cli +++ b/lemonldap-ng-common/scripts/lemonldap-ng-cli @@ -3,12 +3,20 @@ use warnings; use strict; use POSIX; +use Getopt::Long qw(:config pass_through); + +our $opt_user = '__APACHEUSER__'; +our $opt_group = '__APACHEGROUP__'; +GetOptions( + "user=s" => \$opt_user, + "group=s" => \$opt_group +) or die("Error in command line arguments\n"); my $action; eval { - POSIX::setgid( scalar( getgrnam('__APACHEGROUP__') ) ); - POSIX::setuid( scalar( getpwnam('__APACHEUSER__') ) ); + POSIX::setgid( scalar( getgrnam($opt_group) ) ); + POSIX::setuid( scalar( getpwnam($opt_user) ) ); }; for ( my $i = 0 ; $i < @ARGV ; $i++ ) { @@ -68,6 +76,10 @@ Options: - sep : separator of hierarchical values (by default: /) - iniFile : path to an alternate lemonldap-ng.ini file +Additional options: + - --user= : change user running the script + - --group= : change group running the script + See Lemonldap::NG::Manager::Cli(3) for more }; } @@ -170,13 +182,21 @@ Allows you to set the log message that will be displayed in the manager The configuration change will be aborted if it contains errors (default: 0) +=item -force + +Allows you to force overwriting an existing configuration (default: 0) + =item -cfgNum Choose a particular configuration number (default: latest) -=item -force +=item -sep -Allows you to force overwriting an existing configuration (default: 0) +Allows you to define hierarchical separator + +=item -iniFile + +Allows you to set an alternative ini file =back @@ -189,11 +209,13 @@ L =over -=item David Coutateur, Edavid.jose.delassus@gmail.comE +=item Clement Oudot, Eclement@oodo.netE + +=item Xavier Guimard, Eyadd@debian.orgE -=item Clement Oudot, Eclem.oudot@gmail.comE +=item Maxime Besson, Emaxime.besson@worteks.comE -=item Xavier Guimard, Ex.guimard@free.frE +=item Christophe Maudoux, Echrmdx@gmail.comE =back @@ -205,7 +227,7 @@ L =head1 DOWNLOAD Lemonldap::NG is available at -L +L =head1 COPYRIGHT AND LICENSE diff --git a/lemonldap-ng-common/scripts/lemonldap-ng-sessions b/lemonldap-ng-common/scripts/lemonldap-ng-sessions index 3be01a9b816277cc236cab626bb3a054f84ebf43..39a7e2b0e2d4e44bf2d98df5eba1e21e53a2f7ac 100755 --- a/lemonldap-ng-common/scripts/lemonldap-ng-sessions +++ b/lemonldap-ng-common/scripts/lemonldap-ng-sessions @@ -10,11 +10,13 @@ use strict; use Getopt::Long; use Pod::Usage; -our $VERSION = "2.0.9"; +our $VERSION = "2.0.12"; # Options my $opts = {}; my $help; +my $opt_user = '__APACHEUSER__'; +my $opt_group = '__APACHEGROUP__'; GetOptions( 'help|h' => \$help, @@ -23,13 +25,15 @@ GetOptions( 'backend|b=s' => \$opts->{backend}, 'persistent|p' => \$opts->{persistent}, 'id-only|i' => \$opts->{idonly}, + 'user|u=s' => \$opt_user, + 'group|g=s' => \$opt_group, ) or pod2usage( -exitcode => 1, -verbose => 0 ); pod2usage( -exitcode => 0, -verbose => 2 ) if $help; eval { - POSIX::setgid( scalar( getgrnam('__APACHEGROUP__') ) ); - POSIX::setuid( scalar( getpwnam('__APACHEUSER__') ) ); + POSIX::setgid( scalar( getgrnam($opt_group) ) ); + POSIX::setuid( scalar( getpwnam($opt_user) ) ); }; my $action = shift @ARGV; @@ -127,7 +131,8 @@ Options: --persistent Search in persistent sessions --where Set search filter (search/delete only) --id-only Only return IDs (search only) - + --user Change user running the script + --group Change group running the script =head1 COMMANDS @@ -167,8 +172,11 @@ Examples lemonldap-ng-sessions search --where uid=dwho \ --id-only + lemonldap-ng-sessions search --backend persistent \ + --where _session_uid=dwho + lemonldap-ng-sessions search --where uid=dwho \ - --select authenticationLevel + --select authenticationLevel =head2 Delete @@ -191,6 +199,8 @@ Examples: lemonldap-ng-sessions delete --where uid=dwho + lemonldap-ng-sessions delete --persistent --where _session_uid=dwho + =head2 Set Key lemonldap-ng-sessions setKey [ ...] @@ -283,7 +293,7 @@ Examples: =item B<--persistent>,B<-p> -This options is a shortcut for specifying --backend persistent and using +This option is a shortcut for specifying --backend persistent and using the UID hash as a session ID Example: @@ -298,7 +308,7 @@ is the same as =item B<--id-only>,B<-i> -This option replace the standard JSON output format with a simpler format of +This option replaces the standard JSON output format with a simpler format of one session ID per line. This allows some intersting combos using xargs. For example, if you want to @@ -307,7 +317,13 @@ remove all sessions started by "dwho" lemonldap-ng-sessions search --where uid=dwho --id-only | \ xargs lemonldap-ng-sessions delete +=item B<--user>,B<-u> + +This option forces the system user that runs the script. + +=item B<--group>,B<-g> +This option forces the system group that runs the script. =back diff --git a/lemonldap-ng-common/t/03-Common-Conf-RDBI.t b/lemonldap-ng-common/t/03-Common-Conf-RDBI.t index c7158caf248c07e1732318068ffb8583317d89fe..3ed0fde1f49056a0e8a053beb3ca7609bc4c6b26 100644 --- a/lemonldap-ng-common/t/03-Common-Conf-RDBI.t +++ b/lemonldap-ng-common/t/03-Common-Conf-RDBI.t @@ -44,7 +44,6 @@ SKIP: { ok( $h->_dbh->do( "CREATE TABLE lmConfig ( cfgNum int not null, field varchar(255) NOT NULL DEFAULT '', value longblob, PRIMARY KEY (cfgNum,field))" - ), 'Test database created' ); diff --git a/lemonldap-ng-handler/META.json b/lemonldap-ng-handler/META.json index 6d1016e5b204cb2ed886753fed3395ae011305c2..fd159f7f56d0073320e8b18125e2130a4d00e7e7 100644 --- a/lemonldap-ng-handler/META.json +++ b/lemonldap-ng-handler/META.json @@ -4,7 +4,7 @@ "Xavier Guimard , Clément Oudot " ], "dynamic_config" : 1, - "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010", + "generated_by" : "ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010", "license" : [ "open_source" ], diff --git a/lemonldap-ng-handler/META.yml b/lemonldap-ng-handler/META.yml index 6d4ca3c723e516e208d8201d6ef47eb2da64fa12..62698370c12c29fa45c28e2b380e34314b1fcfe9 100644 --- a/lemonldap-ng-handler/META.yml +++ b/lemonldap-ng-handler/META.yml @@ -13,7 +13,7 @@ build_requires: configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 -generated_by: 'ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010' +generated_by: 'ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010' license: open_source meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/DevOps.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/DevOps.pm index 49b186f6bc4f17f3ad30a815b370d4ac39492514..9612067cdaa926b3e5d97968903a56e208ccc525 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/DevOps.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/DevOps.pm @@ -22,7 +22,7 @@ sub checkMaintenanceMode { $class->tsv->{defaultCondition}->{$vhost} and ( time() - $class->tsv->{lastVhostUpdate}->{$vhost} < - $class->tsv->{checkTime} ) + $class->checkTime ) ); return $class->Lemonldap::NG::Handler::Main::checkMaintenanceMode($req); diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/OAuth2.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/OAuth2.pm index ddcd16ab8b29d60682761b4923855e7163529c03..6101948a41b093596e1d5bfa35fe6ad332f182e6 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/OAuth2.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/OAuth2.pm @@ -3,7 +3,7 @@ use Lemonldap::NG::Common::JWT qw(getAccessTokenSessionId); use strict; -our $VERSION = '2.0.8'; +our $VERSION = '2.0.12'; sub retrieveSession { my ( $class, $req, $id ) = @_; @@ -11,16 +11,17 @@ sub retrieveSession { # Retrieve regular session if this is not an offline access token unless ($offlineId) { - my $data = { - %{ - $class->Lemonldap::NG::Handler::Main::retrieveSession( $req, - $id ) - }, - $class->_getTokenAttributes($req) - }; - - # Update cache - $class->data($data); + my $data = + $class->Lemonldap::NG::Handler::Main::retrieveSession( $req, $id ); + if ( ref($data) eq "HASH" ) { + $data = { %{$data}, $class->_getTokenAttributes($req) }; + + # Update cache + $class->data($data); + } + else { + $req->data->{oauth2_error} = 'invalid_token'; + } return $data; } @@ -93,6 +94,10 @@ sub fetchId { return; } my $infos = $class->getOIDCInfos($access_token_sid); + unless ($infos) { + $req->data->{oauth2_error} = 'invalid_token'; + return; + } # Store scope and rpid for future session attributes if ( $infos->{rp} ) { @@ -147,6 +152,20 @@ sub getOIDCInfos { unless ( $oidcSession->error ) { $class->logger->debug("Get OIDC session $id"); + # Verify that session is valid + unless ( $oidcSession->data->{_utime} ) { + $class->logger->error("_utime missing from Access Token session"); + return; + } + + my $ttl = $class->tsv->{timeout} - time + $oidcSession->data->{_utime}; + $class->logger->debug( "Session TTL = " . $ttl ); + + if ( time - $oidcSession->data->{_utime} > $class->tsv->{timeout} ) { + $class->logger->info("Access Token session $id expired"); + return; + } + $infos = { %{ $oidcSession->data } }; } else { diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Jail.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Jail.pm index 40eec458fb2ecb8c56516c1b5e30eb587b5a0631..486ccccf6610dbf4913812f0572dac7f0fe3c9c0 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Jail.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Jail.pm @@ -26,7 +26,7 @@ has multiValuesSeparator => ( is => 'rw', isa => 'Maybe[Str]' ); has jail => ( is => 'rw' ); has error => ( is => 'rw' ); -our $VERSION = '2.0.11'; +our $VERSION = '2.0.12'; our @builtCustomFunctions; ## @imethod protected build_jail() @@ -122,7 +122,7 @@ sub encrypt { } sub token { - return encrypt( join( ':', time, @_ ) ); + return $_[0] ? encrypt( join( ':', time, @_ ) ) : encrypt(time); } ## @method reval diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Reload.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Reload.pm index e6660982a8aa2172ef5edf298e306ab7120bb3e1..cfb24ab583e0a9a0cbc8d412c4ec2827f7e6b14d 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Reload.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Reload.pm @@ -1,6 +1,6 @@ package Lemonldap::NG::Handler::Main::Reload; -our $VERSION = '2.0.10'; +our $VERSION = '2.0.12'; package Lemonldap::NG::Handler::Main; @@ -59,9 +59,16 @@ sub checkConf { } $Lemonldap::NG::Common::Conf::msg = ''; - if ( $force or !$class->cfgNum or $class->cfgNum != $conf->{cfgNum} ) { + if ( $force + or !$class->cfgNum + or !$class->cfgDate + or $class->cfgNum != $conf->{cfgNum} + or $class->cfgDate != $conf->{cfgDate} ) + { $class->logger->debug("Get configuration $conf->{cfgNum}"); - unless ( $class->cfgNum( $conf->{cfgNum} ) ) { + unless ( $class->cfgNum( $conf->{cfgNum} ) + && $class->cfgDate( $conf->{cfgDate} ) ) + { $class->logger->error('No configuration available'); return 0; } @@ -80,7 +87,7 @@ sub checkConf { } } } - $class->tsv->{checkTime} = $conf->{checkTime} if ( $conf->{checkTime} ); + $class->checkTime( $conf->{checkTime} ) if $conf->{checkTime}; $class->lastCheck( time() ); $class->logger->debug("$class: configuration is up to date"); return 1; @@ -204,6 +211,7 @@ sub defaultValuesInit { useSafeJail whatToTrace handlerInternalCache handlerServiceTokenTTL customToTrace lwpOpts lwpSslOpts authChoiceAuthBasic authChoiceParam hiddenAttributes + upgradeSession ) ); diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm index d7e7b7e491cceb9baaf821852ebeb2642a120485..b424c64539928b82408e40aaa1e756a0a4817ae6 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm @@ -1,7 +1,7 @@ # Main running methods file package Lemonldap::NG::Handler::Main::Run; -our $VERSION = '2.0.10'; +our $VERSION = '2.0.12'; package Lemonldap::NG::Handler::Main; @@ -139,7 +139,9 @@ sub run { } # Try to recover cookie and user session - if ( $id = $class->fetchId($req) + $id = $class->fetchId($req); + $class->data( {} ) unless($id); + if ( $id and $session = $class->retrieveSession( $req, $id ) ) { @@ -346,7 +348,6 @@ sub grant { return $cond->( $req, $session ) if ($cond); $vhost ||= $class->resolveAlias($req); - my $level = $class->getLevel( $req, $uri ); # Using VH authentification level if exists @@ -356,9 +357,11 @@ sub grant { "User authentication level = $session->{authenticationLevel}"); $class->logger->debug("Required authentication level = $level"); $class->logger->warn( -"User rejected due to insufficient authentication level -> Session upgrade enabled" - ); - $session->{_upgrade} = 1; + 'User rejected due to insufficient authentication level'); + if ( $class->tsv->{upgradeSession} ) { + $class->logger->warn(' -> Session upgrade enabled'); + $session->{_upgrade} = 1; + } return 0; } } diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/SharedVariables.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/SharedVariables.pm index 67906127e593b74fea70135030212d4e42e4888a..24a65fd1c1fc017a4c89dee8d5ebd9443594c19f 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/SharedVariables.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/SharedVariables.pm @@ -1,8 +1,8 @@ package Lemonldap::NG::Handler::Main::SharedVariables; -our $VERSION = '2.0.0'; +our $VERSION = '2.0.12'; -# Since handler has no instances but only static classes, this module provides +# Since handler has no instance but only static classes, this module provides # classes properties with accessors package Lemonldap::NG::Handler::Main; @@ -14,6 +14,7 @@ BEGIN { our $_tshv = { tsv => {}, cfgNum => 0, + cfgDate => 0, lastCheck => 0, checkTime => 600, confAcc => {}, @@ -30,7 +31,7 @@ BEGIN { foreach ( keys %$_tshv ) { eval " sub $_ { my \$v = \$_[1]; - \$_tshv->{$_} = \$v if(defined \$v); + \$_tshv->{$_} = \$v if (defined \$v); return \$_tshv->{$_}; }"; die $@ if ($@); @@ -41,7 +42,7 @@ BEGIN { foreach ( keys %$_v ) { eval " sub $_ { my \$v = \$_[1]; - \$_v->{$_} = \$v if(\$v); + \$_v->{$_} = \$v if (\$v); return \$_v->{$_}; }"; die $@ if ($@); diff --git a/lemonldap-ng-handler/t/60-Lemonldap-NG-Handler-PSGI.t b/lemonldap-ng-handler/t/60-Lemonldap-NG-Handler-PSGI.t index 08e598839cb2a4b49dcdbfcde2107adefb27e3d2..cea6fe850beb97a1febd8e85982a2efb140960c9 100644 --- a/lemonldap-ng-handler/t/60-Lemonldap-NG-Handler-PSGI.t +++ b/lemonldap-ng-handler/t/60-Lemonldap-NG-Handler-PSGI.t @@ -234,7 +234,7 @@ ok( $client->_get( '/', undef, 'foo.example.fr', "lemonldap=$sessionId" ), 'Reject "foo.example.fr"' ); -ok( $res->[0] == 302, ' Code is 302' ) or explain( $res, 302 ); +ok( $res->[0] == 403, ' Code is 403' ) or explain( $res, 403 ); count(2); ok( @@ -243,7 +243,7 @@ ok( ), 'Reject "foo.example.org/orgdeny"' ); -ok( $res->[0] == 302, ' Code is 302' ) or explain( $res, 302 ); +ok( $res->[0] == 403, ' Code is 403' ) or explain( $res, 403 ); count(2); ok( @@ -261,7 +261,7 @@ ok( ), 'Reject "abfoo.example.org/orgdeny"' ); -ok( $res->[0] == 302, ' Code is 302' ) or explain( $res, 302 ); +ok( $res->[0] == 403, ' Code is 403' ) or explain( $res, 403 ); count(2); ok( @@ -287,7 +287,7 @@ ok( $client->_get( '/', undef, 'abfoo.example.org', "lemonldap=$sessionId" ), 'Reject "abfoo.example.org/"' ); -ok( $res->[0] == 302, ' Code is 302' ) or explain( $res, 302 ); +ok( $res->[0] == 403, ' Code is 403' ) or explain( $res, 403 ); count(2); ok( diff --git a/lemonldap-ng-handler/t/71-Lemonldap-NG-Handler-PSGI-OAuth2.t b/lemonldap-ng-handler/t/71-Lemonldap-NG-Handler-PSGI-OAuth2.t index e32194e2f66f3034375a3de30d89df62dfe69db8..e91a9cee3b0b7104efecd6b424d3db774c6e8fc5 100644 --- a/lemonldap-ng-handler/t/71-Lemonldap-NG-Handler-PSGI-OAuth2.t +++ b/lemonldap-ng-handler/t/71-Lemonldap-NG-Handler-PSGI-OAuth2.t @@ -4,7 +4,7 @@ BEGIN { require 't/test-psgi-lib.pm'; } -my $maintests = 21; +my $maintests = 25; init( 'Lemonldap::NG::Handler::Server', @@ -57,7 +57,7 @@ Lemonldap::NG::Common::Session->new( { info => { "user_session_id" => $sessionId, "_type" => "access_token", - "_utime" => time, + "_utime" => ( time - 72000 + 300 ), "rp" => "rp-example2", "scope" => "openid email read" } @@ -74,7 +74,7 @@ Lemonldap::NG::Common::Session->new( { info => { "offline_session_id" => '000999000', "_type" => "refresh_token", - "_utime" => time, + "_utime" => ( time - 72000 + 300 ), "rp" => "rp-example", "scope" => "openid email read" } @@ -117,6 +117,7 @@ ok( # Check headers %h = @{ $res->[1] }; +is( $res->[0], 401, "Got correct HTTP code" ); is( $h{'WWW-Authenticate'}, 'Bearer', 'Got WWW-Authenticate: Bearer' ); # Request with invalid Access Token @@ -210,6 +211,24 @@ is( $h{'Auth-ClientConfKey'}, 'rp-example', 'Client confkey correctly transmitted' ); like( $h{'Auth-Scope'}, qr/\bemail\b/, 'Scope correctly transmitted' ); +Time::Fake->offset("+600s"); +ok( + $res = $client->_get( + '/read', undef, + 'test1.example.com', '', + VHOSTTYPE => 'OAuth2', + HTTP_AUTHORIZATION => 'Bearer 999888777', + ), + 'Invalid access token' +); +%h = @{ $res->[1] }; +is( $res->[0], 401, "Access was rejected" ); +is( + $h{'WWW-Authenticate'}, + 'Bearer error="invalid_token"', + 'Got correct error code' +); + count($maintests); done_testing( count() ); clean(); diff --git a/lemonldap-ng-manager/MANIFEST b/lemonldap-ng-manager/MANIFEST index c96f260c2c9da00e4f3d74330ef9b0c8c7b545c2..485c1841076b471653a07ee88a3dc38bad47d2dc 100644 --- a/lemonldap-ng-manager/MANIFEST +++ b/lemonldap-ng-manager/MANIFEST @@ -248,6 +248,7 @@ t/15-combination.t t/16-cli.t t/17-extra2f.t t/20-test-coverage.t +t/30-DBI-Cli.t t/40-sessions.t t/50-notifications-DBI.t t/50-notifications.t @@ -267,6 +268,7 @@ t/jsonfiles/14-bad.json t/jsonfiles/15-combination.json t/jsonfiles/17-extra2f.json t/jsonfiles/70-diff.json +t/lemonldap-ng-DBI-conf.ini t/lemonldap-ng-dbi.ini t/lemonldap-ng-noDiff.ini t/lemonldap-ng.ini diff --git a/lemonldap-ng-manager/META.json b/lemonldap-ng-manager/META.json index cf9b1aa5ae10543c0abd76754f1183f84bd10fca..2c12d8b94cbffcd5c598c64bb5bdb2060130b89c 100644 --- a/lemonldap-ng-manager/META.json +++ b/lemonldap-ng-manager/META.json @@ -4,7 +4,7 @@ "Xavier Guimard , Clément Oudot " ], "dynamic_config" : 1, - "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010", + "generated_by" : "ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010", "license" : [ "open_source" ], diff --git a/lemonldap-ng-manager/META.yml b/lemonldap-ng-manager/META.yml index 773e52745c6ab63ca35811b5c470023e490b6f0b..aa9e396de5101602687eb6ec0388a20cd0716a2f 100644 --- a/lemonldap-ng-manager/META.yml +++ b/lemonldap-ng-manager/META.yml @@ -9,7 +9,7 @@ build_requires: configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 -generated_by: 'ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010' +generated_by: 'ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010' license: open_source meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Common.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Common.pm index b8d33a9a9e2bf8ee204c60b6e23820cf3560dd9f..94c9204516b333d1887c2b48aef02d1d66c46614 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Common.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Common.pm @@ -1,6 +1,6 @@ package Lemonldap::NG::Manager::Api::Common; -our $VERSION = '2.0.10'; +our $VERSION = '2.0.12'; package Lemonldap::NG::Manager::Api; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm index 1bc788ed944f2cb68c0f2f1723c87c7ae01e2081..35aa8b6ca7dde673486dbd7cf51e251ba8a73a64 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm @@ -1,6 +1,6 @@ package Lemonldap::NG::Manager::Api::Providers::OidcRp; -our $VERSION = '2.0.10'; +our $VERSION = '2.0.12'; package Lemonldap::NG::Manager::Api; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm index 68f9ab78057b48800e41dd50c14b5e7629516154..9af441f42c9498d7bcc057c5f9bb34da2ff96271 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm @@ -811,6 +811,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.] 'casStorageOptions' => { 'type' => 'keyTextContainer' }, + 'casStrictMatching' => { + 'default' => 0, + 'type' => 'bool' + }, 'cda' => { 'default' => 0, 'type' => 'bool' @@ -2148,9 +2152,15 @@ m[^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?: 'type' => 'keyTextContainer' }, 'oidcOPMetaDataJSON' => { + 'keyTest' => sub { + 1; + }, 'type' => 'file' }, 'oidcOPMetaDataJWKS' => { + 'keyTest' => sub { + 1; + }, 'type' => 'file' }, 'oidcOPMetaDataNodes' => { @@ -2439,11 +2449,48 @@ m[^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?: 'oidcRPMetaDataOptionsUserIDAttr' => { 'type' => 'text' }, + 'oidcRPMetaDataOptionsUserInfoSignAlg' => { + 'default' => '', + 'select' => [ { + 'k' => '', + 'v' => 'JSON' + }, + { + 'k' => 'none', + 'v' => 'JWT/None' + }, + { + 'k' => 'HS256', + 'v' => 'JWT/HS256' + }, + { + 'k' => 'HS384', + 'v' => 'JWT/HS384' + }, + { + 'k' => 'HS512', + 'v' => 'JWT/HS512' + }, + { + 'k' => 'RS256', + 'v' => 'JWT/RS256' + }, + { + 'k' => 'RS384', + 'v' => 'JWT/RS384' + }, + { + 'k' => 'RS512', + 'v' => 'JWT/RS512' + } + ], + 'type' => 'select' + }, 'oidcRPMetaDataScopeRules' => { 'default' => {}, 'test' => { 'keyMsgFail' => '__badMacroName__', - 'keyTest' => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/, + 'keyTest' => qr/^[\x21\x23-\x5B\x5D-\x7E]+$/, 'test' => sub { return perlExpr(@_); } @@ -2474,6 +2521,10 @@ m[^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?: 'default' => 0, 'type' => 'bool' }, + 'oidcServiceAllowOnlyDeclaredScopes' => { + 'default' => 0, + 'type' => 'bool' + }, 'oidcServiceAuthorizationCodeExpiration' => { 'default' => 60, 'type' => 'int' @@ -2771,6 +2822,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.] 'default' => 1, 'type' => 'boolOrExpr' }, + 'portalDisplayManageSessions' => { + 'default' => 1, + 'type' => 'boolOrExpr' + }, 'portalDisplayOidcConsents' => { 'default' => '$_oidcConsents && $_oidcConsents =~ /\\w+/', 'type' => 'boolOrExpr' @@ -2791,6 +2846,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.] 'default' => 0, 'type' => 'bool' }, + 'portalEnablePasswordDisplay' => { + 'default' => 0, + 'type' => 'bool' + }, 'portalErrorOnExpiredSession' => { 'default' => 1, 'type' => 'bool' @@ -3892,13 +3951,16 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.] 'sfOnlyUpgrade' => { 'type' => 'bool' }, + 'sfRegisterTimeout' => { + 'type' => 'int' + }, 'sfRemovedMsgRule' => { 'default' => 0, 'type' => 'boolOrExpr' }, 'sfRemovedNotifMsg' => { 'default' => - '_removedSF_ expired second factor(s) has/have been removed!', +'_removedSF_ expired second factor(s) has/have been removed (_nameSF_)!', 'type' => 'text' }, 'sfRemovedNotifRef' => { @@ -4107,10 +4169,6 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a- 'default' => 6, 'type' => 'int' }, - 'totp2fDisplayExistingSecret' => { - 'default' => 0, - 'type' => 'bool' - }, 'totp2fInterval' => { 'default' => 30, 'type' => 'int' @@ -4135,10 +4193,6 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a- 'totp2fTTL' => { 'type' => 'int' }, - 'totp2fUserCanChangeKey' => { - 'default' => 0, - 'type' => 'bool' - }, 'totp2fUserCanRemoveKey' => { 'default' => 1, 'type' => 'bool' diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build.pm index 16a60b20cae489e790c1af40e70d3d257bee98cb..12e9585e25dcb2845ca0aaef89a4676c1a46a94e 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build.pm @@ -9,6 +9,7 @@ use Lemonldap::NG::Manager::Build::CTrees; use Lemonldap::NG::Manager::Build::PortalConstants; use Lemonldap::NG::Manager::Conf::Zero; use Data::Dumper; +use Regexp::Common 'URI'; use Regexp::Assemble; use JSON; use Getopt::Std; @@ -466,6 +467,8 @@ sub buildPortalConstants() { printf STDERR $format, $self->portalConstantsFile; open( F, '>', $self->portalConstantsFile ) or die($!); + my $urire = $RE{URI}{HTTP}{ -scheme=>qr/https?/ }{-keep}; + $urire =~ s/([\$\@])/\\$1/g; my $content = < 'Lemonldap::NG::Handler::PSGI::Main'; +use constant URIRE => qr{$urire}; use constant { EOF for my $pe ( @@ -499,7 +503,7 @@ $portalConstsStr } # EXPORTER PARAMETERS -our \@EXPORT_OK = ( 'portalConsts', 'HANDLER', $exports ); +our \@EXPORT_OK = ( 'portalConsts', 'HANDLER', 'URIRE', $exports ); our %EXPORT_TAGS = ( 'all' => [ \@EXPORT_OK, 'import' ], ); our \@EXPORT = qw(import PE_OK); @@ -694,9 +698,18 @@ sub scanTree { } } } + if ($prefix) { push @cnodesKeys, $leaf; } + + # issue 2439 + # FIXME: in future versions, oidcOPMetaDataJSON and samlIDPMetaDataXML shoud + # behave the same + if ( $leaf =~ /^oidcOPMetaData(?:JSON|JWKS)$/ ) { + push @simpleHashKeys, $leaf; + } + if ( $attr->{type} =~ /^(?:catAndAppList|\w+Container)$/ ) { $jleaf->{cnodes} = $prefix . $leaf; unless ( $prefix or $leaf =~ $reIgnoreKeys ) { diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm index 732568b5afb71a9e0c175158374f0920db55d5f4..22f045995f4a920c4c3bf2e3b68b51ea883531c0 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm @@ -1218,6 +1218,11 @@ sub attributes { default => '$_oidcConsents && $_oidcConsents =~ /\w+/', documentation => 'Display OIDC consent tab in portal', }, + portalDisplayManageSessions => { + type => 'boolOrExpr', + default => 1, + documentation => 'Display Session Manager tab in portal', + }, portalDisplayGeneratePassword => { default => 1, type => 'bool', @@ -1229,6 +1234,11 @@ sub attributes { type => 'bool', documentation => 'Display link to refresh the user session', }, + portalEnablePasswordDisplay => { + default => 0, + type => 'bool', + documentation => 'Allow to display password in login form', + }, # Cookies cookieExpiration => { @@ -1873,17 +1883,6 @@ sub attributes { default => 6, documentation => 'Number of digits for TOTP code', }, - totp2fDisplayExistingSecret => { - type => 'bool', - default => 0, - documentation => - 'Display existing TOTP secret in registration form', - }, - totp2fUserCanChangeKey => { - type => 'bool', - default => 0, - documentation => 'Authorize users to change existing TOTP secret', - }, totp2fUserCanRemoveKey => { type => 'bool', default => 1, @@ -2419,6 +2418,11 @@ sub attributes { type => 'keyTextContainer', documentation => 'Apache::Session module parameters', }, + casStrictMatching => { + default => 0, + type => 'bool', + documentation => 'Disable host-based matching of CAS services', + }, issuerDBCASActivation => { default => 0, type => 'bool', @@ -3232,10 +3236,14 @@ sub attributes { sfRemovedNotifMsg => { type => 'text', default => - '_removedSF_ expired second factor(s) has/have been removed!', +'_removedSF_ expired second factor(s) has/have been removed (_nameSF_)!', help => 'secondfactor.html', documentation => 'Notification message', }, + sfRegisterTimeout => { + type => 'int', + documentation => 'Timeout for 2F registration process', + }, available2F => { type => 'text', default => 'UTOTP,TOTP,U2F,REST,Mail2F,Ext2F,Yubikey,Radius', @@ -4082,6 +4090,11 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?: default => 0, documentation => 'OpenID Connect allow dynamic client registration', }, + oidcServiceAllowOnlyDeclaredScopes => { + type => 'bool', + default => 0, + documentation => 'OpenID Connect allow only declared scopes', + }, oidcServiceAllowAuthorizationCodeFlow => { type => 'bool', default => 1, @@ -4152,8 +4165,14 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?: oidcRPMetaDataOptions => { type => 'subContainer', }, # OpenID Connect providers - oidcOPMetaDataJSON => { type => 'file', }, - oidcOPMetaDataJWKS => { type => 'file', }, + oidcOPMetaDataJSON => { + type => 'file', + keyTest => sub { 1 } + }, + oidcOPMetaDataJWKS => { + type => 'file', + keyTest => sub { 1 } + }, oidcOPMetaDataExportedVars => { type => 'keyTextContainer', default => { @@ -4242,6 +4261,20 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?: ], default => 'RS256', }, + oidcRPMetaDataOptionsUserInfoSignAlg => { + type => 'select', + select => [ + { k => '', v => 'JSON' }, + { k => 'none', v => 'JWT/None' }, + { k => 'HS256', v => 'JWT/HS256' }, + { k => 'HS384', v => 'JWT/HS384' }, + { k => 'HS512', v => 'JWT/HS512' }, + { k => 'RS256', v => 'JWT/RS256' }, + { k => 'RS384', v => 'JWT/RS384' }, + { k => 'RS512', v => 'JWT/RS512' }, + ], + default => '', + }, oidcRPMetaDataOptionsAccessTokenJWT => { type => 'bool', default => 0 }, oidcRPMetaDataOptionsAccessTokenClaims => { type => 'bool', default => 0 }, @@ -4338,7 +4371,9 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?: type => 'keyTextContainer', help => 'idpopenidconnect.html#scope-rules', test => { - keyTest => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/, + + # RFC6749 + keyTest => qr/^[\x21\x23-\x5B\x5D-\x7E]+$/, keyMsgFail => '__badMacroName__', test => sub { return perlExpr(@_) }, }, diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm index ba77a5479053d90ae85b742506a3f2c94258a0ab..ad1c5fbbe3dd377c4015c893d8b1dbfc9451a03a 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm @@ -14,7 +14,7 @@ package Lemonldap::NG::Manager::Build::CTrees; -our $VERSION = '2.0.8'; +our $VERSION = '2.0.12'; sub cTrees { return { @@ -224,6 +224,7 @@ sub cTrees { nodes => [ 'oidcRPMetaDataOptionsIDTokenSignAlg', 'oidcRPMetaDataOptionsAccessTokenSignAlg', + 'oidcRPMetaDataOptionsUserInfoSignAlg', 'oidcRPMetaDataOptionsRequirePKCE', 'oidcRPMetaDataOptionsAllowOffline', 'oidcRPMetaDataOptionsAllowPasswordGrant', diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Tree.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Tree.pm index f5b70d2bd8e628a9095eea780b949ec4697f6961..3c96556d3622e5ef7c6c027fa683416c105bbfb5 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Tree.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Tree.pm @@ -17,7 +17,7 @@ package Lemonldap::NG::Manager::Build::Tree; -our $VERSION = '2.0.11'; +our $VERSION = '2.0.12'; # TODO: Missing: # * activeTimer @@ -43,6 +43,7 @@ sub tree { 'portalDisplayAppslist', 'portalDisplayLoginHistory', 'portalDisplayOidcConsents', + 'portalDisplayManageSessions', ] }, 'applicationList' @@ -79,6 +80,7 @@ sub tree { 'portalRequireOldPassword', 'hideOldPassword', 'mailOnPasswordChange', + 'portalEnablePasswordDisplay', ] }, { @@ -393,9 +395,9 @@ sub tree { help => 'authrest.html', form => 'simpleInputContainer', nodes => [ - 'restAuthnLevel', 'restAuthUrl', - 'restUserDBUrl', 'restPwdConfirmUrl', - 'restPwdModifyUrl', 'restFindUserDBUrl' + 'restAuthnLevel', 'restAuthUrl', + 'restUserDBUrl', 'restPwdConfirmUrl', + 'restPwdModifyUrl' ] }, { @@ -825,6 +827,7 @@ sub tree { 'findUser', 'findUserWildcard', 'findUserControl', + 'restFindUserDBUrl', 'findUserSearchingAttributes', 'findUserExcludingAttributes' ] @@ -878,13 +881,11 @@ sub tree { nodes => [ 'totp2fActivation', 'totp2fSelfRegistration', + 'totp2fUserCanRemoveKey', 'totp2fIssuer', 'totp2fInterval', 'totp2fRange', 'totp2fDigits', - 'totp2fDisplayExistingSecret', - 'totp2fUserCanChangeKey', - 'totp2fUserCanRemoveKey', 'totp2fTTL', 'totp2fAuthnLevel', 'totp2fLabel', @@ -909,12 +910,12 @@ sub tree { nodes => [ 'yubikey2fActivation', 'yubikey2fSelfRegistration', + 'yubikey2fUserCanRemoveKey', 'yubikey2fClientID', 'yubikey2fSecretKey', 'yubikey2fNonce', 'yubikey2fUrl', 'yubikey2fPublicIDSize', - 'yubikey2fUserCanRemoveKey', 'yubikey2fFromSessionAttribute', 'yubikey2fTTL', 'yubikey2fAuthnLevel', @@ -981,6 +982,7 @@ sub tree { 'sfRemovedNotifMsg', ], }, + 'sfRegisterTimeout', ] }, { @@ -1325,6 +1327,7 @@ sub tree { ], }, 'oidcServiceAllowDynamicRegistration', + 'oidcServiceAllowOnlyDeclaredScopes', 'oidcServiceAllowAuthorizationCodeFlow', 'oidcServiceAllowImplicitFlow', 'oidcServiceAllowHybridFlow', @@ -1353,6 +1356,7 @@ sub tree { 'casStorage', 'casStorageOptions', 'casAttributes', + 'casStrictMatching', ] }, diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Cli.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Cli.pm index f8bb665dc38a6a3399f65b606d178af605d649b1..1abcc1685a293d1477913c49364aa1cc7028c235 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Cli.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Cli.pm @@ -7,7 +7,7 @@ use Data::Dumper; use JSON; use Lemonldap::NG::Common::Conf::ReConstants; -our $VERSION = '2.0.10'; +our $VERSION = '2.0.12'; $Data::Dumper::Useperl = 1; extends('Lemonldap::NG::Manager::Cli::Lib'); @@ -416,8 +416,7 @@ sub _setKey { sub _save { my ( $self, $new ) = @_; require Lemonldap::NG::Manager::Conf::Parser; - my $parser = Lemonldap::NG::Manager::Conf::Parser->new( - { + my $parser = Lemonldap::NG::Manager::Conf::Parser->new( { newConf => $new, refConf => $self->mgr->hLoadedPlugins->{conf}->currentConf, req => $self->req diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf.pm index d9c42bf694a19946c9b2dedbca6171c669d3afe2..6372c500b0bb2f6736649c523216d824bd5e1e25 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf.pm @@ -24,7 +24,7 @@ extends qw( Lemonldap::NG::Common::Conf::RESTServer ); -our $VERSION = '2.0.10'; +our $VERSION = '2.0.12'; ############################# # I. INITIALIZATION METHODS # @@ -123,7 +123,7 @@ sub newRSAKey { my $keys = { 'private' => $rsa->get_private_key_string(), 'public' => $rsa->get_public_key_x509_string(), - 'hash' => md5_base64($rsa->get_public_key_string()), + 'hash' => md5_base64( $rsa->get_public_key_string() ), }; if ( $query->{password} ) { my $pem = Convert::PEM->new( @@ -345,26 +345,25 @@ sub newConf { # Body must be json my $new = $req->jsonBodyToObj; - unless ( defined($new) ) { - return $self->sendError( $req, undef, 400 ); - } + return $self->sendError( $req, undef, 400 ) unless ( defined $new ); - # Verify that cfgNum has been asked - unless ( defined $req->params('cfgNum') ) { - return $self->sendError( $req, "Missing configuration number", 400 ); - } + # Verify that cfgNum has been sent + return $self->sendError( $req, "Missing configuration number", 400 ) + unless ( defined $req->params('cfgNum') ); + + # # Verify that cfgDate has been sent + # return $self->sendError( $req, "Missing configuration date", 400 ) + # unless ( defined $req->params('cfgDate') ); # Set current conf to cfgNum - unless ( defined $self->getConfByNum( $req->params('cfgNum') ) ) { - return $self->sendError( - $req, - "Configuration " - . $req->params('cfgNum') - . " not available " - . $Lemonldap::NG::Common::Conf::msg, - 400 - ); - } + return $self->sendError( + $req, + "Configuration " + . $req->params('cfgNum') + . " not available " + . $Lemonldap::NG::Common::Conf::msg, + 400 + ) unless ( defined $self->getConfByNum( $req->params('cfgNum') ) ); # Parse new conf require Lemonldap::NG::Manager::Conf::Parser; @@ -372,13 +371,20 @@ sub newConf { { tree => $new, refConf => $self->currentConf, req => $req } ); # If ref conf isn't last conf, consider conf changed - my $cfgNum = $self->confAcc->lastCfg; - unless ( defined $cfgNum ) { - $req->error($Lemonldap::NG::Common::Conf::msg); - } + my $currentCfgNum = $self->confAcc->lastCfg; + $req->error($Lemonldap::NG::Common::Conf::msg) + unless ( defined $currentCfgNum ); return $self->sendError( $req, undef, 400 ) if ( $req->error ); - - if ( $cfgNum ne $req->params('cfgNum') ) { $parser->confChanged(1); } + my $currentConf = + $self->confAcc->getConf( + { CfgNum => $currentCfgNum, raw => 1, noCache => 1 } ); + my $currentCfgDate = $currentConf->{cfgDate}; + $self->logger->debug( + "Current CfgNum/cfgDate: $currentCfgNum/$currentCfgDate"); + $parser->confChanged(1) + if ( $currentCfgNum ne $req->params('cfgNum') + || $req->params('cfgDate') + && $req->params('cfgDate') ne $currentCfgDate ); my $res = { result => $parser->check( $self->p ) }; @@ -396,39 +402,38 @@ sub newConf { } } if ( $res->{result} ) { - if ( $self->p->{demoMode} ) { - $res->{message} = '__demoModeOn__'; + my %args; + $args{force} = 1 if ( $req->params('force') ); + if ( $req->params('cfgDate') ) { + $args{cfgDate} = $req->params('cfgDate'); + $args{currentCfgDate} = $currentCfgDate; + } + my $s = CONFIG_WAS_CHANGED; + $s = $self->confAcc->saveConf( $parser->newConf, %args ) + unless ( @{ $parser->{needConfirmation} } && !$args{force} ); + if ( $s > 0 ) { + $self->userLogger->notice( + 'User ' . $self->p->userId($req) . " has stored conf $s" ); + $res->{result} = 1; + $res->{cfgNum} = $s; + if ( my $status = $self->applyConf( $parser->newConf ) ) { + push @{ $res->{details}->{__applyResult__} }, + { message => "$_: $status->{$_}" } + foreach ( keys %$status ); + } } else { - my %args; - $args{force} = 1 if ( $req->params('force') ); - my $s = CONFIG_WAS_CHANGED; - $s = $self->confAcc->saveConf( $parser->newConf, %args ) - unless ( @{ $parser->{needConfirmation} } && !$args{force} ); - if ( $s > 0 ) { - $self->userLogger->notice( - 'User ' . $self->p->userId($req) . " has stored conf $s" ); - $res->{result} = 1; - $res->{cfgNum} = $s; - if ( my $status = $self->applyConf( $parser->newConf ) ) { - push @{ $res->{details}->{__applyResult__} }, - { message => "$_: $status->{$_}" } - foreach ( keys %$status ); - } + $self->userLogger->notice( + 'Saving attempt rejected, asking for confirmation to ' + . $self->p->userId($req) ); + $res->{result} = 0; + if ( $s == CONFIG_WAS_CHANGED ) { + $res->{needConfirm} = 1; + $res->{message} .= '__needConfirmation__' + unless @{ $parser->{needConfirmation} }; } else { - $self->userLogger->notice( - 'Saving attempt rejected, asking for confirmation to ' - . $self->p->userId($req) ); - $res->{result} = 0; - if ( $s == CONFIG_WAS_CHANGED ) { - $res->{needConfirm} = 1; - $res->{message} .= '__needConfirmation__' - unless @{ $parser->{needConfirmation} }; - } - else { - $res->{message} = $Lemonldap::NG::Common::Conf::msg; - } + $res->{message} = $Lemonldap::NG::Common::Conf::msg; } } } diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Parser.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Parser.pm index 3f0d7e93f50da11cf34310eac43c8c97339c2436..edeeaeab35065933769d8117ed09f48135a5cc62 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Parser.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Parser.pm @@ -26,7 +26,7 @@ use JSON 'to_json'; use Lemonldap::NG::Common::Conf::ReConstants; use Lemonldap::NG::Manager::Attributes; -our $VERSION = '2.0.9'; +our $VERSION = '2.0.12'; extends 'Lemonldap::NG::Common::Conf::Compact'; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Tests.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Tests.pm index ad40bce8acacefbd335edb38b734f6a176d19be9..1fc7b4698af6797180f8cdca9c5f9a207dedcf20 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Tests.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Tests.pm @@ -954,8 +954,8 @@ sub tests { $hasPwdBE ||= 1 unless $mods[2] eq 'Null'; } return ( -1, - 'Password module is enabled without AuthChoice password backend' ) - unless $hasPwdBE; +'Password module is enabled without AuthChoice password backend' + ) unless $hasPwdBE; } return 1; }, @@ -990,7 +990,10 @@ sub tests { # AuthChoice parameters must exist AuthChoiceParams => sub { - return 1 unless %{ $conf->{authChoiceModules} }; + return 1 + unless ( $conf->{authChoiceModules} + and %{ $conf->{authChoiceModules} } + and $conf->{authentication} eq 'Choice' ); foreach (qw(AuthBasic FindUser)) { if ( $conf->{"authChoice$_"} ) { my $test = $conf->{"authChoice$_"}; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Zero.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Zero.pm index 591fc9964d84eb5296bd221e5971eca87868cf85..406ff93b6bc709c9493a63e3ce3789ac68343555 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Zero.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Zero.pm @@ -2,7 +2,7 @@ package Lemonldap::NG::Manager::Conf::Zero; use strict; -our $VERSION = '2.0.9'; +our $VERSION = '2.0.12'; sub zeroConf { my ( $domain, $sessionDir, $persistentSessionDir, $notificationDir, $cacheDir ) = @_; @@ -167,7 +167,7 @@ sub zeroConf { }, "manager.$domain" => { 'default' => 'inGroup("timelords") or $uid eq "rtyler"', -'(?#Configuration)^/(.*?\.(fcgi|psgi)/)?(manager\.html|confs/|$)' +'(?#Configuration)^/(.*?\.(fcgi|psgi)/)?(manager\.html|confs|prx/|$)' => 'inGroup("timelords")', '(?#Sessions)/(.*?\.(fcgi|psgi)/)?sessions' => 'inGroup("timelords") or $uid eq "rtyler"', diff --git a/lemonldap-ng-manager/scripts/lmConfigEditor b/lemonldap-ng-manager/scripts/lmConfigEditor old mode 100644 new mode 100755 index 1d336a112628c752262c96d580f00a90a6e967aa..6d23891c0b67cf926b7371d4c5260ed46600d54d --- a/lemonldap-ng-manager/scripts/lmConfigEditor +++ b/lemonldap-ng-manager/scripts/lmConfigEditor @@ -10,13 +10,22 @@ use English qw(-no_match_vars); use File::Temp; use POSIX qw(setuid setgid); use Safe; +use Getopt::Long; use strict; my $cli = Lemonldap::NG::Manager::Cli::Lib->new; +our $opt_user = '__APACHEUSER__'; +our $opt_group = '__APACHEGROUP__'; + +GetOptions( + "user=s" => \$opt_user, + "group=s" => \$opt_group +) or die("Error in command line arguments\n"); + eval { - setgid( ( getgrnam('__APACHEGROUP__') )[2] ); - setuid( ( getpwnam('__APACHEUSER__') )[2] ); + setgid( ( getgrnam($opt_group) )[2] ); + setuid( ( getpwnam($opt_user) )[2] ); print STDERR "Running as uid $EUID and gid $EGID\n"; }; diff --git a/lemonldap-ng-manager/site/coffee/2ndfa.coffee b/lemonldap-ng-manager/site/coffee/2ndfa.coffee index 3a82f0ec236a3c506c1652e65c2de4ee27bd1995..3f721f49b5f57a8d0aa3ed3d5e1e529640aeb7db 100644 --- a/lemonldap-ng-manager/site/coffee/2ndfa.coffee +++ b/lemonldap-ng-manager/site/coffee/2ndfa.coffee @@ -175,7 +175,6 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', title: title nodes: tmp time = session._utime - id = session._session_id # 1. Replace values if needed for key, value of session @@ -236,7 +235,6 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', nodes: subres return { _utime: time - id: id nodes: res } @@ -244,6 +242,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', sessionId = scope.$modelValue.session $http.get("#{scriptname}sfa/#{sessionType}/#{sessionId}").then (response) -> $scope.currentSession = transformSession response.data + $scope.currentSession.id = sessionId $scope.showT = false $scope.localeDate = (s) -> diff --git a/lemonldap-ng-manager/site/coffee/manager.coffee b/lemonldap-ng-manager/site/coffee/manager.coffee index 66a532026be84e52a2b8f71706506b04a3c0fc53..6abe9c7bfc7620147ae8d7cbfaee0077aff287b1 100644 --- a/lemonldap-ng-manager/site/coffee/manager.coffee +++ b/lemonldap-ng-manager/site/coffee/manager.coffee @@ -189,7 +189,7 @@ llapp.controller 'TreeCtrl', [ id: "cfgLog" title: "cfgLog" data: if $scope.result then $scope.result else '' - $http.post("#{window.confPrefix}?cfgNum=#{$scope.currentCfg.cfgNum}#{if $scope.forceSave then "&force=1" else ''}", $scope.data).then (response) -> + $http.post("#{window.confPrefix}?cfgNum=#{$scope.currentCfg.cfgNum}&cfgDate=#{$scope.currentCfg.cfgDate}#{if $scope.forceSave then "&force=1" else ''}", $scope.data).then (response) -> $scope.data.pop() _checkSaveResponse response.data ,(response) -> diff --git a/lemonldap-ng-manager/site/coffee/sessions.coffee b/lemonldap-ng-manager/site/coffee/sessions.coffee index 87d454eb498e347c4a469d043efaac315f6da4e1..ec2d6a5999a75df1d35510d217a12adebe409433 100644 --- a/lemonldap-ng-manager/site/coffee/sessions.coffee +++ b/lemonldap-ng-manager/site/coffee/sessions.coffee @@ -114,7 +114,6 @@ categories = saml: ['_idp', '_idpConfKey', '_samlToken', '_lassoSessionDump', '_lassoIdentityDump'] groups: ['groups', 'hGroups'] ldap: ['dn'] - BrowserID: ['_browserIdAnswer', '_browserIdAnswerRaw'] OpenIDConnect: ['_oidc_id_token', '_oidc_OP', '_oidc_access_token'] sfaTitle: ['_2fDevices'] oidcConsents: ['_oidcConsents'] @@ -177,11 +176,11 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', # Delete RP Consent $scope.deleteOIDCConsent = (rp, epoch) -> items = document.querySelectorAll(".data-#{epoch}") - for e in items - e.remove() $scope.waiting = true $http['delete']("#{scriptname}sessions/OIDCConsent/#{sessionType}/#{$scope.currentSession.id}?rp=#{rp}&epoch=#{epoch}").then (response) -> $scope.waiting = false + for e in items + e.remove() , (resp) -> $scope.waiting = false $scope.showT = false @@ -194,8 +193,6 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', $scope.currentScope.remove() $scope.waiting = false , (resp) -> - $scope.currentSession = null - $scope.currentScope.remove() $scope.waiting = false # Open node diff --git a/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.js b/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.js index 07b7d80b9182953a47b3895af45406400b226d4d..4f1190200d31ddc4e859656c9720b2b21e610cc0 100644 --- a/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.js +++ b/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.js @@ -150,7 +150,7 @@ $scope.displaySession = function(scope) { var sessionId, transformSession; transformSession = function(session) { - var _insert, _stToStr, array, arrayDate, attr, attrs, category, epoch, i, id, k, key, len, len1, name, pattern, res, sfDevice, subres, time, title, value; + var _insert, _stToStr, array, arrayDate, attr, attrs, category, epoch, i, k, key, len, len1, name, pattern, res, sfDevice, subres, time, title, value; _stToStr = function(s) { return s; }; @@ -176,7 +176,6 @@ } }; time = session._utime; - id = session._session_id; for (key in session) { value = session[key]; if (!value) { @@ -256,14 +255,14 @@ } return { _utime: time, - id: id, nodes: res }; }; $scope.currentScope = scope; sessionId = scope.$modelValue.session; $http.get(scriptname + "sfa/" + sessionType + "/" + sessionId).then(function(response) { - return $scope.currentSession = transformSession(response.data); + $scope.currentSession = transformSession(response.data); + return $scope.currentSession.id = sessionId; }); return $scope.showT = false; }; diff --git a/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.min.js b/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.min.js index 28a769f75a9fd9c4cea055a898d154eb48b42109..b6dc245e1a07bed0854c7d352e240f9d1b79dc77 100644 --- a/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.min.js +++ b/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.min.js @@ -1 +1 @@ -(function(){var S,o,f,g,e;e=function(e,t){return $("#msg").html(window.translate(e)),$("#color").removeClass("message-positive message-warning alert-success alert-warning"),$("#color").addClass("message-"+t),"positive"===t&&(t="success"),$("#color").addClass("alert-"+t)},g={_whatToTrace:[function(e,t){return"groupBy=substr("+e+",1)"},function(e,t){return e+"="+t+"*"}]},f={_whatToTrace:function(e,t,n,a){return console.log("overSchema => level",n,"over",a),1===n&&t.length>a?e+"="+t+"*&groupBy=substr("+e+","+(n+a+1)+")":null}},S={dateTitle:["_utime","_startTime","_updateTime"],sfaTitle:["_2fDevices"]},o={home:[]},angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]).controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function(k,t,e,n,i){var p,a,r,d;return k.links=links,k.menulinks=menulinks,k.staticPrefix=staticPrefix,k.scriptname=scriptname,k.formPrefix=formPrefix,k.availableLanguages=availableLanguages,k.waiting=!0,k.showM=!1,k.showT=!0,k.data=[],k.currentScope=null,k.currentSession=null,k.menu=o,k.searchString="",k.U2FCheck="1",k.TOTPCheck="1",k.UBKCheck="1",k.translateP=t.translateP,k.translate=t.translate,k.translateTitle=function(e){return t.translateField(e,"title")},d="persistent",k.menuClick=function(e){if(e.popup)window.open(e.popup);else switch(e.action||(e.action=e.title),typeof e.action){case"function":e.action(k.currentNode,k),k[e.action]();break;case"string":k[e.action]();break;default:console.log(typeof e.action)}return k.showM=!1},k.search2FA=function(e){return e&&(k.searchString=""),k.currentSession=null,k.data=[],k.updateTree2("",k.data,0,0)},k.delete2FA=function(e,t){var n,a,r;for(n=0,r=(a=document.querySelectorAll(".data-"+t)).length;n level",n,"over",r),1===n&&t.length>r?e+"="+t+"*&groupBy=substr("+e+","+(n+r+1)+")":null}},S={dateTitle:["_utime","_startTime","_updateTime"],sfaTitle:["_2fDevices"]},o={home:[]},angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]).controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function(_,t,e,n,i){var p,r,a,f;return _.links=links,_.menulinks=menulinks,_.staticPrefix=staticPrefix,_.scriptname=scriptname,_.formPrefix=formPrefix,_.availableLanguages=availableLanguages,_.waiting=!0,_.showM=!1,_.showT=!0,_.data=[],_.currentScope=null,_.currentSession=null,_.menu=o,_.searchString="",_.U2FCheck="1",_.TOTPCheck="1",_.UBKCheck="1",_.translateP=t.translateP,_.translate=t.translate,_.translateTitle=function(e){return t.translateField(e,"title")},f="persistent",_.menuClick=function(e){if(e.popup)window.open(e.popup);else switch(e.action||(e.action=e.title),typeof e.action){case"function":e.action(_.currentNode,_),_[e.action]();break;case"string":_[e.action]();break;default:console.log(typeof e.action)}return _.showM=!1},_.search2FA=function(e){return e&&(_.searchString=""),_.currentSession=null,_.data=[],_.updateTree2("",_.data,0,0)},_.delete2FA=function(e,t){var n,r,a;for(n=0,a=(r=document.querySelectorAll(".data-"+t)).length;n level",n,"over",o),1===n&&t.length>o?e+"="+t+"*&groupBy=substr("+e+","+(n+o+1)+")":null},ipAddr:function(e,t,n,o){return console.log("overScheme => level",n,"over",o),0 level",n,"over",o),3 level",n,"over",o),1===n&&t.length>o?e+"="+t+"*&groupBy=substr("+e+","+(n+o+1)+")":null}},M={dateTitle:["_utime","_startTime","_updateTime","_lastAuthnUTime","_lastSeen"],connectionTitle:["ipAddr","_timezone","_url"],authenticationTitle:["_session_id","_user","_password","authenticationLevel"],modulesTitle:["_auth","_userDB","_passwordDB","_issuerDB","_authChoice","_authMulti","_userDBMulti"],saml:["_idp","_idpConfKey","_samlToken","_lassoSessionDump","_lassoIdentityDump"],groups:["groups","hGroups"],ldap:["dn"],BrowserID:["_browserIdAnswer","_browserIdAnswerRaw"],OpenIDConnect:["_oidc_id_token","_oidc_OP","_oidc_access_token"],sfaTitle:["_2fDevices"],oidcConsents:["_oidcConsents"]},r={session:[{title:"deleteSession",icon:"trash"}],home:[]},angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]).controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function(H,t,i,e,d){var f,n,o,g;return H.links=links,H.menulinks=menulinks,H.staticPrefix=staticPrefix,H.scriptname=scriptname,H.formPrefix=formPrefix,H.impPrefix=impPrefix,H.sessionTTL=sessionTTL,H.availableLanguages=availableLanguages,H.waiting=!0,H.showM=!1,H.showT=!0,H.data=[],H.currentScope=null,H.currentSession=null,H.menu=r,H.translateP=t.translateP,H.translate=t.translate,H.translateTitle=function(e){return t.translateField(e,"title")},g="global",H.menuClick=function(e){if(e.popup)window.open(e.popup);else switch(e.action||(e.action=e.title),typeof e.action){case"function":e.action(H.currentNode,H);break;case"string":H[e.action]();break;default:console.log(typeof e.action)}return H.showM=!1},H.deleteOIDCConsent=function(e,t){var n,o,r;for(n=0,r=(o=document.querySelectorAll(".data-"+t)).length;nt.title?1:e.title real attribute"),b.push(l)):x.push(l);return O=x.concat(b),A.push({title:"__attributesAndMacros__",nodes:O}),{_utime:k,nodes:A}},H.currentScope=e,t=e.$modelValue.session,d.get(scriptname+"sessions/"+g+"/"+t).then(function(e){return H.currentSession=n(e.data),H.currentSession.id=t}),H.showT=!1},H.localeDate=function(e){return new Date(1e3*e).toLocaleString()},H.isValid=function(e,t){var n,o,r;return r=i.path(),o=Date.now()/1e3,console.log("Path",r),console.log("Session epoch",e),console.log("Current date",o),console.log("Session TTL",sessionTTL),n=o-e level",n,"over",o),1===n&&t.length>o?e+"="+t+"*&groupBy=substr("+e+","+(n+o+1)+")":null},ipAddr:function(e,t,n,o){return console.log("overScheme => level",n,"over",o),0 level",n,"over",o),3 level",n,"over",o),1===n&&t.length>o?e+"="+t+"*&groupBy=substr("+e+","+(n+o+1)+")":null}},M={dateTitle:["_utime","_startTime","_updateTime","_lastAuthnUTime","_lastSeen"],connectionTitle:["ipAddr","_timezone","_url"],authenticationTitle:["_session_id","_user","_password","authenticationLevel"],modulesTitle:["_auth","_userDB","_passwordDB","_issuerDB","_authChoice","_authMulti","_userDBMulti"],saml:["_idp","_idpConfKey","_samlToken","_lassoSessionDump","_lassoIdentityDump"],groups:["groups","hGroups"],ldap:["dn"],OpenIDConnect:["_oidc_id_token","_oidc_OP","_oidc_access_token"],sfaTitle:["_2fDevices"],oidcConsents:["_oidcConsents"]},i={session:[{title:"deleteSession",icon:"trash"}],home:[]};angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]).controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function(H,t,r,e,o){var p,n,d;return H.links=links,H.menulinks=menulinks,H.staticPrefix=staticPrefix,H.scriptname=scriptname,H.formPrefix=formPrefix,H.impPrefix=impPrefix,H.sessionTTL=sessionTTL,H.availableLanguages=availableLanguages,H.waiting=!0,H.showM=!1,H.showT=!0,H.data=[],H.currentScope=null,H.currentSession=null,H.menu=i,H.translateP=t.translateP,H.translate=t.translate,H.translateTitle=function(e){return t.translateField(e,"title")},d="global",H.menuClick=function(e){if(e.popup)window.open(e.popup);else switch(e.action||(e.action=e.title),typeof e.action){case"function":e.action(H.currentNode,H);break;case"string":H[e.action]();break;default:console.log(typeof e.action)}return H.showM=!1},H.deleteOIDCConsent=function(e,t){var i=document.querySelectorAll(".data-"+t);return H.waiting=!0,o.delete(scriptname+"sessions/OIDCConsent/"+d+"/"+H.currentSession.id+"?rp="+e+"&epoch="+t).then(function(e){var t,n,o,r;for(H.waiting=!1,r=[],n=0,o=i.length;nt.title?1:e.title real attribute"),B.push(i)):A.push(i);return I=A.concat(B),L.push({title:"__attributesAndMacros__",nodes:I}),{_utime:E,nodes:L}};return H.currentScope=e,t=e.$modelValue.session,o.get(scriptname+"sessions/"+d+"/"+t).then(function(e){return H.currentSession=n(e.data),H.currentSession.id=t}),H.showT=!1},H.localeDate=function(e){return new Date(1e3*e).toLocaleString()},H.isValid=function(e,t){var n=r.path(),o=Date.now()/1e3;return console.log("Path",n),console.log("Session epoch",e),console.log("Current date",o),console.log("Session TTL",sessionTTL),e=o-e