aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-auth-ldap/src/test
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 /server/sonar-auth-ldap/src/test
parent69dd7210a6086f1e7687aee5ee2986a91cab0885 (diff)
downloadsonarqube-274c9faaf56a985b6221b923ddf964b809b8aa9d.tar.gz
sonarqube-274c9faaf56a985b6221b923ddf964b809b8aa9d.zip
SONAR-12471 Embed LDAP authentication
Diffstat (limited to 'server/sonar-auth-ldap/src/test')
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/CallbackHandlerImplTest.java48
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/ContextHelperTest.java53
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/KerberosTest.java84
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAuthenticatorTest.java129
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAutoDiscoveryWarningLogTest.java100
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAutodiscoveryTest.java93
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapGroupMappingTest.java65
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapGroupsProviderTest.java149
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapModuleTest.java38
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapRealmTest.java82
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapReferralsTest.java69
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSearchTest.java118
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSettingsFactory.java95
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSettingsManagerTest.java201
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapUserMappingTest.java76
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapUsersProviderTest.java77
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/server/ApacheDS.java234
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/server/LdapServer.java68
-rw-r--r--server/sonar-auth-ldap/src/test/resources/conf/krb5.conf20
-rw-r--r--server/sonar-auth-ldap/src/test/resources/conf/sasl_mech.properties23
-rw-r--r--server/sonar-auth-ldap/src/test/resources/krb.ldif55
-rw-r--r--server/sonar-auth-ldap/src/test/resources/logback-test.xml45
-rw-r--r--server/sonar-auth-ldap/src/test/resources/static-groups.example.org.ldif81
-rw-r--r--server/sonar-auth-ldap/src/test/resources/users-apacheds.ldif88
-rw-r--r--server/sonar-auth-ldap/src/test/resources/users.example.org.ldif98
-rw-r--r--server/sonar-auth-ldap/src/test/resources/users.infosupport.com.ldif98
26 files changed, 2287 insertions, 0 deletions
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/CallbackHandlerImplTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/CallbackHandlerImplTest.java
new file mode 100644
index 00000000000..c0acc7801c0
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/CallbackHandlerImplTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.auth.ldap;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class CallbackHandlerImplTest {
+
+ @Test
+ public void test() throws Exception {
+ NameCallback nameCallback = new NameCallback("username");
+ PasswordCallback passwordCallback = new PasswordCallback("password", false);
+ new CallbackHandlerImpl("tester", "secret").handle(new Callback[] {nameCallback, passwordCallback});
+
+ assertThat(nameCallback.getName()).isEqualTo("tester");
+ assertThat(passwordCallback.getPassword()).isEqualTo("secret".toCharArray());
+ }
+
+ @Test(expected = UnsupportedCallbackException.class)
+ public void unsupportedCallback() throws Exception {
+ new CallbackHandlerImpl("tester", "secret").handle(new Callback[] {mock(Callback.class)});
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/ContextHelperTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/ContextHelperTest.java
new file mode 100644
index 00000000000..73029df57cd
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/ContextHelperTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.auth.ldap;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import org.junit.Test;
+
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+
+public class ContextHelperTest {
+
+ @Test
+ public void shouldSwallow() throws Exception {
+ Context context = mock(Context.class);
+ doThrow(new NamingException()).when(context).close();
+ ContextHelper.close(context, true);
+ ContextHelper.closeQuietly(context);
+ }
+
+ @Test(expected = NamingException.class)
+ public void shouldNotSwallow() throws Exception {
+ Context context = mock(Context.class);
+ doThrow(new NamingException()).when(context).close();
+ ContextHelper.close(context, false);
+ }
+
+ @Test
+ public void normal() throws NamingException {
+ ContextHelper.close(null, true);
+ ContextHelper.closeQuietly(null);
+ ContextHelper.close(mock(Context.class), true);
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/KerberosTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/KerberosTest.java
new file mode 100644
index 00000000000..c49205a83b5
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/KerberosTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.auth.ldap;
+
+import java.io.File;
+import javax.servlet.http.HttpServletRequest;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.security.Authenticator;
+import org.sonar.api.security.ExternalGroupsProvider;
+import org.sonar.auth.ldap.server.LdapServer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class KerberosTest {
+
+ static {
+ System.setProperty("java.security.krb5.conf", new File("target/krb5.conf").getAbsolutePath());
+ }
+
+ @ClassRule
+ public static LdapServer server = new LdapServer("/krb.ldif");
+
+ @Test
+ public void test() {
+ MapSettings settings = configure();
+ LdapRealm ldapRealm = new LdapRealm(new LdapSettingsManager(settings, new LdapAutodiscovery()));
+
+ ldapRealm.init();
+
+ assertThat(ldapRealm.doGetAuthenticator().doAuthenticate(new Authenticator.Context("Godin@EXAMPLE.ORG", "wrong_user_password", Mockito.mock(HttpServletRequest.class))))
+ .isFalse();
+ assertThat(ldapRealm.doGetAuthenticator().doAuthenticate(new Authenticator.Context("Godin@EXAMPLE.ORG", "user_password", Mockito.mock(HttpServletRequest.class)))).isTrue();
+ // Using default realm from krb5.conf:
+ assertThat(ldapRealm.doGetAuthenticator().doAuthenticate(new Authenticator.Context("Godin", "user_password", Mockito.mock(HttpServletRequest.class)))).isTrue();
+
+ assertThat(ldapRealm.getGroupsProvider().doGetGroups(new ExternalGroupsProvider.Context("godin", Mockito.mock(HttpServletRequest.class)))).containsOnly("sonar-users");
+ }
+
+ @Test
+ public void wrong_bind_password() {
+ MapSettings settings = configure()
+ .setProperty("ldap.bindPassword", "wrong_bind_password");
+ LdapRealm ldapRealm = new LdapRealm(new LdapSettingsManager(settings, new LdapAutodiscovery()));
+ try {
+ ldapRealm.init();
+ Assert.fail();
+ } catch (LdapException e) {
+ assertThat(e.getMessage()).isEqualTo("Unable to open LDAP connection");
+ }
+ }
+
+ private static MapSettings configure() {
+ return new MapSettings()
+ .setProperty("ldap.url", server.getUrl())
+ .setProperty("ldap.authentication", LdapContextFactory.AUTH_METHOD_GSSAPI)
+ .setProperty("ldap.bindDn", "SonarQube@EXAMPLE.ORG")
+ .setProperty("ldap.bindPassword", "bind_password")
+ .setProperty("ldap.user.baseDn", "ou=users,dc=example,dc=org")
+ .setProperty("ldap.group.baseDn", "ou=groups,dc=example,dc=org")
+ .setProperty("ldap.group.request", "(&(objectClass=groupOfUniqueNames)(uniqueMember={dn}))");
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAuthenticatorTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAuthenticatorTest.java
new file mode 100644
index 00000000000..da91691ee46
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAuthenticatorTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.auth.ldap;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.auth.ldap.server.LdapServer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LdapAuthenticatorTest {
+
+ /**
+ * A reference to the original ldif file
+ */
+ public static final String USERS_EXAMPLE_ORG_LDIF = "/users.example.org.ldif";
+ /**
+ * A reference to an aditional ldif file.
+ */
+ public static final String USERS_INFOSUPPORT_COM_LDIF = "/users.infosupport.com.ldif";
+ @ClassRule
+ public static LdapServer exampleServer = new LdapServer(USERS_EXAMPLE_ORG_LDIF);
+ @ClassRule
+ public static LdapServer infosupportServer = new LdapServer(USERS_INFOSUPPORT_COM_LDIF, "infosupport.com", "dc=infosupport,dc=com");
+
+ @Test
+ public void testNoConnection() {
+ exampleServer.disableAnonymousAccess();
+ try {
+ LdapSettingsManager settingsManager = new LdapSettingsManager(LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_SIMPLE),
+ new LdapAutodiscovery());
+ LdapAuthenticator authenticator = new LdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
+ authenticator.authenticate("godin", "secret1");
+ } finally {
+ exampleServer.enableAnonymousAccess();
+ }
+ }
+
+ @Test
+ public void testSimple() {
+ LdapSettingsManager settingsManager = new LdapSettingsManager(LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_SIMPLE),
+ new LdapAutodiscovery());
+ LdapAuthenticator authenticator = new LdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
+
+ assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
+ assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
+
+ assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
+ assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
+
+ assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
+ // SONARPLUGINS-2493
+ assertThat(authenticator.authenticate("godin", "")).isFalse();
+ assertThat(authenticator.authenticate("godin", null)).isFalse();
+ }
+
+ @Test
+ public void testSimpleMultiLdap() {
+ LdapSettingsManager settingsManager = new LdapSettingsManager(
+ LdapSettingsFactory.generateAuthenticationSettings(exampleServer, infosupportServer, LdapContextFactory.AUTH_METHOD_SIMPLE), new LdapAutodiscovery());
+ LdapAuthenticator authenticator = new LdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
+
+ assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
+ assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
+
+ assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
+ assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
+
+ assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
+ // SONARPLUGINS-2493
+ assertThat(authenticator.authenticate("godin", "")).isFalse();
+ assertThat(authenticator.authenticate("godin", null)).isFalse();
+
+ // SONARPLUGINS-2793
+ assertThat(authenticator.authenticate("robby", "secret1")).isTrue();
+ assertThat(authenticator.authenticate("robby", "wrong")).isFalse();
+ }
+
+ @Test
+ public void testSasl() {
+ LdapSettingsManager settingsManager = new LdapSettingsManager(LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_CRAM_MD5),
+ new LdapAutodiscovery());
+ LdapAuthenticator authenticator = new LdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
+
+ assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
+ assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
+
+ assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
+ assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
+
+ assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
+ }
+
+ @Test
+ public void testSaslMultipleLdap() {
+ LdapSettingsManager settingsManager = new LdapSettingsManager(
+ LdapSettingsFactory.generateAuthenticationSettings(exampleServer, infosupportServer, LdapContextFactory.AUTH_METHOD_CRAM_MD5), new LdapAutodiscovery());
+ LdapAuthenticator authenticator = new LdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
+
+ assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
+ assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
+
+ assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
+ assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
+
+ assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
+
+ assertThat(authenticator.authenticate("robby", "secret1")).isTrue();
+ assertThat(authenticator.authenticate("robby", "wrong")).isFalse();
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAutoDiscoveryWarningLogTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAutoDiscoveryWarningLogTest.java
new file mode 100644
index 00000000000..d002577a44c
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAutoDiscoveryWarningLogTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.auth.ldap;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.auth.ldap.server.ApacheDS;
+import org.sonar.auth.ldap.server.LdapServer;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class LdapAutoDiscoveryWarningLogTest {
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @ClassRule
+ public static LdapServer server = new LdapServer("/users.example.org.ldif");
+
+ @Test
+ public void does_not_display_log_when_not_using_auto_discovery() {
+ MapSettings settings = new MapSettings()
+ .setProperty("ldap.url", server.getUrl());
+ LdapRealm realm = new LdapRealm(new LdapSettingsManager(settings, new LdapAutodiscovery()));
+ assertThat(realm.getName()).isEqualTo("LDAP");
+
+ realm.init();
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty();
+ }
+
+ @Test
+ public void display_warning_log_when_using_auto_discovery_to_detect_server_url() {
+ LdapAutodiscovery ldapAutodiscovery = mock(LdapAutodiscovery.class);
+ when(ldapAutodiscovery.getLdapServers("example.org")).thenReturn(singletonList(new LdapAutodiscovery.LdapSrvRecord(server.getUrl(), 1, 1)));
+ // ldap.url setting is not set
+ LdapRealm realm = new LdapRealm(new LdapSettingsManager(new MapSettings().setProperty("ldap.realm", "example.org"),
+ ldapAutodiscovery));
+
+ realm.init();
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains("Auto-discovery feature is deprecated, please use 'ldap.url' to specify LDAP url");
+ }
+
+ @Test
+ public void display_warning_log_when_using_auto_discovery_to_detect_user_baseDn_on_single_server() {
+ // ldap.user.baseDn setting is not set
+ MapSettings settings = new MapSettings().setProperty("ldap.url", server.getUrl()).setProperty("ldap.realm", "example.org");
+ LdapRealm realm = new LdapRealm(new LdapSettingsManager(settings, new LdapAutodiscovery()));
+
+ realm.init();
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly("Auto-discovery feature is deprecated, please use 'ldap.user.baseDn' to specify user search dn");
+ }
+
+ @Test
+ public void display_warning_log_when_using_auto_discovery_to_detect_user_baseDn_on_multiple_servers() throws Exception {
+ ApacheDS server2 = ApacheDS.start("example.org", "dc=example,dc=org", "target/ldap-work2/");
+ server2.importLdif(LdapAutoDiscoveryWarningLogTest.class.getResourceAsStream("/users.example.org.ldif"));
+ MapSettings settings = new MapSettings()
+ .setProperty("ldap.servers", "example,infosupport")
+ // ldap.XXX.user.baseDn settings are not set on both servers
+ .setProperty("ldap.example.url", server.getUrl())
+ .setProperty("ldap.example.realm", "example.org")
+ .setProperty("ldap.infosupport.url", server2.getUrl())
+ .setProperty("ldap.infosupport.realm", "infosupport.org");
+ LdapRealm realm = new LdapRealm(new LdapSettingsManager(settings, new LdapAutodiscovery()));
+
+ realm.init();
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly(
+ "Auto-discovery feature is deprecated, please use 'ldap.example.user.baseDn' to specify user search dn",
+ "Auto-discovery feature is deprecated, please use 'ldap.infosupport.user.baseDn' to specify user search dn");
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAutodiscoveryTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAutodiscoveryTest.java
new file mode 100644
index 00000000000..989a3305fd7
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAutodiscoveryTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.auth.ldap;
+
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.auth.ldap.LdapAutodiscovery.LdapSrvRecord;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class LdapAutodiscoveryTest {
+
+ @Test
+ public void testGetDnsDomain() {
+ assertThat(LdapAutodiscovery.getDnsDomainName("localhost")).isNull();
+ assertThat(LdapAutodiscovery.getDnsDomainName("godin.example.org")).isEqualTo("example.org");
+ assertThat(LdapAutodiscovery.getDnsDomainName("godin.usr.example.org")).isEqualTo("usr.example.org");
+ }
+
+ @Test
+ public void testGetDnsDomainWithoutParameter() {
+ try {
+ LdapAutodiscovery.getDnsDomainName();
+ } catch (UnknownHostException e) {
+ fail(e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetDnsDomainDn() {
+ assertThat(LdapAutodiscovery.getDnsDomainDn("example.org")).isEqualTo("dc=example,dc=org");
+ }
+
+ @Test
+ public void testEqualsAndHashCode() {
+ assertThat(new LdapSrvRecord("http://foo:389", 1, 1)).isEqualTo(new LdapSrvRecord("http://foo:389", 2, 0));
+ assertThat(new LdapSrvRecord("http://foo:389", 1, 1)).isNotEqualTo(new LdapSrvRecord("http://foo:388", 1, 1));
+
+ assertThat(new LdapSrvRecord("http://foo:389", 1, 1).hashCode()).isEqualTo(new LdapSrvRecord("http://foo:389", 1, 1).hashCode());
+ }
+
+ @Test
+ public void testGetLdapServer() throws NamingException {
+ DirContext context = mock(DirContext.class);
+ Attributes attributes = mock(Attributes.class);
+ Attribute attribute = mock(Attribute.class);
+ NamingEnumeration namingEnumeration = mock(NamingEnumeration.class);
+
+ when(context.getAttributes(Mockito.anyString(), Mockito.<String[]>anyObject())).thenReturn(attributes);
+ when(attributes.get(Mockito.eq("srv"))).thenReturn(attribute);
+ when(attribute.getAll()).thenReturn(namingEnumeration);
+ when(namingEnumeration.hasMore()).thenReturn(true, true, true, true, true, false);
+ when(namingEnumeration.next())
+ .thenReturn("10 40 389 ldap5.example.org.")
+ .thenReturn("0 10 389 ldap3.example.org")
+ .thenReturn("0 60 389 ldap1.example.org")
+ .thenReturn("0 30 389 ldap2.example.org")
+ .thenReturn("10 60 389 ldap4.example.org");
+
+ assertThat(new LdapAutodiscovery().getLdapServers(context, "example.org.")).extracting("serverUrl")
+ .isEqualTo(
+ Arrays.asList("ldap://ldap1.example.org:389", "ldap://ldap2.example.org:389", "ldap://ldap3.example.org:389", "ldap://ldap4.example.org:389",
+ "ldap://ldap5.example.org:389"));
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapGroupMappingTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapGroupMappingTest.java
new file mode 100644
index 00000000000..5a9acab0469
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapGroupMappingTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.auth.ldap;
+
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LdapGroupMappingTest {
+
+ @Test
+ public void defaults() {
+ LdapGroupMapping groupMapping = new LdapGroupMapping(new MapSettings(), "ldap");
+
+ assertThat(groupMapping.getBaseDn()).isNull();
+ assertThat(groupMapping.getIdAttribute()).isEqualTo("cn");
+ assertThat(groupMapping.getRequest()).isEqualTo("(&(objectClass=groupOfUniqueNames)(uniqueMember={0}))");
+ assertThat(groupMapping.getRequiredUserAttributes()).isEqualTo(new String[] {"dn"});
+
+ assertThat(groupMapping.toString()).isEqualTo("LdapGroupMapping{" +
+ "baseDn=null," +
+ " idAttribute=cn," +
+ " requiredUserAttributes=[dn]," +
+ " request=(&(objectClass=groupOfUniqueNames)(uniqueMember={0}))}");
+ }
+
+ @Test
+ public void backward_compatibility() {
+ MapSettings settings = new MapSettings()
+ .setProperty("ldap.group.objectClass", "group")
+ .setProperty("ldap.group.memberAttribute", "member");
+ LdapGroupMapping groupMapping = new LdapGroupMapping(settings, "ldap");
+
+ assertThat(groupMapping.getRequest()).isEqualTo("(&(objectClass=group)(member={0}))");
+ }
+
+ @Test
+ public void custom_request() {
+ MapSettings settings = new MapSettings()
+ .setProperty("ldap.group.request", "(&(|(objectClass=posixGroup)(objectClass=groupOfUniqueNames))(|(memberUid={uid})(uniqueMember={dn})))");
+ LdapGroupMapping groupMapping = new LdapGroupMapping(settings, "ldap");
+
+ assertThat(groupMapping.getRequest()).isEqualTo("(&(|(objectClass=posixGroup)(objectClass=groupOfUniqueNames))(|(memberUid={0})(uniqueMember={1})))");
+ assertThat(groupMapping.getRequiredUserAttributes()).isEqualTo(new String[] {"uid", "dn"});
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapGroupsProviderTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapGroupsProviderTest.java
new file mode 100644
index 00000000000..a2aab4bc3c0
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapGroupsProviderTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.auth.ldap;
+
+import java.util.Collection;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.auth.ldap.server.LdapServer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LdapGroupsProviderTest {
+
+ /**
+ * A reference to the original ldif file
+ */
+ public static final String USERS_EXAMPLE_ORG_LDIF = "/users.example.org.ldif";
+ /**
+ * A reference to an aditional ldif file.
+ */
+ public static final String USERS_INFOSUPPORT_COM_LDIF = "/users.infosupport.com.ldif";
+
+ @ClassRule
+ public static LdapServer exampleServer = new LdapServer(USERS_EXAMPLE_ORG_LDIF);
+ @ClassRule
+ public static LdapServer infosupportServer = new LdapServer(USERS_INFOSUPPORT_COM_LDIF, "infosupport.com", "dc=infosupport,dc=com");
+
+ @Test
+ public void defaults() throws Exception {
+ MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, null);
+
+ LdapSettingsManager settingsManager = new LdapSettingsManager(settings, new LdapAutodiscovery());
+ LdapGroupsProvider groupsProvider = new LdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
+ Collection<String> groups;
+
+ groups = groupsProvider.getGroups("tester");
+ assertThat(groups).containsOnly("sonar-users");
+
+ groups = groupsProvider.getGroups("godin");
+ assertThat(groups).containsOnly("sonar-users", "sonar-developers");
+
+ groups = groupsProvider.getGroups("notfound");
+ assertThat(groups).isEmpty();
+ }
+
+ @Test
+ public void defaultsMultipleLdap() {
+ MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
+
+ LdapSettingsManager settingsManager = new LdapSettingsManager(settings, new LdapAutodiscovery());
+ LdapGroupsProvider groupsProvider = new LdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
+
+ Collection<String> groups;
+
+ groups = groupsProvider.getGroups("tester");
+ assertThat(groups).containsOnly("sonar-users");
+
+ groups = groupsProvider.getGroups("godin");
+ assertThat(groups).containsOnly("sonar-users", "sonar-developers");
+
+ groups = groupsProvider.getGroups("notfound");
+ assertThat(groups).isEmpty();
+
+ groups = groupsProvider.getGroups("testerInfo");
+ assertThat(groups).containsOnly("sonar-users");
+
+ groups = groupsProvider.getGroups("robby");
+ assertThat(groups).containsOnly("sonar-users", "sonar-developers");
+ }
+
+ @Test
+ public void posix() {
+ MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, null);
+ settings.setProperty("ldap.group.request", "(&(objectClass=posixGroup)(memberUid={uid}))");
+ LdapSettingsManager settingsManager = new LdapSettingsManager(settings, new LdapAutodiscovery());
+ LdapGroupsProvider groupsProvider = new LdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
+
+ Collection<String> groups;
+
+ groups = groupsProvider.getGroups("godin");
+ assertThat(groups).containsOnly("linux-users");
+ }
+
+ @Test
+ public void posixMultipleLdap() {
+ MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
+ settings.setProperty("ldap.example.group.request", "(&(objectClass=posixGroup)(memberUid={uid}))");
+ settings.setProperty("ldap.infosupport.group.request", "(&(objectClass=posixGroup)(memberUid={uid}))");
+ LdapSettingsManager settingsManager = new LdapSettingsManager(settings, new LdapAutodiscovery());
+ LdapGroupsProvider groupsProvider = new LdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
+
+ Collection<String> groups;
+
+ groups = groupsProvider.getGroups("godin");
+ assertThat(groups).containsOnly("linux-users");
+
+ groups = groupsProvider.getGroups("robby");
+ assertThat(groups).containsOnly("linux-users");
+ }
+
+ @Test
+ public void mixed() {
+ MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
+ settings.setProperty("ldap.example.group.request", "(&(|(objectClass=groupOfUniqueNames)(objectClass=posixGroup))(|(uniqueMember={dn})(memberUid={uid})))");
+ LdapSettingsManager settingsManager = new LdapSettingsManager(settings, new LdapAutodiscovery());
+ LdapGroupsProvider groupsProvider = new LdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
+
+ Collection<String> groups;
+
+ groups = groupsProvider.getGroups("godin");
+ assertThat(groups).containsOnly("sonar-users", "sonar-developers", "linux-users");
+ }
+
+ @Test
+ public void mixedMultipleLdap() {
+ MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
+ settings.setProperty("ldap.example.group.request", "(&(|(objectClass=groupOfUniqueNames)(objectClass=posixGroup))(|(uniqueMember={dn})(memberUid={uid})))");
+ settings.setProperty("ldap.infosupport.group.request", "(&(|(objectClass=groupOfUniqueNames)(objectClass=posixGroup))(|(uniqueMember={dn})(memberUid={uid})))");
+ LdapSettingsManager settingsManager = new LdapSettingsManager(settings, new LdapAutodiscovery());
+ LdapGroupsProvider groupsProvider = new LdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
+
+ Collection<String> groups;
+
+ groups = groupsProvider.getGroups("godin");
+ assertThat(groups).containsOnly("sonar-users", "sonar-developers", "linux-users");
+
+ groups = groupsProvider.getGroups("robby");
+ assertThat(groups).containsOnly("sonar-users", "sonar-developers", "linux-users");
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapModuleTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapModuleTest.java
new file mode 100644
index 00000000000..c231f765af2
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapModuleTest.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.auth.ldap;
+
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
+
+public class LdapModuleTest {
+
+ @Test
+ public void verify_count_of_added_components() {
+ ComponentContainer container = new ComponentContainer();
+ new LdapModule().configure(container);
+ assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 3);
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapRealmTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapRealmTest.java
new file mode 100644
index 00000000000..232269a4083
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapRealmTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.auth.ldap;
+
+import javax.servlet.http.HttpServletRequest;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.security.ExternalGroupsProvider;
+import org.sonar.api.security.ExternalUsersProvider;
+import org.sonar.auth.ldap.server.LdapServer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class LdapRealmTest {
+
+ @ClassRule
+ public static LdapServer server = new LdapServer("/users.example.org.ldif");
+
+ @Test
+ public void normal() {
+ MapSettings settings = new MapSettings()
+ .setProperty("ldap.url", server.getUrl());
+ LdapRealm realm = new LdapRealm(new LdapSettingsManager(settings, new LdapAutodiscovery()));
+ assertThat(realm.getName()).isEqualTo("LDAP");
+ realm.init();
+ assertThat(realm.doGetAuthenticator()).isInstanceOf(LdapAuthenticator.class);
+ assertThat(realm.getUsersProvider()).isInstanceOf(ExternalUsersProvider.class).isInstanceOf(LdapUsersProvider.class);
+ assertThat(realm.getGroupsProvider()).isNull();
+ }
+
+ @Test
+ public void noConnection() {
+ MapSettings settings = new MapSettings()
+ .setProperty("ldap.url", "ldap://no-such-host")
+ .setProperty("ldap.group.baseDn", "cn=groups,dc=example,dc=org");
+ LdapRealm realm = new LdapRealm(new LdapSettingsManager(settings, new LdapAutodiscovery()));
+ assertThat(realm.getName()).isEqualTo("LDAP");
+ try {
+ realm.init();
+ fail("Since there is no connection, the init method has to throw an exception.");
+ } catch (LdapException e) {
+ assertThat(e).hasMessage("Unable to open LDAP connection");
+ }
+ assertThat(realm.doGetAuthenticator()).isInstanceOf(LdapAuthenticator.class);
+ assertThat(realm.getUsersProvider()).isInstanceOf(ExternalUsersProvider.class).isInstanceOf(LdapUsersProvider.class);
+ assertThat(realm.getGroupsProvider()).isInstanceOf(ExternalGroupsProvider.class).isInstanceOf(LdapGroupsProvider.class);
+
+ try {
+ realm.getUsersProvider().doGetUserDetails(new ExternalUsersProvider.Context("tester", Mockito.mock(HttpServletRequest.class)));
+ fail("Since there is no connection, the doGetUserDetails method has to throw an exception.");
+ } catch (LdapException e) {
+ assertThat(e.getMessage()).contains("Unable to retrieve details for user tester");
+ }
+ try {
+ realm.getGroupsProvider().doGetGroups(new ExternalGroupsProvider.Context("tester", Mockito.mock(HttpServletRequest.class)));
+ fail("Since there is no connection, the doGetGroups method has to throw an exception.");
+ } catch (LdapException e) {
+ assertThat(e.getMessage()).contains("Unable to retrieve details for user tester");
+ }
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapReferralsTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapReferralsTest.java
new file mode 100644
index 00000000000..a635eea0a0f
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapReferralsTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.auth.ldap;
+
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.auth.ldap.server.LdapServer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LdapReferralsTest {
+
+ @ClassRule
+ public static LdapServer server = new LdapServer("/users.example.org.ldif");
+
+ Map<String, LdapContextFactory> underTest;
+
+ @Test
+ public void referral_is_set_to_follow_when_followReferrals_setting_is_set_to_true() {
+ underTest = createFactories("ldap.followReferrals", "true");
+
+ LdapContextFactory contextFactory = underTest.values().iterator().next();
+ assertThat(contextFactory.getReferral()).isEqualTo("follow");
+ }
+
+ @Test
+ public void referral_is_set_to_ignore_when_followReferrals_setting_is_set_to_false() {
+ underTest = createFactories("ldap.followReferrals", "false");
+
+ LdapContextFactory contextFactory = underTest.values().iterator().next();
+ assertThat(contextFactory.getReferral()).isEqualTo("ignore");
+ }
+
+ @Test
+ public void referral_is_set_to_follow_when_no_followReferrals_setting() {
+ underTest = createFactories(null, null);
+
+ LdapContextFactory contextFactory = underTest.values().iterator().next();
+ assertThat(contextFactory.getReferral()).isEqualTo("follow");
+ }
+
+ private static Map<String, LdapContextFactory> createFactories(@Nullable String propertyKey, @Nullable String propertyValue) {
+ Settings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(server, null);
+ if (propertyKey != null) {
+ settings.setProperty(propertyKey, propertyValue);
+ }
+ return new LdapSettingsManager(settings, new LdapAutodiscovery()).getContextFactories();
+ }
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSearchTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSearchTest.java
new file mode 100644
index 00000000000..d1b99d5169c
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSearchTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.auth.ldap;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Map;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchControls;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.auth.ldap.server.LdapServer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LdapSearchTest {
+
+ @ClassRule
+ public static LdapServer server = new LdapServer("/users.example.org.ldif");
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private static Map<String, LdapContextFactory> contextFactories;
+
+ @BeforeClass
+ public static void init() {
+ contextFactories = new LdapSettingsManager(LdapSettingsFactory.generateSimpleAnonymousAccessSettings(server, null), new LdapAutodiscovery()).getContextFactories();
+ }
+
+ @Test
+ public void subtreeSearch() throws Exception {
+ LdapSearch search = new LdapSearch(contextFactories.values().iterator().next())
+ .setBaseDn("dc=example,dc=org")
+ .setRequest("(objectClass={0})")
+ .setParameters("inetOrgPerson")
+ .returns("objectClass");
+
+ assertThat(search.getBaseDn()).isEqualTo("dc=example,dc=org");
+ assertThat(search.getScope()).isEqualTo(SearchControls.SUBTREE_SCOPE);
+ assertThat(search.getRequest()).isEqualTo("(objectClass={0})");
+ assertThat(search.getParameters()).isEqualTo(new String[] {"inetOrgPerson"});
+ assertThat(search.getReturningAttributes()).isEqualTo(new String[] {"objectClass"});
+ assertThat(search.toString()).isEqualTo("LdapSearch{baseDn=dc=example,dc=org, scope=subtree, request=(objectClass={0}), parameters=[inetOrgPerson], attributes=[objectClass]}");
+ assertThat(enumerationToArrayList(search.find()).size()).isEqualTo(3);
+ thrown.expect(NamingException.class);
+ thrown.expectMessage("Non unique result for " + search.toString());
+ search.findUnique();
+ }
+
+ @Test
+ public void oneLevelSearch() throws Exception {
+ LdapSearch search = new LdapSearch(contextFactories.values().iterator().next())
+ .setBaseDn("dc=example,dc=org")
+ .setScope(SearchControls.ONELEVEL_SCOPE)
+ .setRequest("(objectClass={0})")
+ .setParameters("inetOrgPerson")
+ .returns("cn");
+
+ assertThat(search.getBaseDn()).isEqualTo("dc=example,dc=org");
+ assertThat(search.getScope()).isEqualTo(SearchControls.ONELEVEL_SCOPE);
+ assertThat(search.getRequest()).isEqualTo("(objectClass={0})");
+ assertThat(search.getParameters()).isEqualTo(new String[] {"inetOrgPerson"});
+ assertThat(search.getReturningAttributes()).isEqualTo(new String[] {"cn"});
+ assertThat(search.toString()).isEqualTo("LdapSearch{baseDn=dc=example,dc=org, scope=onelevel, request=(objectClass={0}), parameters=[inetOrgPerson], attributes=[cn]}");
+ assertThat(enumerationToArrayList(search.find()).size()).isEqualTo(0);
+ assertThat(search.findUnique()).isNull();
+ }
+
+ @Test
+ public void objectSearch() throws Exception {
+ LdapSearch search = new LdapSearch(contextFactories.values().iterator().next())
+ .setBaseDn("cn=bind,ou=users,dc=example,dc=org")
+ .setScope(SearchControls.OBJECT_SCOPE)
+ .setRequest("(objectClass={0})")
+ .setParameters("uidObject")
+ .returns("uid");
+
+ assertThat(search.getBaseDn()).isEqualTo("cn=bind,ou=users,dc=example,dc=org");
+ assertThat(search.getScope()).isEqualTo(SearchControls.OBJECT_SCOPE);
+ assertThat(search.getRequest()).isEqualTo("(objectClass={0})");
+ assertThat(search.getParameters()).isEqualTo(new String[] {"uidObject"});
+ assertThat(search.getReturningAttributes()).isEqualTo(new String[] {"uid"});
+ assertThat(search.toString()).isEqualTo(
+ "LdapSearch{baseDn=cn=bind,ou=users,dc=example,dc=org, scope=object, request=(objectClass={0}), parameters=[uidObject], attributes=[uid]}");
+ assertThat(enumerationToArrayList(search.find()).size()).isEqualTo(1);
+ assertThat(search.findUnique()).isNotNull();
+ }
+
+ private static <E> ArrayList<E> enumerationToArrayList(Enumeration<E> enumeration) {
+ ArrayList<E> result = new ArrayList<>();
+ while (enumeration.hasMoreElements()) {
+ result.add(enumeration.nextElement());
+ }
+ return result;
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSettingsFactory.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSettingsFactory.java
new file mode 100644
index 00000000000..d08b3d8f407
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSettingsFactory.java
@@ -0,0 +1,95 @@
+/*
+ * 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.auth.ldap;
+
+import javax.annotation.Nullable;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.auth.ldap.server.LdapServer;
+
+/**
+ * Create Settings for most used test cases.
+ */
+public class LdapSettingsFactory {
+
+ /**
+ * Generate simple settings for 2 ldap servers that allows anonymous access.
+ *
+ * @return The specific settings.
+ */
+ public static MapSettings generateSimpleAnonymousAccessSettings(LdapServer exampleServer, @Nullable LdapServer infosupportServer) {
+ MapSettings settings = new MapSettings();
+
+ if (infosupportServer != null) {
+ settings.setProperty("ldap.servers", "example,infosupport");
+
+ settings.setProperty("ldap.example.url", exampleServer.getUrl())
+ .setProperty("ldap.example.user.baseDn", "ou=users,dc=example,dc=org")
+ .setProperty("ldap.example.group.baseDn", "ou=groups,dc=example,dc=org");
+ settings.setProperty("ldap.infosupport.url", infosupportServer.getUrl())
+ .setProperty("ldap.infosupport.user.baseDn", "ou=users,dc=infosupport,dc=com")
+ .setProperty("ldap.infosupport.group.baseDn", "ou=groups,dc=infosupport,dc=com");
+ } else {
+ settings.setProperty("ldap.url", exampleServer.getUrl())
+ .setProperty("ldap.user.baseDn", "ou=users,dc=example,dc=org")
+ .setProperty("ldap.group.baseDn", "ou=groups,dc=example,dc=org");
+ }
+ return settings;
+ }
+
+ /**
+ * Generate settings for 2 ldap servers.
+ *
+ * @param exampleServer The first ldap server.
+ * @param infosupportServer The second ldap server.
+ * @return The specific settings.
+ */
+ public static MapSettings generateAuthenticationSettings(LdapServer exampleServer, @Nullable LdapServer infosupportServer, String authMethod) {
+ MapSettings settings = new MapSettings();
+
+ if (infosupportServer != null) {
+ settings.setProperty("ldap.servers", "example,infosupport");
+
+ settings.setProperty("ldap.example.url", exampleServer.getUrl())
+ .setProperty("ldap.example.bindDn", LdapContextFactory.AUTH_METHOD_SIMPLE.equals(authMethod) ? "cn=bind,ou=users,dc=example,dc=org" : "bind")
+ .setProperty("ldap.example.bindPassword", "bindpassword")
+ .setProperty("ldap.example.authentication", authMethod)
+ .setProperty("ldap.example.realm", "example.org")
+ .setProperty("ldap.example.user.baseDn", "ou=users,dc=example,dc=org")
+ .setProperty("ldap.example.group.baseDn", "ou=groups,dc=example,dc=org");
+
+ settings.setProperty("ldap.infosupport.url", infosupportServer.getUrl())
+ .setProperty("ldap.infosupport.bindDn", LdapContextFactory.AUTH_METHOD_SIMPLE.equals(authMethod) ? "cn=bind,ou=users,dc=infosupport,dc=com" : "bind")
+ .setProperty("ldap.infosupport.bindPassword", "bindpassword")
+ .setProperty("ldap.infosupport.authentication", authMethod)
+ .setProperty("ldap.infosupport.realm", "infosupport.com")
+ .setProperty("ldap.infosupport.user.baseDn", "ou=users,dc=infosupport,dc=com")
+ .setProperty("ldap.infosupport.group.baseDn", "ou=groups,dc=infosupport,dc=com");
+ } else {
+ settings.setProperty("ldap.url", exampleServer.getUrl())
+ .setProperty("ldap.bindDn", LdapContextFactory.AUTH_METHOD_SIMPLE.equals(authMethod) ? "cn=bind,ou=users,dc=example,dc=org" : "bind")
+ .setProperty("ldap.bindPassword", "bindpassword")
+ .setProperty("ldap.authentication", authMethod)
+ .setProperty("ldap.realm", "example.org")
+ .setProperty("ldap.user.baseDn", "ou=users,dc=example,dc=org")
+ .setProperty("ldap.group.baseDn", "ou=groups,dc=example,dc=org");
+ }
+ return settings;
+ }
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSettingsManagerTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSettingsManagerTest.java
new file mode 100644
index 00000000000..88d16dedec8
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSettingsManagerTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.auth.ldap;
+
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.Settings;
+import org.sonar.api.config.internal.MapSettings;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.auth.ldap.LdapAutodiscovery.LdapSrvRecord;
+
+public class LdapSettingsManagerTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void shouldFailWhenNoLdapUrl() {
+ Settings settings = generateMultipleLdapSettingsWithUserAndGroupMapping();
+ settings.removeProperty("ldap.example.url");
+ LdapSettingsManager settingsManager = new LdapSettingsManager(settings, new LdapAutodiscovery());
+
+ thrown.expect(LdapException.class);
+ thrown.expectMessage("The property 'ldap.example.url' property is empty while it is mandatory.");
+ settingsManager.getContextFactories();
+ }
+
+ @Test
+ public void shouldFailWhenMixingSingleAndMultipleConfiguration() {
+ Settings settings = generateMultipleLdapSettingsWithUserAndGroupMapping();
+ settings.setProperty("ldap.url", "ldap://foo");
+ LdapSettingsManager settingsManager = new LdapSettingsManager(settings, new LdapAutodiscovery());
+
+ thrown.expect(LdapException.class);
+ thrown
+ .expectMessage(
+ "When defining multiple LDAP servers with the property 'ldap.servers', all LDAP properties must be linked to one of those servers. Please remove properties like 'ldap.url', 'ldap.realm', ...");
+ settingsManager.getContextFactories();
+ }
+
+ @Test
+ public void testContextFactoriesWithSingleLdap() throws Exception {
+ LdapSettingsManager settingsManager = new LdapSettingsManager(
+ generateSingleLdapSettingsWithUserAndGroupMapping(), new LdapAutodiscovery());
+ assertThat(settingsManager.getContextFactories().size()).isEqualTo(1);
+ }
+
+ /**
+ * Test there are 2 @link{org.sonar.plugins.ldap.LdapContextFactory}s found.
+ *
+ * @throws Exception
+ * This is not expected.
+ */
+ @Test
+ public void testContextFactoriesWithMultipleLdap() throws Exception {
+ LdapSettingsManager settingsManager = new LdapSettingsManager(
+ generateMultipleLdapSettingsWithUserAndGroupMapping(), new LdapAutodiscovery());
+ assertThat(settingsManager.getContextFactories().size()).isEqualTo(2);
+ // We do it twice to make sure the settings keep the same.
+ assertThat(settingsManager.getContextFactories().size()).isEqualTo(2);
+ }
+
+ @Test
+ public void testAutodiscover() throws Exception {
+ LdapAutodiscovery ldapAutodiscovery = mock(LdapAutodiscovery.class);
+ LdapSrvRecord ldap1 = new LdapSrvRecord("ldap://localhost:189", 1, 1);
+ LdapSrvRecord ldap2 = new LdapSrvRecord("ldap://localhost:1899", 1, 1);
+ when(ldapAutodiscovery.getLdapServers("example.org")).thenReturn(Arrays.asList(ldap1, ldap2));
+ LdapSettingsManager settingsManager = new LdapSettingsManager(
+ generateAutodiscoverSettings(), ldapAutodiscovery);
+ assertThat(settingsManager.getContextFactories().size()).isEqualTo(2);
+ }
+
+ @Test
+ public void testAutodiscoverFailed() throws Exception {
+ LdapAutodiscovery ldapAutodiscovery = mock(LdapAutodiscovery.class);
+ when(ldapAutodiscovery.getLdapServers("example.org")).thenReturn(Collections.<LdapSrvRecord>emptyList());
+ LdapSettingsManager settingsManager = new LdapSettingsManager(
+ generateAutodiscoverSettings(), ldapAutodiscovery);
+
+ thrown.expect(LdapException.class);
+ thrown.expectMessage("The property 'ldap.url' is empty and SonarQube is not able to auto-discover any LDAP server.");
+
+ settingsManager.getContextFactories();
+ }
+
+ /**
+ * Test there are 2 @link{org.sonar.plugins.ldap.LdapUserMapping}s found.
+ *
+ * @throws Exception
+ * This is not expected.
+ */
+ @Test
+ public void testUserMappings() throws Exception {
+ LdapSettingsManager settingsManager = new LdapSettingsManager(
+ generateMultipleLdapSettingsWithUserAndGroupMapping(), new LdapAutodiscovery());
+ assertThat(settingsManager.getUserMappings().size()).isEqualTo(2);
+ // We do it twice to make sure the settings keep the same.
+ assertThat(settingsManager.getUserMappings().size()).isEqualTo(2);
+ }
+
+ /**
+ * Test there are 2 @link{org.sonar.plugins.ldap.LdapGroupMapping}s found.
+ *
+ * @throws Exception
+ * This is not expected.
+ */
+ @Test
+ public void testGroupMappings() throws Exception {
+ LdapSettingsManager settingsManager = new LdapSettingsManager(
+ generateMultipleLdapSettingsWithUserAndGroupMapping(), new LdapAutodiscovery());
+ assertThat(settingsManager.getGroupMappings().size()).isEqualTo(2);
+ // We do it twice to make sure the settings keep the same.
+ assertThat(settingsManager.getGroupMappings().size()).isEqualTo(2);
+ }
+
+ /**
+ * Test what happens when no configuration is set.
+ * Normally there will be a contextFactory, but the autodiscovery doesn't work for the test server.
+ * @throws Exception
+ */
+ @Test
+ public void testEmptySettings() throws Exception {
+ LdapSettingsManager settingsManager = new LdapSettingsManager(
+ new MapSettings(), new LdapAutodiscovery());
+
+ thrown.expect(LdapException.class);
+ thrown.expectMessage("The property 'ldap.url' is empty and no realm configured to try auto-discovery.");
+ settingsManager.getContextFactories();
+ }
+
+ private MapSettings generateMultipleLdapSettingsWithUserAndGroupMapping() {
+ MapSettings settings = new MapSettings();
+
+ settings.setProperty("ldap.servers", "example,infosupport");
+
+ settings.setProperty("ldap.example.url", "/users.example.org.ldif")
+ .setProperty("ldap.example.user.baseDn", "ou=users,dc=example,dc=org")
+ .setProperty("ldap.example.group.baseDn", "ou=groups,dc=example,dc=org")
+ .setProperty("ldap.example.group.request",
+ "(&(objectClass=posixGroup)(memberUid={uid}))");
+
+ settings.setProperty("ldap.infosupport.url", "/users.infosupport.com.ldif")
+ .setProperty("ldap.infosupport.user.baseDn",
+ "ou=users,dc=infosupport,dc=com")
+ .setProperty("ldap.infosupport.group.baseDn",
+ "ou=groups,dc=infosupport,dc=com")
+ .setProperty("ldap.infosupport.group.request",
+ "(&(objectClass=posixGroup)(memberUid={uid}))");
+
+ return settings;
+ }
+
+ private MapSettings generateSingleLdapSettingsWithUserAndGroupMapping() {
+ MapSettings settings = new MapSettings();
+
+ settings.setProperty("ldap.url", "/users.example.org.ldif")
+ .setProperty("ldap.user.baseDn", "ou=users,dc=example,dc=org")
+ .setProperty("ldap.group.baseDn", "ou=groups,dc=example,dc=org")
+ .setProperty("ldap.group.request",
+ "(&(objectClass=posixGroup)(memberUid={uid}))");
+
+ return settings;
+ }
+
+ private MapSettings generateAutodiscoverSettings() {
+ MapSettings settings = new MapSettings();
+
+ settings.setProperty("ldap.realm", "example.org")
+ .setProperty("ldap.user.baseDn", "ou=users,dc=example,dc=org")
+ .setProperty("ldap.group.baseDn", "ou=groups,dc=example,dc=org")
+ .setProperty("ldap.group.request",
+ "(&(objectClass=posixGroup)(memberUid={uid}))");
+
+ return settings;
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapUserMappingTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapUserMappingTest.java
new file mode 100644
index 00000000000..f0f1876e917
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapUserMappingTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.auth.ldap;
+
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LdapUserMappingTest {
+
+ @Test
+ public void defaults() {
+ LdapUserMapping userMapping = new LdapUserMapping(new MapSettings(), "ldap");
+ assertThat(userMapping.getBaseDn()).isNull();
+ assertThat(userMapping.getRequest()).isEqualTo("(&(objectClass=inetOrgPerson)(uid={0}))");
+ assertThat(userMapping.getRealNameAttribute()).isEqualTo("cn");
+ assertThat(userMapping.getEmailAttribute()).isEqualTo("mail");
+
+ assertThat(userMapping.toString()).isEqualTo("LdapUserMapping{" +
+ "baseDn=null," +
+ " request=(&(objectClass=inetOrgPerson)(uid={0}))," +
+ " realNameAttribute=cn," +
+ " emailAttribute=mail}");
+ }
+
+ @Test
+ public void activeDirectory() {
+ MapSettings settings = new MapSettings()
+ .setProperty("ldap.user.baseDn", "cn=users")
+ .setProperty("ldap.user.objectClass", "user")
+ .setProperty("ldap.user.loginAttribute", "sAMAccountName");
+
+ LdapUserMapping userMapping = new LdapUserMapping(settings, "ldap");
+ LdapSearch search = userMapping.createSearch(null, "tester");
+ assertThat(search.getBaseDn()).isEqualTo("cn=users");
+ assertThat(search.getRequest()).isEqualTo("(&(objectClass=user)(sAMAccountName={0}))");
+ assertThat(search.getParameters()).isEqualTo(new String[] {"tester"});
+ assertThat(search.getReturningAttributes()).isNull();
+
+ assertThat(userMapping.toString()).isEqualTo("LdapUserMapping{" +
+ "baseDn=cn=users," +
+ " request=(&(objectClass=user)(sAMAccountName={0}))," +
+ " realNameAttribute=cn," +
+ " emailAttribute=mail}");
+ }
+
+ @Test
+ public void realm() {
+ MapSettings settings = new MapSettings()
+ .setProperty("ldap.realm", "example.org")
+ .setProperty("ldap.userObjectClass", "user")
+ .setProperty("ldap.loginAttribute", "sAMAccountName");
+
+ LdapUserMapping userMapping = new LdapUserMapping(settings, "ldap");
+ assertThat(userMapping.getBaseDn()).isEqualTo("dc=example,dc=org");
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapUsersProviderTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapUsersProviderTest.java
new file mode 100644
index 00000000000..90e61e59fea
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapUsersProviderTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.auth.ldap;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.api.security.UserDetails;
+import org.sonar.auth.ldap.server.LdapServer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LdapUsersProviderTest {
+ /**
+ * A reference to the original ldif file
+ */
+ public static final String USERS_EXAMPLE_ORG_LDIF = "/users.example.org.ldif";
+ /**
+ * A reference to an aditional ldif file.
+ */
+ public static final String USERS_INFOSUPPORT_COM_LDIF = "/users.infosupport.com.ldif";
+
+ @ClassRule
+ public static LdapServer exampleServer = new LdapServer(USERS_EXAMPLE_ORG_LDIF);
+ @ClassRule
+ public static LdapServer infosupportServer = new LdapServer(USERS_INFOSUPPORT_COM_LDIF, "infosupport.com", "dc=infosupport,dc=com");
+
+ @Test
+ public void test() throws Exception {
+ Settings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
+ LdapSettingsManager settingsManager = new LdapSettingsManager(settings, new LdapAutodiscovery());
+ LdapUsersProvider usersProvider = new LdapUsersProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings());
+
+ UserDetails details;
+
+ details = usersProvider.getUserDetails("godin");
+ assertThat(details.getName()).isEqualTo("Evgeny Mandrikov");
+ assertThat(details.getEmail()).isEqualTo("godin@example.org");
+
+ details = usersProvider.getUserDetails("tester");
+ assertThat(details.getName()).isEqualTo("Tester Testerovich");
+ assertThat(details.getEmail()).isEqualTo("tester@example.org");
+
+ details = usersProvider.getUserDetails("without_email");
+ assertThat(details.getName()).isEqualTo("Without Email");
+ assertThat(details.getEmail()).isEqualTo("");
+
+ details = usersProvider.getUserDetails("notfound");
+ assertThat(details).isNull();
+
+ details = usersProvider.getUserDetails("robby");
+ assertThat(details.getName()).isEqualTo("Robby Developer");
+ assertThat(details.getEmail()).isEqualTo("rd@infosupport.com");
+
+ details = usersProvider.getUserDetails("testerInfo");
+ assertThat(details.getName()).isEqualTo("Tester Testerovich");
+ assertThat(details.getEmail()).isEqualTo("tester@infosupport.com");
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/server/ApacheDS.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/server/ApacheDS.java
new file mode 100644
index 00000000000..6f2f1f4d66f
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/server/ApacheDS.java
@@ -0,0 +1,234 @@
+/*
+ * 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.auth.ldap.server;
+
+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.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 org.apache.directory.server.ldap.LdapServer ldapServer;
+ private KdcServer kdcServer;
+
+ private ApacheDS(String realm, String baseDn) {
+ this.realm = realm;
+ this.baseDn = baseDn;
+ ldapServer = new org.apache.directory.server.ldap.LdapServer();
+ }
+
+ public static ApacheDS start(String realm, String baseDn, String workDir) throws Exception {
+ return start(realm, baseDn, workDir + realm, null);
+ }
+
+ static ApacheDS start(String realm, String baseDn) throws Exception {
+ return start(realm, baseDn, "target/ldap-work/" + realm, null);
+ }
+
+ private 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();
+ }
+
+ void stop() throws Exception {
+ kdcServer.stop();
+ kdcServer = null;
+ ldapServer.stop();
+ ldapServer = null;
+ directoryService.shutdown();
+ directoryService = null;
+ }
+
+ 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();
+ }
+ }
+ }
+ }
+
+ void disableAnonymousAccess() {
+ directoryService.setAllowAnonymousAccess(false);
+ }
+
+ 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/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/server/LdapServer.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/server/LdapServer.java
new file mode 100644
index 00000000000..6539ac5a662
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/server/LdapServer.java
@@ -0,0 +1,68 @@
+/*
+ * 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.auth.ldap.server;
+
+import org.junit.rules.ExternalResource;
+
+public class LdapServer extends ExternalResource {
+
+ private ApacheDS server;
+ private String ldif;
+ private final String realm;
+ private final String baseDn;
+
+ public LdapServer(String ldifResourceName) {
+ this(ldifResourceName, "example.org", "dc=example,dc=org");
+ }
+
+ public LdapServer(String ldifResourceName, String realm, String baseDn) {
+ this.ldif = ldifResourceName;
+ this.realm = realm;
+ this.baseDn = baseDn;
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ server = ApacheDS.start(realm, baseDn);
+ server.importLdif(LdapServer.class.getResourceAsStream(ldif));
+ }
+
+ @Override
+ protected void after() {
+ try {
+ server.stop();
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public String getUrl() {
+ return server.getUrl();
+ }
+
+ public void disableAnonymousAccess() {
+ server.disableAnonymousAccess();
+ }
+
+ public void enableAnonymousAccess() {
+ server.enableAnonymousAccess();
+ }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/resources/conf/krb5.conf b/server/sonar-auth-ldap/src/test/resources/conf/krb5.conf
new file mode 100644
index 00000000000..04fd9f9423a
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/resources/conf/krb5.conf
@@ -0,0 +1,20 @@
+[libdefaults]
+ default_realm = EXAMPLE.ORG
+
+[realms]
+ EXAMPLE.ORG = {
+ kdc = localhost:6088
+ }
+ INFOSUPPORT.COM = {
+ kdc = localhost:6089
+ }
+
+[domain_realm]
+ .example.org = EXAMPLE.ORG
+ example.org = EXAMPLE.ORG
+ .infosupport.com = INFOSUPPORT.COM
+ infosupport.com = INFOSUPPORT.COM
+
+[login]
+ krb4_convert = true
+ krb4_get_tickets = false
diff --git a/server/sonar-auth-ldap/src/test/resources/conf/sasl_mech.properties b/server/sonar-auth-ldap/src/test/resources/conf/sasl_mech.properties
new file mode 100644
index 00000000000..f3d209e335c
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/resources/conf/sasl_mech.properties
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+ldap.url:ldap://localhost:1024
+# TODO don't work as expected
+ldap.authentication:DIGEST-MD5 CRAM-MD5
+#ldap.realm: example.org
diff --git a/server/sonar-auth-ldap/src/test/resources/krb.ldif b/server/sonar-auth-ldap/src/test/resources/krb.ldif
new file mode 100644
index 00000000000..6c8235dc91e
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/resources/krb.ldif
@@ -0,0 +1,55 @@
+dn: dc=example,dc=org
+dc: example
+objectClass: domain
+objectClass: top
+
+dn: ou=Users,dc=example,dc=org
+objectClass: organizationalUnit
+objectClass: top
+ou: Users
+
+dn: uid=krbtgt,ou=Users,dc=example,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: KDC Service
+sn: Service
+uid: krbtgt
+userPassword: secret
+krb5PrincipalName: krbtgt/EXAMPLE.ORG@EXAMPLE.ORG
+krb5KeyVersionNumber: 0
+
+dn: cn=SonarQube,ou=Users,dc=example,dc=org
+objectClass: top
+objectClass: organizationalRole
+objectClass: simpleSecurityObject
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: SonarQube
+userPassword: bind_password
+krb5PrincipalName: SonarQube@EXAMPLE.ORG
+krb5KeyVersionNumber: 0
+
+dn: uid=godin,ou=Users,dc=example,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: Evgeny Mandrikov
+sn: Mandrikov
+uid: godin
+userPassword: user_password
+krb5PrincipalName: Godin@EXAMPLE.ORG
+krb5KeyVersionNumber: 0
+
+dn: ou=Groups,dc=example,dc=org
+objectclass:organizationalunit
+ou: groups
+
+dn: cn=sonar-users,ou=Groups,dc=example,dc=org
+objectclass: groupOfUniqueNames
+cn: sonar-users
+uniqueMember: uid=godin,ou=Users,dc=example,dc=org
diff --git a/server/sonar-auth-ldap/src/test/resources/logback-test.xml b/server/sonar-auth-ldap/src/test/resources/logback-test.xml
new file mode 100644
index 00000000000..cc9519d939a
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/resources/logback-test.xml
@@ -0,0 +1,45 @@
+<?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.sonar">
+ <level value="DEBUG"/>
+ </logger>
+
+ <logger name="org.apache.directory">
+ <level value="ERROR"/>
+ </logger>
+
+ <root>
+ <level value="INFO"/>
+ <appender-ref ref="STDOUT"/>
+ </root>
+
+</configuration>
diff --git a/server/sonar-auth-ldap/src/test/resources/static-groups.example.org.ldif b/server/sonar-auth-ldap/src/test/resources/static-groups.example.org.ldif
new file mode 100644
index 00000000000..857efc7c508
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/resources/static-groups.example.org.ldif
@@ -0,0 +1,81 @@
+dn: dc=example,dc=org
+objectClass: domain
+objectClass: extensibleObject
+objectClass: top
+dc: example
+
+#
+# USERS
+#
+
+dn: ou=users,dc=example,dc=org
+objectClass: organizationalUnit
+objectClass: top
+ou: users
+
+# Bind user
+dn: cn=bind,ou=users,dc=example,dc=org
+objectClass: organizationalRole
+objectClass: simpleSecurityObject
+objectClass: top
+cn: bind
+userpassword: bindpassword
+
+# Typical user
+dn: cn=Evgeny Mandrikov,ou=users,dc=example,dc=org
+objectClass: organizationalPerson
+objectClass: person
+objectClass: extensibleObject
+objectClass: uidObject
+objectClass: inetOrgPerson
+objectClass: top
+cn: Evgeny Mandrikov
+sn: Mandrikov
+givenname: Evgeny
+mail: godin@example.org
+uid: godin
+userpassword: secret1
+
+# Just one more user
+dn: cn=Tester Testerovich,ou=users,dc=example,dc=org
+objectClass: organizationalPerson
+objectClass: person
+objectClass: extensibleObject
+objectClass: uidObject
+objectClass: inetOrgPerson
+objectClass: top
+cn: Tester Testerovich
+givenname: Tester
+sn: Testerovich
+mail: tester@example.org
+uid: tester
+userpassword: secret2
+
+#
+# GROUPS
+#
+
+dn: ou=groups,dc=example,dc=org
+objectclass:organizationalunit
+ou: groups
+
+# sonar-users
+dn: cn=sonar-users,ou=groups,dc=example,dc=org
+objectclass: groupOfUniqueNames
+cn: sonar-users
+uniqueMember: cn=Tester Testerovich,ou=users,dc=example,dc=org
+uniqueMember: cn=Evgeny Mandrikov,ou=users,dc=example,dc=org
+
+# sonar-developers
+dn: cn=sonar-developers,ou=groups,dc=example,dc=org
+objectclass: groupOfUniqueNames
+cn: sonar-developers
+uniqueMember: cn=Evgeny Mandrikov,ou=users,dc=example,dc=org
+
+# linux-users
+dn: cn=linux-users,ou=groups,dc=example,dc=org
+objectclass: posixGroup
+objectclass: top
+cn: linux-users
+gidNumber: 10000
+memberUid: godin
diff --git a/server/sonar-auth-ldap/src/test/resources/users-apacheds.ldif b/server/sonar-auth-ldap/src/test/resources/users-apacheds.ldif
new file mode 100644
index 00000000000..d0231512459
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/resources/users-apacheds.ldif
@@ -0,0 +1,88 @@
+dn: dc=example,dc=org
+objectClass: domain
+objectClass: extensibleObject
+objectClass: top
+dc: example
+
+#
+# USERS
+#
+
+dn: ou=users,dc=example,dc=org
+objectClass: organizationalUnit
+objectClass: top
+ou: users
+
+dn: cn=bind,ou=users,dc=example,dc=org
+objectClass: organizationalRole
+objectClass: uidObject
+objectClass: simpleSecurityObject
+objectClass: top
+cn: bind
+uid: sonar
+userpassword: bindpassword
+
+dn: cn=Evgeny Mandrikov,ou=users,dc=example,dc=org
+objectClass: organizationalPerson
+objectClass: person
+objectClass: extensibleObject
+objectClass: uidObject
+objectClass: inetOrgPerson
+objectClass: top
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: Evgeny Mandrikov
+givenname: Evgeny
+mail: godin@example.org
+sn: Mandrikov
+uid: godin
+userpassword: secret1
+krb5PrincipalName: godin@EXAMPLE.ORG
+krb5KeyVersionNumber: 0
+
+dn: cn=Tester Testerovich,ou=users,dc=example,dc=org
+objectClass: organizationalPerson
+objectClass: person
+objectClass: extensibleObject
+objectClass: uidObject
+objectClass: inetOrgPerson
+objectClass: top
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: Tester Testerovich
+givenname: Tester
+mail: tester@example.org
+sn: Testerovich
+uid: tester
+userpassword: secret2
+krb5PrincipalName: tester@EXAMPLE.ORG
+krb5KeyVersionNumber: 0
+
+####
+# For Krb5
+####
+dn: uid=krbtgt,ou=users,dc=example,dc=org
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: top
+objectClass: krb5principal
+objectClass: krb5kdcentry
+sn: Service
+cn: KDC Service
+uid: krbtgt
+userPassword: secret
+krb5PrincipalName: krbtgt/EXAMPLE.ORG@EXAMPLE.ORG
+krb5KeyVersionNumber: 0
+
+dn: uid=ldap,ou=users,dc=example,dc=org
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: top
+objectClass: krb5principal
+objectClass: krb5kdcentry
+sn: Service
+cn: LDAP Service
+uid: ldap
+userPassword: randall
+krb5PrincipalName: ldap/localhost@EXAMPLE.COM
+krb5KeyVersionNumber: 0
diff --git a/server/sonar-auth-ldap/src/test/resources/users.example.org.ldif b/server/sonar-auth-ldap/src/test/resources/users.example.org.ldif
new file mode 100644
index 00000000000..3dc462afbb0
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/resources/users.example.org.ldif
@@ -0,0 +1,98 @@
+dn: dc=example,dc=org
+objectClass: domain
+objectClass: extensibleObject
+objectClass: top
+dc: example
+
+#
+# USERS
+#
+
+dn: ou=users,dc=example,dc=org
+objectClass: organizationalUnit
+objectClass: top
+ou: users
+
+# Bind user
+dn: cn=bind,ou=users,dc=example,dc=org
+objectClass: organizationalRole
+objectClass: uidObject
+objectClass: simpleSecurityObject
+objectClass: top
+cn: bind
+uid: sonar
+userpassword: bindpassword
+
+# Typical user
+dn: cn=Evgeny Mandrikov,ou=users,dc=example,dc=org
+objectClass: organizationalPerson
+objectClass: person
+objectClass: extensibleObject
+objectClass: uidObject
+objectClass: inetOrgPerson
+objectClass: top
+cn: Evgeny Mandrikov
+givenname: Evgeny
+sn: Mandrikov
+mail: godin@example.org
+uid: godin
+userpassword: secret1
+
+# Just one more user
+dn: cn=Tester Testerovich,ou=users,dc=example,dc=org
+objectClass: organizationalPerson
+objectClass: person
+objectClass: extensibleObject
+objectClass: uidObject
+objectClass: inetOrgPerson
+objectClass: top
+cn: Tester Testerovich
+givenname: Tester
+sn: Testerovich
+mail: tester@example.org
+uid: tester
+userpassword: secret2
+
+# Special case which can cause NPE
+dn: cn=Without Email,ou=users,dc=example,dc=org
+objectClass: organizationalPerson
+objectClass: person
+objectClass: extensibleObject
+objectClass: uidObject
+objectClass: inetOrgPerson
+objectClass: top
+cn: Without Email
+givenname: Without
+sn: Email
+uid: without_email
+userpassword: secret3
+
+
+#
+# GROUPS
+#
+
+dn: ou=groups,dc=example,dc=org
+objectclass:organizationalunit
+ou: groups
+
+# sonar-users
+dn: cn=sonar-users,ou=groups,dc=example,dc=org
+objectclass: groupOfUniqueNames
+cn: sonar-users
+uniqueMember: cn=Tester Testerovich,ou=users,dc=example,dc=org
+uniqueMember: cn=Evgeny Mandrikov,ou=users,dc=example,dc=org
+
+# sonar-developers
+dn: cn=sonar-developers,ou=groups,dc=example,dc=org
+objectclass: groupOfUniqueNames
+cn: sonar-developers
+uniqueMember: cn=Evgeny Mandrikov,ou=users,dc=example,dc=org
+
+# linux-users
+dn: cn=linux-users,ou=groups,dc=example,dc=org
+objectclass: posixGroup
+objectclass: top
+cn: linux-users
+gidNumber: 10000
+memberUid: godin \ No newline at end of file
diff --git a/server/sonar-auth-ldap/src/test/resources/users.infosupport.com.ldif b/server/sonar-auth-ldap/src/test/resources/users.infosupport.com.ldif
new file mode 100644
index 00000000000..a08174bf72b
--- /dev/null
+++ b/server/sonar-auth-ldap/src/test/resources/users.infosupport.com.ldif
@@ -0,0 +1,98 @@
+dn: dc=infosupport,dc=com
+objectClass: domain
+objectClass: extensibleObject
+objectClass: top
+dc: infosupport
+
+#
+# USERS
+#
+
+dn: ou=users,dc=infosupport,dc=com
+objectClass: organizationalUnit
+objectClass: top
+ou: users
+
+# Bind user
+dn: cn=bind,ou=users,dc=infosupport,dc=com
+objectClass: organizationalRole
+objectClass: uidObject
+objectClass: simpleSecurityObject
+objectClass: top
+cn: bind
+uid: sonar
+userpassword: bindpassword
+
+# Typical user
+dn: cn=Robby Developer,ou=users,dc=infosupport,dc=com
+objectClass: organizationalPerson
+objectClass: person
+objectClass: extensibleObject
+objectClass: uidObject
+objectClass: inetOrgPerson
+objectClass: top
+cn: Robby Developer
+givenname: Robby
+sn: Developer
+mail: rd@infosupport.com
+uid: robby
+userpassword: secret1
+
+# Just one more user
+dn: cn=Tester Testerovich,ou=users,dc=infosupport,dc=com
+objectClass: organizationalPerson
+objectClass: person
+objectClass: extensibleObject
+objectClass: uidObject
+objectClass: inetOrgPerson
+objectClass: top
+cn: Tester Testerovich
+givenname: Tester
+sn: Testerovich
+mail: tester@infosupport.com
+uid: testerInfo
+userpassword: secret2
+
+# Special case which can cause NPE
+dn: cn=Without Email,ou=users,dc=infosupport,dc=com
+objectClass: organizationalPerson
+objectClass: person
+objectClass: extensibleObject
+objectClass: uidObject
+objectClass: inetOrgPerson
+objectClass: top
+cn: Without Email
+givenname: Without
+sn: Email
+uid: without_email
+userpassword: secret3
+
+
+#
+# GROUPS
+#
+
+dn: ou=groups,dc=infosupport,dc=com
+objectclass:organizationalunit
+ou: groups
+
+# sonar-users
+dn: cn=sonar-users,ou=groups,dc=infosupport,dc=com
+objectclass: groupOfUniqueNames
+cn: sonar-users
+uniqueMember: cn=Robby Developer,ou=users,dc=infosupport,dc=com
+uniqueMember: cn=Tester Testerovich,ou=users,dc=infosupport,dc=com
+
+# sonar-developers
+dn: cn=sonar-developers,ou=groups,dc=infosupport,dc=com
+objectclass: groupOfUniqueNames
+cn: sonar-developers
+uniqueMember: cn=Robby Developer,ou=users,dc=infosupport,dc=com
+
+# linux-users
+dn: cn=linux-users,ou=groups,dc=infosupport,dc=com
+objectclass: posixGroup
+objectclass: top
+cn: linux-users
+gidNumber: 10000
+memberUid: robby \ No newline at end of file