]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-17508 Decouple LDAP authentication from sonar-plugin-API
authorAurelien Poscia <aurelien.poscia@sonarsource.com>
Tue, 25 Oct 2022 10:03:35 +0000 (12:03 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 2 Nov 2022 20:03:01 +0000 (20:03 +0000)
26 files changed:
server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapAuthenticator.java [new file with mode: 0644]
server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapGroupsProvider.java [new file with mode: 0644]
server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapUsersProvider.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/LdapUserDetails.java [new file with mode: 0644]
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 [new file with mode: 0644]
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapGroupsProviderTest.java [new file with mode: 0644]
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapUsersProviderTest.java [new file with mode: 0644]
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/KerberosTest.java
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAuthenticatorTest.java [deleted file]
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAutoDiscoveryWarningLogTest.java
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapGroupsProviderTest.java [deleted file]
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapRealmTest.java
server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapUsersProviderTest.java [deleted file]
server/sonar-webserver-auth/build.gradle
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/AuthenticationModule.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/CredentialsAuthentication.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/LdapCredentialsAuthentication.java [new file with mode: 0644]
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistration.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/SecurityRealmFactory.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/CredentialsAuthenticationTest.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/LdapCredentialsAuthenticationTest.java [new file with mode: 0644]
server/sonar-webserver-auth/src/test/java/org/sonar/server/user/SecurityRealmFactoryTest.java

diff --git a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapAuthenticator.java b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapAuthenticator.java
new file mode 100644 (file)
index 0000000..b4d2479
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * 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 java.util.Map;
+import javax.naming.NamingException;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchResult;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+/**
+ * @author Evgeny Mandrikov
+ */
+@ServerSide
+public class DefaultLdapAuthenticator implements LdapAuthenticator {
+
+  private static final Logger LOG = Loggers.get(DefaultLdapAuthenticator.class);
+  private final Map<String, LdapContextFactory> contextFactories;
+  private final Map<String, LdapUserMapping> userMappings;
+
+  public DefaultLdapAuthenticator(Map<String, LdapContextFactory> contextFactories, Map<String, LdapUserMapping> userMappings) {
+    this.contextFactories = contextFactories;
+    this.userMappings = userMappings;
+  }
+
+  @Override
+  public boolean doAuthenticate(Context context) {
+    return authenticate(context.getUsername(), context.getPassword());
+  }
+
+  /**
+   * Authenticate the user against LDAP servers until first success.
+   *
+   * @param login    The login to use.
+   * @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) {
+    for (Map.Entry<String, LdapUserMapping> ldapEntry : userMappings.entrySet()) {
+      String ldapKey = ldapEntry.getKey();
+      LdapUserMapping ldapUserMapping = ldapEntry.getValue();
+      LdapContextFactory ldapContextFactory = contextFactories.get(ldapKey);
+      final String principal;
+      if (ldapContextFactory.isSasl()) {
+        principal = login;
+      } else {
+        SearchResult result = findUser(login, ldapKey, ldapUserMapping, ldapContextFactory);
+        if (result == null) {
+          continue;
+        }
+        principal = result.getNameInNamespace();
+      }
+      boolean passwordValid = isPasswordValid(password, ldapKey, ldapContextFactory, principal);
+      if (passwordValid) {
+        return true;
+      }
+    }
+    LOG.debug("User {} not found", login);
+    return false;
+  }
+
+  private static SearchResult findUser(String login, String ldapKey, LdapUserMapping ldapUserMapping, LdapContextFactory ldapContextFactory) {
+    SearchResult result;
+    try {
+      result = ldapUserMapping.createSearch(ldapContextFactory, login).findUnique();
+    } catch (NamingException e) {
+      LOG.debug("User {} not found in server {}: {}", login, ldapKey, e.getMessage());
+      return null;
+    }
+    if (result == null) {
+      LOG.debug("User {} not found in {}", login, ldapKey);
+      return null;
+    }
+    return result;
+  }
+
+  private boolean isPasswordValid(String password, String ldapKey, LdapContextFactory ldapContextFactory, String principal) {
+    if (ldapContextFactory.isGssapi()) {
+      return checkPasswordUsingGssapi(principal, password, ldapKey);
+    }
+    return checkPasswordUsingBind(principal, password, ldapKey);
+  }
+
+  private boolean checkPasswordUsingBind(String principal, String password, String ldapKey) {
+    if (StringUtils.isEmpty(password)) {
+      LOG.debug("Password is blank.");
+      return false;
+    }
+    InitialDirContext context = null;
+    try {
+      context = contextFactories.get(ldapKey).createUserContext(principal, password);
+      return true;
+    } catch (NamingException e) {
+      LOG.debug("Password not valid for user {} in server {}: {}", principal, ldapKey, e.getMessage());
+      return false;
+    } finally {
+      ContextHelper.closeQuietly(context);
+    }
+  }
+
+  private boolean checkPasswordUsingGssapi(String principal, String password, String ldapKey) {
+    // Use our custom configuration to avoid reliance on external config
+    Configuration.setConfiguration(new Krb5LoginConfiguration());
+    LoginContext lc;
+    try {
+      lc = new LoginContext(getClass().getName(), new CallbackHandlerImpl(principal, password));
+      lc.login();
+    } catch (LoginException e) {
+      // Bad username: Client not found in Kerberos database
+      // Bad password: Integrity check on decrypted field failed
+      LOG.debug("Password not valid for {} in server {}: {}", principal, ldapKey, e.getMessage());
+      return false;
+    }
+    try {
+      lc.logout();
+    } catch (LoginException e) {
+      LOG.warn("Logout fails", e);
+    }
+    return true;
+  }
+
+}
diff --git a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapGroupsProvider.java b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapGroupsProvider.java
new file mode 100644 (file)
index 0000000..0a41951
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * 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 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;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchResult;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import static java.lang.String.format;
+
+/**
+ * @author Evgeny Mandrikov
+ */
+@ServerSide
+public class DefaultLdapGroupsProvider implements LdapGroupsProvider {
+
+  private static final Logger LOG = Loggers.get(DefaultLdapGroupsProvider.class);
+
+  private final Map<String, LdapContextFactory> contextFactories;
+  private final Map<String, LdapUserMapping> userMappings;
+  private final Map<String, LdapGroupMapping> groupMappings;
+
+  public DefaultLdapGroupsProvider(Map<String, LdapContextFactory> contextFactories, Map<String, LdapUserMapping> userMappings, Map<String, LdapGroupMapping> groupMapping) {
+    this.contextFactories = contextFactories;
+    this.userMappings = userMappings;
+    this.groupMappings = groupMapping;
+  }
+
+  @Override
+  public Collection<String> doGetGroups(Context context) {
+    return getGroups(context.getUsername());
+  }
+
+  /**
+   * @throws LdapException if unable to retrieve groups
+   */
+  public Collection<String> getGroups(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));
+          }
+        }
+      }
+    }
+    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;
+    try {
+      LOG.debug("Requesting groups for user {}", username);
+
+      searchResult = 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));
+    }
+    return searchResult;
+  }
+
+  /**
+   * Map all the groups.
+   *
+   * @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
+   */
+  private Collection<String> mapGroups(String serverKey, NamingEnumeration<SearchResult> searchResult) throws NamingException {
+    Set<String> groups = new HashSet<>();
+    while (searchResult.hasMoreElements()) {
+      SearchResult obj = searchResult.nextElement();
+      Attributes attributes = obj.getAttributes();
+      String groupId = (String) attributes.get(groupMappings.get(serverKey).getIdAttribute()).get();
+      groups.add(groupId);
+    }
+    return groups;
+  }
+
+}
diff --git a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapUsersProvider.java b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/DefaultLdapUsersProvider.java
new file mode 100644 (file)
index 0000000..72d3a51
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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 java.util.Map;
+import javax.annotation.Nullable;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchResult;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import static java.lang.String.format;
+
+/**
+ * @author Evgeny Mandrikov
+ */
+@ServerSide
+public class DefaultLdapUsersProvider implements LdapUsersProvider {
+
+  private static final Logger LOG = Loggers.get(DefaultLdapUsersProvider.class);
+  private final Map<String, LdapContextFactory> contextFactories;
+  private final Map<String, LdapUserMapping> userMappings;
+
+  public DefaultLdapUsersProvider(Map<String, LdapContextFactory> contextFactories, Map<String, LdapUserMapping> userMappings) {
+    this.contextFactories = contextFactories;
+    this.userMappings = userMappings;
+  }
+
+  private static String getAttributeValue(@Nullable Attribute attribute) throws NamingException {
+    if (attribute == null) {
+      return "";
+    }
+    return (String) attribute.get();
+  }
+
+  @Override
+  public LdapUserDetails doGetUserDetails(Context context) {
+    return getUserDetails(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) {
+    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);
+      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 = 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);
+        }
+      } else {
+        // user not found
+        LOG.debug("User {} not found in {}", username, serverKey);
+      }
+    }
+    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 {
+    Attributes attributes = searchResult.getAttributes();
+    LdapUserDetails details;
+    details = new LdapUserDetails();
+    details.setName(getAttributeValue(attributes.get(ldapUserMapping.getRealNameAttribute())));
+    details.setEmail(getAttributeValue(attributes.get(ldapUserMapping.getEmailAttribute())));
+    return details;
+  }
+
+}
index 0222a2aaeb69a022a375481aacb48c75b40ded3f..ef2132b6c73873b45358129981be92c3ee00caf5 100644 (file)
  */
 package org.sonar.auth.ldap;
 
