aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorAntoine Vinot <antoine.vinot@sonarsource.com>2022-10-25 11:56:20 +0200
committersonartech <sonartech@sonarsource.com>2022-11-02 20:03:01 +0000
commit60e4b88e4567c1af3051b6cdc4c8858f8b0fca21 (patch)
treec16129850760037f3e3f1f56dbf6f15bc0b34594 /server
parenta22087da3ca968ed5a592ed03d47f282b6b59d63 (diff)
downloadsonarqube-60e4b88e4567c1af3051b6cdc4c8858f8b0fca21.tar.gz
sonarqube-60e4b88e4567c1af3051b6cdc4c8858f8b0fca21.zip
SONAR-17508 - Fix SSF-327
Diffstat (limited to 'server')
-rw-r--r--server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapAuthenticator.java12
-rw-r--r--server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapGroupsProvider.java59
-rw-r--r--server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapUsersProvider.java50
-rw-r--r--server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapAuthenticationResult.java53
-rw-r--r--server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapAuthenticator.java2
-rw-r--r--server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapGroupsProvider.java12
-rw-r--r--server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapRealm.java4
-rw-r--r--server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapSettingsManager.java2
-rw-r--r--server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapUsersProvider.java16
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapAuthenticatorTest.java110
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapGroupsProviderTest.java98
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapUsersProviderTest.java59
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/KerberosTest.java60
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapRealmTest.java45
-rw-r--r--server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSearchTest.java12
-rw-r--r--server/sonar-auth-ldap/src/test/resources/users.example.org.ldif17
-rw-r--r--server/sonar-auth-ldap/src/test/resources/users.infosupport.com.ldif33
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/LdapCredentialsAuthentication.java44
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java65
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/IdentityProviderRepositoryTest.java7
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/LdapCredentialsAuthenticationTest.java151
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserRegistrarImplTest.java160
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/UpdateIdentityProviderAction.java37
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/UpdateIdentityProviderActionTest.java53
24 files changed, 819 insertions, 342 deletions
diff --git a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapAuthenticator.java b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapAuthenticator.java
index b4d2479c3fa..26c6d299090 100644
--- a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapAuthenticator.java
+++ b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapAuthenticator.java
@@ -47,7 +47,7 @@ public class DefaultLdapAuthenticator implements LdapAuthenticator {
}
@Override
- public boolean doAuthenticate(Context context) {
+ public LdapAuthenticationResult doAuthenticate(Context context) {
return authenticate(context.getUsername(), context.getPassword());
}
@@ -58,7 +58,7 @@ public class DefaultLdapAuthenticator implements LdapAuthenticator {
* @param password The password to use.
* @return false if specified user cannot be authenticated with specified password on any LDAP server
*/
- public boolean authenticate(String login, String password) {
+ private LdapAuthenticationResult authenticate(String login, String password) {
for (Map.Entry<String, LdapUserMapping> ldapEntry : userMappings.entrySet()) {
String ldapKey = ldapEntry.getKey();
LdapUserMapping ldapUserMapping = ldapEntry.getValue();
@@ -75,11 +75,11 @@ public class DefaultLdapAuthenticator implements LdapAuthenticator {
}
boolean passwordValid = isPasswordValid(password, ldapKey, ldapContextFactory, principal);
if (passwordValid) {
- return true;
+ return LdapAuthenticationResult.success(ldapKey);
}
}
LOG.debug("User {} not found", login);
- return false;
+ return LdapAuthenticationResult.failed();
}
private static SearchResult findUser(String login, String ldapKey, LdapUserMapping ldapUserMapping, LdapContextFactory ldapContextFactory) {
@@ -87,11 +87,11 @@ public class DefaultLdapAuthenticator implements LdapAuthenticator {
try {
result = ldapUserMapping.createSearch(ldapContextFactory, login).findUnique();
} catch (NamingException e) {
- LOG.debug("User {} not found in server {}: {}", login, ldapKey, e.getMessage());
+ LOG.debug("User {} not found in server <{}>: {}", login, ldapKey, e.toString());
return null;
}
if (result == null) {
- LOG.debug("User {} not found in {}", login, ldapKey);
+ LOG.debug("User {} not found in <{}>", login, ldapKey);
return null;
}
return result;
diff --git a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapGroupsProvider.java b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapGroupsProvider.java
index 0a4195133c4..a4a4dc8f078 100644
--- a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapGroupsProvider.java
+++ b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapGroupsProvider.java
@@ -19,10 +19,8 @@
*/
package org.sonar.auth.ldap;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.naming.NamingEnumeration;
@@ -53,74 +51,57 @@ public class DefaultLdapGroupsProvider implements LdapGroupsProvider {
this.groupMappings = groupMapping;
}
+ /**
+ * @throws LdapException if unable to retrieve groups
+ */
@Override
public Collection<String> doGetGroups(Context context) {
- return getGroups(context.getUsername());
+ return getGroups(context.getServerKey(), context.getUsername());
}
- /**
- * @throws LdapException if unable to retrieve groups
- */
- public Collection<String> getGroups(String username) {
+ private Collection<String> getGroups(String serverKey, String username) {
checkPrerequisites(username);
Set<String> groups = new HashSet<>();
- List<LdapException> exceptions = new ArrayList<>();
- for (String serverKey : userMappings.keySet()) {
- if (groupMappings.containsKey(serverKey)) {
- SearchResult searchResult = searchUserGroups(username, exceptions, serverKey);
- if (searchResult != null) {
- try {
- NamingEnumeration<SearchResult> result = groupMappings
- .get(serverKey)
- .createSearch(contextFactories.get(serverKey), searchResult).find();
- groups.addAll(mapGroups(serverKey, result));
- // if no exceptions occur, we found the user and his groups and mapped his details.
- break;
- } catch (NamingException e) {
- // just in case if Sonar silently swallowed exception
- LOG.debug(e.getMessage(), e);
- exceptions.add(new LdapException(format("Unable to retrieve groups for user %s in %s", username, serverKey), e));
- }
+ if (groupMappings.containsKey(serverKey)) {
+ SearchResult searchResult = searchUserGroups(username, serverKey);
+ if (searchResult != null) {
+ try {
+ NamingEnumeration<SearchResult> result = groupMappings
+ .get(serverKey)
+ .createSearch(contextFactories.get(serverKey), searchResult).find();
+ groups.addAll(mapGroups(serverKey, result));
+ } catch (NamingException e) {
+ LOG.debug(e.getMessage(), e);
+ throw new LdapException(format("Unable to retrieve groups for user %s in server with key <%s>", username, serverKey), e);
}
}
}
- checkResults(groups, exceptions);
return groups;
}
- private static void checkResults(Set<String> groups, List<LdapException> exceptions) {
- if (groups.isEmpty() && !exceptions.isEmpty()) {
- // No groups found and there is an exception so there is a reason the user could not be found.
- throw exceptions.iterator().next();
- }
- }
-
private void checkPrerequisites(String username) {
if (userMappings.isEmpty() || groupMappings.isEmpty()) {
throw new LdapException(format("Unable to retrieve details for user %s: No user or group mapping found.", username));
}
}
- private SearchResult searchUserGroups(String username, List<LdapException> exceptions, String serverKey) {
- SearchResult searchResult = null;
+ private SearchResult searchUserGroups(String username, String serverKey) {
try {
LOG.debug("Requesting groups for user {}", username);
-
- searchResult = userMappings.get(serverKey).createSearch(contextFactories.get(serverKey), username)
+ return userMappings.get(serverKey).createSearch(contextFactories.get(serverKey), username)
.returns(groupMappings.get(serverKey).getRequiredUserAttributes())
.findUnique();
} catch (NamingException e) {
// just in case if Sonar silently swallowed exception
LOG.debug(e.getMessage(), e);
- exceptions.add(new LdapException(format("Unable to retrieve groups for user %s in %s", username, serverKey), e));
+ throw new LdapException(format("Unable to retrieve groups for user %s in server with key <%s>", username, serverKey), e);
}
- return searchResult;
}
/**
* Map all the groups.
*
- * @param serverKey The index we use to choose the correct {@link LdapGroupMapping}.
+ * @param serverKey The index we use to choose the correct {@link LdapGroupMapping}.
* @param searchResult The {@link SearchResult} from the search for the user.
* @return A {@link Collection} of groups the user is member of.
* @throws NamingException
diff --git a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapUsersProvider.java b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapUsersProvider.java
index 72d3a51e902..77ff3f8d73c 100644
--- a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapUsersProvider.java
+++ b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapUsersProvider.java
@@ -55,57 +55,39 @@ public class DefaultLdapUsersProvider implements LdapUsersProvider {
@Override
public LdapUserDetails doGetUserDetails(Context context) {
- return getUserDetails(context.getUsername());
+ return getUserDetails(context.getServerKey(), context.getUsername());
}
/**
* @return details for specified user, or null if such user doesn't exist
* @throws LdapException if unable to retrieve details
*/
- public LdapUserDetails getUserDetails(String username) {
+ private LdapUserDetails getUserDetails(String serverKey, String username) {
LOG.debug("Requesting details for user {}", username);
// If there are no userMappings available, we can not retrieve user details.
- if (userMappings.isEmpty()) {
- String errorMessage = format("Unable to retrieve details for user %s: No user mapping found.", username);
+ LdapUserMapping ldapUserMapping = userMappings.get(serverKey);
+ if (ldapUserMapping == null) {
+ String errorMessage = format("Unable to retrieve details for user %s and server key %s: No user mapping found.", username, serverKey);
LOG.debug(errorMessage);
throw new LdapException(errorMessage);
}
- LdapUserDetails details = null;
- LdapException exception = null;
- for (Map.Entry<String, LdapUserMapping> serverEntry : userMappings.entrySet()) {
- String serverKey = serverEntry.getKey();
- LdapUserMapping ldapUserMapping = serverEntry.getValue();
+ SearchResult searchResult;
+ try {
+ searchResult = ldapUserMapping.createSearch(contextFactories.get(serverKey), username)
+ .returns(ldapUserMapping.getEmailAttribute(), ldapUserMapping.getRealNameAttribute())
+ .findUnique();
- SearchResult searchResult = null;
- try {
- searchResult = ldapUserMapping.createSearch(contextFactories.get(serverKey), username)
- .returns(ldapUserMapping.getEmailAttribute(), ldapUserMapping.getRealNameAttribute())
- .findUnique();
- } catch (NamingException e) {
- // just in case if Sonar silently swallowed exception
- LOG.debug(e.getMessage(), e);
- exception = new LdapException("Unable to retrieve details for user " + username + " in " + serverKey, e);
- }
if (searchResult != null) {
- try {
- details = mapUserDetails(ldapUserMapping, searchResult);
- // if no exceptions occur, we found the user and mapped his details.
- break;
- } catch (NamingException e) {
- // just in case if Sonar silently swallowed exception
- LOG.debug(e.getMessage(), e);
- exception = new LdapException("Unable to retrieve details for user " + username + " in " + serverKey, e);
- }
+ return mapUserDetails(ldapUserMapping, searchResult);
} else {
- // user not found
LOG.debug("User {} not found in {}", username, serverKey);
+ return null;
}
+ } catch (NamingException e) {
+ // just in case if Sonar silently swallowed exception
+ LOG.debug(e.getMessage(), e);
+ throw new LdapException("Unable to retrieve details for user " + username + " in " + serverKey, e);
}
- if (details == null && exception != null) {
- // No user found and there is an exception so there is a reason the user could not be found.
- throw exception;
- }
- return details;
}
private static LdapUserDetails mapUserDetails(LdapUserMapping ldapUserMapping, SearchResult searchResult) throws NamingException {
diff --git a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapAuthenticationResult.java b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapAuthenticationResult.java
new file mode 100644
index 00000000000..b28872022c1
--- /dev/null
+++ b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapAuthenticationResult.java
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 static org.sonar.api.utils.Preconditions.checkState;
+
+public final class LdapAuthenticationResult {
+
+ private final boolean success;
+
+ private final String serverKey;
+
+ private LdapAuthenticationResult(boolean success, @Nullable String serverKey) {
+ this.success = success;
+ this.serverKey = serverKey;
+ }
+
+ public static LdapAuthenticationResult failed() {
+ return new LdapAuthenticationResult(false, null);
+ }
+
+ public static LdapAuthenticationResult success(String serverKey) {
+ return new LdapAuthenticationResult(true, serverKey);
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public String getServerKey() {
+ checkState(isSuccess(), "serverKey is only set for successful authentication.");
+ return serverKey;
+ }
+}
diff --git a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapAuthenticator.java b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapAuthenticator.java
index ef2132b6c73..39f9b396857 100644
--- a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapAuthenticator.java
+++ b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapAuthenticator.java
@@ -30,7 +30,7 @@ public interface LdapAuthenticator {
* @return true if user was successfully authenticated with specified credentials, false otherwise
* @throws RuntimeException in case of unexpected error such as connection failure
*/
- boolean doAuthenticate(LdapAuthenticator.Context context);
+ LdapAuthenticationResult doAuthenticate(LdapAuthenticator.Context context);
final class Context {
private String username;
diff --git a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapGroupsProvider.java b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapGroupsProvider.java
index 3a3c2701294..71ab051f8ec 100644
--- a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapGroupsProvider.java
+++ b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapGroupsProvider.java
@@ -27,14 +27,20 @@ public interface LdapGroupsProvider {
Collection<String> doGetGroups(Context context);
final class Context {
- private String username;
- private HttpServletRequest request;
+ private final String serverKey;
+ private final String username;
+ private final HttpServletRequest request;
- public Context(String username, HttpServletRequest request) {
+ public Context(String serverKey, String username, HttpServletRequest request) {
+ this.serverKey = serverKey;
this.username = username;
this.request = request;
}
+ public String getServerKey() {
+ return serverKey;
+ }
+
public String getUsername() {
return username;
}
diff --git a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapRealm.java b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapRealm.java
index 1b03964ea58..49dbc11b4c0 100644
--- a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapRealm.java
+++ b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapRealm.java
@@ -22,12 +22,16 @@ package org.sonar.auth.ldap;
import java.util.Map;
import org.sonar.api.server.ServerSide;
+import static org.sonar.auth.ldap.LdapSettingsManager.DEFAULT_LDAP_SERVER_KEY;
+
/**
* @author Evgeny Mandrikov
*/
@ServerSide
public class LdapRealm {
+ public static final String LDAP_SECURITY_REALM = "LDAP";
+ public static final String DEFAULT_LDAP_IDENTITY_PROVIDER_ID = LDAP_SECURITY_REALM + "_" + DEFAULT_LDAP_SERVER_KEY;
private LdapUsersProvider usersProvider;
private LdapGroupsProvider groupsProvider;
private LdapAuthenticator authenticator;
diff --git a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapSettingsManager.java b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapSettingsManager.java
index ee52f521ccd..db14f7bfee6 100644
--- a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapSettingsManager.java
+++ b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapSettingsManager.java
@@ -37,11 +37,11 @@ import static org.sonar.auth.ldap.LdapAutodiscovery.LdapSrvRecord;
@ServerSide
public class LdapSettingsManager {
+ public static final String DEFAULT_LDAP_SERVER_KEY = "default";
private static final Logger LOG = Loggers.get(LdapSettingsManager.class);
private static final String LDAP_SERVERS_PROPERTY = "ldap.servers";
private static final String LDAP_PROPERTY_PREFIX = "ldap";
- private static final String DEFAULT_LDAP_SERVER_KEY = "<default>";
private final Configuration config;
private final LdapAutodiscovery ldapAutodiscovery;
private Map<String, LdapUserMapping> userMappings = null;
diff --git a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapUsersProvider.java b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapUsersProvider.java
index 4b1870e4bbc..7891585c4bd 100644
--- a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapUsersProvider.java
+++ b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapUsersProvider.java
@@ -19,7 +19,6 @@
*/
package org.sonar.auth.ldap;
-import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
public interface LdapUsersProvider {
@@ -27,11 +26,15 @@ public interface LdapUsersProvider {
LdapUserDetails doGetUserDetails(Context context);
final class Context {
- private String username;
- private HttpServletRequest request;
+ private final String username;
- public Context(@Nullable String username, HttpServletRequest request) {
+ private final String serverKey;
+
+ private final HttpServletRequest request;
+
+ public Context(String serverKey, String username, HttpServletRequest request) {
this.username = username;
+ this.serverKey = serverKey;
this.request = request;
}
@@ -39,8 +42,13 @@ public interface LdapUsersProvider {
return username;
}
+ public String getServerKey() {
+ return serverKey;
+ }
+
public HttpServletRequest getRequest() {
return request;
}
+
}
}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapAuthenticatorTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapAuthenticatorTest.java
index cf56c14eec5..2559d8836d9 100644
--- a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapAuthenticatorTest.java
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapAuthenticatorTest.java
@@ -19,11 +19,13 @@
*/
package org.sonar.auth.ldap;
+import javax.servlet.http.HttpServletRequest;
import org.junit.ClassRule;
import org.junit.Test;
import org.sonar.auth.ldap.server.LdapServer;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
public class DefaultLdapAuthenticatorTest {
@@ -32,7 +34,7 @@ public class DefaultLdapAuthenticatorTest {
*/
public static final String USERS_EXAMPLE_ORG_LDIF = "/users.example.org.ldif";
/**
- * A reference to an aditional ldif file.
+ * A reference to an additional ldif file.
*/
public static final String USERS_INFOSUPPORT_COM_LDIF = "/users.infosupport.com.ldif";
@ClassRule
@@ -44,11 +46,12 @@ public class DefaultLdapAuthenticatorTest {
public void testNoConnection() {
exampleServer.disableAnonymousAccess();
try {
- LdapSettingsManager settingsManager = new LdapSettingsManager(LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_SIMPLE).asConfig(),
+ LdapSettingsManager settingsManager = new LdapSettingsManager(
+ LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_SIMPLE).asConfig(),
new LdapAutodiscovery());
DefaultLdapAuthenticator authenticator = new DefaultLdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
- boolean authenticate = authenticator.authenticate("godin", "secret1");
- assertThat(authenticate).isTrue();
+ boolean isAuthenticationSuccessful = authenticator.doAuthenticate(createContext("godin", "secret1")).isSuccess();
+ assertThat(isAuthenticationSuccessful).isTrue();
} finally {
exampleServer.enableAnonymousAccess();
}
@@ -56,20 +59,27 @@ public class DefaultLdapAuthenticatorTest {
@Test
public void testSimple() {
- LdapSettingsManager settingsManager = new LdapSettingsManager(LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_SIMPLE).asConfig(),
+ LdapSettingsManager settingsManager = new LdapSettingsManager(
+ LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_SIMPLE).asConfig(),
new LdapAutodiscovery());
DefaultLdapAuthenticator authenticator = new DefaultLdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
- assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
- assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
+ LdapAuthenticationResult user1Success = authenticator.doAuthenticate(createContext("godin", "secret1"));
+ assertThat(user1Success.isSuccess()).isTrue();
+ assertThat(user1Success.getServerKey()).isEqualTo("default");
+
+ assertThat(authenticator.doAuthenticate(createContext("godin", "wrong")).isSuccess()).isFalse();
- assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
- assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
+ LdapAuthenticationResult user2Success = authenticator.doAuthenticate(createContext("tester", "secret2"));
+ assertThat(user2Success.isSuccess()).isTrue();
+ assertThat(user2Success.getServerKey()).isEqualTo("default");
- assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
+ assertThat(authenticator.doAuthenticate(createContext("tester", "wrong")).isSuccess()).isFalse();
+
+ assertThat(authenticator.doAuthenticate(createContext("notfound", "wrong")).isSuccess()).isFalse();
// SONARPLUGINS-2493
- assertThat(authenticator.authenticate("godin", "")).isFalse();
- assertThat(authenticator.authenticate("godin", null)).isFalse();
+ assertThat(authenticator.doAuthenticate(createContext("godin", "")).isSuccess()).isFalse();
+ assertThat(authenticator.doAuthenticate(createContext("godin", null)).isSuccess()).isFalse();
}
@Test
@@ -78,35 +88,53 @@ public class DefaultLdapAuthenticatorTest {
LdapSettingsFactory.generateAuthenticationSettings(exampleServer, infosupportServer, LdapContextFactory.AUTH_METHOD_SIMPLE).asConfig(), new LdapAutodiscovery());
DefaultLdapAuthenticator authenticator = new DefaultLdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
- assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
- assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
+ LdapAuthenticationResult user1Success = authenticator.doAuthenticate(createContext("godin", "secret1"));
+ assertThat(user1Success.isSuccess()).isTrue();
+ assertThat(user1Success.getServerKey()).isEqualTo("example");
+ assertThat(authenticator.doAuthenticate(createContext("godin", "wrong")).isSuccess()).isFalse();
+
+ LdapAuthenticationResult user2Server1Success = authenticator.doAuthenticate(createContext("tester", "secret2"));
+ assertThat(user2Server1Success.isSuccess()).isTrue();
+ assertThat(user2Server1Success.getServerKey()).isEqualTo("example");
- assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
- assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
+ LdapAuthenticationResult user2Server2Success = authenticator.doAuthenticate(createContext("tester", "secret3"));
+ assertThat(user2Server2Success.isSuccess()).isTrue();
+ assertThat(user2Server2Success.getServerKey()).isEqualTo("infosupport");
- assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
+ assertThat(authenticator.doAuthenticate(createContext("tester", "wrong")).isSuccess()).isFalse();
+
+ assertThat(authenticator.doAuthenticate(createContext("notfound", "wrong")).isSuccess()).isFalse();
// SONARPLUGINS-2493
- assertThat(authenticator.authenticate("godin", "")).isFalse();
- assertThat(authenticator.authenticate("godin", null)).isFalse();
+ assertThat(authenticator.doAuthenticate(createContext("godin", "")).isSuccess()).isFalse();
+ assertThat(authenticator.doAuthenticate(createContext("godin", null)).isSuccess()).isFalse();
// SONARPLUGINS-2793
- assertThat(authenticator.authenticate("robby", "secret1")).isTrue();
- assertThat(authenticator.authenticate("robby", "wrong")).isFalse();
+ LdapAuthenticationResult user3Success = authenticator.doAuthenticate(createContext("robby", "secret1"));
+ assertThat(user3Success.isSuccess()).isTrue();
+ assertThat(user3Success.getServerKey()).isEqualTo("infosupport");
+ assertThat(authenticator.doAuthenticate(createContext("robby", "wrong")).isSuccess()).isFalse();
}
@Test
public void testSasl() {
- LdapSettingsManager settingsManager = new LdapSettingsManager(LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_CRAM_MD5).asConfig(),
+ LdapSettingsManager settingsManager = new LdapSettingsManager(
+ LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_CRAM_MD5).asConfig(),
new LdapAutodiscovery());
DefaultLdapAuthenticator authenticator = new DefaultLdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
- assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
- assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
+ LdapAuthenticationResult user1Success = authenticator.doAuthenticate(createContext("godin", "secret1"));
+ assertThat(user1Success.isSuccess()).isTrue();
+ assertThat(user1Success.getServerKey()).isEqualTo("default");
+
+ assertThat(authenticator.doAuthenticate(createContext("godin", "wrong")).isSuccess()).isFalse();
- assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
- assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
+ LdapAuthenticationResult user2Success = authenticator.doAuthenticate(createContext("tester", "secret2"));
+ assertThat(user2Success.isSuccess()).isTrue();
+ assertThat(user2Success.getServerKey()).isEqualTo("default");
- assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
+ assertThat(authenticator.doAuthenticate(createContext("tester", "wrong")).isSuccess()).isFalse();
+
+ assertThat(authenticator.doAuthenticate(createContext("notfound", "wrong")).isSuccess()).isFalse();
}
@Test
@@ -115,16 +143,30 @@ public class DefaultLdapAuthenticatorTest {
LdapSettingsFactory.generateAuthenticationSettings(exampleServer, infosupportServer, LdapContextFactory.AUTH_METHOD_CRAM_MD5).asConfig(), new LdapAutodiscovery());
DefaultLdapAuthenticator authenticator = new DefaultLdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
- assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
- assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
+ LdapAuthenticationResult user1Success = authenticator.doAuthenticate(createContext("godin", "secret1"));
+ assertThat(user1Success.isSuccess()).isTrue();
+ assertThat(authenticator.doAuthenticate(createContext("godin", "wrong")).isSuccess()).isFalse();
+
+ LdapAuthenticationResult user2Server1Success = authenticator.doAuthenticate(createContext("tester", "secret2"));
+ assertThat(user2Server1Success.isSuccess()).isTrue();
+ assertThat(user2Server1Success.getServerKey()).isEqualTo("example");
+
+ LdapAuthenticationResult user2Server2Success = authenticator.doAuthenticate(createContext("tester", "secret3"));
+ assertThat(user2Server2Success.isSuccess()).isTrue();
+ assertThat(user2Server2Success.getServerKey()).isEqualTo("infosupport");
- assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
- assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
+ assertThat(authenticator.doAuthenticate(createContext("tester", "wrong")).isSuccess()).isFalse();
- assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
+ assertThat(authenticator.doAuthenticate(createContext("notfound", "wrong")).isSuccess()).isFalse();
+
+ LdapAuthenticationResult user3Success = authenticator.doAuthenticate(createContext("robby", "secret1"));
+ assertThat(user3Success.isSuccess()).isTrue();
+
+ assertThat(authenticator.doAuthenticate(createContext("robby", "wrong")).isSuccess()).isFalse();
+ }
- assertThat(authenticator.authenticate("robby", "secret1")).isTrue();
- assertThat(authenticator.authenticate("robby", "wrong")).isFalse();
+ private static LdapAuthenticator.Context createContext(String username, String password) {
+ return new LdapAuthenticator.Context(username, password, mock(HttpServletRequest.class));
}
}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapGroupsProviderTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapGroupsProviderTest.java
index dd759b2804e..6a527e6cb0a 100644
--- a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapGroupsProviderTest.java
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapGroupsProviderTest.java
@@ -20,12 +20,14 @@
package org.sonar.auth.ldap;
import java.util.Collection;
+import javax.servlet.http.HttpServletRequest;
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;
+import static org.mockito.Mockito.mock;
public class DefaultLdapGroupsProviderTest {
@@ -44,58 +46,71 @@ public class DefaultLdapGroupsProviderTest {
public static LdapServer infosupportServer = new LdapServer(USERS_INFOSUPPORT_COM_LDIF, "infosupport.com", "dc=infosupport,dc=com");
@Test
- public void defaults() {
+ public void doGetGroups_when_single_server_without_key() {
MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, null);
LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
- DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
- Collection<String> groups;
+ DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(),
+ settingsManager.getGroupMappings());
- groups = groupsProvider.getGroups("tester");
+ Collection<String> groups = getGroupsForContext(createContextForDefaultServer("tester"), groupsProvider);
assertThat(groups).containsOnly("sonar-users");
- groups = groupsProvider.getGroups("godin");
+ groups = getGroupsForContext(createContextForDefaultServer("godin"), groupsProvider);
assertThat(groups).containsOnly("sonar-users", "sonar-developers");
- groups = groupsProvider.getGroups("notfound");
+ groups = getGroupsForContext(createContextForDefaultServer("unknown_user"), groupsProvider);
assertThat(groups).isEmpty();
}
@Test
- public void defaultsMultipleLdap() {
+ public void doGetGroups_when_two_ldap_servers() {
MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
- DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
+ DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(),
+ settingsManager.getGroupMappings());
- Collection<String> groups;
-
- groups = groupsProvider.getGroups("tester");
+ Collection<String> groups = getGroupsForContext(createContextForExampleServer("tester"), groupsProvider);
assertThat(groups).containsOnly("sonar-users");
- groups = groupsProvider.getGroups("godin");
+ groups = getGroupsForContext(createContextForExampleServer("godin"), groupsProvider);
assertThat(groups).containsOnly("sonar-users", "sonar-developers");
- groups = groupsProvider.getGroups("notfound");
+ groups = getGroupsForContext(createContextForExampleServer("unknown_user"), groupsProvider);
assertThat(groups).isEmpty();
- groups = groupsProvider.getGroups("testerInfo");
+ groups = getGroupsForContext(createContextForInfoSupportServer("testerInfo"), groupsProvider);
assertThat(groups).containsOnly("sonar-users");
- groups = groupsProvider.getGroups("robby");
+ groups = getGroupsForContext(createContextForInfoSupportServer("robby"), groupsProvider);
assertThat(groups).containsOnly("sonar-users", "sonar-developers");
}
@Test
+ public void doGetGroups_when_two_ldap_servers_with_same_username_resolves_groups_from_right_server() {
+ MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
+
+ LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
+ DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(),
+ settingsManager.getGroupMappings());
+
+ Collection<String> groups = getGroupsForContext(createContextForExampleServer("duplicated"), groupsProvider);
+ assertThat(groups).containsOnly("sonar-users");
+
+ groups = getGroupsForContext(createContextForInfoSupportServer("duplicated"), groupsProvider);
+ assertThat(groups).containsOnly("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.asConfig(), new LdapAutodiscovery());
- DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
-
- Collection<String> groups;
+ DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(),
+ settingsManager.getGroupMappings());
- groups = groupsProvider.getGroups("godin");
+ Collection<String> groups = getGroupsForContext(createContextForDefaultServer("godin"), groupsProvider);
assertThat(groups).containsOnly("linux-users");
}
@@ -105,27 +120,29 @@ public class DefaultLdapGroupsProviderTest {
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.asConfig(), new LdapAutodiscovery());
- DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
-
- Collection<String> groups;
+ DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(),
+ settingsManager.getGroupMappings());
- groups = groupsProvider.getGroups("godin");
+ Collection<String> groups = getGroupsForContext(createContextForExampleServer("godin"), groupsProvider);
assertThat(groups).containsOnly("linux-users");
- groups = groupsProvider.getGroups("robby");
+ groups = getGroupsForContext(createContextForInfoSupportServer("robby"), groupsProvider);
assertThat(groups).containsOnly("linux-users");
}
+ private static Collection<String> getGroupsForContext(LdapGroupsProvider.Context context, DefaultLdapGroupsProvider groupsProvider) {
+ return groupsProvider.doGetGroups(context);
+ }
+
@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.asConfig(), new LdapAutodiscovery());
- DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
-
- Collection<String> groups;
+ DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(),
+ settingsManager.getGroupMappings());
- groups = groupsProvider.getGroups("godin");
+ Collection<String> groups = getGroupsForContext(createContextForExampleServer("godin"), groupsProvider);
assertThat(groups).containsOnly("sonar-users", "sonar-developers", "linux-users");
}
@@ -135,15 +152,30 @@ public class DefaultLdapGroupsProviderTest {
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.asConfig(), new LdapAutodiscovery());
- DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
+ DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(),
+ settingsManager.getGroupMappings());
- Collection<String> groups;
-
- groups = groupsProvider.getGroups("godin");
+ Collection<String> groups = getGroupsForContext(createContextForExampleServer("godin"), groupsProvider);
assertThat(groups).containsOnly("sonar-users", "sonar-developers", "linux-users");
- groups = groupsProvider.getGroups("robby");
+ groups = getGroupsForContext(createContextForInfoSupportServer("robby"), groupsProvider);
assertThat(groups).containsOnly("sonar-users", "sonar-developers", "linux-users");
}
+ private static LdapGroupsProvider.Context createContextForDefaultServer(String userName) {
+ return createContext("default", userName);
+ }
+
+ private static LdapGroupsProvider.Context createContextForExampleServer(String userName) {
+ return createContext("example", userName);
+ }
+
+ private static LdapGroupsProvider.Context createContextForInfoSupportServer(String userName) {
+ return createContext("infosupport", userName);
+ }
+
+ private static LdapGroupsProvider.Context createContext(String serverName, String userName) {
+ return new LdapGroupsProvider.Context(serverName, userName, mock(HttpServletRequest.class));
+ }
+
}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapUsersProviderTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapUsersProviderTest.java
index 79f601b520f..cde909e415b 100644
--- a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapUsersProviderTest.java
+++ b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapUsersProviderTest.java
@@ -19,12 +19,14 @@
*/
package org.sonar.auth.ldap;
+import javax.servlet.http.HttpServletRequest;
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;
+import static org.mockito.Mockito.mock;
public class DefaultLdapUsersProviderTest {
/**
@@ -32,7 +34,7 @@ public class DefaultLdapUsersProviderTest {
*/
public static final String USERS_EXAMPLE_ORG_LDIF = "/users.example.org.ldif";
/**
- * A reference to an aditional ldif file.
+ * A reference to an additional ldif file.
*/
public static final String USERS_INFOSUPPORT_COM_LDIF = "/users.infosupport.com.ldif";
@@ -42,35 +44,54 @@ public class DefaultLdapUsersProviderTest {
public static LdapServer infosupportServer = new LdapServer(USERS_INFOSUPPORT_COM_LDIF, "infosupport.com", "dc=infosupport,dc=com");
@Test
- public void test() {
+ public void test_user_from_first_server() {
MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
DefaultLdapUsersProvider usersProvider = new DefaultLdapUsersProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings());
- LdapUserDetails details;
-
- details = usersProvider.getUserDetails("godin");
+ LdapUserDetails details = usersProvider.doGetUserDetails(createContext("example", "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()).isEmpty();
-
- details = usersProvider.getUserDetails("notfound");
- assertThat(details).isNull();
+ @Test
+ public void test_user_from_second_server() {
+ MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
+ LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
+ DefaultLdapUsersProvider usersProvider = new DefaultLdapUsersProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings());
- details = usersProvider.getUserDetails("robby");
+ LdapUserDetails details = usersProvider.doGetUserDetails(createContext("infosupport", "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");
}
+ @Test
+ public void test_user_on_multiple_servers() {
+ MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
+ LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
+ DefaultLdapUsersProvider usersProvider = new DefaultLdapUsersProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings());
+
+ LdapUserDetails detailsExample = usersProvider.doGetUserDetails(createContext("example", "tester"));
+ assertThat(detailsExample.getName()).isEqualTo("Tester Testerovich");
+ assertThat(detailsExample.getEmail()).isEqualTo("tester@example.org");
+
+ LdapUserDetails detailsInfoSupport = usersProvider.doGetUserDetails(createContext("infosupport", "tester"));
+ assertThat(detailsInfoSupport.getName()).isEqualTo("Tester Testerovich Testerov");
+ assertThat(detailsInfoSupport.getEmail()).isEqualTo("tester@example2.org");
+ }
+
+ @Test
+ public void test_user_doesnt_exist() {
+ MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
+ LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
+ DefaultLdapUsersProvider usersProvider = new DefaultLdapUsersProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings());
+
+ LdapUserDetails details = usersProvider.doGetUserDetails(createContext("example", "notfound"));
+ assertThat(details).isNull();
+ }
+
+ private static LdapUsersProvider.Context createContext(String serverKey, String username) {
+ return new LdapUsersProvider.Context(serverKey, username, mock(HttpServletRequest.class));
+ }
}
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
index ac08ebe08fc..b0e49f746d3 100644
--- 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
@@ -21,7 +21,7 @@ package org.sonar.auth.ldap;
import java.io.File;
import javax.servlet.http.HttpServletRequest;
-import org.junit.Assert;
+import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.mockito.Mockito;
@@ -29,6 +29,7 @@ import org.sonar.api.config.internal.MapSettings;
import org.sonar.auth.ldap.server.LdapServer;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class KerberosTest {
@@ -39,33 +40,58 @@ public class KerberosTest {
@ClassRule
public static LdapServer server = new LdapServer("/krb.ldif");
- @Test
- public void test() {
- MapSettings settings = configure();
- LdapRealm ldapRealm = new LdapRealm(new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery()));
+ LdapAuthenticator authenticator;
+ LdapRealm ldapRealm;
+ @Before
+ public void before() {
+ MapSettings settings = configure();
+ ldapRealm = new LdapRealm(new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery()));
ldapRealm.init();
- assertThat(ldapRealm.doGetAuthenticator().doAuthenticate(new LdapAuthenticator.Context("Godin@EXAMPLE.ORG", "wrong_user_password", Mockito.mock(HttpServletRequest.class))))
- .isFalse();
- assertThat(ldapRealm.doGetAuthenticator().doAuthenticate(new LdapAuthenticator.Context("Godin@EXAMPLE.ORG", "user_password", Mockito.mock(HttpServletRequest.class)))).isTrue();
+ authenticator = ldapRealm.doGetAuthenticator();
+ }
+
+ @Test
+ public void test_wrong_password() {
+ LdapAuthenticator.Context wrongPasswordContext = new LdapAuthenticator.Context("Godin@EXAMPLE.ORG", "wrong_user_password", Mockito.mock(HttpServletRequest.class));
+ assertThat(authenticator.doAuthenticate(wrongPasswordContext).isSuccess()).isFalse();
+ }
+
+ @Test
+ public void test_correct_password() {
+
+ LdapAuthenticator.Context correctPasswordContext = new LdapAuthenticator.Context("Godin@EXAMPLE.ORG", "user_password", Mockito.mock(HttpServletRequest.class));
+ assertThat(authenticator.doAuthenticate(correctPasswordContext).isSuccess()).isTrue();
+
+ }
+
+ @Test
+ public void test_default_realm() {
+
// Using default realm from krb5.conf:
- assertThat(ldapRealm.doGetAuthenticator().doAuthenticate(new LdapAuthenticator.Context("Godin", "user_password", Mockito.mock(HttpServletRequest.class)))).isTrue();
+ LdapAuthenticator.Context defaultRealmContext = new LdapAuthenticator.Context("Godin", "user_password", Mockito.mock(HttpServletRequest.class));
+ assertThat(authenticator.doAuthenticate(defaultRealmContext).isSuccess()).isTrue();
+ }
- assertThat(ldapRealm.getGroupsProvider().doGetGroups(new LdapGroupsProvider.Context("godin", Mockito.mock(HttpServletRequest.class)))).containsOnly("sonar-users");
+ @Test
+ public void test_groups() {
+ LdapGroupsProvider groupsProvider = ldapRealm.getGroupsProvider();
+ LdapGroupsProvider.Context groupsContext = new LdapGroupsProvider.Context("default", "godin", Mockito.mock(HttpServletRequest.class));
+ assertThat(groupsProvider.doGetGroups(groupsContext))
+ .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.asConfig(), new LdapAutodiscovery()));
- try {
- ldapRealm.init();
- Assert.fail();
- } catch (LdapException e) {
- assertThat(e.getMessage()).isEqualTo("Unable to open LDAP connection");
- }
+ LdapRealm wrongPasswordRealm = new LdapRealm(new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery()));
+
+ assertThatThrownBy(wrongPasswordRealm::init)
+ .isInstanceOf(LdapException.class)
+ .hasMessage("Unable to open LDAP connection");
+
}
private static MapSettings configure() {
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
index ba1656009ef..a194ace6ac3 100644
--- 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
@@ -27,7 +27,7 @@ import org.sonar.api.config.internal.MapSettings;
import org.sonar.auth.ldap.server.LdapServer;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class LdapRealmTest {
@@ -49,32 +49,29 @@ public class LdapRealmTest {
public void noConnection() {
MapSettings settings = new MapSettings()
.setProperty("ldap.url", "ldap://no-such-host")
- .setProperty("ldap.group.baseDn", "cn=groups,dc=example,dc=org");
+ .setProperty("ldap.group.baseDn", "cn=groups,dc=example,dc=org")
+ .setProperty("ldap.user.baseDn", "cn=users,dc=example,dc=org");
LdapRealm realm = new LdapRealm(new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery()));
- 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");
- }
+ assertThatThrownBy(realm::init).isInstanceOf(LdapException.class).hasMessage("Unable to open LDAP connection");
+
assertThat(realm.doGetAuthenticator()).isInstanceOf(DefaultLdapAuthenticator.class);
- assertThat(realm.getUsersProvider()).isInstanceOf(LdapUsersProvider.class).isInstanceOf(DefaultLdapUsersProvider.class);
- assertThat(realm.getGroupsProvider()).isInstanceOf(LdapGroupsProvider.class).isInstanceOf(DefaultLdapGroupsProvider.class);
- try {
- LdapUsersProvider.Context userContext = new DefaultLdapUsersProvider.Context("tester", Mockito.mock(HttpServletRequest.class));
- realm.getUsersProvider().doGetUserDetails(userContext);
- 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 {
- LdapGroupsProvider.Context groupsContext = new DefaultLdapGroupsProvider.Context("tester", Mockito.mock(HttpServletRequest.class));
- realm.getGroupsProvider().doGetGroups(groupsContext);
- 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");
- }
+ LdapUsersProvider usersProvider = realm.getUsersProvider();
+ assertThat(usersProvider).isInstanceOf(LdapUsersProvider.class).isInstanceOf(DefaultLdapUsersProvider.class);
+
+ LdapGroupsProvider groupsProvider = realm.getGroupsProvider();
+ assertThat(groupsProvider).isInstanceOf(LdapGroupsProvider.class).isInstanceOf(DefaultLdapGroupsProvider.class);
+
+ LdapUsersProvider.Context userContext = new DefaultLdapUsersProvider.Context("<default>", "tester", Mockito.mock(HttpServletRequest.class));
+ assertThatThrownBy(() -> usersProvider.doGetUserDetails(userContext))
+ .isInstanceOf(LdapException.class)
+ .hasMessage("Unable to retrieve details for user tester and server key <default>: No user mapping found.");
+
+ LdapGroupsProvider.Context groupsContext = new DefaultLdapGroupsProvider.Context("default", "tester", Mockito.mock(HttpServletRequest.class));
+ assertThatThrownBy(() -> groupsProvider.doGetGroups(groupsContext))
+ .isInstanceOf(LdapException.class)
+ .hasMessage("Unable to retrieve groups for user tester in server with key <default>");
+
}
}
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
index 721fc546a36..d584cf21476 100644
--- 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
@@ -24,6 +24,7 @@ import java.util.Enumeration;
import java.util.Map;
import javax.naming.NamingException;
import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
@@ -57,12 +58,19 @@ public class LdapSearchTest {
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);
+ assertThat(enumerationToArrayList(search.find()))
+ .extracting(SearchResult::getName)
+ .containsExactlyInAnyOrder(
+ "cn=Without Email,ou=users",
+ "cn=Evgeny Mandrikov,ou=users",
+ "cn=Tester Testerovich,ou=users",
+ "cn=duplicated,ou=users"
+ );
+
assertThatThrownBy(search::findUnique)
.isInstanceOf(NamingException.class)
.hasMessage("Non unique result for " + search.toString());
-
}
@Test
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
index 3dc462afbb0..fe3341c92f9 100644
--- a/server/sonar-auth-ldap/src/test/resources/users.example.org.ldif
+++ b/server/sonar-auth-ldap/src/test/resources/users.example.org.ldif
@@ -23,6 +23,20 @@ cn: bind
uid: sonar
userpassword: bindpassword
+# Duplicated user on infosupport ldap
+dn: cn=duplicated,ou=users,dc=example,dc=org
+objectClass: organizationalPerson
+objectClass: person
+objectClass: extensibleObject
+objectClass: uidObject
+objectClass: inetOrgPerson
+objectClass: top
+cn: duplicated
+uid: duplicated
+sn: Duplicated
+mail: duplicated@example.org
+userpassword: duplicated
+
# Typical user
dn: cn=Evgeny Mandrikov,ou=users,dc=example,dc=org
objectClass: organizationalPerson
@@ -82,6 +96,7 @@ 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
+uniqueMember: cn=duplicated,ou=users,dc=example,dc=org
# sonar-developers
dn: cn=sonar-developers,ou=groups,dc=example,dc=org
@@ -95,4 +110,4 @@ objectclass: posixGroup
objectclass: top
cn: linux-users
gidNumber: 10000
-memberUid: godin \ No newline at end of file
+memberUid: godin
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
index a08174bf72b..d57addd4bf3 100644
--- a/server/sonar-auth-ldap/src/test/resources/users.infosupport.com.ldif
+++ b/server/sonar-auth-ldap/src/test/resources/users.infosupport.com.ldif
@@ -23,6 +23,20 @@ cn: bind
uid: sonar
userpassword: bindpassword
+# Duplicated user on example ldap
+dn: cn=duplicated,ou=users,dc=infosupport,dc=com
+objectClass: organizationalPerson
+objectClass: person
+objectClass: extensibleObject
+objectClass: uidObject
+objectClass: inetOrgPerson
+objectClass: top
+cn: duplicated
+uid: duplicated
+sn: Duplicated
+mail: duplicated@infosupport.com
+userpassword: duplicated
+
# Typical user
dn: cn=Robby Developer,ou=users,dc=infosupport,dc=com
objectClass: organizationalPerson
@@ -53,6 +67,22 @@ mail: tester@infosupport.com
uid: testerInfo
userpassword: secret2
+# User repeated on multiple servers
+dn: cn=Tester Testerovich Testerov,ou=users,dc=infosupport,dc=com
+objectClass: organizationalPerson
+objectClass: person
+objectClass: extensibleObject
+objectClass: uidObject
+objectClass: inetOrgPerson
+objectClass: top
+cn: Tester Testerovich Testerov
+givenname: Tester
+sn: Testerovich
+mail: tester@example2.org
+uid: tester
+userpassword: secret3
+
+
# Special case which can cause NPE
dn: cn=Without Email,ou=users,dc=infosupport,dc=com
objectClass: organizationalPerson
@@ -88,6 +118,7 @@ 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
+uniqueMember: cn=duplicated,ou=users,dc=infosupport,dc=com
# linux-users
dn: cn=linux-users,ou=groups,dc=infosupport,dc=com
@@ -95,4 +126,4 @@ objectclass: posixGroup
objectclass: top
cn: linux-users
gidNumber: 10000
-memberUid: robby \ No newline at end of file
+memberUid: robby
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/LdapCredentialsAuthentication.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/LdapCredentialsAuthentication.java
index adbbe639ff9..b41295d0562 100644
--- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/LdapCredentialsAuthentication.java
+++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/LdapCredentialsAuthentication.java
@@ -30,6 +30,7 @@ import org.sonar.api.server.authentication.IdentityProvider;
import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
+import org.sonar.auth.ldap.LdapAuthenticationResult;
import org.sonar.auth.ldap.LdapAuthenticator;
import org.sonar.auth.ldap.LdapGroupsProvider;
import org.sonar.auth.ldap.LdapRealm;
@@ -40,7 +41,6 @@ import org.sonar.process.ProcessProperties;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationEvent.Source;
import org.sonar.server.authentication.event.AuthenticationException;
-import org.sonar.server.user.ExternalIdentity;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.apache.commons.lang.StringUtils.trimToNull;
@@ -90,25 +90,26 @@ public class LdapCredentialsAuthentication {
private UserDto doAuthenticate(Credentials credentials, HttpServletRequest request, AuthenticationEvent.Method method) {
try {
- LdapUsersProvider.Context ldapUsersProviderContext = new LdapUsersProvider.Context(credentials.getLogin(), request);
- LdapUserDetails details = ldapUsersProvider.doGetUserDetails(ldapUsersProviderContext);
- if (details == null) {
+ LdapAuthenticator.Context ldapAuthenticatorContext = new LdapAuthenticator.Context(credentials.getLogin(), credentials.getPassword().orElse(null), request);
+ LdapAuthenticationResult authenticationResult = ldapAuthenticator.doAuthenticate(ldapAuthenticatorContext);
+ if (!authenticationResult.isSuccess()) {
throw AuthenticationException.newBuilder()
.setSource(realmEventSource(method))
.setLogin(credentials.getLogin())
- .setMessage("No user details")
+ .setMessage("Realm returned authenticate=false")
.build();
}
- LdapAuthenticator.Context ldapAuthenticatorContext = new LdapAuthenticator.Context(credentials.getLogin(), credentials.getPassword().orElse(null), request);
- boolean status = ldapAuthenticator.doAuthenticate(ldapAuthenticatorContext);
- if (!status) {
+
+ LdapUsersProvider.Context ldapUsersProviderContext = new LdapUsersProvider.Context(authenticationResult.getServerKey(), credentials.getLogin(), request);
+ LdapUserDetails ldapUserDetails = ldapUsersProvider.doGetUserDetails(ldapUsersProviderContext);
+ if (ldapUserDetails == null) {
throw AuthenticationException.newBuilder()
.setSource(realmEventSource(method))
.setLogin(credentials.getLogin())
- .setMessage("Realm returned authenticate=false")
+ .setMessage("No user details")
.build();
}
- UserDto userDto = synchronize(credentials.getLogin(), details, request, method);
+ UserDto userDto = synchronize(credentials.getLogin(), authenticationResult.getServerKey(), ldapUserDetails, request, method);
authenticationEvent.loginSuccess(request, credentials.getLogin(), realmEventSource(method));
return userDto;
} catch (AuthenticationException e) {
@@ -128,21 +129,21 @@ public class LdapCredentialsAuthentication {
return Source.realm(method, "ldap");
}
- private UserDto synchronize(String userLogin, LdapUserDetails details, HttpServletRequest request, AuthenticationEvent.Method method) {
- String name = details.getName();
+ private UserDto synchronize(String userLogin, String serverKey, LdapUserDetails userDetails, HttpServletRequest request, AuthenticationEvent.Method method) {
+ String name = userDetails.getName();
UserIdentity.Builder userIdentityBuilder = UserIdentity.builder()
.setName(isEmpty(name) ? userLogin : name)
- .setEmail(trimToNull(details.getEmail()))
+ .setEmail(trimToNull(userDetails.getEmail()))
.setProviderLogin(userLogin);
if (ldapGroupsProvider != null) {
- LdapGroupsProvider.Context context = new LdapGroupsProvider.Context(userLogin, request);
+ LdapGroupsProvider.Context context = new LdapGroupsProvider.Context(serverKey, userLogin, request);
Collection<String> groups = ldapGroupsProvider.doGetGroups(context);
userIdentityBuilder.setGroups(new HashSet<>(groups));
}
return userRegistrar.register(
UserRegistration.builder()
.setUserIdentity(userIdentityBuilder.build())
- .setProvider(new ExternalIdentityProvider())
+ .setProvider(new LdapIdentityProvider(serverKey))
.setSource(realmEventSource(method))
.build());
}
@@ -154,15 +155,22 @@ public class LdapCredentialsAuthentication {
return credentials;
}
- private static class ExternalIdentityProvider implements IdentityProvider {
+ private static class LdapIdentityProvider implements IdentityProvider {
+
+ private final String key;
+
+ private LdapIdentityProvider(String ldapServerKey) {
+ this.key = LDAP_SECURITY_REALM + "_" + ldapServerKey;
+ }
+
@Override
public String getKey() {
- return ExternalIdentity.SQ_AUTHORITY;
+ return key;
}
@Override
public String getName() {
- return ExternalIdentity.SQ_AUTHORITY;
+ return getKey();
}
@Override
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java
index 1baa5cf5fa8..5ad18c321d0 100644
--- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java
+++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java
@@ -40,7 +40,7 @@ import org.sonar.db.DbSession;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserGroupDto;
-import org.sonar.server.authentication.event.AuthenticationEvent;
+import org.sonar.server.authentication.event.AuthenticationEvent.Source;
import org.sonar.server.authentication.event.AuthenticationException;
import org.sonar.server.user.ExternalIdentity;
import org.sonar.server.user.NewUser;
@@ -51,11 +51,15 @@ import org.sonar.server.usergroups.DefaultGroupFinder;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+import static org.sonar.server.user.UserSession.IdentityProvider.SONARQUBE;
public class UserRegistrarImpl implements UserRegistrar {
+ public static final String SQ_AUTHORITY = "sonarqube";
+ public static final String LDAP_PROVIDER_PREFIX = "LDAP_";
private static final Logger LOGGER = Loggers.get(UserRegistrarImpl.class);
- private static final String SQ_AUTHORITY = "sonarqube";
+ private static final String GITHUB_PROVIDER = "github";
+ private static final String GITLAB_PROVIDER = "gitlab";
private final DbClient dbClient;
private final UserUpdater userUpdater;
@@ -82,29 +86,50 @@ public class UserRegistrarImpl implements UserRegistrar {
}
@CheckForNull
- private UserDto getUser(DbSession dbSession, UserIdentity userIdentity, IdentityProvider provider, AuthenticationEvent.Source source) {
+ private UserDto getUser(DbSession dbSession, UserIdentity userIdentity, IdentityProvider provider, Source source) {
// First, try to authenticate using the external ID
- UserDto user = dbClient.userDao().selectByExternalIdAndIdentityProvider(dbSession, getProviderIdOrProviderLogin(userIdentity), provider.getKey());
- if (user != null) {
- return user;
- }
-
// Then, try with the external login, for instance when external ID has changed or is not used by the provider
- user = dbClient.userDao().selectByExternalLoginAndIdentityProvider(dbSession, userIdentity.getProviderLogin(), provider.getKey());
- if (user == null) {
- return null;
- }
- // all gitlab users have an external ID, so the other two authentication methods should never be used
- if (provider.getKey().equals("gitlab")) {
+ return retrieveUserByExternalIdAndIdentityProvider(dbSession, userIdentity, provider)
+ .or(() -> retrieveUserByExternalLoginAndIdentityProvider(dbSession, userIdentity, provider, source))
+ .or(() -> retrieveUserByLogin(dbSession, userIdentity, provider))
+ .orElse(null);
+ }
+
+ private Optional<UserDto> retrieveUserByExternalIdAndIdentityProvider(DbSession dbSession, UserIdentity userIdentity, IdentityProvider provider) {
+ return Optional.ofNullable(dbClient.userDao().selectByExternalIdAndIdentityProvider(dbSession, getProviderIdOrProviderLogin(userIdentity), provider.getKey()));
+ }
+
+ private Optional<UserDto> retrieveUserByExternalLoginAndIdentityProvider(DbSession dbSession, UserIdentity userIdentity, IdentityProvider provider, Source source) {
+ return Optional.ofNullable(dbClient.userDao().selectByExternalLoginAndIdentityProvider(dbSession, userIdentity.getProviderLogin(), provider.getKey()))
+ .filter(user -> validateAlmSpecificData(user, provider.getKey(), userIdentity, source));
+ }
+
+ private Optional<UserDto> retrieveUserByLogin(DbSession dbSession, UserIdentity userIdentity, IdentityProvider provider) {
+ return Optional.ofNullable(dbClient.userDao().selectByLogin(dbSession, userIdentity.getProviderLogin()))
+ .filter(user -> shouldPerformLdapIdentityProviderMigration(user, provider));
+ }
+
+ private static boolean shouldPerformLdapIdentityProviderMigration(UserDto user, IdentityProvider identityProvider) {
+ boolean isLdapIdentityProvider = identityProvider.getKey().startsWith(LDAP_PROVIDER_PREFIX);
+ boolean hasSonarQubeExternalIdentityProvider = SONARQUBE.getKey().equals(user.getExternalIdentityProvider());
+
+ return isLdapIdentityProvider && hasSonarQubeExternalIdentityProvider && !user.isLocal();
+ }
+
+ private static boolean validateAlmSpecificData(UserDto user, String key, UserIdentity userIdentity, Source source) {
+ // All gitlab users have an external ID, so the other two authentication methods should never be used
+ if (GITLAB_PROVIDER.equals(key)) {
throw failAuthenticationException(userIdentity, source);
- } else if (provider.getKey().equals("github")) {
+ }
+
+ if (GITHUB_PROVIDER.equals(key)) {
validateEmailToAvoidLoginRecycling(userIdentity, user, source);
}
- return user;
+ return true;
}
- private static void validateEmailToAvoidLoginRecycling(UserIdentity userIdentity, UserDto user, AuthenticationEvent.Source source) {
+ private static void validateEmailToAvoidLoginRecycling(UserIdentity userIdentity, UserDto user, Source source) {
String dbEmail = user.getEmail();
if (dbEmail == null) {
@@ -119,12 +144,12 @@ public class UserRegistrarImpl implements UserRegistrar {
}
}
- private static AuthenticationException failAuthenticationException(UserIdentity userIdentity, AuthenticationEvent.Source source) {
+ private static AuthenticationException failAuthenticationException(UserIdentity userIdentity, Source source) {
String message = String.format("Failed to authenticate with login '%s'", userIdentity.getProviderLogin());
return authException(userIdentity, source, message, message);
}
- private static AuthenticationException authException(UserIdentity userIdentity, AuthenticationEvent.Source source, String message, String publicMessage) {
+ private static AuthenticationException authException(UserIdentity userIdentity, Source source, String message, String publicMessage) {
return AuthenticationException.newBuilder()
.setSource(source)
.setLogin(userIdentity.getProviderLogin())
@@ -215,7 +240,7 @@ public class UserRegistrarImpl implements UserRegistrar {
.filter(Objects::nonNull)
// user should be member of default group only when organizations are disabled, as the IdentityProvider API doesn't handle yet
// organizations
- .filter(group -> !defaultGroup.isPresent() || !group.getUuid().equals(defaultGroup.get().getUuid()))
+ .filter(group -> defaultGroup.isEmpty() || !group.getUuid().equals(defaultGroup.get().getUuid()))
.forEach(groupDto -> {
LOGGER.debug("Removing group '{}' from user '{}'", groupDto.getName(), userDto.getLogin());
dbClient.userGroupDao().delete(dbSession, groupDto, userDto);
diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/IdentityProviderRepositoryTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/IdentityProviderRepositoryTest.java
index 442a911ddc4..2133a170de1 100644
--- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/IdentityProviderRepositoryTest.java
+++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/IdentityProviderRepositoryTest.java
@@ -29,18 +29,17 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class IdentityProviderRepositoryTest {
-
- static IdentityProvider GITHUB = new TestIdentityProvider()
+ private static IdentityProvider GITHUB = new TestIdentityProvider()
.setKey("github")
.setName("Github")
.setEnabled(true);
- static IdentityProvider BITBUCKET = new TestIdentityProvider()
+ private static IdentityProvider BITBUCKET = new TestIdentityProvider()
.setKey("bitbucket")
.setName("Bitbucket")
.setEnabled(true);
- static IdentityProvider DISABLED = new TestIdentityProvider()
+ private static IdentityProvider DISABLED = new TestIdentityProvider()
.setKey("disabled")
.setName("Disabled")
.setEnabled(false);
diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/LdapCredentialsAuthenticationTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/LdapCredentialsAuthenticationTest.java
index d4ce1980f10..d3c1edbaf4a 100644
--- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/LdapCredentialsAuthenticationTest.java
+++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/LdapCredentialsAuthenticationTest.java
@@ -19,6 +19,7 @@
*/
package org.sonar.server.authentication;
+import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.junit.Before;
import org.junit.Test;
@@ -26,6 +27,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.server.authentication.IdentityProvider;
+import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.auth.ldap.LdapAuthenticator;
import org.sonar.auth.ldap.LdapGroupsProvider;
import org.sonar.auth.ldap.LdapRealm;
@@ -40,12 +43,15 @@ import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.refEq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
+import static org.sonar.auth.ldap.LdapAuthenticationResult.failed;
+import static org.sonar.auth.ldap.LdapAuthenticationResult.success;
import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC;
import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC_TOKEN;
@@ -55,10 +61,27 @@ public class LdapCredentialsAuthenticationTest {
private static final String LOGIN = "LOGIN";
private static final String PASSWORD = "PASSWORD";
- private static final String REALM_NAME = "ldap";
+ private static final String LDAP_SECURITY_REALM_NAME = "ldap";
+ private static final String SERVER_KEY = "superServerKey";
+ private static final String EXPECTED_EXTERNAL_PROVIDER_ID = "LDAP_superServerKey";
- private MapSettings settings = new MapSettings();
- private TestUserRegistrar userRegistrar = new TestUserRegistrar();
+ private static final LdapUserDetails LDAP_USER_DETAILS;
+
+ static {
+ LDAP_USER_DETAILS = new LdapUserDetails();
+ LDAP_USER_DETAILS.setName("name");
+ }
+
+ private static final LdapUserDetails LDAP_USER_DETAILS_WITH_EMAIL;
+
+ static {
+ LDAP_USER_DETAILS_WITH_EMAIL = new LdapUserDetails();
+ LDAP_USER_DETAILS_WITH_EMAIL.setName("name");
+ LDAP_USER_DETAILS_WITH_EMAIL.setEmail("email");
+ }
+
+ private final MapSettings settings = new MapSettings();
+ private final TestUserRegistrar userRegistrar = new TestUserRegistrar();
@Mock
private AuthenticationEvent authenticationEvent;
@@ -94,114 +117,112 @@ public class LdapCredentialsAuthenticationTest {
when(ldapRealm.getGroupsProvider()).thenReturn(null);
underTest = new LdapCredentialsAuthentication(settings.asConfig(), userRegistrar, authenticationEvent, ldapRealm);
- when(ldapAuthenticator.doAuthenticate(any(LdapAuthenticator.Context.class))).thenReturn(true);
- LdapUserDetails userDetails = new LdapUserDetails();
- userDetails.setName("name");
- userDetails.setEmail("email");
- when(ldapUsersProvider.doGetUserDetails(any(LdapUsersProvider.Context.class))).thenReturn(userDetails);
+ LdapAuthenticator.Context authenticationContext = new LdapAuthenticator.Context(LOGIN, PASSWORD, request);
+ when(ldapAuthenticator.doAuthenticate(refEq(authenticationContext))).thenReturn(success(SERVER_KEY));
+
+ LdapUsersProvider.Context expectedUserContext = new LdapUsersProvider.Context(SERVER_KEY, LOGIN, request);
+ when(ldapUsersProvider.doGetUserDetails(refEq(expectedUserContext))).thenReturn(LDAP_USER_DETAILS_WITH_EMAIL);
underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
+ UserIdentity identity = userRegistrar.getAuthenticatorParameters().getUserIdentity();
assertThat(userRegistrar.isAuthenticated()).isTrue();
- assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo(LOGIN);
- assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().getProviderId()).isNull();
- assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().getName()).isEqualTo("name");
- assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().getEmail()).isEqualTo("email");
- assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().shouldSyncGroups()).isFalse();
- verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
+ assertThat(identity.getProviderLogin()).isEqualTo(LOGIN);
+ assertThat(identity.getProviderId()).isNull();
+ assertThat(identity.getName()).isEqualTo("name");
+ assertThat(identity.getEmail()).isEqualTo("email");
+ assertThat(identity.shouldSyncGroups()).isFalse();
+
+ verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
verify(ldapRealm).init();
}
@Test
- public void authenticate_with_sonarqube_identity_provider() {
- when(ldapAuthenticator.doAuthenticate(any(LdapAuthenticator.Context.class))).thenReturn(true);
- LdapUserDetails userDetails = new LdapUserDetails();
- userDetails.setName("name");
- userDetails.setEmail("email");
- when(ldapUsersProvider.doGetUserDetails(any(LdapUsersProvider.Context.class))).thenReturn(userDetails);
-
- underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
+ public void authenticate_with_ldap() {
+ executeAuthenticate(LDAP_USER_DETAILS_WITH_EMAIL);
+ IdentityProvider provider = userRegistrar.getAuthenticatorParameters().getProvider();
assertThat(userRegistrar.isAuthenticated()).isTrue();
- assertThat(userRegistrar.getAuthenticatorParameters().getProvider().getKey()).isEqualTo("sonarqube");
- assertThat(userRegistrar.getAuthenticatorParameters().getProvider().getName()).isEqualTo("sonarqube");
- assertThat(userRegistrar.getAuthenticatorParameters().getProvider().getDisplay()).isNull();
- assertThat(userRegistrar.getAuthenticatorParameters().getProvider().isEnabled()).isTrue();
- verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
+ assertThat(provider.getKey()).isEqualTo(EXPECTED_EXTERNAL_PROVIDER_ID);
+ assertThat(provider.getName()).isEqualTo(EXPECTED_EXTERNAL_PROVIDER_ID);
+ assertThat(provider.getDisplay()).isNull();
+ assertThat(provider.isEnabled()).isTrue();
+ verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
verify(ldapRealm).init();
}
@Test
public void login_is_used_when_no_name_provided() {
- when(ldapAuthenticator.doAuthenticate(any(LdapAuthenticator.Context.class))).thenReturn(true);
LdapUserDetails userDetails = new LdapUserDetails();
userDetails.setEmail("email");
- when(ldapUsersProvider.doGetUserDetails(any(LdapUsersProvider.Context.class))).thenReturn(userDetails);
- underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
+ executeAuthenticate(userDetails);
- assertThat(userRegistrar.getAuthenticatorParameters().getProvider().getName()).isEqualTo("sonarqube");
- verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
+ assertThat(userRegistrar.getAuthenticatorParameters().getProvider().getName()).isEqualTo(EXPECTED_EXTERNAL_PROVIDER_ID);
+ verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
}
@Test
public void authenticate_with_group_sync() {
- when(ldapGroupsProvider.doGetGroups(any(LdapGroupsProvider.Context.class))).thenReturn(asList("group1", "group2"));
- executeAuthenticate();
+ LdapGroupsProvider.Context expectedGroupContext = new LdapGroupsProvider.Context(SERVER_KEY, LOGIN, request);
+ when(ldapGroupsProvider.doGetGroups(refEq(expectedGroupContext))).thenReturn(asList("group1", "group2"));
+
+ executeAuthenticate(LDAP_USER_DETAILS);
assertThat(userRegistrar.isAuthenticated()).isTrue();
assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().shouldSyncGroups()).isTrue();
- verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
+ verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
}
@Test
public void use_login_if_user_details_contains_no_name() {
- when(ldapAuthenticator.doAuthenticate(any(LdapAuthenticator.Context.class))).thenReturn(true);
+
LdapUserDetails userDetails = new LdapUserDetails();
userDetails.setName(null);
- when(ldapUsersProvider.doGetUserDetails(any(LdapUsersProvider.Context.class))).thenReturn(userDetails);
- underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
+ executeAuthenticate(userDetails);
assertThat(userRegistrar.isAuthenticated()).isTrue();
assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().getName()).isEqualTo(LOGIN);
- verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
+ verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
}
@Test
public void use_downcase_login() {
settings.setProperty("sonar.authenticator.downcase", true);
- executeAuthenticate("LOGIN");
+ mockLdapAuthentication(LOGIN.toLowerCase());
+ mockLdapUserDetailsRetrieval(LOGIN.toLowerCase(), LDAP_USER_DETAILS);
+
+ underTest.authenticate(new Credentials(LOGIN.toLowerCase(), PASSWORD), request, BASIC);
assertThat(userRegistrar.isAuthenticated()).isTrue();
assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("login");
- verify(authenticationEvent).loginSuccess(request, "login", Source.realm(BASIC, REALM_NAME));
+ verify(authenticationEvent).loginSuccess(request, "login", Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
}
@Test
public void does_not_user_downcase_login() {
settings.setProperty("sonar.authenticator.downcase", false);
- executeAuthenticate("LoGiN");
+ mockLdapAuthentication("LoGiN");
+ mockLdapUserDetailsRetrieval("LoGiN", LDAP_USER_DETAILS);
+
+ underTest.authenticate(new Credentials("LoGiN", PASSWORD), request, BASIC);
assertThat(userRegistrar.isAuthenticated()).isTrue();
assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("LoGiN");
- verify(authenticationEvent).loginSuccess(request, "LoGiN", Source.realm(BASIC, REALM_NAME));
+ verify(authenticationEvent).loginSuccess(request, "LoGiN", Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
}
@Test
public void fail_to_authenticate_when_user_details_are_null() {
- when(ldapAuthenticator.doAuthenticate(any(LdapAuthenticator.Context.class))).thenReturn(true);
-
- when(ldapUsersProvider.doGetUserDetails(any(LdapUsersProvider.Context.class))).thenReturn(null);
- Credentials credentials = new Credentials(LOGIN, PASSWORD);
- assertThatThrownBy(() -> underTest.authenticate(credentials, request, BASIC))
+ assertThatThrownBy(() -> executeAuthenticate(null))
.hasMessage("No user details")
.isInstanceOf(AuthenticationException.class)
- .hasFieldOrPropertyWithValue("source", Source.realm(BASIC, REALM_NAME))
+ .hasFieldOrPropertyWithValue("source", Source.realm(BASIC, LDAP_SECURITY_REALM_NAME))
.hasFieldOrPropertyWithValue("login", LOGIN);
verifyNoInteractions(authenticationEvent);
@@ -209,17 +230,18 @@ public class LdapCredentialsAuthenticationTest {
@Test
public void fail_to_authenticate_when_external_authentication_fails() {
- when(ldapUsersProvider.doGetUserDetails(any(LdapUsersProvider.Context.class))).thenReturn(new LdapUserDetails());
- when(ldapAuthenticator.doAuthenticate(any(LdapAuthenticator.Context.class))).thenReturn(false);
+ LdapAuthenticator.Context authenticationContext = new LdapAuthenticator.Context(LOGIN, PASSWORD, request);
+ when(ldapAuthenticator.doAuthenticate(refEq(authenticationContext))).thenReturn(failed());
Credentials credentials = new Credentials(LOGIN, PASSWORD);
assertThatThrownBy(() -> underTest.authenticate(credentials, request, BASIC))
.hasMessage("Realm returned authenticate=false")
.isInstanceOf(AuthenticationException.class)
- .hasFieldOrPropertyWithValue("source", Source.realm(BASIC, REALM_NAME))
+ .hasFieldOrPropertyWithValue("source", Source.realm(BASIC, LDAP_SECURITY_REALM_NAME))
.hasFieldOrPropertyWithValue("login", LOGIN);
+ verifyNoInteractions(ldapUsersProvider);
verifyNoInteractions(authenticationEvent);
}
@@ -229,15 +251,14 @@ public class LdapCredentialsAuthenticationTest {
String expectedMessage = "emulating exception in doAuthenticate";
doThrow(new IllegalArgumentException(expectedMessage)).when(ldapAuthenticator).doAuthenticate(any(LdapAuthenticator.Context.class));
- when(ldapUsersProvider.doGetUserDetails(any(LdapUsersProvider.Context.class))).thenReturn(new LdapUserDetails());
-
Credentials credentials = new Credentials(LOGIN, PASSWORD);
assertThatThrownBy(() -> underTest.authenticate(credentials, request, BASIC_TOKEN))
.hasMessage(expectedMessage)
.isInstanceOf(AuthenticationException.class)
- .hasFieldOrPropertyWithValue("source", Source.realm(BASIC_TOKEN, REALM_NAME))
+ .hasFieldOrPropertyWithValue("source", Source.realm(BASIC_TOKEN, LDAP_SECURITY_REALM_NAME))
.hasFieldOrPropertyWithValue("login", LOGIN);
+ verifyNoInteractions(ldapUsersProvider);
verifyNoInteractions(authenticationEvent);
}
@@ -252,16 +273,20 @@ public class LdapCredentialsAuthenticationTest {
verifyNoInteractions(ldapRealm);
}
- private void executeAuthenticate() {
- executeAuthenticate(LOGIN);
+ private void executeAuthenticate(@Nullable LdapUserDetails userDetails) {
+ mockLdapAuthentication(LOGIN);
+ mockLdapUserDetailsRetrieval(LOGIN, userDetails);
+ underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
}
- private void executeAuthenticate(String login) {
- when(ldapAuthenticator.doAuthenticate(any(LdapAuthenticator.Context.class))).thenReturn(true);
- LdapUserDetails userDetails = new LdapUserDetails();
- userDetails.setName("name");
- when(ldapUsersProvider.doGetUserDetails(any(LdapUsersProvider.Context.class))).thenReturn(userDetails);
- underTest.authenticate(new Credentials(login, PASSWORD), request, BASIC);
+ private void mockLdapAuthentication(String login) {
+ LdapAuthenticator.Context authenticationContext = new LdapAuthenticator.Context(login, PASSWORD, request);
+ when(ldapAuthenticator.doAuthenticate(refEq(authenticationContext))).thenReturn(success(SERVER_KEY));
+ }
+
+ private void mockLdapUserDetailsRetrieval(String login, @Nullable LdapUserDetails userDetails) {
+ LdapUsersProvider.Context expectedUserContext = new LdapUsersProvider.Context(SERVER_KEY, login, request);
+ when(ldapUsersProvider.doGetUserDetails(refEq(expectedUserContext))).thenReturn(userDetails);
}
}
diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserRegistrarImplTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserRegistrarImplTest.java
index 43def63369f..4826eb57fef 100644
--- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserRegistrarImplTest.java
+++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserRegistrarImplTest.java
@@ -51,6 +51,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.sonar.db.user.UserTesting.newUserDto;
+import static org.sonar.server.authentication.UserRegistrarImpl.LDAP_PROVIDER_PREFIX;
+import static org.sonar.server.authentication.UserRegistrarImpl.SQ_AUTHORITY;
import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC;
public class UserRegistrarImplTest {
@@ -237,13 +239,13 @@ public class UserRegistrarImplTest {
.setAllowsUsersToSignUp(false);
Source source = Source.realm(AuthenticationEvent.Method.FORM, identityProvider.getName());
- assertThatThrownBy(() -> {
- underTest.register(UserRegistration.builder()
- .setUserIdentity(USER_IDENTITY)
- .setProvider(identityProvider)
- .setSource(source)
- .build());
- })
+ UserRegistration registration = UserRegistration.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(identityProvider)
+ .setSource(source)
+ .build();
+
+ assertThatThrownBy(() -> underTest.register(registration))
.isInstanceOf(AuthenticationException.class)
.hasMessage("User signup disabled for provider 'github'")
.hasFieldOrPropertyWithValue("source", source)
@@ -430,6 +432,150 @@ public class UserRegistrarImplTest {
}
@Test
+ public void authenticate_user_eligible_for_ldap_migration() {
+ String providerKey = LDAP_PROVIDER_PREFIX + "PROVIDER";
+
+ UserRegistration registration = UserRegistration.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(new TestIdentityProvider()
+ .setKey(providerKey)
+ .setName("name of provider")
+ .setEnabled(true))
+ .setSource(Source.local(BASIC))
+ .build();
+
+ UserDto user = db.users().insertUser(u -> u
+ .setLogin(USER_IDENTITY.getProviderLogin())
+ .setName("name")
+ .setEmail("another-email@sonarsource.com")
+ .setExternalId("id")
+ .setLocal(false)
+ .setExternalIdentityProvider(SQ_AUTHORITY));
+
+ underTest.register(registration);
+
+ assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
+ .extracting(
+ UserDto::getLogin,
+ UserDto::getName,
+ UserDto::getEmail,
+ UserDto::getExternalId,
+ UserDto::getExternalLogin,
+ UserDto::getExternalIdentityProvider,
+ UserDto::isActive,
+ UserDto::isLocal
+ ).contains(user.getLogin(), "John", "john@email.com", "ABCD", "johndoo", providerKey, true, false);
+ }
+
+ @Test
+ public void authenticate_local_user_for_ldap_migration_is_not_possible() {
+ String providerKey = LDAP_PROVIDER_PREFIX + "PROVIDER";
+
+ UserRegistration registration = UserRegistration.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(new TestIdentityProvider()
+ .setKey(providerKey)
+ .setName("name of provider")
+ .setAllowsUsersToSignUp(true)
+ .setEnabled(true))
+ .setSource(Source.local(BASIC))
+ .build();
+
+ UserDto user = db.users().insertUser(u -> u
+ .setLogin(USER_IDENTITY.getProviderLogin())
+ .setName("name")
+ .setEmail("another-email@sonarsource.com")
+ .setExternalId("id")
+ .setLocal(true)
+ .setExternalIdentityProvider(SQ_AUTHORITY));
+
+ underTest.register(registration);
+
+ assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
+ .extracting(
+ UserDto::getLogin,
+ UserDto::getName,
+ UserDto::getEmail,
+ UserDto::getExternalId,
+ UserDto::getExternalIdentityProvider,
+ UserDto::isActive,
+ UserDto::isLocal
+ ).contains(user.getLogin(), "name", "another-email@sonarsource.com", "id", "sonarqube", true, true);
+ }
+
+ @Test
+ public void authenticate_user_for_ldap_migration_is_not_possible_when_external_identity_provider_is_not_sonarqube() {
+ String providerKey = LDAP_PROVIDER_PREFIX + "PROVIDER";
+
+ UserRegistration registration = UserRegistration.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(new TestIdentityProvider()
+ .setKey(providerKey)
+ .setName("name of provider")
+ .setAllowsUsersToSignUp(true)
+ .setEnabled(true))
+ .setSource(Source.local(BASIC))
+ .build();
+
+ UserDto user = db.users().insertUser(u -> u
+ .setLogin(USER_IDENTITY.getProviderLogin())
+ .setName("name")
+ .setEmail("another-email@sonarsource.com")
+ .setExternalId("id")
+ .setLocal(false)
+ .setExternalIdentityProvider("not_sonarqube"));
+
+ underTest.register(registration);
+
+ assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
+ .extracting(
+ UserDto::getLogin,
+ UserDto::getName,
+ UserDto::getEmail,
+ UserDto::getExternalId,
+ UserDto::getExternalIdentityProvider,
+ UserDto::isActive,
+ UserDto::isLocal
+ ).contains(user.getLogin(), "name", "another-email@sonarsource.com", "id", "not_sonarqube", true, false);
+ }
+
+ @Test
+ public void authenticate_user_for_ldap_migration_is_not_possible_when_identity_provider_key_is_not_prefixed_properly() {
+ String providerKey = "INVALID_PREFIX" + LDAP_PROVIDER_PREFIX + "PROVIDER";
+
+ UserRegistration registration = UserRegistration.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(new TestIdentityProvider()
+ .setKey(providerKey)
+ .setName("name of provider")
+ .setAllowsUsersToSignUp(true)
+ .setEnabled(true))
+ .setSource(Source.local(BASIC))
+ .build();
+
+ UserDto user = db.users().insertUser(u -> u
+ .setLogin(USER_IDENTITY.getProviderLogin())
+ .setName("name")
+ .setEmail("another-email@sonarsource.com")
+ .setExternalId("id")
+ .setLocal(false)
+ .setExternalIdentityProvider(SQ_AUTHORITY));
+
+ underTest.register(registration);
+
+ assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
+ .extracting(
+ UserDto::getLogin,
+ UserDto::getName,
+ UserDto::getEmail,
+ UserDto::getExternalId,
+ UserDto::getExternalIdentityProvider,
+ UserDto::isActive,
+ UserDto::isLocal
+ ).contains(user.getLogin(), "name", "another-email@sonarsource.com", "id", "sonarqube", true, false);
+ }
+
+ @Test
public void authenticate_and_update_existing_user_matching_external_login_if_email_is_missing() {
db.users().insertUser(u -> u
.setLogin("Old login")
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/UpdateIdentityProviderAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/UpdateIdentityProviderAction.java
index c652954e12a..8f307125177 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/UpdateIdentityProviderAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/UpdateIdentityProviderAction.java
@@ -23,6 +23,7 @@ import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.sonar.api.server.authentication.IdentityProvider;
+import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
@@ -40,6 +41,8 @@ import org.sonar.server.user.UserUpdater;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
+import static org.sonar.auth.ldap.LdapRealm.DEFAULT_LDAP_IDENTITY_PROVIDER_ID;
+import static org.sonar.auth.ldap.LdapRealm.LDAP_SECURITY_REALM;
import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY;
import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_UPDATE_IDENTITY_PROVIDER;
import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOGIN;
@@ -50,11 +53,11 @@ public class UpdateIdentityProviderAction implements UsersWsAction {
private final DbClient dbClient;
private final IdentityProviderRepository identityProviderRepository;
+
private final UserUpdater userUpdater;
private final UserSession userSession;
- public UpdateIdentityProviderAction(DbClient dbClient, IdentityProviderRepository identityProviderRepository,
- UserUpdater userUpdater, UserSession userSession) {
+ public UpdateIdentityProviderAction(DbClient dbClient, IdentityProviderRepository identityProviderRepository, UserUpdater userUpdater, UserSession userSession) {
this.dbClient = dbClient;
this.identityProviderRepository = identityProviderRepository;
this.userUpdater = userUpdater;
@@ -78,9 +81,15 @@ public class UpdateIdentityProviderAction implements UsersWsAction {
action.createParam(PARAM_LOGIN)
.setDescription("User login")
.setRequired(true);
+
action.createParam(PARAM_NEW_EXTERNAL_PROVIDER)
.setRequired(true)
- .setDescription("New external provider. Only authentication system installed are available. Use 'sonarqube' identity provider for LDAP.");
+ .setDescription("New external provider. Only authentication system installed are available. " +
+ "Use 'LDAP' identity provider for single server LDAP setup." +
+ "User 'LDAP_{serverKey}' identity provider for multiple LDAP server setup.");
+
+ action.setChangelog(
+ new Change("9.8", String.format("Use of 'sonarqube' for the value of '%s' is deprecated.", PARAM_NEW_EXTERNAL_PROVIDER)));
action.createParam(PARAM_NEW_EXTERNAL_IDENTITY)
.setDescription("New external identity, usually the login used in the authentication system. "
@@ -106,18 +115,22 @@ public class UpdateIdentityProviderAction implements UsersWsAction {
}
private void checkEnabledIdentityProviders(String newExternalProvider) {
- List<String> availableIdentityProviders = getAvailableIdentityProviders();
- checkArgument(availableIdentityProviders.contains(newExternalProvider), "Value of parameter 'newExternalProvider' (%s) must be one of: [%s]", newExternalProvider,
- String.join(", ", availableIdentityProviders));
+ List<String> allowedIdentityProviders = getAvailableIdentityProviders();
+
+ boolean isAllowedProvider = allowedIdentityProviders.contains(newExternalProvider) || isLdapIdentityProvider(newExternalProvider);
+ checkArgument(isAllowedProvider, "Value of parameter 'newExternalProvider' (%s) must be one of: [%s] or [%s]", newExternalProvider,
+ String.join(", ", allowedIdentityProviders), String.join(", ", "LDAP", "LDAP_{serverKey}"));
}
private List<String> getAvailableIdentityProviders() {
- List<String> discoveredProviders = identityProviderRepository.getAllEnabledAndSorted()
+ return identityProviderRepository.getAllEnabledAndSorted()
.stream()
.map(IdentityProvider::getKey)
.collect(Collectors.toList());
- discoveredProviders.add(SQ_AUTHORITY);
- return discoveredProviders;
+ }
+
+ private static boolean isLdapIdentityProvider(String identityProviderKey) {
+ return identityProviderKey.startsWith(LDAP_SECURITY_REALM + "_");
}
private UserDto getUser(DbSession dbSession, String login) {
@@ -138,11 +151,15 @@ public class UpdateIdentityProviderAction implements UsersWsAction {
private static UpdateIdentityProviderRequest toWsRequest(Request request) {
return UpdateIdentityProviderRequest.builder()
.setLogin(request.mandatoryParam(PARAM_LOGIN))
- .setNewExternalProvider(request.mandatoryParam(PARAM_NEW_EXTERNAL_PROVIDER))
+ .setNewExternalProvider(replaceDeprecatedSonarqubeIdentityProviderByLdapForSonar17508(request.mandatoryParam(PARAM_NEW_EXTERNAL_PROVIDER)))
.setNewExternalIdentity(request.param(PARAM_NEW_EXTERNAL_IDENTITY))
.build();
}
+ private static String replaceDeprecatedSonarqubeIdentityProviderByLdapForSonar17508(String newExternalProvider) {
+ return newExternalProvider.equals(SQ_AUTHORITY) || newExternalProvider.equals(LDAP_SECURITY_REALM) ? DEFAULT_LDAP_IDENTITY_PROVIDER_ID : newExternalProvider;
+ }
+
static class UpdateIdentityProviderRequest {
private final String login;
private final String newExternalProvider;
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/UpdateIdentityProviderActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/UpdateIdentityProviderActionTest.java
index 22186bd1960..ecce5a22ab8 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/UpdateIdentityProviderActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/UpdateIdentityProviderActionTest.java
@@ -22,6 +22,7 @@ package org.sonar.server.user.ws;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.config.internal.MapSettings;
+import org.sonar.auth.ldap.LdapSettingsManager;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
@@ -68,6 +69,8 @@ public class UpdateIdentityProviderActionTest {
private final UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
private final CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(dbClient, settings.asConfig());
+ private final LdapSettingsManager ldapSettingsManager = mock(LdapSettingsManager.class);
+
private final WsActionTester underTest = new WsActionTester(new UpdateIdentityProviderAction(dbClient, identityProviderRepository,
new UserUpdater(mock(NewUserNotifier.class), dbClient, userIndexer, new DefaultGroupFinder(db.getDbClient()), settings.asConfig(), null, localAuthentication),
userSession));
@@ -128,6 +131,54 @@ public class UpdateIdentityProviderActionTest {
}
@Test
+ public void change_identity_provider_of_a_local_user_to_ldap_default_using_sonarqube_as_parameter() {
+ String userLogin = "login-1";
+ String newExternalIdentityProvider = "LDAP_default";
+ createUser(true, userLogin, userLogin, SQ_AUTHORITY);
+ TestRequest request = underTest.newRequest()
+ .setParam("login", userLogin)
+ .setParam("newExternalProvider", "sonarqube");
+
+ request.execute();
+ assertThat(dbClient.userDao().selectByExternalLoginAndIdentityProvider(dbSession, userLogin, newExternalIdentityProvider))
+ .isNotNull()
+ .extracting(UserDto::isLocal, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider)
+ .contains(false, userLogin, newExternalIdentityProvider);
+ }
+
+ @Test
+ public void change_identity_provider_of_a_local_user_to_ldap_default_using_ldap_as_parameter() {
+ String userLogin = "login-1";
+ String newExternalIdentityProvider = "LDAP_default";
+ createUser(true, userLogin, userLogin, SQ_AUTHORITY);
+ TestRequest request = underTest.newRequest()
+ .setParam("login", userLogin)
+ .setParam("newExternalProvider", "LDAP");
+
+ request.execute();
+ assertThat(dbClient.userDao().selectByExternalLoginAndIdentityProvider(dbSession, userLogin, newExternalIdentityProvider))
+ .isNotNull()
+ .extracting(UserDto::isLocal, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider)
+ .contains(false, userLogin, newExternalIdentityProvider);
+ }
+
+ @Test
+ public void change_identity_provider_of_a_local_user_to_ldap_default_using_ldap_server_key_as_parameter() {
+ String userLogin = "login-1";
+ String newExternalIdentityProvider = "LDAP_server42";
+ createUser(true, userLogin, userLogin, SQ_AUTHORITY);
+ TestRequest request = underTest.newRequest()
+ .setParam("login", userLogin)
+ .setParam("newExternalProvider", newExternalIdentityProvider);
+
+ request.execute();
+ assertThat(dbClient.userDao().selectByExternalLoginAndIdentityProvider(dbSession, userLogin, newExternalIdentityProvider))
+ .isNotNull()
+ .extracting(UserDto::isLocal, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider)
+ .contains(false, userLogin, newExternalIdentityProvider);
+ }
+
+ @Test
public void fail_if_user_not_exist() {
TestRequest request = underTest.newRequest()
.setParam("login", "not-existing")
@@ -147,7 +198,7 @@ public class UpdateIdentityProviderActionTest {
assertThatThrownBy(request::execute)
.isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Value of parameter 'newExternalProvider' (not-existing) must be one of: [github, gitlab, sonarqube]");
+ .hasMessage("Value of parameter 'newExternalProvider' (not-existing) must be one of: [github, gitlab] or [LDAP, LDAP_{serverKey}]");
}
@Test