From 239a835a35e8df6504be145c5994358874b4ec5c Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Thu, 23 Nov 2017 16:51:17 +0100 Subject: [PATCH] SONAR-9000 Filter on membership in WS api/organizations/search --- .../db/organization/OrganizationQuery.java | 23 +++++-- .../db/organization/OrganizationMapper.xml | 4 ++ .../db/organization/OrganizationDaoTest.java | 36 ++++++++++ .../server/organization/ws/SearchAction.java | 36 ++++++++-- .../organization/ws/SearchActionTest.java | 69 +++++++++++++------ 5 files changed, 135 insertions(+), 33 deletions(-) diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationQuery.java b/server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationQuery.java index cfa220de493..79ded911581 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationQuery.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationQuery.java @@ -30,9 +30,21 @@ import static org.sonar.core.util.stream.MoreCollectors.toSet; public class OrganizationQuery { private static final OrganizationQuery NO_QUERY = newOrganizationQueryBuilder().build(); private final Set keys; + private final Integer userId; private OrganizationQuery(Builder builder) { this.keys = builder.keys; + this.userId = builder.member; + } + + @CheckForNull + public Set getKeys() { + return keys; + } + + @CheckForNull + public Integer getMember() { + return userId; } public static OrganizationQuery returnAll() { @@ -43,13 +55,9 @@ public class OrganizationQuery { return new Builder(); } - @CheckForNull - public Set getKeys() { - return keys; - } - public static class Builder { private Set keys; + private Integer member; private Builder() { // use static factory method @@ -64,6 +72,11 @@ public class OrganizationQuery { return this; } + public Builder setMember(@Nullable Integer userId) { + this.member = userId; + return this; + } + public OrganizationQuery build() { return new OrganizationQuery(this); } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/organization/OrganizationMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/organization/OrganizationMapper.xml index 457d2639594..4f23af35bea 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/organization/OrganizationMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/organization/OrganizationMapper.xml @@ -97,6 +97,10 @@ from organizations org + + inner join organization_members om on org.uuid=om.organization_uuid + and om.user_id=#{query.member,jdbcType=INTEGER} + org.kee in diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/organization/OrganizationDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/organization/OrganizationDaoTest.java index 12740780895..a747b2a82bf 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/organization/OrganizationDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/organization/OrganizationDaoTest.java @@ -498,6 +498,42 @@ public class OrganizationDaoTest { .containsExactly("uuid1", "uuid3"); } + @Test + public void selectByQuery_filter_on_a_member() { + OrganizationDto organization = dbTester.organizations().insert(); + OrganizationDto anotherOrganization = dbTester.organizations().insert(); + OrganizationDto organizationWithoutMember = dbTester.organizations().insert(); + UserDto user = dbTester.users().insertUser(); + dbTester.organizations().addMember(organization, user); + dbTester.organizations().addMember(anotherOrganization, user); + + List result = underTest.selectByQuery(dbSession, OrganizationQuery.newOrganizationQueryBuilder().setMember(user.getId()).build(), forPage(1).andSize(100)); + + assertThat(result).extracting(OrganizationDto::getUuid) + .containsExactlyInAnyOrder(organization.getUuid(), anotherOrganization.getUuid()) + .doesNotContain(organizationWithoutMember.getUuid()); + } + + @Test + public void selectByQuery_filter_on_a_member_and_keys() { + OrganizationDto organization = dbTester.organizations().insert(); + OrganizationDto anotherOrganization = dbTester.organizations().insert(); + OrganizationDto organizationWithoutKeyProvided = dbTester.organizations().insert(); + OrganizationDto organizationWithoutMember = dbTester.organizations().insert(); + UserDto user = dbTester.users().insertUser(); + dbTester.organizations().addMember(organization, user); + dbTester.organizations().addMember(anotherOrganization, user); + dbTester.organizations().addMember(organizationWithoutKeyProvided, user); + + List result = underTest.selectByQuery(dbSession, OrganizationQuery.newOrganizationQueryBuilder() + .setKeys(Arrays.asList(organization.getKey(), anotherOrganization.getKey(), organizationWithoutMember.getKey())) + .setMember(user.getId()).build(), forPage(1).andSize(100)); + + assertThat(result).extracting(OrganizationDto::getUuid) + .containsExactlyInAnyOrder(organization.getUuid(), anotherOrganization.getUuid()) + .doesNotContain(organizationWithoutKeyProvided.getUuid(), organizationWithoutMember.getUuid()); + } + @Test public void getDefaultTemplates_returns_empty_when_table_is_empty() { assertThat(underTest.getDefaultTemplates(dbSession, ORGANIZATION_DTO_1.getUuid())).isEmpty(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/SearchAction.java index 957bf0d7111..edceff388b5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/SearchAction.java @@ -20,6 +20,7 @@ package org.sonar.server.organization.ws; import java.util.List; +import javax.annotation.CheckForNull; import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; @@ -29,6 +30,7 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.organization.OrganizationQuery; +import org.sonar.server.user.UserSession; import org.sonarqube.ws.Organizations; import org.sonarqube.ws.Organizations.Organization; @@ -39,16 +41,18 @@ import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.Common.Paging; public class SearchAction implements OrganizationsWsAction { - private static final String PARAM_ORGANIZATIONS = "organizations"; - private static final String PARAM_MEMBER = "member"; + static final String PARAM_ORGANIZATIONS = "organizations"; + static final String PARAM_MEMBER = "member"; private static final String ACTION = "search"; private static final int MAX_SIZE = 500; private final DbClient dbClient; + private final UserSession userSession; private final OrganizationsWsSupport wsSupport; - public SearchAction(DbClient dbClient, OrganizationsWsSupport wsSupport) { + public SearchAction(DbClient dbClient, UserSession userSession, OrganizationsWsSupport wsSupport) { this.dbClient = dbClient; + this.userSession = userSession; this.wsSupport = wsSupport; } @@ -70,22 +74,30 @@ public class SearchAction implements OrganizationsWsAction { .setRequired(false) .setSince("6.3"); + action.createParam(PARAM_MEMBER) + .setDescription("Filter organizations based on whether the authenticated user is a member. If false, no filter applies.") + .setSince("7.0") + .setDefaultValue(String.valueOf(false)) + .setBooleanPossibleValues(); + action.addPagingParams(100, MAX_SIZE); } @Override public void handle(Request request, Response response) throws Exception { try (DbSession dbSession = dbClient.openSession(false)) { + Integer userId = getUserIdIfFilterMembership(request); List organizations = getOrganizationKeys(request); - OrganizationQuery organizationQuery = newOrganizationQueryBuilder() + OrganizationQuery dbQuery = newOrganizationQueryBuilder() .setKeys(organizations) + .setMember(userId) .build(); - int total = dbClient.organizationDao().countByQuery(dbSession, organizationQuery); + int total = dbClient.organizationDao().countByQuery(dbSession, dbQuery); Paging paging = buildWsPaging(request, total); List dtos = dbClient.organizationDao().selectByQuery( dbSession, - organizationQuery, + dbQuery, forPage(paging.getPageIndex()).andSize(paging.getPageSize())); writeResponse(request, response, dtos, paging); } @@ -107,6 +119,18 @@ public class SearchAction implements OrganizationsWsAction { .build(); } + @CheckForNull + private Integer getUserIdIfFilterMembership(Request request) { + boolean filterOnAuthenticatedUser = request.mandatoryParamAsBoolean(PARAM_MEMBER); + if (!filterOnAuthenticatedUser) { + return null; + } + + userSession.checkLoggedIn(); + return userSession.getUserId(); + } + + @CheckForNull private static List getOrganizationKeys(Request request) { List organizations = request.paramAsStrings(PARAM_ORGANIZATIONS); if (organizations != null) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/SearchActionTest.java index 62b0a59d998..77adf57e92e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/SearchActionTest.java @@ -35,7 +35,9 @@ import org.sonar.core.util.Uuids; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.user.UserDto; import org.sonar.server.organization.OrganizationValidationImpl; +import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.WsActionTester; import org.sonarqube.ws.Common.Paging; @@ -47,6 +49,7 @@ import static java.lang.String.valueOf; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.server.organization.ws.SearchAction.PARAM_MEMBER; import static org.sonar.test.JsonAssert.assertJson; public class SearchActionTest { @@ -64,23 +67,25 @@ public class SearchActionTest { private System2 system2 = mock(System2.class); @Rule - public DbTester dbTester = DbTester.create(system2).setDisableDefaultOrganization(true); + public UserSessionRule userSession = UserSessionRule.standalone(); + @Rule + public DbTester db = DbTester.create(system2).setDisableDefaultOrganization(true); @Rule public ExpectedException expectedException = ExpectedException.none(); - private SearchAction underTest = new SearchAction(dbTester.getDbClient(), new OrganizationsWsSupport(new OrganizationValidationImpl())); - private WsActionTester wsTester = new WsActionTester(underTest); + private SearchAction underTest = new SearchAction(db.getDbClient(), userSession, new OrganizationsWsSupport(new OrganizationValidationImpl())); + private WsActionTester ws = new WsActionTester(underTest); @Test - public void verify_define() { - WebService.Action action = wsTester.getDef(); + public void definition() { + WebService.Action action = ws.getDef(); assertThat(action.key()).isEqualTo("search"); assertThat(action.isPost()).isFalse(); assertThat(action.description()).isEqualTo("Search for organizations"); assertThat(action.isInternal()).isTrue(); assertThat(action.since()).isEqualTo("6.2"); assertThat(action.handler()).isEqualTo(underTest); - assertThat(action.params()).hasSize(3); + assertThat(action.params()).hasSize(4); assertThat(action.responseExample()).isEqualTo(getClass().getResource("search-example.json")); WebService.Param organizations = action.param("organizations"); @@ -100,6 +105,11 @@ public class SearchActionTest { assertThat(pageSize.defaultValue()).isEqualTo("100"); assertThat(pageSize.maximumValue()).isEqualTo(500); assertThat(pageSize.description()).isEqualTo("Page size. Must be greater than 0 and less than 500"); + + WebService.Param member = action.param("member"); + assertThat(member.since()).isEqualTo("7.0"); + assertThat(member.defaultValue()).isEqualTo(String.valueOf(false)); + assertThat(member.isRequired()).isFalse(); } @Test @@ -119,9 +129,12 @@ public class SearchActionTest { .setName("Foo Company") .setGuarded(true)); - String response = executeJsonRequest(null, 25); + TestRequest request = ws.newRequest() + .setMediaType(MediaTypes.JSON); + populateRequest(request, null, 25); + String response = request.execute().getInput(); - assertJson(response).isSimilarTo(wsTester.getDef().responseExampleAsString()); + assertJson(response).isSimilarTo(ws.getDef().responseExampleAsString()); } @Test @@ -213,19 +226,19 @@ public class SearchActionTest { insertOrganization(ORGANIZATION_DTO.setUuid("uuid5").setKey("key-5")); insertOrganization(ORGANIZATION_DTO.setUuid("uuid4").setKey("key-4")); - SearchWsResponse response = executeRequest(1, 1, "key-1", "key-3", "key-5"); + SearchWsResponse response = call(1, 1, "key-1", "key-3", "key-5"); assertThat(response.getOrganizationsList()).extracting(Organization::getKey).containsOnly("key-5"); assertThat(response.getPaging()).extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal).containsOnly(1, 1, 3); - response = executeRequest(1, 2, "key-1", "key-3", "key-5"); + response = call(1, 2, "key-1", "key-3", "key-5"); assertThat(response.getOrganizationsList()).extracting(Organization::getKey).containsOnly("key-5", "key-1"); assertThat(response.getPaging()).extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal).containsOnly(1, 2, 3); - response = executeRequest(2, 2, "key-1", "key-3", "key-5"); + response = call(2, 2, "key-1", "key-3", "key-5"); assertThat(response.getOrganizationsList()).extracting(Organization::getKey).containsOnly("key-3"); assertThat(response.getPaging()).extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal).containsOnly(2, 2, 3); - response = executeRequest(null, null); + response = call(null, null); assertThat(response.getOrganizationsList()).extracting(Organization::getKey).hasSize(5); assertThat(response.getPaging()).extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal).containsOnly(1, 100, 5); } @@ -240,27 +253,39 @@ public class SearchActionTest { .containsExactly(ORGANIZATION_DTO.getKey()); } + @Test + public void filter_organization_user_is_member_of() { + UserDto user = db.users().insertUser(); + userSession.logIn(user); + OrganizationDto organization = db.organizations().insert(); + OrganizationDto organizationWithoutMember = db.organizations().insert(); + db.organizations().addMember(organization, user); + + SearchWsResponse result = call(ws.newRequest().setParam(PARAM_MEMBER, String.valueOf(true))); + + assertThat(result.getOrganizationsList()).extracting(Organization::getKey) + .containsExactlyInAnyOrder(organization.getKey()) + .doesNotContain(organizationWithoutMember.getKey()); + } + private List executeRequestAndReturnList(@Nullable Integer page, @Nullable Integer pageSize, String... keys) { - return executeRequest(page, pageSize, keys).getOrganizationsList(); + return call(page, pageSize, keys).getOrganizationsList(); } - private SearchWsResponse executeRequest(@Nullable Integer page, @Nullable Integer pageSize, String... keys) { - TestRequest request = wsTester.newRequest(); - populateRequest(request, page, pageSize, keys); + private SearchWsResponse call(TestRequest request) { return request.executeProtobuf(SearchWsResponse.class); } private void insertOrganization(OrganizationDto dto) { - DbSession dbSession = dbTester.getSession(); - dbTester.getDbClient().organizationDao().insert(dbSession, dto, false); + DbSession dbSession = db.getSession(); + db.getDbClient().organizationDao().insert(dbSession, dto, false); dbSession.commit(); } - private String executeJsonRequest(@Nullable Integer page, @Nullable Integer pageSize, String... keys) { - TestRequest request = wsTester.newRequest() - .setMediaType(MediaTypes.JSON); + private SearchWsResponse call(@Nullable Integer page, @Nullable Integer pageSize, String... keys) { + TestRequest request = ws.newRequest(); populateRequest(request, page, pageSize, keys); - return request.execute().getInput(); + return call(request); } private void populateRequest(TestRequest request, @Nullable Integer page, @Nullable Integer pageSize, String... keys) { -- 2.39.5