-import java.util.Map;
-import javax.naming.NamingException;
-import javax.naming.directory.InitialDirContext;
-import javax.naming.directory.SearchResult;
-import javax.security.auth.login.Configuration;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.security.Authenticator;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
 
-/**
- * @author Evgeny Mandrikov
- */
-public class LdapAuthenticator extends Authenticator {
-
-  private static final Logger LOG = Loggers.get(LdapAuthenticator.class);
-  private final Map<String, LdapContextFactory> contextFactories;
-  private final Map<String, LdapUserMapping> userMappings;
-
-  public LdapAuthenticator(Map<String, LdapContextFactory> contextFactories, Map<String, LdapUserMapping> userMappings) {
-    this.contextFactories = contextFactories;
-    this.userMappings = userMappings;
-  }
+import static java.util.Objects.requireNonNull;
 
-  @Override
-  public boolean doAuthenticate(Context context) {
-    return authenticate(context.getUsername(), context.getPassword());
-  }
+public interface LdapAuthenticator {
 
   /**
-   * Authenticate the user against LDAP servers until first success.
-   * @param login The login to use.
-   * @param password The password to use.
-   * @return false if specified user cannot be authenticated with specified password on any LDAP server
+   * @return true if user was successfully authenticated with specified credentials, false otherwise
+   * @throws RuntimeException in case of unexpected error such as connection failure
    */
-  public boolean authenticate(String login, String password) {
-    for (String ldapKey : userMappings.keySet()) {
-      final String principal;
-      if (contextFactories.get(ldapKey).isSasl()) {
-        principal = login;
-      } else {
-        final SearchResult result;
-        try {
-          result = userMappings.get(ldapKey).createSearch(contextFactories.get(ldapKey), login).findUnique();
-        } catch (NamingException e) {
-          LOG.debug("User {} not found in server {}: {}", login, ldapKey, e.getMessage());
-          continue;
-        }
-        if (result == null) {
-          LOG.debug("User {} not found in {}", login, ldapKey);
-          continue;
-        }
-        principal = result.getNameInNamespace();
-      }
-      boolean passwordValid;
-      if (contextFactories.get(ldapKey).isGssapi()) {
-        passwordValid = checkPasswordUsingGssapi(principal, password, ldapKey);
-      } else {
-        passwordValid = checkPasswordUsingBind(principal, password, ldapKey);
-      }
-      if (passwordValid) {
-        return true;
-      }
-    }
-    LOG.debug("User {} not found", login);
-    return false;
-  }
+  boolean doAuthenticate(LdapAuthenticator.Context context);
+
+  final class Context {
+    private String username;
+    private String password;
+    private HttpServletRequest request;
 
-  private boolean checkPasswordUsingBind(String principal, String password, String ldapKey) {
-    if (StringUtils.isEmpty(password)) {
-      LOG.debug("Password is blank.");
-      return false;
+    public Context(@Nullable String username, @Nullable String password, HttpServletRequest request) {
+      requireNonNull(request);
+      this.request = request;
+      this.username = username;
+      this.password = password;
     }
-    InitialDirContext context = null;
-    try {
-      context = contextFactories.get(ldapKey).createUserContext(principal, password);
-      return true;
-    } catch (NamingException e) {
-      LOG.debug("Password not valid for user {} in server {}: {}", principal, ldapKey, e.getMessage());
-      return false;
-    } finally {
-      ContextHelper.closeQuietly(context);
+
+    /**
+     * Username can be null, for example when using <a href="http://www.jasig.org/cas">CAS</a>.
+     */
+    public String getUsername() {
+      return username;
     }
-  }
 
-  private boolean checkPasswordUsingGssapi(String principal, String password, String ldapKey) {
-    // Use our custom configuration to avoid reliance on external config
-    Configuration.setConfiguration(new Krb5LoginConfiguration());
-    LoginContext lc;
-    try {
-      lc = new LoginContext(getClass().getName(), new CallbackHandlerImpl(principal, password));
-      lc.login();
-    } catch (LoginException e) {
-      // Bad username: Client not found in Kerberos database
-      // Bad password: Integrity check on decrypted field failed
-      LOG.debug("Password not valid for {} in server {}: {}", principal, ldapKey, e.getMessage());
-      return false;
+    /**
+     * Password can be null, for example when using <a href="http://www.jasig.org/cas">CAS</a>.
+     */
+    public String getPassword() {
+      return password;
     }
-    try {
-      lc.logout();
-    } catch (LoginException e) {
-      LOG.warn("Logout fails", e);
+
+    public HttpServletRequest getRequest() {
+      return request;
     }
-    return true;
   }
-
 }
index 9bf4f6b79a5b983cdc9c6e40eef8f171b7b3a0bd..3a3c2701294f302d3cf4b8a37a31982fe80c5c1d 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;
-import javax.naming.NamingException;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.SearchResult;
-import org.sonar.api.security.ExternalGroupsProvider;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
+import javax.servlet.http.HttpServletRequest;
 
-import static java.lang.String.format;
+public interface LdapGroupsProvider {
 
-/**
- * @author Evgeny Mandrikov
- */
-public class LdapGroupsProvider extends ExternalGroupsProvider {
-
-  private static final Logger LOG = Loggers.get(LdapGroupsProvider.class);
+  Collection<String> doGetGroups(Context context);
 
-  private final Map<String, LdapContextFactory> contextFactories;
-  private final Map<String, LdapUserMapping> userMappings;
-  private final Map<String, LdapGroupMapping> groupMappings;
+  final class Context {
+    private String username;
+    private HttpServletRequest request;
 
-  public LdapGroupsProvider(Map<String, LdapContextFactory> contextFactories, Map<String, LdapUserMapping> userMappings, Map<String, LdapGroupMapping> groupMapping) {
-    this.contextFactories = contextFactories;
-    this.userMappings = userMappings;
-    this.groupMappings = groupMapping;
-  }
-
-  @Override
-  public Collection<String> doGetGroups(Context context) {
-    return getGroups(context.getUsername());
-  }
-
-  /**
-   * @throws LdapException if unable to retrieve groups
-   */
-  public Collection<String> getGroups(String username) {
-    checkPrerequisites(username);
-    Set<String> groups = new HashSet<>();
-    List<LdapException> exceptions = new ArrayList<>();
-    for (String serverKey : userMappings.keySet()) {
-      if (!groupMappings.containsKey(serverKey)) {
-        // No group mapping for this ldap instance.
-        continue;
-      }
-      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));
-        }
-      } else {
-        // user not found
-        continue;
-      }
+    public Context(String username, HttpServletRequest request) {
+      this.username = username;
+      this.request = request;
     }
-    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();
+    public String getUsername() {
+      return username;
     }
-  }
 
-  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));
+    public HttpServletRequest getRequest() {
+      return request;
     }
   }
-
-  private SearchResult searchUserGroups(String username, List<LdapException> exceptions, String serverKey) {
-    SearchResult searchResult = null;
-    try {
-      LOG.debug("Requesting groups for user {}", username);
-
-      searchResult = 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));
-    }
-    return searchResult;
-  }
-
-  /**
-   * Map all the groups.
-   *
-   * @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
-   */
-  private Collection<String> mapGroups(String serverKey, NamingEnumeration<SearchResult> searchResult) throws NamingException {
-    Set<String> groups = new HashSet<>();
-    while (searchResult.hasMoreElements()) {
-      SearchResult obj = searchResult.nextElement();
-      Attributes attributes = obj.getAttributes();
-      String groupId = (String) attributes.get(groupMappings.get(serverKey).getIdAttribute()).get();
-      groups.add(groupId);
-    }
-    return groups;
-  }
-
 }
index 5c4b7fbd03462d3e360a2f852de2c685fd3a5cdd..1b03964ea58cabf0cee0f3d63fee4bb115a5f747 100644 (file)
 package org.sonar.auth.ldap;
 
 import java.util.Map;
-import org.sonar.api.security.Authenticator;
-import org.sonar.api.security.ExternalGroupsProvider;
-import org.sonar.api.security.ExternalUsersProvider;
-import org.sonar.api.security.SecurityRealm;
+import org.sonar.api.server.ServerSide;
 
 /**
  * @author Evgeny Mandrikov
  */
