aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-testing-ldap/src
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2019-09-27 16:25:53 +0200
committerSonarTech <sonartech@sonarsource.com>2019-10-07 20:21:06 +0200
commit274c9faaf56a985b6221b923ddf964b809b8aa9d (patch)
tree20c8e239c4f53e982520911db715c22738608ae4 /sonar-testing-ldap/src
parent69dd7210a6086f1e7687aee5ee2986a91cab0885 (diff)
downloadsonarqube-274c9faaf56a985b6221b923ddf964b809b8aa9d.tar.gz
sonarqube-274c9faaf56a985b6221b923ddf964b809b8aa9d.zip
SONAR-12471 Embed LDAP authentication
Diffstat (limited to 'sonar-testing-ldap/src')
-rw-r--r--sonar-testing-ldap/src/main/java/org/sonar/ldap/ApacheDS.java240
-rw-r--r--sonar-testing-ldap/src/main/java/org/sonar/ldap/package-info.java20
-rw-r--r--sonar-testing-ldap/src/test/java/org/sonar/ldap/ApacheDSTest.java38
-rw-r--r--sonar-testing-ldap/src/test/resources/change.ldif5
-rw-r--r--sonar-testing-ldap/src/test/resources/delete.ldif2
-rw-r--r--sonar-testing-ldap/src/test/resources/init.ldif9
-rw-r--r--sonar-testing-ldap/src/test/resources/logback-test.xml41
7 files changed, 355 insertions, 0 deletions
diff --git a/sonar-testing-ldap/src/main/java/org/sonar/ldap/ApacheDS.java b/sonar-testing-ldap/src/main/java/org/sonar/ldap/ApacheDS.java
new file mode 100644
index 00000000000..cfcb880dee0
--- /dev/null
+++ b/sonar-testing-ldap/src/main/java/org/sonar/ldap/ApacheDS.java
@@ -0,0 +1,240 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.ldap;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms;
+import org.apache.directory.api.ldap.model.entry.DefaultEntry;
+import org.apache.directory.api.ldap.model.entry.DefaultModification;
+import org.apache.directory.api.ldap.model.entry.ModificationOperation;
+import org.apache.directory.api.ldap.model.exception.LdapOperationException;
+import org.apache.directory.api.ldap.model.ldif.ChangeType;
+import org.apache.directory.api.ldap.model.ldif.LdifEntry;
+import org.apache.directory.api.ldap.model.ldif.LdifReader;
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.directory.api.util.FileUtils;
+import org.apache.directory.server.core.api.CoreSession;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.InstanceLayout;
+import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory;
+import org.apache.directory.server.core.kerberos.KeyDerivationInterceptor;
+import org.apache.directory.server.core.partition.impl.avl.AvlPartition;
+import org.apache.directory.server.kerberos.KerberosConfig;
+import org.apache.directory.server.kerberos.kdc.KdcServer;
+import org.apache.directory.server.ldap.LdapServer;
+import org.apache.directory.server.ldap.handlers.sasl.MechanismHandler;
+import org.apache.directory.server.ldap.handlers.sasl.cramMD5.CramMd5MechanismHandler;
+import org.apache.directory.server.ldap.handlers.sasl.digestMD5.DigestMd5MechanismHandler;
+import org.apache.directory.server.ldap.handlers.sasl.gssapi.GssapiMechanismHandler;
+import org.apache.directory.server.ldap.handlers.sasl.plain.PlainMechanismHandler;
+import org.apache.directory.server.protocol.shared.transport.TcpTransport;
+import org.apache.directory.server.protocol.shared.transport.UdpTransport;
+import org.apache.directory.server.xdbm.impl.avl.AvlIndex;
+import org.apache.mina.util.AvailablePortFinder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class ApacheDS {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ApacheDS.class);
+
+ private final String realm;
+ private final String baseDn;
+
+ private DirectoryService directoryService;
+ private LdapServer ldapServer;
+ private KdcServer kdcServer;
+
+ private ApacheDS(String realm, String baseDn) {
+ this.realm = realm;
+ this.baseDn = baseDn;
+ ldapServer = new LdapServer();
+ }
+
+ public static ApacheDS start(String realm, String baseDn, String workDir, Integer port) throws Exception {
+ return new ApacheDS(realm, baseDn)
+ .startDirectoryService(workDir)
+ .startKdcServer()
+ .startLdapServer(port == null ? AvailablePortFinder.getNextAvailable(1024) : port)
+ .activateNis();
+ }
+
+ public static ApacheDS start(String realm, String baseDn, String workDir) throws Exception {
+ return start(realm, baseDn, workDir + realm, null);
+ }
+
+ public static ApacheDS start(String realm, String baseDn) throws Exception {
+ return start(realm, baseDn, "target/ldap-work/" + realm, null);
+ }
+
+ public void stop() {
+ try {
+ kdcServer.stop();
+ kdcServer = null;
+ ldapServer.stop();
+ ldapServer = null;
+ directoryService.shutdown();
+ directoryService = null;
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public String getUrl() {
+ return "ldap://localhost:" + ldapServer.getPort();
+ }
+
+ /**
+ * Stream will be closed automatically.
+ */
+ public void importLdif(InputStream is) throws Exception {
+ try (LdifReader reader = new LdifReader(is)) {
+ CoreSession coreSession = directoryService.getAdminSession();
+ // see LdifFileLoader
+ for (LdifEntry ldifEntry : reader) {
+ String ldif = ldifEntry.toString();
+ LOG.info(ldif);
+ if (ChangeType.Add == ldifEntry.getChangeType() || /* assume "add" by default */ ChangeType.None == ldifEntry.getChangeType()) {
+ coreSession.add(new DefaultEntry(coreSession.getDirectoryService().getSchemaManager(), ldifEntry.getEntry()));
+ } else if (ChangeType.Modify == ldifEntry.getChangeType()) {
+ coreSession.modify(ldifEntry.getDn(), ldifEntry.getModifications());
+ } else if (ChangeType.Delete == ldifEntry.getChangeType()) {
+ coreSession.delete(ldifEntry.getDn());
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ }
+
+ public void disableAnonymousAccess() {
+ directoryService.setAllowAnonymousAccess(false);
+ }
+
+ public void enableAnonymousAccess() {
+ directoryService.setAllowAnonymousAccess(true);
+ }
+
+ private ApacheDS startDirectoryService(String workDirStr) throws Exception {
+ DefaultDirectoryServiceFactory factory = new DefaultDirectoryServiceFactory();
+ factory.init(realm);
+
+ directoryService = factory.getDirectoryService();
+ directoryService.getChangeLog().setEnabled(false);
+ directoryService.setShutdownHookEnabled(false);
+ directoryService.setAllowAnonymousAccess(true);
+
+ File workDir = new File(workDirStr);
+ if (workDir.exists()) {
+ FileUtils.deleteDirectory(workDir);
+ }
+ InstanceLayout instanceLayout = new InstanceLayout(workDir);
+ directoryService.setInstanceLayout(instanceLayout);
+
+ AvlPartition partition = new AvlPartition(directoryService.getSchemaManager());
+ partition.setId("Test");
+ partition.setSuffixDn(new Dn(directoryService.getSchemaManager(), baseDn));
+ partition.addIndexedAttributes(
+ new AvlIndex<>("ou"),
+ new AvlIndex<>("uid"),
+ new AvlIndex<>("dc"),
+ new AvlIndex<>("objectClass"));
+ partition.initialize();
+ directoryService.addPartition(partition);
+ directoryService.addLast(new KeyDerivationInterceptor());
+
+ directoryService.shutdown();
+ directoryService.startup();
+
+ return this;
+ }
+
+ private ApacheDS startLdapServer(int port) throws Exception {
+ ldapServer.setTransports(new TcpTransport(port));
+ ldapServer.setDirectoryService(directoryService);
+
+ // Setup SASL mechanisms
+ Map<String, MechanismHandler> mechanismHandlerMap = new HashMap<>();
+ mechanismHandlerMap.put(SupportedSaslMechanisms.PLAIN, new PlainMechanismHandler());
+ mechanismHandlerMap.put(SupportedSaslMechanisms.CRAM_MD5, new CramMd5MechanismHandler());
+ mechanismHandlerMap.put(SupportedSaslMechanisms.DIGEST_MD5, new DigestMd5MechanismHandler());
+ mechanismHandlerMap.put(SupportedSaslMechanisms.GSSAPI, new GssapiMechanismHandler());
+ ldapServer.setSaslMechanismHandlers(mechanismHandlerMap);
+
+ ldapServer.setSaslHost("localhost");
+ ldapServer.setSaslRealms(Collections.singletonList(realm));
+ // TODO ldapServer.setSaslPrincipal();
+ // The base DN containing users that can be SASL authenticated.
+ ldapServer.setSearchBaseDn(baseDn);
+
+ ldapServer.start();
+
+ return this;
+ }
+
+ private ApacheDS startKdcServer() throws IOException, LdapOperationException {
+ int port = AvailablePortFinder.getNextAvailable(6088);
+
+ KerberosConfig kdcConfig = new KerberosConfig();
+ kdcConfig.setServicePrincipal("krbtgt/EXAMPLE.ORG@EXAMPLE.ORG");
+ kdcConfig.setPrimaryRealm("EXAMPLE.ORG");
+ kdcConfig.setPaEncTimestampRequired(false);
+
+ kdcServer = new KdcServer(kdcConfig);
+ kdcServer.setSearchBaseDn("dc=example,dc=org");
+ kdcServer.addTransports(new UdpTransport("localhost", port));
+ kdcServer.setDirectoryService(directoryService);
+ kdcServer.start();
+
+ FileUtils.writeStringToFile(new File("target/krb5.conf"), ""
+ + "[libdefaults]\n"
+ + " default_realm = EXAMPLE.ORG\n"
+ + "\n"
+ + "[realms]\n"
+ + " EXAMPLE.ORG = {\n"
+ + " kdc = localhost:" + port + "\n"
+ + " }\n"
+ + "\n"
+ + "[domain_realm]\n"
+ + " .example.org = EXAMPLE.ORG\n"
+ + " example.org = EXAMPLE.ORG\n",
+ StandardCharsets.UTF_8.name());
+
+ return this;
+ }
+
+ /**
+ * This seems to be required for objectClass posixGroup.
+ */
+ private ApacheDS activateNis() throws Exception {
+ directoryService.getAdminSession().modify(
+ new Dn("cn=nis,ou=schema"),
+ new DefaultModification(ModificationOperation.REPLACE_ATTRIBUTE, "m-disabled", "FALSE"));
+ return this;
+ }
+
+}
diff --git a/sonar-testing-ldap/src/main/java/org/sonar/ldap/package-info.java b/sonar-testing-ldap/src/main/java/org/sonar/ldap/package-info.java
new file mode 100644
index 00000000000..50daadb290a
--- /dev/null
+++ b/sonar-testing-ldap/src/main/java/org/sonar/ldap/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.ldap;
diff --git a/sonar-testing-ldap/src/test/java/org/sonar/ldap/ApacheDSTest.java b/sonar-testing-ldap/src/test/java/org/sonar/ldap/ApacheDSTest.java
new file mode 100644
index 00000000000..a193f7bb7e3
--- /dev/null
+++ b/sonar-testing-ldap/src/test/java/org/sonar/ldap/ApacheDSTest.java
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.ldap;
+
+import org.junit.Test;
+
+public class ApacheDSTest {
+
+ @Test
+ public void start_and_stop_apache_server() throws Exception {
+ ApacheDS apacheDS = ApacheDS.start("example.org", "dc=example,dc=org");
+ apacheDS.importLdif(ApacheDS.class.getResourceAsStream("/init.ldif"));
+ apacheDS.importLdif(ApacheDS.class.getResourceAsStream("/change.ldif"));
+ apacheDS.importLdif(ApacheDS.class.getResourceAsStream("/delete.ldif"));
+ apacheDS.disableAnonymousAccess();
+ apacheDS.enableAnonymousAccess();
+ apacheDS.stop();
+ }
+
+}
diff --git a/sonar-testing-ldap/src/test/resources/change.ldif b/sonar-testing-ldap/src/test/resources/change.ldif
new file mode 100644
index 00000000000..903ebc0e1f7
--- /dev/null
+++ b/sonar-testing-ldap/src/test/resources/change.ldif
@@ -0,0 +1,5 @@
+dn: cn=Evgeny Mandrikov,dc=example,dc=org
+changetype: modify
+replace: userpassword
+userpassword: 54321
+-
diff --git a/sonar-testing-ldap/src/test/resources/delete.ldif b/sonar-testing-ldap/src/test/resources/delete.ldif
new file mode 100644
index 00000000000..586472648cd
--- /dev/null
+++ b/sonar-testing-ldap/src/test/resources/delete.ldif
@@ -0,0 +1,2 @@
+dn: cn=Evgeny Mandrikov,dc=example,dc=org
+changetype: delete
diff --git a/sonar-testing-ldap/src/test/resources/init.ldif b/sonar-testing-ldap/src/test/resources/init.ldif
new file mode 100644
index 00000000000..4c3ce81f0b4
--- /dev/null
+++ b/sonar-testing-ldap/src/test/resources/init.ldif
@@ -0,0 +1,9 @@
+dn: dc=example,dc=org
+objectClass: domain
+objectClass: top
+dc: example
+
+dn: cn=Evgeny Mandrikov,dc=example,dc=org
+objectClass: inetOrgPerson
+cn: Evgeny Mandrikov
+sn: Mandrikov
diff --git a/sonar-testing-ldap/src/test/resources/logback-test.xml b/sonar-testing-ldap/src/test/resources/logback-test.xml
new file mode 100644
index 00000000000..f0b4f642ca5
--- /dev/null
+++ b/sonar-testing-ldap/src/test/resources/logback-test.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ ~ SonarQube
+ ~ Copyright (C) 2009-2019 SonarSource SA
+ ~ mailto:info AT sonarsource DOT com
+ ~
+ ~ This program is free software; you can redistribute it and/or
+ ~ modify it under the terms of the GNU Lesser General Public
+ ~ License as published by the Free Software Foundation; either
+ ~ version 3 of the License, or (at your option) any later version.
+ ~
+ ~ This program is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ ~ Lesser General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU Lesser General Public License
+ ~ along with this program; if not, write to the Free Software Foundation,
+ ~ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ -->
+
+<configuration>
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>
+ %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n
+ </pattern>
+ </encoder>
+ </appender>
+
+ <logger name="org.apache">
+ <level value="ERROR"/>
+ </logger>
+
+ <root>
+ <level value="INFO"/>
+ <appender-ref ref="STDOUT"/>
+ </root>
+
+</configuration>