diff options
author | Antoine Vinot <antoine.vinot@sonarsource.com> | 2022-10-25 11:56:20 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-11-02 20:03:01 +0000 |
commit | 60e4b88e4567c1af3051b6cdc4c8858f8b0fca21 (patch) | |
tree | c16129850760037f3e3f1f56dbf6f15bc0b34594 /server | |
parent | a22087da3ca968ed5a592ed03d47f282b6b59d63 (diff) | |
download | sonarqube-60e4b88e4567c1af3051b6cdc4c8858f8b0fca21.tar.gz sonarqube-60e4b88e4567c1af3051b6cdc4c8858f8b0fca21.zip |
SONAR-17508 - Fix SSF-327
Diffstat (limited to 'server')
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 |