diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2019-09-27 16:25:53 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-10-07 20:21:06 +0200 |
commit | 274c9faaf56a985b6221b923ddf964b809b8aa9d (patch) | |
tree | 20c8e239c4f53e982520911db715c22738608ae4 /sonar-testing-ldap/src/main/java/org/sonar | |
parent | 69dd7210a6086f1e7687aee5ee2986a91cab0885 (diff) | |
download | sonarqube-274c9faaf56a985b6221b923ddf964b809b8aa9d.tar.gz sonarqube-274c9faaf56a985b6221b923ddf964b809b8aa9d.zip |
SONAR-12471 Embed LDAP authentication
Diffstat (limited to 'sonar-testing-ldap/src/main/java/org/sonar')
-rw-r--r-- | sonar-testing-ldap/src/main/java/org/sonar/ldap/ApacheDS.java | 240 | ||||
-rw-r--r-- | sonar-testing-ldap/src/main/java/org/sonar/ldap/package-info.java | 20 |
2 files changed, 260 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; |