]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-17508 - Fix SSF-327
authorAntoine Vinot <antoine.vinot@sonarsource.com>
Tue, 25 Oct 2022 09:56:20 +0000 (11:56 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 2 Nov 2022 20:03:01 +0000 (20:03 +0000)
25 files changed:
.cirrus.yml
server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapAuthenticator.java
server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapGroupsProvider.java
server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapUsersProvider.java
server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapAuthenticationResult.java [new file with mode: 0644]
server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapAuthenticator.java
server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapGroupsProvider.java
server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapRealm.java
server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapSettingsManager.java
server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapUsersProvider.java
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapAuthenticatorTest.java
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapGroupsProviderTest.java
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapUsersProviderTest.java
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/KerberosTest.java
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapRealmTest.java
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapSearchTest.java
server/sonar-auth-ldap/src/test/resources/users.example.org.ldif
server/sonar-auth-ldap/src/test/resources/users.infosupport.com.ldif
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/LdapCredentialsAuthentication.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/IdentityProviderRepositoryTest.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/LdapCredentialsAuthenticationTest.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserRegistrarImplTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/UpdateIdentityProviderAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/UpdateIdentityProviderActionTest.java

index 5b6aae5702a3ad605e69b9887dfcae2be30bde93..7656232e6c8b0616c3ce549d9c74d4025784d24c 100644 (file)
@@ -91,6 +91,7 @@ default_artifact_template: &DEFAULT_ARTIFACTS_TEMPLATE
       xmlstarlet edit --inplace --delete '//testsuite[@errors=0 and @failures=0]' {} \;
     junit_artifacts:
       path: "**/build/test-results/**/*.xml"
+      type: "text/xml"
       format: junit
     reports_artifacts:
       path: "**/build/reports/**/*"
index b4d2479c3fae84aa4ff99e3dd28e2e2c234e9b82..26c6d2990907049657eec70fd2b3551da265a21c 100644 (file)
@@ -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;
index 0a4195133c46684e1d8dbfe41a2dcf6fd52e52ed..a4a4dc8f07816ce67775a6729b6c1409d38804da 100644 (file)
  */
 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
index 72d3a51e902b306a9615972f10170f1c13f11139..77ff3f8d73c861c48eaa0d04189191b538cc8d02 100644 (file)
@@ -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 (file)
index 0000000..b288720
--- /dev/null
@@ -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;
+  }
+}
index ef2132b6c73873b45358129981be92c3ee00caf5..39f9b396857628a62b8fb8bfcf2c9040909f1b0a 100644 (file)
@@ -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;
index 3a3c2701294f302d3cf4b8a37a31982fe80c5c1d..71ab051f8ec53c5ef5092aff973f0c85e6a6ec26 100644 (file)
@@ -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;
     }
index 1b03964ea58cabf0cee0f3d63fee4bb115a5f747..49dbc11b4c03efb2deb7085185d787f22de1fb23 100644 (file)
@@ -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;
index ee52f521ccda55088887a615b48ce790792c8036..db14f7bfee696bcc3afe6eb85b65b9d4a440dc3a 100644 (file)
@@ -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;
index 4b1870e4bbc0cae7c8d4433a14d8d8068b114bfc..7891585c4bddca6548021157184934be4d3c0d42 100644 (file)
@@ -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;
     }
+
   }
 }
index cf56c14eec5110ebc133c0338420f0009309efd5..2559d8836d9d8e609856f10e6847bc59b4f6bf26 100644 (file)
  */
 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));
   }
 
 }
index dd759b2804e19b4a202da72d07572e069085157c..6a527e6cb0a3766f8ddbc11b2f70a77e8c4e5c8e 100644 (file)
 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));
+  }
+
 }
index 79f601b520f150327bc4eb63526f3ebb7c0316a3..cde909e415ba3d57726dc91cb34cc3efdb5cdb59 100644 (file)
  */
 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));
+  }
 }
index ac08ebe08fcc948567c773bcc65d68b45104e67a..b0e49f746d35727feedb27f679f63648fb41e584 100644 (file)
@@ -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() {
index ba1656009efc96f03becf99e5876c649568f1290..a194ace6ac34773ef9e7fda6e98283cc7f808e33 100644 (file)
@@ -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>");
+
   }
 
 }
index 721fc546a36eec17ea0c93002e79fe53a4f0b8bb..d584cf21476ceefc5ffac04864932986f8a9a7a5 100644 (file)
@@ -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
index 3dc462afbb07b95d4c86cc63de8696841c7823fc..fe3341c92f9995ca97ee48c2a664b58e09a1d1f9 100644 (file)
@@ -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
index a08174bf72b7577052b1b1de9391c862580b9f64..d57addd4bf33bc43eafb784d3a54da0625ab730c 100644 (file)
@@ -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
index adbbe639ff9c96a8f30729b03c2f1346c9352f0c..b41295d0562c8512b383a09d14d4831e59015685 100644 (file)
@@ -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
index 1baa5cf5fa8606260dc6c3cf5837b4d07c927f05..5ad18c321d0849000261d757538defecc5b84322 100644 (file)
@@ -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);
index 442a911ddc480299a07bacaa6fb4674528ba6aee..2133a170de13f6d3bf378e365a90eccc12318781 100644 (file)
@@ -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);
index d4ce1980f108b2c2dc9aeca3823271d1ef78d328..d3c1edbaf4aebfe5283eb6cbb5adee5aabcc0273 100644 (file)
@@ -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);
   }
 
 }
index 43def63369fe3ab4ecfc1554621a2f1d7f7e195c..4826eb57fef0e6f47c8388e01085599adfebb91f 100644 (file)
@@ -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)
@@ -429,6 +431,150 @@ public class UserRegistrarImplTest {
       .contains(user.getLogin(), "John", "john@email.com", "ABCD", "johndoo", "other", true);
   }
 
+  @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
index c652954e12a66a474bc489dbf4dfe2e637092d38..8f307125177fab66d07f9b49980eac16ddaf8d6d 100644 (file)
@@ -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;
index 22186bd196077870ae5beaca2225e955d99c4802..ecce5a22ab8b0fb57c94e7e9b2512a2ac1e6ce1d 100644 (file)
@@ -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));
@@ -127,6 +130,54 @@ public class UpdateIdentityProviderActionTest {
       .contains(false, newExternalIdentity, newExternalIdentityProvider);
   }
 
+  @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()
@@ -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