From 271e2cde317eeee0b5a07bc74394958dc4f8370d Mon Sep 17 00:00:00 2001 From: Sulabh Upadhyay Date: Tue, 22 Sep 2015 15:58:26 +0530 Subject: [PATCH] SONAR-6856 : Add a new API ExternalGroupsProvider.doGetGroups(Context context) to allow plugins to pass groups information to SonarQube Addition of one more api in ExternalGroupsProvider which will pass Context as parameter . doGetGroups(String username) is marked as deprecated Default implementation is provided in both version of doGetGroups so that authentication plugin relying on previous version ( <5.2) and current version 5.2 do not face compact issue on the SonarQube server 5.2 Testing: 1. Unit test for ExternalGroupsProvider is added. 2. Testing of authentication plugin (e.g. LDAP) targeting SonarQube Plugin apis version < 5.2 working with SonarQube server 5.2 3. Testing of LDAP plugin with SSO changes which are tareting SonarQube plugin api version 5.2 working with SonarQube server 5.2 --- .../webapp/WEB-INF/lib/need_authentication.rb | 11 +- .../api/security/ExternalGroupsProvider.java | 37 +++++- .../security/ExternalGroupsProviderTest.java | 107 ++++++++++++++++++ 3 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 sonar-plugin-api/src/test/java/org/sonar/api/security/ExternalGroupsProviderTest.java diff --git a/server/sonar-web/src/main/webapp/WEB-INF/lib/need_authentication.rb b/server/sonar-web/src/main/webapp/WEB-INF/lib/need_authentication.rb index cc4cb53f4eb..e31ed031c40 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/lib/need_authentication.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/lib/need_authentication.rb @@ -76,7 +76,7 @@ class PluginRealm if @java_authenticator context = org.sonar.api.security.Authenticator::Context.new(username, password, servlet_request) status = @java_authenticator.doAuthenticate(context) - status ? synchronize(username, password, details) : nil + status ? synchronize(username, password, details, servlet_request) : nil else # No authenticator nil @@ -114,7 +114,7 @@ class PluginRealm # Authentication in external system was successful - replicate password, details and groups into Sonar # Return the user. # - def synchronize(username, password, details) + def synchronize(username, password, details, servlet_request) username=details.getName() if username.blank? && details user = User.find_by_login(username) @@ -159,7 +159,7 @@ class PluginRealm # Note that validation disabled user.save(false) - synchronize_groups(user) + synchronize_groups(user, servlet_request) # Note that validation disabled user.save(false) end @@ -172,10 +172,11 @@ class PluginRealm user end - def synchronize_groups(user) + def synchronize_groups(user, servlet_request) if @java_groups_provider begin - groups = @java_groups_provider.doGetGroups(user.login) + provider_context = org.sonar.api.security.ExternalGroupsProvider::Context.new(user.login, servlet_request) + groups = @java_groups_provider.doGetGroups(provider_context) rescue Exception => e Rails.logger.error("Error from external groups provider: #{e.message}") else diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/security/ExternalGroupsProvider.java b/sonar-plugin-api/src/main/java/org/sonar/api/security/ExternalGroupsProvider.java index 77341948e04..3a0134b4ad7 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/security/ExternalGroupsProvider.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/security/ExternalGroupsProvider.java @@ -20,19 +20,52 @@ package org.sonar.api.security; import java.util.Collection; +import javax.servlet.http.HttpServletRequest; /** * Note that prefix "do" for names of methods is reserved for future enhancements, thus should not be used in subclasses. * - * @since 2.14 * @see SecurityRealm + * @since 2.14 */ public abstract class ExternalGroupsProvider { /** * @return list of groups associated with specified user, or null if such user doesn't exist * @throws RuntimeException in case of unexpected error such as connection failure + * @deprecated replaced by {@link #doGetGroups(org.sonar.api.security.ExternalGroupsProvider.Context)} since v. 5.2 */ - public abstract Collection doGetGroups(String username); + @Deprecated + public Collection doGetGroups(String username) { + return null; + } + /** + * Override this method in order to load user group information. + * + * @return list of groups associated with specified user, or null if such user doesn't exist + * @throws RuntimeException in case of unexpected error such as connection failure + * @since 5.2 + */ + public Collection doGetGroups(Context context) { + return doGetGroups(context.getUsername()); + } + + public static final class Context { + private String username; + private HttpServletRequest request; + + public Context(String username, HttpServletRequest request) { + this.username = username; + this.request = request; + } + + public String getUsername() { + return username; + } + + public HttpServletRequest getRequest() { + return request; + } + } } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/security/ExternalGroupsProviderTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/security/ExternalGroupsProviderTest.java new file mode 100644 index 00000000000..1c3f43d3b48 --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/security/ExternalGroupsProviderTest.java @@ -0,0 +1,107 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.api.security; + +import com.google.common.base.Preconditions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class ExternalGroupsProviderTest { + @Test + public void doGetGroupsNoOverride() { + ExternalGroupsProvider groupsProvider = new ExternalGroupsProvider() { + }; + + String userName = "foo"; + assertThat(groupsProvider.doGetGroups(userName)).isNull(); + assertThat(groupsProvider.doGetGroups(new ExternalGroupsProvider.Context(userName, + mock(HttpServletRequest.class)))).isNull(); + } + + @Test + public void doGetGroupsTests() { + final Map> userGroupsMap = getTestUserGroupMapping(); + + ExternalGroupsProvider groupsProvider = new ExternalGroupsProvider() { + @Override + public Collection doGetGroups(Context context) { + Preconditions.checkNotNull(context.getUsername()); + Preconditions.checkNotNull(context.getRequest()); + + return userGroupsMap.get(context.getUsername()); + } + }; + + runDoGetGroupsTests(groupsProvider, userGroupsMap); + } + + @Test + public void doGetGroupsDeprecatedApi() { + final Map> userGroupsMap = getTestUserGroupMapping(); + + ExternalGroupsProvider groupsProvider = new ExternalGroupsProvider() { + @Override + public Collection doGetGroups(String username) { + Preconditions.checkNotNull(username); + + return userGroupsMap.get(username); + } + }; + + runDoGetGroupsTests(groupsProvider, userGroupsMap); + } + + private static void runDoGetGroupsTests(ExternalGroupsProvider groupsProvider, Map> userGroupsMap) { + for (Map.Entry> userGroupMapEntry : userGroupsMap.entrySet()) { + Collection groups = groupsProvider.doGetGroups(new ExternalGroupsProvider.Context( + userGroupMapEntry.getKey(), mock(HttpServletRequest.class))); + assertThat(groups).isEqualTo(userGroupMapEntry.getValue()); + } + } + + private static Map> getTestUserGroupMapping() { + Map> userGroupsMap = new HashMap>(); + addUserGroupMapping(userGroupsMap, "userWithOneGroups", new String[] {"group1"}); + addUserGroupMapping(userGroupsMap, "userWithTwoGroups", new String[] {"group1", "group2"}); + addUserGroupMapping(userGroupsMap, "userWithNoGroup", new String[] {}); + addUserGroupMapping(userGroupsMap, "userWithNullGroup", null); + + return userGroupsMap; + } + + private static void addUserGroupMapping(Map> userGroupsMap, String user, @Nullable String[] groups) { + Collection groupsCollection = null; + if (groups != null) { + groupsCollection = new ArrayList(); + groupsCollection.addAll(Arrays.asList(groups)); + } + + userGroupsMap.put(user, groupsCollection); + } +}