From a9719feeec3eb838b4c2ed81b63a207804511802 Mon Sep 17 00:00:00 2001 From: bgamard Date: Fri, 28 Aug 2020 18:09:54 +0200 Subject: [PATCH] LDAP support, courtesy of an anonymous donator --- docs-core/pom.xml | 7 +- .../docs/core/constant/ConfigType.java | 15 +- .../sismics/docs/core/util/ConfigUtil.java | 13 ++ .../LdapAuthenticationHandler.java | 131 +++++++++++++++++ docs-core/src/test/resources/log4j.properties | 3 +- .../sismics/docs/rest/util/ClientUtil.java | 4 +- docs-web/src/dev/resources/log4j.properties | 3 +- .../docs/rest/resource/AppResource.java | 135 ++++++++++++++++++ docs-web/src/main/webapp/src/app/docs/app.js | 9 ++ .../docs/controller/settings/SettingsLdap.js | 33 +++++ docs-web/src/main/webapp/src/index.html | 1 + docs-web/src/main/webapp/src/locale/en.json | 14 ++ docs-web/src/main/webapp/src/locale/fr.json | 14 ++ .../webapp/src/partial/docs/settings.html | 1 + .../src/partial/docs/settings.ldap.html | 88 ++++++++++++ docs-web/src/prod/resources/log4j.properties | 3 +- .../sismics/docs/rest/TestAppResource.java | 102 +++++++++++++ docs-web/src/test/resources/log4j.properties | 1 + docs-web/src/test/resources/test.ldif | 19 +++ pom.xml | 7 + 20 files changed, 597 insertions(+), 6 deletions(-) create mode 100644 docs-core/src/main/java/com/sismics/docs/core/util/authentication/LdapAuthenticationHandler.java create mode 100644 docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsLdap.js create mode 100644 docs-web/src/main/webapp/src/partial/docs/settings.ldap.html create mode 100644 docs-web/src/test/resources/test.ldif diff --git a/docs-core/pom.xml b/docs-core/pom.xml index cba70a6a..fc0cf937 100644 --- a/docs-core/pom.xml +++ b/docs-core/pom.xml @@ -131,7 +131,12 @@ com.squareup.okhttp3 okhttp - + + + org.apache.directory.server + apacheds-all + + org.apache.lucene diff --git a/docs-core/src/main/java/com/sismics/docs/core/constant/ConfigType.java b/docs-core/src/main/java/com/sismics/docs/core/constant/ConfigType.java index 5c7869d3..e5d41008 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/constant/ConfigType.java +++ b/docs-core/src/main/java/com/sismics/docs/core/constant/ConfigType.java @@ -44,5 +44,18 @@ public enum ConfigType { INBOX_PASSWORD, INBOX_TAG, INBOX_AUTOMATIC_TAGS, - INBOX_DELETE_IMPORTED + INBOX_DELETE_IMPORTED, + + /** + * LDAP connection. + */ + LDAP_ENABLED, + LDAP_HOST, + LDAP_PORT, + LDAP_ADMIN_DN, + LDAP_ADMIN_PASSWORD, + LDAP_BASE_DN, + LDAP_FILTER, + LDAP_DEFAULT_EMAIL, + LDAP_DEFAULT_STORAGE } diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/ConfigUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/ConfigUtil.java index 5f77a201..d9c6754b 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/ConfigUtil.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/ConfigUtil.java @@ -50,6 +50,19 @@ public class ConfigUtil { return Integer.parseInt(value); } + /** + * Returns the long value of a configuration parameter. + * + * @param configType Type of the configuration parameter + * @return Long value of the configuration parameter + * @throws IllegalStateException Configuration parameter undefined + */ + public static long getConfigLongValue(ConfigType configType) { + String value = getConfigStringValue(configType); + + return Long.parseLong(value); + } + /** * Returns the boolean value of a configuration parameter. * diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/authentication/LdapAuthenticationHandler.java b/docs-core/src/main/java/com/sismics/docs/core/util/authentication/LdapAuthenticationHandler.java new file mode 100644 index 00000000..d9ddd5da --- /dev/null +++ b/docs-core/src/main/java/com/sismics/docs/core/util/authentication/LdapAuthenticationHandler.java @@ -0,0 +1,131 @@ +package com.sismics.docs.core.util.authentication; + +import com.sismics.docs.core.constant.ConfigType; +import com.sismics.docs.core.constant.Constants; +import com.sismics.docs.core.dao.ConfigDao; +import com.sismics.docs.core.dao.UserDao; +import com.sismics.docs.core.model.jpa.Config; +import com.sismics.docs.core.model.jpa.User; +import com.sismics.docs.core.util.ConfigUtil; +import com.sismics.util.ClasspathScanner; +import org.apache.commons.pool.impl.GenericObjectPool; +import org.apache.directory.api.ldap.model.cursor.EntryCursor; +import org.apache.directory.api.ldap.model.entry.Attribute; +import org.apache.directory.api.ldap.model.entry.Entry; +import org.apache.directory.api.ldap.model.entry.Value; +import org.apache.directory.api.ldap.model.message.SearchScope; +import org.apache.directory.ldap.client.api.DefaultLdapConnectionFactory; +import org.apache.directory.ldap.client.api.LdapConnectionConfig; +import org.apache.directory.ldap.client.api.LdapConnectionPool; +import org.apache.directory.ldap.client.api.PoolableLdapConnectionFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.UUID; + +/** + * LDAP authentication handler. + * + * @author bgamard + */ +@ClasspathScanner.Priority(50) // Before the internal database +public class LdapAuthenticationHandler implements AuthenticationHandler { + /** + * Logger. + */ + private static final Logger log = LoggerFactory.getLogger(LdapAuthenticationHandler.class); + + /** + * LDAP connection pool. + */ + private static LdapConnectionPool pool; + + /** + * Reset the LDAP pool. + */ + public static void reset() { + if (pool != null) { + try { + pool.close(); + } catch (Exception e) { + // NOP + } + } + pool = null; + } + + /** + * Initialize the LDAP pool. + */ + private static void init() { + ConfigDao configDao = new ConfigDao(); + Config ldapEnabled = configDao.getById(ConfigType.LDAP_ENABLED); + if (pool != null || ldapEnabled == null || !Boolean.parseBoolean(ldapEnabled.getValue())) { + return; + } + + LdapConnectionConfig config = new LdapConnectionConfig(); + config.setLdapHost(ConfigUtil.getConfigStringValue(ConfigType.LDAP_HOST)); + config.setLdapPort(ConfigUtil.getConfigIntegerValue(ConfigType.LDAP_PORT)); + config.setName(ConfigUtil.getConfigStringValue(ConfigType.LDAP_ADMIN_DN)); + config.setCredentials(ConfigUtil.getConfigStringValue(ConfigType.LDAP_ADMIN_PASSWORD)); + + DefaultLdapConnectionFactory factory = new DefaultLdapConnectionFactory(config); + GenericObjectPool.Config poolConfig = new GenericObjectPool.Config(); + poolConfig.whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_GROW; + poolConfig.maxWait = 500; + pool = new LdapConnectionPool(new PoolableLdapConnectionFactory(factory), poolConfig); + } + + @Override + public User authenticate(String username, String password) { + init(); + if (pool == null) { + return null; + } + + // Fetch and authenticate the user + Entry userEntry; + try { + EntryCursor cursor = pool.getConnection().search(ConfigUtil.getConfigStringValue(ConfigType.LDAP_BASE_DN), + ConfigUtil.getConfigStringValue(ConfigType.LDAP_FILTER).replace("USERNAME", username), SearchScope.SUBTREE); + if (cursor.next()) { + userEntry = cursor.get(); + pool.getConnection().bind(userEntry.getDn(), password); + } else { + // User not found + return null; + } + } catch (Exception e) { + log.error("Error authenticating \"" + username + "\" using the LDAP", e); + return null; + } + + UserDao userDao = new UserDao(); + User user = userDao.getActiveByUsername(username); + if (user == null) { + // The user is valid but never authenticated, create the user now + log.info("\"" + username + "\" authenticated for the first time, creating the internal user"); + user = new User(); + user.setRoleId(Constants.DEFAULT_USER_ROLE); + user.setUsername(username); + user.setPassword(UUID.randomUUID().toString()); // No authentication using the internal database + Attribute mailAttribute = userEntry.get("mail"); + if (mailAttribute == null || mailAttribute.get() == null) { + user.setEmail(ConfigUtil.getConfigStringValue(ConfigType.LDAP_DEFAULT_EMAIL)); + } else { + Value value = mailAttribute.get(); + user.setEmail(value.getString()); + } + user.setStorageQuota(ConfigUtil.getConfigLongValue(ConfigType.LDAP_DEFAULT_STORAGE)); + try { + userDao.create(user, "admin"); + } catch (Exception e) { + log.error("Error while creating the internal user", e); + return null; + } + } + + return user; + } +} diff --git a/docs-core/src/test/resources/log4j.properties b/docs-core/src/test/resources/log4j.properties index b13ac1dc..7fc5f2b5 100644 --- a/docs-core/src/test/resources/log4j.properties +++ b/docs-core/src/test/resources/log4j.properties @@ -6,4 +6,5 @@ log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender log4j.appender.MEMORY.size=1000 log4j.logger.com.sismics=INFO -log4j.logger.org.hibernate=ERROR \ No newline at end of file +log4j.logger.org.hibernate=ERROR +log4j.logger.org.apache.directory=ERROR \ No newline at end of file diff --git a/docs-web-common/src/test/java/com/sismics/docs/rest/util/ClientUtil.java b/docs-web-common/src/test/java/com/sismics/docs/rest/util/ClientUtil.java index 2213160e..51972eee 100644 --- a/docs-web-common/src/test/java/com/sismics/docs/rest/util/ClientUtil.java +++ b/docs-web-common/src/test/java/com/sismics/docs/rest/util/ClientUtil.java @@ -5,6 +5,7 @@ import com.sismics.util.filter.TokenBasedSecurityFilter; import org.glassfish.jersey.media.multipart.FormDataMultiPart; import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart; +import org.junit.Assert; import javax.json.JsonObject; import javax.ws.rs.client.Entity; @@ -113,7 +114,8 @@ public class ClientUtil { .param("username", username) .param("password", password) .param("remember", remember.toString()))); - + Assert.assertEquals(200, response.getStatus()); + return getAuthenticationCookie(response); } diff --git a/docs-web/src/dev/resources/log4j.properties b/docs-web/src/dev/resources/log4j.properties index 91f327c9..ad707aff 100644 --- a/docs-web/src/dev/resources/log4j.properties +++ b/docs-web/src/dev/resources/log4j.properties @@ -7,4 +7,5 @@ log4j.appender.MEMORY.size=1000 log4j.logger.com.sismics=DEBUG log4j.logger.org.apache.pdfbox=ERROR -log4j.logger.org.glassfish.jersey.servlet.WebComponent=ERROR \ No newline at end of file +log4j.logger.org.glassfish.jersey.servlet.WebComponent=ERROR +log4j.logger.org.apache.directory=ERROR \ No newline at end of file diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java index 60cde6bb..4daad6a4 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java @@ -14,6 +14,7 @@ import com.sismics.docs.core.model.jpa.File; import com.sismics.docs.core.service.InboxService; import com.sismics.docs.core.util.ConfigUtil; import com.sismics.docs.core.util.DirectoryUtil; +import com.sismics.docs.core.util.authentication.LdapAuthenticationHandler; import com.sismics.docs.core.util.jpa.PaginatedList; import com.sismics.docs.core.util.jpa.PaginatedLists; import com.sismics.docs.rest.constant.BaseFunction; @@ -696,4 +697,138 @@ public class AppResource extends BaseResource { .add("status", "ok"); return Response.ok().entity(response.build()).build(); } + + /** + * Get the LDAP authentication configuration. + * + * @api {get} /app/config_ldap Get the LDAP authentication configuration + * @apiName GetAppConfigLdap + * @apiGroup App + * @apiSuccess {Boolean} enabled LDAP authentication enabled + * @apiSuccess {String} host LDAP server host + * @apiSuccess {Integer} port LDAP server port + * @apiSuccess {String} admin_dn Admin DN + * @apiSuccess {String} admin_password Admin password + * @apiSuccess {String} base_dn Base DN + * @apiSuccess {String} filter LDAP filter + * @apiSuccess {String} default_email LDAP default email + * @apiSuccess {Integer} default_storage LDAP default storage + * @apiError (client) ForbiddenError Access denied + * @apiPermission admin + * @apiVersion 1.9.0 + * + * @return Response + */ + @GET + @Path("config_ldap") + public Response getConfigLdap() { + if (!authenticate()) { + throw new ForbiddenClientException(); + } + checkBaseFunction(BaseFunction.ADMIN); + + ConfigDao configDao = new ConfigDao(); + Config enabled = configDao.getById(ConfigType.LDAP_ENABLED); + + JsonObjectBuilder response = Json.createObjectBuilder(); + if (enabled != null && Boolean.parseBoolean(enabled.getValue())) { + // LDAP enabled + response.add("enabled", true) + .add("host", ConfigUtil.getConfigStringValue(ConfigType.LDAP_HOST)) + .add("port", ConfigUtil.getConfigIntegerValue(ConfigType.LDAP_PORT)) + .add("admin_dn", ConfigUtil.getConfigStringValue(ConfigType.LDAP_ADMIN_DN)) + .add("admin_password", ConfigUtil.getConfigStringValue(ConfigType.LDAP_ADMIN_PASSWORD)) + .add("base_dn", ConfigUtil.getConfigStringValue(ConfigType.LDAP_BASE_DN)) + .add("filter", ConfigUtil.getConfigStringValue(ConfigType.LDAP_FILTER)) + .add("default_email", ConfigUtil.getConfigStringValue(ConfigType.LDAP_DEFAULT_EMAIL)) + .add("default_storage", ConfigUtil.getConfigLongValue(ConfigType.LDAP_DEFAULT_STORAGE)); + } else { + // LDAP disabled + response.add("enabled", false); + } + + return Response.ok().entity(response.build()).build(); + } + + /** + * Configure the LDAP authentication. + * + * @api {post} /app/config_ldap Configure the LDAP authentication + * @apiName PostAppConfigLdap + * @apiGroup App + * @apiParam {Boolean} enabled LDAP authentication enabled + * @apiParam {String} host LDAP server host + * @apiParam {Integer} port LDAP server port + * @apiParam {String} admin_dn Admin DN + * @apiParam {String} admin_password Admin password + * @apiParam {String} base_dn Base DN + * @apiParam {String} filter LDAP filter + * @apiParam {String} default_email LDAP default email + * @apiParam {Integer} default_storage LDAP default storage + * @apiError (client) ForbiddenError Access denied + * @apiError (client) ValidationError Validation error + * @apiPermission admin + * @apiVersion 1.9.0 + * + * @param enabled LDAP authentication enabled + * @param host LDAP server host + * @param portStr LDAP server port + * @param adminDn Admin DN + * @param adminPassword Admin password + * @param baseDn Base DN + * @param filter LDAP filter + * @param defaultEmail LDAP default email + * @param defaultStorageStr LDAP default storage + * @return Response + */ + @POST + @Path("config_ldap") + public Response configLdap(@FormParam("enabled") Boolean enabled, + @FormParam("host") String host, + @FormParam("port") String portStr, + @FormParam("admin_dn") String adminDn, + @FormParam("admin_password") String adminPassword, + @FormParam("base_dn") String baseDn, + @FormParam("filter") String filter, + @FormParam("default_email") String defaultEmail, + @FormParam("default_storage") String defaultStorageStr) { + if (!authenticate()) { + throw new ForbiddenClientException(); + } + checkBaseFunction(BaseFunction.ADMIN); + + ConfigDao configDao = new ConfigDao(); + + if (enabled != null && enabled) { + // LDAP enabled, validate everything + ValidationUtil.validateLength(host, "host", 1, 250); + ValidationUtil.validateInteger(portStr, "port"); + ValidationUtil.validateLength(adminDn, "admin_dn", 1, 250); + ValidationUtil.validateLength(adminPassword, "admin_password", 1, 250); + ValidationUtil.validateLength(baseDn, "base_dn", 1, 250); + ValidationUtil.validateLength(filter, "filter", 1, 250); + if (!filter.contains("USERNAME")) { + throw new ClientException("ValidationError", "'filter' must contains 'USERNAME'"); + } + ValidationUtil.validateLength(defaultEmail, "default_email", 1, 250); + ValidationUtil.validateLong(defaultStorageStr, "default_storage"); + configDao.update(ConfigType.LDAP_ENABLED, Boolean.TRUE.toString()); + configDao.update(ConfigType.LDAP_HOST, host); + configDao.update(ConfigType.LDAP_PORT, portStr); + configDao.update(ConfigType.LDAP_ADMIN_DN, adminDn); + configDao.update(ConfigType.LDAP_ADMIN_PASSWORD, adminPassword); + configDao.update(ConfigType.LDAP_BASE_DN, baseDn); + configDao.update(ConfigType.LDAP_FILTER, filter); + configDao.update(ConfigType.LDAP_DEFAULT_EMAIL, defaultEmail); + configDao.update(ConfigType.LDAP_DEFAULT_STORAGE, defaultStorageStr); + } else { + // LDAP disabled + configDao.update(ConfigType.LDAP_ENABLED, Boolean.FALSE.toString()); + } + + // Reset the LDAP pool to reconnect with the new configuration + LdapAuthenticationHandler.reset(); + + return Response.ok().build(); + } } diff --git a/docs-web/src/main/webapp/src/app/docs/app.js b/docs-web/src/main/webapp/src/app/docs/app.js index fe70896d..f49fe8dc 100644 --- a/docs-web/src/main/webapp/src/app/docs/app.js +++ b/docs-web/src/main/webapp/src/app/docs/app.js @@ -244,6 +244,15 @@ angular.module('docs', } } }) + .state('settings.ldap', { + url: '/ldap', + views: { + 'settings': { + templateUrl: 'partial/docs/settings.ldap.html', + controller: 'SettingsLdap' + } + } + }) .state('document', { url: '/document', abstract: true, diff --git a/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsLdap.js b/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsLdap.js new file mode 100644 index 00000000..671561eb --- /dev/null +++ b/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsLdap.js @@ -0,0 +1,33 @@ +'use strict'; + +/** + * Settings LDAP page controller. + */ +angular.module('docs').controller('SettingsLdap', function($scope, Restangular, $translate, $timeout) { + $scope.ldap = { + enabled: false + }; + + // Get the LDAP configuration + Restangular.one('app/config_ldap').get().then(function (data) { + $scope.ldap = data; + if ($scope.ldap.default_storage) { + $scope.ldap.default_storage /= 1000000; + } + }); + + // Edit SMTP config + $scope.saveResult = undefined; + $scope.save = function () { + var ldap = angular.copy($scope.ldap); + if (ldap.default_storage) { + ldap.default_storage *= 1000000; + } + Restangular.one('app').post('config_ldap', ldap).then(function () { + $scope.saveResult = $translate.instant('settings.ldap.saved'); + $timeout(function() { + $scope.saveResult = undefined; + }, 5000); + }); + }; +}); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/index.html b/docs-web/src/main/webapp/src/index.html index 69bbd935..8fa8e03a 100644 --- a/docs-web/src/main/webapp/src/index.html +++ b/docs-web/src/main/webapp/src/index.html @@ -94,6 +94,7 @@ + diff --git a/docs-web/src/main/webapp/src/locale/en.json b/docs-web/src/main/webapp/src/locale/en.json index 4b34de29..f221c8c8 100644 --- a/docs-web/src/main/webapp/src/locale/en.json +++ b/docs-web/src/main/webapp/src/locale/en.json @@ -278,8 +278,22 @@ "menu_vocabularies": "Vocabularies", "menu_configuration": "Configuration", "menu_inbox": "Inbox scanning", + "menu_ldap": "LDAP authentication", "menu_metadata": "Custom metadata", "menu_monitoring": "Monitoring", + "ldap": { + "title": "LDAP authentication", + "enabled": "Enable LDAP authentication", + "host": "LDAP hostname", + "port": "LDAP port (389 by default)", + "admin_dn": "Admin DN", + "admin_password": "Admin password", + "base_dn": "Base search DN", + "filter": "Search filter (must contains USERNAME, eg. \"(uid=USERNAME)\")", + "default_email": "Default email for LDAP user", + "default_storage": "Default storage for LDAP user", + "saved": "LDAP configuration saved successfully" + }, "user": { "title": "Users management", "add_user": "Add a user", diff --git a/docs-web/src/main/webapp/src/locale/fr.json b/docs-web/src/main/webapp/src/locale/fr.json index 09ad12a0..395c469e 100644 --- a/docs-web/src/main/webapp/src/locale/fr.json +++ b/docs-web/src/main/webapp/src/locale/fr.json @@ -278,7 +278,21 @@ "menu_vocabularies": "Vocabulaires", "menu_configuration": "Configuration", "menu_inbox": "Scanning de boîte de réception", + "menu_ldap": "Authentification LDAP", "menu_monitoring": "Monitoring", + "ldap": { + "title": "Authentification LDAP", + "enabled": "Activer l'authentication LDAP", + "host": "Hôte LDAP", + "port": "Port LDAP (389 par défaut)", + "admin_dn": "DN administrateur", + "admin_password": "Mot de passe administrateur", + "base_dn": "DN de recherche", + "filter": "Filtre de recherche (doit contenir USERNAME, cf. \"(uid=USERNAME)\")", + "default_email": "Email par défaut pour les utilisateurs LDAP", + "default_storage": "Stockage par défaut pour les utilisateurs LDAP", + "saved": "Configuration IMAP sauvegardée avec succès" + }, "user": { "title": "Gestion des utilisateurs", "add_user": "Ajouter un utilisateur", diff --git a/docs-web/src/main/webapp/src/partial/docs/settings.html b/docs-web/src/main/webapp/src/partial/docs/settings.html index 30b531e2..47c7ca6f 100644 --- a/docs-web/src/main/webapp/src/partial/docs/settings.html +++ b/docs-web/src/main/webapp/src/partial/docs/settings.html @@ -20,6 +20,7 @@ {{ 'settings.menu_configuration' | translate }} {{ 'settings.menu_metadata' | translate }} {{ 'settings.menu_inbox' | translate }} + {{ 'settings.menu_ldap' | translate }} {{ 'settings.menu_monitoring' | translate }} diff --git a/docs-web/src/main/webapp/src/partial/docs/settings.ldap.html b/docs-web/src/main/webapp/src/partial/docs/settings.ldap.html new file mode 100644 index 00000000..80657de3 --- /dev/null +++ b/docs-web/src/main/webapp/src/partial/docs/settings.ldap.html @@ -0,0 +1,88 @@ +