-public class LdapRealm extends SecurityRealm {
+@ServerSide
+public class LdapRealm {
 
   private LdapUsersProvider usersProvider;
   private LdapGroupsProvider groupsProvider;
@@ -39,43 +37,34 @@ public class LdapRealm extends SecurityRealm {
     this.settingsManager = settingsManager;
   }
 
-  @Override
-  public String getName() {
-    return "LDAP";
-  }
-
   /**
    * Initializes LDAP realm and tests connection.
    *
    * @throws LdapException if a NamingException was thrown during test
    */
-  @Override
   public void init() {
     Map<String, LdapContextFactory> contextFactories = settingsManager.getContextFactories();
     Map<String, LdapUserMapping> userMappings = settingsManager.getUserMappings();
-    usersProvider = new LdapUsersProvider(contextFactories, userMappings);
-    authenticator = new LdapAuthenticator(contextFactories, userMappings);
+    usersProvider = new DefaultLdapUsersProvider(contextFactories, userMappings);
+    authenticator = new DefaultLdapAuthenticator(contextFactories, userMappings);
     Map<String, LdapGroupMapping> groupMappings = settingsManager.getGroupMappings();
     if (!groupMappings.isEmpty()) {
-      groupsProvider = new LdapGroupsProvider(contextFactories, userMappings, groupMappings);
+      groupsProvider = new DefaultLdapGroupsProvider(contextFactories, userMappings, groupMappings);
     }
     for (LdapContextFactory contextFactory : contextFactories.values()) {
       contextFactory.testConnection();
     }
   }
 
-  @Override
-  public Authenticator doGetAuthenticator() {
+  public LdapAuthenticator doGetAuthenticator() {
     return authenticator;
   }
 
-  @Override
-  public ExternalUsersProvider getUsersProvider() {
+  public LdapUsersProvider getUsersProvider() {
     return usersProvider;
   }
 
-  @Override
-  public ExternalGroupsProvider getGroupsProvider() {
+  public LdapGroupsProvider getGroupsProvider() {
     return groupsProvider;
   }
 
diff --git a/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapUserDetails.java b/server/sonar-auth-ldap/src/main/java/org/sonar/auth/ldap/LdapUserDetails.java
new file mode 100644 (file)
index 0000000..251f8e6
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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 org.sonar.api.security.ExternalUsersProvider;
+
+/**
+ * This class is not intended to be subclassed by clients.
+ *
+ * @see ExternalUsersProvider
+ * @since 2.14
+ */
+public final class LdapUserDetails {
+
+  private String name = "";
+  private String email = "";
+  private String userId = "";
+
+  public void setEmail(String email) {
+    this.email = email;
+  }
+
+  public String getEmail() {
+    return email;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * @since 5.2
+   */
+  public void setUserId(String userId) {
+    this.userId = userId;
+  }
+
+  /**
+   * @since 5.2
+   */
+  public String getUserId() {
+    return userId;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("UserDetails{");
+    sb.append("name='").append(name).append('\'');
+    sb.append(", email='").append(email).append('\'');
+    sb.append(", userId='").append(userId).append('\'');
+    sb.append('}');
+    return sb.toString();
+  }
+}
index 360cd4b35607709aadf5a89dff05802c484970b9..4b1870e4bbc0cae7c8d4433a14d8d8068b114bfc 100644 (file)
  */
 package org.sonar.auth.ldap;
 
-import java.util.Map;
 import javax.annotation.Nullable;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.SearchResult;
-import org.sonar.api.security.ExternalUsersProvider;
-import org.sonar.api.security.UserDetails;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
+import javax.servlet.http.HttpServletRequest;
 
-import static java.lang.String.format;
+public interface LdapUsersProvider {
 
-/**
- * @author Evgeny Mandrikov
- */
-public class LdapUsersProvider extends ExternalUsersProvider {
-
-  private static final Logger LOG = Loggers.get(LdapUsersProvider.class);
-  private final Map<String, LdapContextFactory> contextFactories;
-  private final Map<String, LdapUserMapping> userMappings;
+  LdapUserDetails doGetUserDetails(Context context);
 
-  public LdapUsersProvider(Map<String, LdapContextFactory> contextFactories, Map<String, LdapUserMapping> userMappings) {
-    this.contextFactories = contextFactories;
-    this.userMappings = userMappings;
-  }
+  final class Context {
+    private String username;
+    private HttpServletRequest request;
 
-  private static String getAttributeValue(@Nullable Attribute attribute) throws NamingException {
-    if (attribute == null) {
-      return "";
+    public Context(@Nullable String username, HttpServletRequest request) {
+      this.username = username;
+      this.request = request;
     }
-    return (String) attribute.get();
-  }
-
-  @Override
-  public UserDetails doGetUserDetails(Context context) {
-    return getUserDetails(context.getUsername());
-  }
 
-  /**
-   * @return details for specified user, or null if such user doesn't exist
-   * @throws LdapException if unable to retrieve details
-   */
-  public UserDetails getUserDetails(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);
-      LOG.debug(errorMessage);
-      throw new LdapException(errorMessage);
+    public String getUsername() {
+      return username;
     }
-    UserDetails details = null;
-    LdapException exception = null;
-    for (String serverKey : userMappings.keySet()) {
-      SearchResult searchResult = null;
-      try {
-        searchResult = userMappings.get(serverKey).createSearch(contextFactories.get(serverKey), username)
-          .returns(userMappings.get(serverKey).getEmailAttribute(), userMappings.get(serverKey).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(serverKey, 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);
-        }
-      } else {
-        // user not found
-        LOG.debug("User {} not found in {}", username, serverKey);
-        continue;
-      }
-    }
-    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;
-  }
 
-  /**
-   * Map the properties from LDAP to the {@link UserDetails}
-   *
-   * @param serverKey the LDAP index so we use the correct {@link LdapUserMapping}
-   * @return If no exceptions are thrown, a {@link UserDetails} object containing the values from LDAP.
-   * @throws NamingException In case the communication or mapping to the LDAP server fails.
-   */
-  private UserDetails mapUserDetails(String serverKey, SearchResult searchResult) throws NamingException {
-    Attributes attributes = searchResult.getAttributes();
-    UserDetails details;
-    details = new UserDetails();
-    details.setName(getAttributeValue(attributes.get(userMappings.get(serverKey).getRealNameAttribute())));
-    details.setEmail(getAttributeValue(attributes.get(userMappings.get(serverKey).getEmailAttribute())));
-    return details;
+    public HttpServletRequest getRequest() {
+      return request;
+    }
   }
-
 }
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapAuthenticatorTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapAuthenticatorTest.java
new file mode 100644 (file)
index 0000000..cf56c14
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * 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 org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.auth.ldap.server.LdapServer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DefaultLdapAuthenticatorTest {
+
+  /**
+   * A reference to the original ldif file
+   */
+  public static final String USERS_EXAMPLE_ORG_LDIF = "/users.example.org.ldif";
+  /**
+   * A reference to an aditional ldif file.
+   */
+  public static final String USERS_INFOSUPPORT_COM_LDIF = "/users.infosupport.com.ldif";
+  @ClassRule
+  public static LdapServer exampleServer = new LdapServer(USERS_EXAMPLE_ORG_LDIF);
+  @ClassRule
+  public static LdapServer infosupportServer = new LdapServer(USERS_INFOSUPPORT_COM_LDIF, "infosupport.com", "dc=infosupport,dc=com");
+
+  @Test
+  public void testNoConnection() {
+    exampleServer.disableAnonymousAccess();
+    try {
+      LdapSettingsManager settingsManager = new LdapSettingsManager(LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_SIMPLE).asConfig(),
+        new LdapAutodiscovery());
+      DefaultLdapAuthenticator authenticator = new DefaultLdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
+      boolean authenticate = authenticator.authenticate("godin", "secret1");
+      assertThat(authenticate).isTrue();
+    } finally {
+      exampleServer.enableAnonymousAccess();
+    }
+  }
+
+  @Test
+  public void testSimple() {
+    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();
+
+    assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
+    assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
+
+    assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
+    // SONARPLUGINS-2493
+    assertThat(authenticator.authenticate("godin", "")).isFalse();
+    assertThat(authenticator.authenticate("godin", null)).isFalse();
+  }
+
+  @Test
+  public void testSimpleMultiLdap() {
+    LdapSettingsManager settingsManager = new LdapSettingsManager(
+      LdapSettingsFactory.generateAuthenticationSettings(exampleServer, infosupportServer, LdapContextFactory.AUTH_METHOD_SIMPLE).asConfig(), new LdapAutodiscovery());
+    DefaultLdapAuthenticator authenticator = new DefaultLdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
+
+    assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
+    assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
+
+    assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
+    assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
+
+    assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
+    // SONARPLUGINS-2493
+    assertThat(authenticator.authenticate("godin", "")).isFalse();
+    assertThat(authenticator.authenticate("godin", null)).isFalse();
+
+    // SONARPLUGINS-2793
+    assertThat(authenticator.authenticate("robby", "secret1")).isTrue();
+    assertThat(authenticator.authenticate("robby", "wrong")).isFalse();
+  }
+
+  @Test
+  public void testSasl() {
+    LdapSettingsManager settingsManager = new LdapSettingsManager(LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_CRAM_MD5).asConfig(),
+      new LdapAutodiscovery());
+    DefaultLdapAuthenticator authenticator = new DefaultLdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
+
+    assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
+    assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
+
+    assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
+    assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
+
+    assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
+  }
+
+  @Test
+  public void testSaslMultipleLdap() {
+    LdapSettingsManager settingsManager = new LdapSettingsManager(
+      LdapSettingsFactory.generateAuthenticationSettings(exampleServer, infosupportServer, LdapContextFactory.AUTH_METHOD_CRAM_MD5).asConfig(), new LdapAutodiscovery());
+    DefaultLdapAuthenticator authenticator = new DefaultLdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
+
+    assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
+    assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
+
+    assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
+    assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
+
+    assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
+
+    assertThat(authenticator.authenticate("robby", "secret1")).isTrue();
+    assertThat(authenticator.authenticate("robby", "wrong")).isFalse();
+  }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapGroupsProviderTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapGroupsProviderTest.java
new file mode 100644 (file)
index 0000000..dd759b2
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * 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 java.util.Collection;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.auth.ldap.server.LdapServer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DefaultLdapGroupsProviderTest {
+
+  /**
+   * A reference to the original ldif file
+   */
+  public static final String USERS_EXAMPLE_ORG_LDIF = "/users.example.org.ldif";
+  /**
+   * A reference to an aditional ldif file.
+   */
+  public static final String USERS_INFOSUPPORT_COM_LDIF = "/users.infosupport.com.ldif";
+
+  @ClassRule
+  public static LdapServer exampleServer = new LdapServer(USERS_EXAMPLE_ORG_LDIF);
+  @ClassRule
+  public static LdapServer infosupportServer = new LdapServer(USERS_INFOSUPPORT_COM_LDIF, "infosupport.com", "dc=infosupport,dc=com");
+
+  @Test
+  public void defaults() {
+    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;
+
+    groups = groupsProvider.getGroups("tester");
+    assertThat(groups).containsOnly("sonar-users");
+
+    groups = groupsProvider.getGroups("godin");
+    assertThat(groups).containsOnly("sonar-users", "sonar-developers");
+
+    groups = groupsProvider.getGroups("notfound");
+    assertThat(groups).isEmpty();
+  }
+
+  @Test
+  public void defaultsMultipleLdap() {
+    MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
+
+    LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
+    DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
+
+    Collection<String> groups;
+
+    groups = groupsProvider.getGroups("tester");
+    assertThat(groups).containsOnly("sonar-users");
+
+    groups = groupsProvider.getGroups("godin");
+    assertThat(groups).containsOnly("sonar-users", "sonar-developers");
+
+    groups = groupsProvider.getGroups("notfound");
+    assertThat(groups).isEmpty();
+
+    groups = groupsProvider.getGroups("testerInfo");
+    assertThat(groups).containsOnly("sonar-users");
+
+    groups = groupsProvider.getGroups("robby");
+    assertThat(groups).containsOnly("sonar-users", "sonar-developers");
+  }
+
+  @Test
+  public void posix() {
+    MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, null);
+    settings.setProperty("ldap.group.request", "(&(objectClass=posixGroup)(memberUid={uid}))");
+    LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
+    DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
+
+    Collection<String> groups;
+
+    groups = groupsProvider.getGroups("godin");
+    assertThat(groups).containsOnly("linux-users");
+  }
+
+  @Test
+  public void posixMultipleLdap() {
+    MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
+    settings.setProperty("ldap.example.group.request", "(&(objectClass=posixGroup)(memberUid={uid}))");
+    settings.setProperty("ldap.infosupport.group.request", "(&(objectClass=posixGroup)(memberUid={uid}))");
+    LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
+    DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
+
+    Collection<String> groups;
+
+    groups = groupsProvider.getGroups("godin");
+    assertThat(groups).containsOnly("linux-users");
+
+    groups = groupsProvider.getGroups("robby");
+    assertThat(groups).containsOnly("linux-users");
+  }
+
+  @Test
+  public void mixed() {
+    MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
+    settings.setProperty("ldap.example.group.request", "(&(|(objectClass=groupOfUniqueNames)(objectClass=posixGroup))(|(uniqueMember={dn})(memberUid={uid})))");
+    LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
+    DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
+
+    Collection<String> groups;
+
+    groups = groupsProvider.getGroups("godin");
+    assertThat(groups).containsOnly("sonar-users", "sonar-developers", "linux-users");
+  }
+
+  @Test
+  public void mixedMultipleLdap() {
+    MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
+    settings.setProperty("ldap.example.group.request", "(&(|(objectClass=groupOfUniqueNames)(objectClass=posixGroup))(|(uniqueMember={dn})(memberUid={uid})))");
+    settings.setProperty("ldap.infosupport.group.request", "(&(|(objectClass=groupOfUniqueNames)(objectClass=posixGroup))(|(uniqueMember={dn})(memberUid={uid})))");
+    LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
+    DefaultLdapGroupsProvider groupsProvider = new DefaultLdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
+
+    Collection<String> groups;
+
+    groups = groupsProvider.getGroups("godin");
+    assertThat(groups).containsOnly("sonar-users", "sonar-developers", "linux-users");
+
+    groups = groupsProvider.getGroups("robby");
+    assertThat(groups).containsOnly("sonar-users", "sonar-developers", "linux-users");
+  }
+
+}
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapUsersProviderTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/DefaultLdapUsersProviderTest.java
new file mode 100644 (file)
index 0000000..79f601b
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * 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 org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.auth.ldap.server.LdapServer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DefaultLdapUsersProviderTest {
+  /**
+   * A reference to the original ldif file
+   */
+  public static final String USERS_EXAMPLE_ORG_LDIF = "/users.example.org.ldif";
+  /**
+   * A reference to an aditional ldif file.
+   */
+  public static final String USERS_INFOSUPPORT_COM_LDIF = "/users.infosupport.com.ldif";
+
+  @ClassRule
+  public static LdapServer exampleServer = new LdapServer(USERS_EXAMPLE_ORG_LDIF);
+  @ClassRule
+  public static LdapServer infosupportServer = new LdapServer(USERS_INFOSUPPORT_COM_LDIF, "infosupport.com", "dc=infosupport,dc=com");
+
+  @Test
+  public void test() {
+    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");
+    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();
+
+    details = usersProvider.getUserDetails("robby");
+    assertThat(details.getName()).isEqualTo("Robby Developer");
+    assertThat(details.getEmail()).isEqualTo("rd@infosupport.com");
+
+    details = usersProvider.getUserDetails("testerInfo");
+    assertThat(details.getName()).isEqualTo("Tester Testerovich");
+    assertThat(details.getEmail()).isEqualTo("tester@infosupport.com");
+  }
+
+}
index 71f29891286643b78328546d7dbbbe41dc4110fb..ac08ebe08fcc948567c773bcc65d68b45104e67a 100644 (file)
@@ -26,8 +26,6 @@ import org.junit.ClassRule;
 import org.junit.Test;
 import org.mockito.Mockito;
 import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.security.Authenticator;
-import org.sonar.api.security.ExternalGroupsProvider;
 import org.sonar.auth.ldap.server.LdapServer;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -48,13 +46,13 @@ public class KerberosTest {
 
     ldapRealm.init();
 
-    assertThat(ldapRealm.doGetAuthenticator().doAuthenticate(new Authenticator.Context("Godin@EXAMPLE.ORG", "wrong_user_password", Mockito.mock(HttpServletRequest.class))))
+    assertThat(ldapRealm.doGetAuthenticator().doAuthenticate(new LdapAuthenticator.Context("Godin@EXAMPLE.ORG", "wrong_user_password", Mockito.mock(HttpServletRequest.class))))
       .isFalse();
-    assertThat(ldapRealm.doGetAuthenticator().doAuthenticate(new Authenticator.Context("Godin@EXAMPLE.ORG", "user_password", Mockito.mock(HttpServletRequest.class)))).isTrue();
+    assertThat(ldapRealm.doGetAuthenticator().doAuthenticate(new LdapAuthenticator.Context("Godin@EXAMPLE.ORG", "user_password", Mockito.mock(HttpServletRequest.class)))).isTrue();
     // Using default realm from krb5.conf:
-    assertThat(ldapRealm.doGetAuthenticator().doAuthenticate(new Authenticator.Context("Godin", "user_password", Mockito.mock(HttpServletRequest.class)))).isTrue();
+    assertThat(ldapRealm.doGetAuthenticator().doAuthenticate(new LdapAuthenticator.Context("Godin", "user_password", Mockito.mock(HttpServletRequest.class)))).isTrue();
 
-    assertThat(ldapRealm.getGroupsProvider().doGetGroups(new ExternalGroupsProvider.Context("godin", Mockito.mock(HttpServletRequest.class)))).containsOnly("sonar-users");
+    assertThat(ldapRealm.getGroupsProvider().doGetGroups(new LdapGroupsProvider.Context("godin", Mockito.mock(HttpServletRequest.class)))).containsOnly("sonar-users");
   }
 
   @Test
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAuthenticatorTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapAuthenticatorTest.java
deleted file mode 100644 (file)
index b025232..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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 org.junit.ClassRule;
-import org.junit.Test;
-import org.sonar.auth.ldap.server.LdapServer;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class LdapAuthenticatorTest {
-
-  /**
-   * A reference to the original ldif file
-   */
-  public static final String USERS_EXAMPLE_ORG_LDIF = "/users.example.org.ldif";
-  /**
-   * A reference to an aditional ldif file.
-   */
-  public static final String USERS_INFOSUPPORT_COM_LDIF = "/users.infosupport.com.ldif";
-  @ClassRule
-  public static LdapServer exampleServer = new LdapServer(USERS_EXAMPLE_ORG_LDIF);
-  @ClassRule
-  public static LdapServer infosupportServer = new LdapServer(USERS_INFOSUPPORT_COM_LDIF, "infosupport.com", "dc=infosupport,dc=com");
-
-  @Test
-  public void testNoConnection() {
-    exampleServer.disableAnonymousAccess();
-    try {
-      LdapSettingsManager settingsManager = new LdapSettingsManager(LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_SIMPLE).asConfig(),
-        new LdapAutodiscovery());
-      LdapAuthenticator authenticator = new LdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
-      authenticator.authenticate("godin", "secret1");
-    } finally {
-      exampleServer.enableAnonymousAccess();
-    }
-  }
-
-  @Test
-  public void testSimple() {
-    LdapSettingsManager settingsManager = new LdapSettingsManager(LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_SIMPLE).asConfig(),
-      new LdapAutodiscovery());
-    LdapAuthenticator authenticator = new LdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
-
-    assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
-    assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
-
-    assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
-    assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
-
-    assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
-    // SONARPLUGINS-2493
-    assertThat(authenticator.authenticate("godin", "")).isFalse();
-    assertThat(authenticator.authenticate("godin", null)).isFalse();
-  }
-
-  @Test
-  public void testSimpleMultiLdap() {
-    LdapSettingsManager settingsManager = new LdapSettingsManager(
-      LdapSettingsFactory.generateAuthenticationSettings(exampleServer, infosupportServer, LdapContextFactory.AUTH_METHOD_SIMPLE).asConfig(), new LdapAutodiscovery());
-    LdapAuthenticator authenticator = new LdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
-
-    assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
-    assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
-
-    assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
-    assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
-
-    assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
-    // SONARPLUGINS-2493
-    assertThat(authenticator.authenticate("godin", "")).isFalse();
-    assertThat(authenticator.authenticate("godin", null)).isFalse();
-
-    // SONARPLUGINS-2793
-    assertThat(authenticator.authenticate("robby", "secret1")).isTrue();
-    assertThat(authenticator.authenticate("robby", "wrong")).isFalse();
-  }
-
-  @Test
-  public void testSasl() {
-    LdapSettingsManager settingsManager = new LdapSettingsManager(LdapSettingsFactory.generateAuthenticationSettings(exampleServer, null, LdapContextFactory.AUTH_METHOD_CRAM_MD5).asConfig(),
-      new LdapAutodiscovery());
-    LdapAuthenticator authenticator = new LdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
-
-    assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
-    assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
-
-    assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
-    assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
-
-    assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
-  }
-
-  @Test
-  public void testSaslMultipleLdap() {
-    LdapSettingsManager settingsManager = new LdapSettingsManager(
-      LdapSettingsFactory.generateAuthenticationSettings(exampleServer, infosupportServer, LdapContextFactory.AUTH_METHOD_CRAM_MD5).asConfig(), new LdapAutodiscovery());
-    LdapAuthenticator authenticator = new LdapAuthenticator(settingsManager.getContextFactories(), settingsManager.getUserMappings());
-
-    assertThat(authenticator.authenticate("godin", "secret1")).isTrue();
-    assertThat(authenticator.authenticate("godin", "wrong")).isFalse();
-
-    assertThat(authenticator.authenticate("tester", "secret2")).isTrue();
-    assertThat(authenticator.authenticate("tester", "wrong")).isFalse();
-
-    assertThat(authenticator.authenticate("notfound", "wrong")).isFalse();
-
-    assertThat(authenticator.authenticate("robby", "secret1")).isTrue();
-    assertThat(authenticator.authenticate("robby", "wrong")).isFalse();
-  }
-
-}
index 20822a2dbc081e66d6eceff2b7cb5815fec79bda..45f8c4c389b5e2bc142133014c77f4b9fc4b25be 100644 (file)
@@ -46,8 +46,6 @@ public class LdapAutoDiscoveryWarningLogTest {
     MapSettings settings = new MapSettings()
       .setProperty("ldap.url", server.getUrl());
     LdapRealm realm = new LdapRealm(new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery()));
-    assertThat(realm.getName()).isEqualTo("LDAP");
-
     realm.init();
 
     assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty();
@@ -60,7 +58,6 @@ public class LdapAutoDiscoveryWarningLogTest {
     // ldap.url setting is not set
     LdapRealm realm = new LdapRealm(new LdapSettingsManager(new MapSettings().setProperty("ldap.realm", "example.org").asConfig(),
       ldapAutodiscovery));
-
     realm.init();
 
     assertThat(logTester.logs(LoggerLevel.WARN)).contains("Auto-discovery feature is deprecated, please use 'ldap.url' to specify LDAP url");
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapGroupsProviderTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapGroupsProviderTest.java
deleted file mode 100644 (file)
index 8d03958..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * 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 java.util.Collection;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.auth.ldap.server.LdapServer;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class LdapGroupsProviderTest {
-
-  /**
-   * A reference to the original ldif file
-   */
-  public static final String USERS_EXAMPLE_ORG_LDIF = "/users.example.org.ldif";
-  /**
-   * A reference to an aditional ldif file.
-   */
-  public static final String USERS_INFOSUPPORT_COM_LDIF = "/users.infosupport.com.ldif";
-
-  @ClassRule
-  public static LdapServer exampleServer = new LdapServer(USERS_EXAMPLE_ORG_LDIF);
-  @ClassRule
-  public static LdapServer infosupportServer = new LdapServer(USERS_INFOSUPPORT_COM_LDIF, "infosupport.com", "dc=infosupport,dc=com");
-
-  @Test
-  public void defaults() {
-    MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, null);
-
-    LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
-    LdapGroupsProvider groupsProvider = new LdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
-    Collection<String> groups;
-
-    groups = groupsProvider.getGroups("tester");
-    assertThat(groups).containsOnly("sonar-users");
-
-    groups = groupsProvider.getGroups("godin");
-    assertThat(groups).containsOnly("sonar-users", "sonar-developers");
-
-    groups = groupsProvider.getGroups("notfound");
-    assertThat(groups).isEmpty();
-  }
-
-  @Test
-  public void defaultsMultipleLdap() {
-    MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
-
-    LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
-    LdapGroupsProvider groupsProvider = new LdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
-
-    Collection<String> groups;
-
-    groups = groupsProvider.getGroups("tester");
-    assertThat(groups).containsOnly("sonar-users");
-
-    groups = groupsProvider.getGroups("godin");
-    assertThat(groups).containsOnly("sonar-users", "sonar-developers");
-
-    groups = groupsProvider.getGroups("notfound");
-    assertThat(groups).isEmpty();
-
-    groups = groupsProvider.getGroups("testerInfo");
-    assertThat(groups).containsOnly("sonar-users");
-
-    groups = groupsProvider.getGroups("robby");
-    assertThat(groups).containsOnly("sonar-users", "sonar-developers");
-  }
-
-  @Test
-  public void posix() {
-    MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, null);
-    settings.setProperty("ldap.group.request", "(&(objectClass=posixGroup)(memberUid={uid}))");
-    LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
-    LdapGroupsProvider groupsProvider = new LdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
-
-    Collection<String> groups;
-
-    groups = groupsProvider.getGroups("godin");
-    assertThat(groups).containsOnly("linux-users");
-  }
-
-  @Test
-  public void posixMultipleLdap() {
-    MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
-    settings.setProperty("ldap.example.group.request", "(&(objectClass=posixGroup)(memberUid={uid}))");
-    settings.setProperty("ldap.infosupport.group.request", "(&(objectClass=posixGroup)(memberUid={uid}))");
-    LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
-    LdapGroupsProvider groupsProvider = new LdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
-
-    Collection<String> groups;
-
-    groups = groupsProvider.getGroups("godin");
-    assertThat(groups).containsOnly("linux-users");
-
-    groups = groupsProvider.getGroups("robby");
-    assertThat(groups).containsOnly("linux-users");
-  }
-
-  @Test
-  public void mixed() {
-    MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
-    settings.setProperty("ldap.example.group.request", "(&(|(objectClass=groupOfUniqueNames)(objectClass=posixGroup))(|(uniqueMember={dn})(memberUid={uid})))");
-    LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
-    LdapGroupsProvider groupsProvider = new LdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
-
-    Collection<String> groups;
-
-    groups = groupsProvider.getGroups("godin");
-    assertThat(groups).containsOnly("sonar-users", "sonar-developers", "linux-users");
-  }
-
-  @Test
-  public void mixedMultipleLdap() {
-    MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
-    settings.setProperty("ldap.example.group.request", "(&(|(objectClass=groupOfUniqueNames)(objectClass=posixGroup))(|(uniqueMember={dn})(memberUid={uid})))");
-    settings.setProperty("ldap.infosupport.group.request", "(&(|(objectClass=groupOfUniqueNames)(objectClass=posixGroup))(|(uniqueMember={dn})(memberUid={uid})))");
-    LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
-    LdapGroupsProvider groupsProvider = new LdapGroupsProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings(), settingsManager.getGroupMappings());
-
-    Collection<String> groups;
-
-    groups = groupsProvider.getGroups("godin");
-    assertThat(groups).containsOnly("sonar-users", "sonar-developers", "linux-users");
-
-    groups = groupsProvider.getGroups("robby");
-    assertThat(groups).containsOnly("sonar-users", "sonar-developers", "linux-users");
-  }
-
-}
index 56e9ea0755390774097f1ebc911e6bb394b2d9c9..ba1656009efc96f03becf99e5876c649568f1290 100644 (file)
@@ -24,8 +24,6 @@ import org.junit.ClassRule;
 import org.junit.Test;
 import org.mockito.Mockito;
 import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.security.ExternalGroupsProvider;
-import org.sonar.api.security.ExternalUsersProvider;
 import org.sonar.auth.ldap.server.LdapServer;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -41,10 +39,9 @@ public class LdapRealmTest {
     MapSettings settings = new MapSettings()
       .setProperty("ldap.url", server.getUrl());
     LdapRealm realm = new LdapRealm(new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery()));
-    assertThat(realm.getName()).isEqualTo("LDAP");
     realm.init();
-    assertThat(realm.doGetAuthenticator()).isInstanceOf(LdapAuthenticator.class);
-    assertThat(realm.getUsersProvider()).isInstanceOf(ExternalUsersProvider.class).isInstanceOf(LdapUsersProvider.class);
+    assertThat(realm.doGetAuthenticator()).isInstanceOf(DefaultLdapAuthenticator.class);
+    assertThat(realm.getUsersProvider()).isInstanceOf(LdapUsersProvider.class).isInstanceOf(DefaultLdapUsersProvider.class);
     assertThat(realm.getGroupsProvider()).isNull();
   }
 
@@ -54,25 +51,26 @@ public class LdapRealmTest {
       .setProperty("ldap.url", "ldap://no-such-host")
       .setProperty("ldap.group.baseDn", "cn=groups,dc=example,dc=org");
     LdapRealm realm = new LdapRealm(new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery()));
-    assertThat(realm.getName()).isEqualTo("LDAP");
     try {
       realm.init();
       fail("Since there is no connection, the init method has to throw an exception.");
     } catch (LdapException e) {
       assertThat(e).hasMessage("Unable to open LDAP connection");
     }
-    assertThat(realm.doGetAuthenticator()).isInstanceOf(LdapAuthenticator.class);
-    assertThat(realm.getUsersProvider()).isInstanceOf(ExternalUsersProvider.class).isInstanceOf(LdapUsersProvider.class);
-    assertThat(realm.getGroupsProvider()).isInstanceOf(ExternalGroupsProvider.class).isInstanceOf(LdapGroupsProvider.class);
+    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 {
-      realm.getUsersProvider().doGetUserDetails(new ExternalUsersProvider.Context("tester", Mockito.mock(HttpServletRequest.class)));
+      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 {
-      realm.getGroupsProvider().doGetGroups(new ExternalGroupsProvider.Context("tester", Mockito.mock(HttpServletRequest.class)));
+      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");
diff --git a/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapUsersProviderTest.java b/server/sonar-auth-ldap/src/test/java/org/sonar/auth/ldap/LdapUsersProviderTest.java
deleted file mode 100644 (file)
index edbf6e2..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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 org.junit.ClassRule;
-import org.junit.Test;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.security.UserDetails;
-import org.sonar.auth.ldap.server.LdapServer;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class LdapUsersProviderTest {
-  /**
-   * A reference to the original ldif file
-   */
-  public static final String USERS_EXAMPLE_ORG_LDIF = "/users.example.org.ldif";
-  /**
-   * A reference to an aditional ldif file.
-   */
-  public static final String USERS_INFOSUPPORT_COM_LDIF = "/users.infosupport.com.ldif";
-
-  @ClassRule
-  public static LdapServer exampleServer = new LdapServer(USERS_EXAMPLE_ORG_LDIF);
-  @ClassRule
-  public static LdapServer infosupportServer = new LdapServer(USERS_INFOSUPPORT_COM_LDIF, "infosupport.com", "dc=infosupport,dc=com");
-
-  @Test
-  public void test() {
-    MapSettings settings = LdapSettingsFactory.generateSimpleAnonymousAccessSettings(exampleServer, infosupportServer);
-    LdapSettingsManager settingsManager = new LdapSettingsManager(settings.asConfig(), new LdapAutodiscovery());
-    LdapUsersProvider usersProvider = new LdapUsersProvider(settingsManager.getContextFactories(), settingsManager.getUserMappings());
-
-    UserDetails details;
-
-    details = usersProvider.getUserDetails("godin");
-    assertThat(details.getName()).isEqualTo("Evgeny Mandrikov");
-    assertThat(details.getEmail()).isEqualTo("godin@example.org");
-
-    details = usersProvider.getUserDetails("tester");
-    assertThat(details.getName()).isEqualTo("Tester Testerovich");
-    assertThat(details.getEmail()).isEqualTo("tester@example.org");
-
-    details = usersProvider.getUserDetails("without_email");
-    assertThat(details.getName()).isEqualTo("Without Email");
-    assertThat(details.getEmail()).isEmpty();
-
-    details = usersProvider.getUserDetails("notfound");
-    assertThat(details).isNull();
-
-    details = usersProvider.getUserDetails("robby");
-    assertThat(details.getName()).isEqualTo("Robby Developer");
-    assertThat(details.getEmail()).isEqualTo("rd@infosupport.com");
-
-    details = usersProvider.getUserDetails("testerInfo");
-    assertThat(details.getName()).isEqualTo("Tester Testerovich");
-    assertThat(details.getEmail()).isEqualTo("tester@infosupport.com");
-  }
-
-}
index 70141fa24f58f2fcf4c1c28a6d9f4c1b9e2af9e4..d12c870ed9cfd102966e83f20eadda342a84dba3 100644 (file)
@@ -17,6 +17,7 @@ dependencies {
   compile project(':server:sonar-server-common')
   compile project(':server:sonar-webserver-api')
   compile project(':sonar-plugin-api-impl')
+  compile project(':server:sonar-auth-ldap')
   compile 'org.mindrot:jbcrypt'
 
   compileOnly 'com.google.code.findbugs:jsr305'
index 5beed48ec0cba4aec4428e7a9e95f18b28769004..781250620a8b9e02b2dacedc53fe33f0511c01b0 100644 (file)
@@ -33,6 +33,7 @@ public class AuthenticationModule extends Module {
       BasicAuthentication.class,
       CredentialsAuthentication.class,
       CredentialsExternalAuthentication.class,
+      LdapCredentialsAuthentication.class,
       CredentialsLocalAuthentication.class,
       DefaultAdminCredentialsVerifierFilter.class,
       GithubWebhookAuthentication.class,
index 3933ba9d251837a04133332fee35ab7044f82a43..c2e05801c9410fcf0820c6e2902303ef9dbed2eb 100644 (file)
@@ -40,13 +40,16 @@ public class CredentialsAuthentication {
   private final AuthenticationEvent authenticationEvent;
   private final CredentialsExternalAuthentication externalAuthentication;
   private final CredentialsLocalAuthentication localAuthentication;
+  private final LdapCredentialsAuthentication ldapCredentialsAuthentication;
 
   public CredentialsAuthentication(DbClient dbClient, AuthenticationEvent authenticationEvent,
-    CredentialsExternalAuthentication externalAuthentication, CredentialsLocalAuthentication localAuthentication) {
+    CredentialsExternalAuthentication externalAuthentication, CredentialsLocalAuthentication localAuthentication,
+    LdapCredentialsAuthentication ldapCredentialsAuthentication) {
     this.dbClient = dbClient;
     this.authenticationEvent = authenticationEvent;
     this.externalAuthentication = externalAuthentication;
     this.localAuthentication = localAuthentication;
+    this.ldapCredentialsAuthentication = ldapCredentialsAuthentication;
   }
 
   public UserDto authenticate(Credentials credentials, HttpServletRequest request, Method method) {
@@ -64,7 +67,8 @@ public class CredentialsAuthentication {
       authenticationEvent.loginSuccess(request, localUser.getLogin(), Source.local(method));
       return localUser;
     }
-    Optional<UserDto> externalUser = externalAuthentication.authenticate(credentials, request, method);
+    Optional<UserDto> externalUser = externalAuthentication.authenticate(credentials, request, method)
+      .or(() -> ldapCredentialsAuthentication.authenticate(credentials, request, method));
     if (externalUser.isPresent()) {
       return externalUser.get();
     }
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/LdapCredentialsAuthentication.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/LdapCredentialsAuthentication.java
new file mode 100644 (file)
index 0000000..adbbe63
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * 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.server.authentication;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.server.authentication.Display;
+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.LdapAuthenticator;
+import org.sonar.auth.ldap.LdapGroupsProvider;
+import org.sonar.auth.ldap.LdapRealm;
+import org.sonar.auth.ldap.LdapUserDetails;
+import org.sonar.auth.ldap.LdapUsersProvider;
+import org.sonar.db.user.UserDto;
+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;
+
+public class LdapCredentialsAuthentication {
+
+  private static final String LDAP_SECURITY_REALM = "LDAP";
+
+  private static final Logger LOG = Loggers.get(LdapCredentialsAuthentication.class);
+
+  private final Configuration configuration;
+  private final UserRegistrar userRegistrar;
+  private final AuthenticationEvent authenticationEvent;
+
+  private final LdapAuthenticator ldapAuthenticator;
+  private final LdapUsersProvider ldapUsersProvider;
+  private final LdapGroupsProvider ldapGroupsProvider;
+  private final boolean isLdapAuthActivated;
+
+  public LdapCredentialsAuthentication(Configuration configuration,
+    UserRegistrar userRegistrar, AuthenticationEvent authenticationEvent, LdapRealm ldapRealm) {
+    this.configuration = configuration;
+    this.userRegistrar = userRegistrar;
+    this.authenticationEvent = authenticationEvent;
+
+    String realmName = configuration.get(ProcessProperties.Property.SONAR_SECURITY_REALM.getKey()).orElse(null);
+    this.isLdapAuthActivated = LDAP_SECURITY_REALM.equals(realmName);
+
+    if (isLdapAuthActivated) {
+      ldapRealm.init();
+      this.ldapAuthenticator = ldapRealm.doGetAuthenticator();
+      this.ldapUsersProvider = ldapRealm.getUsersProvider();
+      this.ldapGroupsProvider = ldapRealm.getGroupsProvider();
+    } else {
+      this.ldapAuthenticator = null;
+      this.ldapUsersProvider = null;
+      this.ldapGroupsProvider = null;
+    }
+  }
+
+  public Optional<UserDto> authenticate(Credentials credentials, HttpServletRequest request, AuthenticationEvent.Method method) {
+    if (isLdapAuthActivated) {
+      return Optional.of(doAuthenticate(fixCase(credentials), request, method));
+    }
+    return Optional.empty();
+  }
+
+  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) {
+        throw AuthenticationException.newBuilder()
+          .setSource(realmEventSource(method))
+          .setLogin(credentials.getLogin())
+          .setMessage("No user details")
+          .build();
+      }
+      LdapAuthenticator.Context ldapAuthenticatorContext = new LdapAuthenticator.Context(credentials.getLogin(), credentials.getPassword().orElse(null), request);
+      boolean status = ldapAuthenticator.doAuthenticate(ldapAuthenticatorContext);
+      if (!status) {
+        throw AuthenticationException.newBuilder()
+          .setSource(realmEventSource(method))
+          .setLogin(credentials.getLogin())
+          .setMessage("Realm returned authenticate=false")
+          .build();
+      }
+      UserDto userDto = synchronize(credentials.getLogin(), details, request, method);
+      authenticationEvent.loginSuccess(request, credentials.getLogin(), realmEventSource(method));
+      return userDto;
+    } catch (AuthenticationException e) {
+      throw e;
+    } catch (Exception e) {
+      // It seems that with Realm API it's expected to log the error and to not authenticate the user
+      LOG.error("Error during authentication", e);
+      throw AuthenticationException.newBuilder()
+        .setSource(realmEventSource(method))
+        .setLogin(credentials.getLogin())
+        .setMessage(e.getMessage())
+        .build();
+    }
+  }
+
+  private static Source realmEventSource(AuthenticationEvent.Method method) {
+    return Source.realm(method, "ldap");
+  }
+
+  private UserDto synchronize(String userLogin, LdapUserDetails details, HttpServletRequest request, AuthenticationEvent.Method method) {
+    String name = details.getName();
+    UserIdentity.Builder userIdentityBuilder = UserIdentity.builder()
+      .setName(isEmpty(name) ? userLogin : name)
+      .setEmail(trimToNull(details.getEmail()))
+      .setProviderLogin(userLogin);
+    if (ldapGroupsProvider != null) {
+      LdapGroupsProvider.Context context = new LdapGroupsProvider.Context(userLogin, request);
+      Collection<String> groups = ldapGroupsProvider.doGetGroups(context);
+      userIdentityBuilder.setGroups(new HashSet<>(groups));
+    }
+    return userRegistrar.register(
+      UserRegistration.builder()
+        .setUserIdentity(userIdentityBuilder.build())
+        .setProvider(new ExternalIdentityProvider())
+        .setSource(realmEventSource(method))
+        .build());
+  }
+
+  private Credentials fixCase(Credentials credentials) {
+    if (configuration.getBoolean("sonar.authenticator.downcase").orElse(false)) {
+      return new Credentials(credentials.getLogin().toLowerCase(Locale.ENGLISH), credentials.getPassword().orElse(null));
+    }
+    return credentials;
+  }
+
+  private static class ExternalIdentityProvider implements IdentityProvider {
+    @Override
+    public String getKey() {
+      return ExternalIdentity.SQ_AUTHORITY;
+    }
+
+    @Override
+    public String getName() {
+      return ExternalIdentity.SQ_AUTHORITY;
+    }
+
+    @Override
+    public Display getDisplay() {
+      return null;
+    }
+
+    @Override
+    public boolean isEnabled() {
+      return true;
+    }
+
+    @Override
+    public boolean allowsUsersToSignUp() {
+      return true;
+    }
+  }
+
+}
index da7cb9b276c1408b589a4d21516f2e301bc81131..26e27a4e79727077a622b0160a87532ec3f71859 100644 (file)
@@ -28,7 +28,7 @@ import org.sonar.server.authentication.event.AuthenticationEvent;
 
 import static java.util.Objects.requireNonNull;
 
-class UserRegistration {
+public class UserRegistration {
 
   private final UserIdentity userIdentity;
   private final IdentityProvider provider;
@@ -59,7 +59,7 @@ class UserRegistration {
     return organizationAlmIds;
   }
 
-  static UserRegistration.Builder builder() {
+  public static UserRegistration.Builder builder() {
     return new Builder();
   }
 
index 1330478d0f31963fa161e1164dd36f069c2206ab..17f50a5ab138c8433e8fb0f976722b87de070f3c 100644 (file)
@@ -21,8 +21,8 @@ package org.sonar.server.user;
 
 import javax.annotation.Nullable;
 import org.apache.commons.lang.StringUtils;
-import org.sonar.api.Startable;
 import org.sonar.api.CoreProperties;
+import org.sonar.api.Startable;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.security.LoginPasswordAuthenticator;
 import org.sonar.api.security.SecurityRealm;
@@ -30,10 +30,10 @@ import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.SonarException;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
+import org.springframework.beans.factory.annotation.Autowired;
 
 import static org.sonar.process.ProcessProperties.Property.SONAR_AUTHENTICATOR_IGNORE_STARTUP_FAILURE;
 import static org.sonar.process.ProcessProperties.Property.SONAR_SECURITY_REALM;
-import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * @since 2.14
@@ -41,6 +41,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 @ServerSide
 public class SecurityRealmFactory implements Startable {
 
+  private static final String LDAP_SECURITY_REALM = "LDAP";
   private final boolean ignoreStartupFailure;
   private final SecurityRealm realm;
 
@@ -49,6 +50,12 @@ public class SecurityRealmFactory implements Startable {
     ignoreStartupFailure = config.getBoolean(SONAR_AUTHENTICATOR_IGNORE_STARTUP_FAILURE.getKey()).orElse(false);
     String realmName = config.get(SONAR_SECURITY_REALM.getKey()).orElse(null);
     String className = config.get(CoreProperties.CORE_AUTHENTICATOR_CLASS).orElse(null);
+
+    if (LDAP_SECURITY_REALM.equals(realmName)) {
+      realm = null;
+      return;
+    }
+
     SecurityRealm selectedRealm = null;
     if (!StringUtils.isEmpty(realmName)) {
       selectedRealm = selectRealm(realms, realmName);
@@ -66,6 +73,7 @@ public class SecurityRealmFactory implements Startable {
       selectedRealm = new CompatibilityRealm(authenticator);
     }
     realm = selectedRealm;
+
   }
 
   @Autowired(required = false)
index b02bb5fe85964d57f7abf1a390caec861b424ee2..984467262b4b97588ac67425073c08e3ea380880 100644 (file)
@@ -65,7 +65,9 @@ public class CredentialsAuthenticationTest {
   private final MapSettings settings = new MapSettings().setProperty("sonar.internal.pbkdf2.iterations", NUMBER_OF_PBKDF2_ITERATIONS);
   private final CredentialsExternalAuthentication externalAuthentication = mock(CredentialsExternalAuthentication.class);
   private final CredentialsLocalAuthentication localAuthentication = spy(new CredentialsLocalAuthentication(dbClient, settings.asConfig()));
-  private final CredentialsAuthentication underTest = new CredentialsAuthentication(dbClient, authenticationEvent, externalAuthentication, localAuthentication);
+  private final LdapCredentialsAuthentication ldapCredentialsAuthentication = mock(LdapCredentialsAuthentication.class);
+  private final CredentialsAuthentication underTest = new CredentialsAuthentication(dbClient, authenticationEvent, externalAuthentication, localAuthentication,
+    ldapCredentialsAuthentication);
 
   @Test
   public void authenticate_local_user() {
@@ -110,12 +112,31 @@ public class CredentialsAuthenticationTest {
     executeAuthenticate(BASIC);
 
     verify(externalAuthentication).authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
+    verifyNoInteractions(ldapCredentialsAuthentication);
     verifyNoInteractions(authenticationEvent);
   }
 
   @Test
-  public void fail_to_authenticate_authenticate_external_user_when_no_external_authentication() {
+  public void authenticate_ldap_user() {
+    when(externalAuthentication.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC)).thenReturn(Optional.empty());
+
+    String externalId = "12345";
+    when(ldapCredentialsAuthentication.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC)).thenReturn(Optional.of(newUserDto().setExternalId(externalId)));
+    insertUser(newUserDto()
+      .setLogin(LOGIN)
+      .setLocal(false));
+
+    assertThat(executeAuthenticate(BASIC).getExternalId()).isEqualTo(externalId);
+
+    verify(externalAuthentication).authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
+    verify(ldapCredentialsAuthentication).authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
+    verifyNoInteractions(authenticationEvent);
+  }
+
+  @Test
+  public void fail_to_authenticate_external_user_when_no_external_and_ldap_authentication() {
     when(externalAuthentication.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC_TOKEN)).thenReturn(Optional.empty());
+    when(ldapCredentialsAuthentication.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC_TOKEN)).thenReturn(Optional.empty());
     insertUser(newUserDto()
       .setLogin(LOGIN)
       .setLocal(false));
@@ -126,6 +147,8 @@ public class CredentialsAuthenticationTest {
       .hasFieldOrPropertyWithValue("source", Source.local(BASIC_TOKEN))
       .hasFieldOrPropertyWithValue("login", LOGIN);
 
+    verify(externalAuthentication).authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC_TOKEN);
+    verify(ldapCredentialsAuthentication).authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC_TOKEN);
     verifyNoInteractions(authenticationEvent);
   }
 
diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/LdapCredentialsAuthenticationTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/LdapCredentialsAuthenticationTest.java
new file mode 100644 (file)
index 0000000..d4ce198
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * 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.server.authentication;
+
+import javax.servlet.http.HttpServletRequest;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.auth.ldap.LdapAuthenticator;
+import org.sonar.auth.ldap.LdapGroupsProvider;
+import org.sonar.auth.ldap.LdapRealm;
+import org.sonar.auth.ldap.LdapUserDetails;
+import org.sonar.auth.ldap.LdapUsersProvider;
+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 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.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.server.authentication.event.AuthenticationEvent.Method.BASIC;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC_TOKEN;
+
+@RunWith(MockitoJUnitRunner.Silent.class)
+public class LdapCredentialsAuthenticationTest {
+
+  private static final String LOGIN = "LOGIN";
+  private static final String PASSWORD = "PASSWORD";
+
+  private static final String REALM_NAME = "ldap";
+
+  private MapSettings settings = new MapSettings();
+  private TestUserRegistrar userRegistrar = new TestUserRegistrar();
+
+  @Mock
+  private AuthenticationEvent authenticationEvent;
+
+  @Mock
+  private HttpServletRequest request = mock(HttpServletRequest.class);
+
+  @Mock
+  private LdapAuthenticator ldapAuthenticator;
+  @Mock
+  private LdapGroupsProvider ldapGroupsProvider;
+  @Mock
+  private LdapUsersProvider ldapUsersProvider;
+  @Mock
+  private LdapRealm ldapRealm;
+
+  private LdapCredentialsAuthentication underTest;
+
+  @Before
+  public void setUp() throws Exception {
+    settings.setProperty(ProcessProperties.Property.SONAR_SECURITY_REALM.getKey(), "LDAP");
+    when(ldapRealm.doGetAuthenticator()).thenReturn(ldapAuthenticator);
+    when(ldapRealm.getUsersProvider()).thenReturn(ldapUsersProvider);
+    when(ldapRealm.getGroupsProvider()).thenReturn(ldapGroupsProvider);
+    underTest = new LdapCredentialsAuthentication(settings.asConfig(), userRegistrar, authenticationEvent, ldapRealm);
+  }
+
+  @Test
+  public void authenticate_with_null_group_provider() {
+    reset(ldapRealm);
+    when(ldapRealm.doGetAuthenticator()).thenReturn(ldapAuthenticator);
+    when(ldapRealm.getUsersProvider()).thenReturn(ldapUsersProvider);
+    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);
+
+    underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
+
+    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));
+    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);
+
+    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));
+    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);
+
+    assertThat(userRegistrar.getAuthenticatorParameters().getProvider().getName()).isEqualTo("sonarqube");
+    verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
+  }
+
+  @Test
+  public void authenticate_with_group_sync() {
+    when(ldapGroupsProvider.doGetGroups(any(LdapGroupsProvider.Context.class))).thenReturn(asList("group1", "group2"));
+
+    executeAuthenticate();
+
+    assertThat(userRegistrar.isAuthenticated()).isTrue();
+    assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().shouldSyncGroups()).isTrue();
+    verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, 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);
+
+    assertThat(userRegistrar.isAuthenticated()).isTrue();
+    assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().getName()).isEqualTo(LOGIN);
+    verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
+  }
+
+  @Test
+  public void use_downcase_login() {
+    settings.setProperty("sonar.authenticator.downcase", true);
+
+    executeAuthenticate("LOGIN");
+
+    assertThat(userRegistrar.isAuthenticated()).isTrue();
+    assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("login");
+    verify(authenticationEvent).loginSuccess(request, "login", Source.realm(BASIC, REALM_NAME));
+  }
+
+  @Test
+  public void does_not_user_downcase_login() {
+    settings.setProperty("sonar.authenticator.downcase", false);
+
+    executeAuthenticate("LoGiN");
+
+    assertThat(userRegistrar.isAuthenticated()).isTrue();
+    assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("LoGiN");
+    verify(authenticationEvent).loginSuccess(request, "LoGiN", Source.realm(BASIC, 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))
+      .hasMessage("No user details")
+      .isInstanceOf(AuthenticationException.class)
+      .hasFieldOrPropertyWithValue("source", Source.realm(BASIC, REALM_NAME))
+      .hasFieldOrPropertyWithValue("login", LOGIN);
+
+    verifyNoInteractions(authenticationEvent);
+  }
+
+  @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);
+
+    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("login", LOGIN);
+
+    verifyNoInteractions(authenticationEvent);
+
+  }
+
+  @Test
+  public void fail_to_authenticate_when_any_exception_is_thrown() {
+    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("login", LOGIN);
+
+    verifyNoInteractions(authenticationEvent);
+  }
+
+  @Test
+  public void return_empty_user_when_ldap_not_activated() {
+    reset(ldapRealm);
+    settings.clear();
+    underTest = new LdapCredentialsAuthentication(settings.asConfig(), userRegistrar, authenticationEvent, ldapRealm);
+
+    assertThat(underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC)).isEmpty();
+    verifyNoInteractions(authenticationEvent);
+    verifyNoInteractions(ldapRealm);
+  }
+
+  private void executeAuthenticate() {
+    executeAuthenticate(LOGIN);
+  }
+
+  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);
+  }
+
+}
index e88aad033da90ff85081ca5daa47c04d2abf3576..2b8065c44e2e3bff50c4cf3401b26c1a04f4656b 100644 (file)
@@ -60,6 +60,15 @@ public class SecurityRealmFactoryTest {
     assertThat(factory.hasExternalAuthentication()).isFalse();
   }
 
+  @Test
+  public void return_null_if_realm_is_ldap() {
+    settings.setProperty("sonar.security.realm", "LDAP");
+    SecurityRealmFactory factory = new SecurityRealmFactory(settings.asConfig());
+    factory.start();
+    assertThat(factory.getRealm()).isNull();
+    assertThat(factory.hasExternalAuthentication()).isFalse();
+  }
+
   @Test
   public void realm_not_found() {
     settings.setProperty("sonar.security.realm", "Fake");