+
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+
+ +
{{ 'filter.filesize.mb' | translate }}
+
+
+ +
+ {{ 'validation.number' | translate }} +
+
+ +
+
+ +
+
+
+ +
+ {{ saveResult }} +
\ No newline at end of file diff --git a/docs-web/src/prod/resources/log4j.properties b/docs-web/src/prod/resources/log4j.properties index 75203ec9..c1f0ebf5 100644 --- a/docs-web/src/prod/resources/log4j.properties +++ b/docs-web/src/prod/resources/log4j.properties @@ -7,4 +7,5 @@ log4j.appender.MEMORY.size=1000 log4j.logger.com.sismics=INFO log4j.logger.org.apache.pdfbox=ERROR -log4j.logger.org.glassfish.jersey.servlet.WebComponent=ERROR \ No newline at end of file +log4j.logger.org.glassfish.jersey.servlet.WebComponent=ERROR +log4j.logger.org.apache.directory=ERROR \ No newline at end of file diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestAppResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestAppResource.java index 68ce4f4f..7cc3d1ef 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestAppResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestAppResource.java @@ -1,10 +1,20 @@ package com.sismics.docs.rest; +import com.google.common.io.Resources; import com.icegreen.greenmail.util.GreenMail; import com.icegreen.greenmail.util.GreenMailUtil; import com.icegreen.greenmail.util.ServerSetup; import com.sismics.docs.core.model.context.AppContext; import com.sismics.util.filter.TokenBasedSecurityFilter; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.server.core.api.DirectoryService; +import org.apache.directory.server.core.api.partition.Partition; +import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory; +import org.apache.directory.server.core.factory.DirectoryServiceFactory; +import org.apache.directory.server.core.partition.impl.avl.AvlPartition; +import org.apache.directory.server.ldap.LdapServer; +import org.apache.directory.server.protocol.shared.store.LdifFileLoader; +import org.apache.directory.server.protocol.shared.transport.TcpTransport; import org.junit.Assert; import org.junit.Test; @@ -14,6 +24,7 @@ import javax.ws.rs.client.Entity; import javax.ws.rs.core.Form; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import java.io.File; /** @@ -331,4 +342,95 @@ public class TestAppResource extends BaseJerseyTest { greenMail.stop(); } + + /** + * Test the LDAP authentication. + */ + @Test + public void testLdapAuthentication() throws Exception { + // Start LDAP server + final DirectoryServiceFactory factory = new DefaultDirectoryServiceFactory(); + factory.init("Test"); + + final DirectoryService directoryService = factory.getDirectoryService(); + directoryService.getChangeLog().setEnabled(false); + directoryService.setShutdownHookEnabled(true); + + final Partition partition = new AvlPartition(directoryService.getSchemaManager()); + partition.setId("Test"); + partition.setSuffixDn(new Dn(directoryService.getSchemaManager(), "o=TEST")); + partition.initialize(); + directoryService.addPartition(partition); + + final LdapServer ldapServer = new LdapServer(); + ldapServer.setTransports(new TcpTransport("localhost", 11389)); + ldapServer.setDirectoryService(directoryService); + + directoryService.startup(); + ldapServer.start(); + + // Load test data in LDAP + new LdifFileLoader(directoryService.getAdminSession(), new File(Resources.getResource("test.ldif").getFile()), null).execute(); + + // Login admin + String adminToken = clientUtil.login("admin", "admin", false); + + // Get the LDAP configuration + JsonObject json = target().path("/app/config_ldap").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .get(JsonObject.class); + Assert.assertFalse(json.getBoolean("enabled")); + + // Change LDAP configuration + target().path("/app/config_ldap").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .post(Entity.form(new Form() + .param("enabled", "true") + .param("host", "localhost") + .param("port", "11389") + .param("admin_dn", "uid=admin,ou=system") + .param("admin_password", "secret") + .param("base_dn", "o=TEST") + .param("filter", "(&(objectclass=inetOrgPerson)(uid=USERNAME))") + .param("default_email", "devnull@teedy.io") + .param("default_storage", "100000000") + ), JsonObject.class); + + // Get the LDAP configuration + json = target().path("/app/config_ldap").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .get(JsonObject.class); + Assert.assertTrue(json.getBoolean("enabled")); + Assert.assertEquals("localhost", json.getString("host")); + Assert.assertEquals(11389, json.getJsonNumber("port").intValue()); + Assert.assertEquals("uid=admin,ou=system", json.getString("admin_dn")); + Assert.assertEquals("secret", json.getString("admin_password")); + Assert.assertEquals("o=TEST", json.getString("base_dn")); + Assert.assertEquals("(&(objectclass=inetOrgPerson)(uid=USERNAME))", json.getString("filter")); + Assert.assertEquals("devnull@teedy.io", json.getString("default_email")); + Assert.assertEquals(100000000L, json.getJsonNumber("default_storage").longValue()); + + // Login with a LDAP user + String ldapTopen = clientUtil.login("ldap1", "secret", false); + + // Check user informations + json = target().path("/user").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, ldapTopen) + .get(JsonObject.class); + Assert.assertEquals("ldap1@teedy.io", json.getString("email")); + + // List all documents + json = target().path("/document/list") + .queryParam("sort_column", 3) + .queryParam("asc", true) + .request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, ldapTopen) + .get(JsonObject.class); + JsonArray documents = json.getJsonArray("documents"); + Assert.assertEquals(0, documents.size()); + + // Stop LDAP server + ldapServer.stop(); + directoryService.shutdown(); + } } \ No newline at end of file diff --git a/docs-web/src/test/resources/log4j.properties b/docs-web/src/test/resources/log4j.properties index 85def7fe..38dd0d51 100644 --- a/docs-web/src/test/resources/log4j.properties +++ b/docs-web/src/test/resources/log4j.properties @@ -10,3 +10,4 @@ log4j.logger.com.sismics.util.jpa=ERROR log4j.logger.org.hibernate=ERROR log4j.logger.org.apache.pdfbox=INFO log4j.logger.com.mchange=ERROR +log4j.logger.org.apache.directory=ERROR \ No newline at end of file diff --git a/docs-web/src/test/resources/test.ldif b/docs-web/src/test/resources/test.ldif new file mode 100644 index 00000000..480faf77 --- /dev/null +++ b/docs-web/src/test/resources/test.ldif @@ -0,0 +1,19 @@ +version: 1 + +dn: o=TEST +objectclass: domain +objectclass: top +objectclass: extensibleObject +dc: TEST +o: TEST + +dn: uid=ldap1,o=TEST +objectClass: top +objectClass: inetOrgPerson +objectClass: person +objectClass: organizationalPerson +cn: ldap1 +sn: LDAP 1 +uid: ldap1 +userPassword: secret +mail: ldap1@teedy.io \ No newline at end of file diff --git a/pom.xml b/pom.xml index 27b996f1..b308da03 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,7 @@ 1.6.2 1.11.3 3.11.0 + 2.0.0-M17 9.4.17.v20190418 9.4.17.v20190418 @@ -459,6 +460,12 @@ ${org.postgresql.postgresql.version}
+ + org.apache.directory.server + apacheds-all + ${org.apache.directory.server.apacheds-all.version} + + com.twelvemonkeys.servlet