From: Teryk Bellahsene Date: Fri, 24 Nov 2017 09:24:41 +0000 (+0100) Subject: SONAR-9000 Add admin action to WS api/organizations/search X-Git-Tag: 7.0-RC1~273 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=db8fa0acfd35ecaf87f7a2295d35475b7956a350;p=sonarqube.git SONAR-9000 Add admin action to WS api/organizations/search --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsSupport.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsSupport.java index 7fff042cb82..cd4981c921b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsSupport.java +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsSupport.java @@ -24,7 +24,7 @@ import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; import org.sonar.db.organization.OrganizationDto; import org.sonar.server.organization.OrganizationValidation; -import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.Organizations.Organization; import static org.sonar.core.util.Protobuf.setNullable; import static org.sonar.server.organization.OrganizationValidation.DESCRIPTION_MAX_LENGTH; @@ -107,19 +107,18 @@ public class OrganizationsWsSupport { .setExampleValue("https://www.foo.com/foo.png"); } - Organizations.Organization toOrganization(OrganizationDto dto) { - return toOrganization(Organizations.Organization.newBuilder(), dto); + Organization.Builder toOrganization(OrganizationDto dto) { + return toOrganization(Organization.newBuilder(), dto); } - Organizations.Organization toOrganization(Organizations.Organization.Builder builder, OrganizationDto dto) { + Organization.Builder toOrganization(Organization.Builder builder, OrganizationDto dto) { builder - .clear() .setName(dto.getName()) .setKey(dto.getKey()) .setGuarded(dto.isGuarded()); setNullable(dto.getDescription(), builder::setDescription); setNullable(dto.getUrl(), builder::setUrl); setNullable(dto.getAvatarUrl(), builder::setAvatar); - return builder.build(); + return builder; } } 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 edceff388b5..0adba4dfacd 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 java.util.Set; import javax.annotation.CheckForNull; import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; @@ -34,9 +35,11 @@ import org.sonar.server.user.UserSession; import org.sonarqube.ws.Organizations; import org.sonarqube.ws.Organizations.Organization; +import static java.util.Collections.emptySet; +import static org.sonar.core.util.stream.MoreCollectors.toSet; import static org.sonar.db.Pagination.forPage; import static org.sonar.db.organization.OrganizationQuery.newOrganizationQueryBuilder; -import static org.sonar.server.ws.WsUtils.checkRequest; +import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.Common.Paging; @@ -70,7 +73,7 @@ public class SearchAction implements OrganizationsWsAction { action.createParam(PARAM_ORGANIZATIONS) .setDescription("Comma-separated list of organization keys") .setExampleValue(String.join(",", "my-org-1", "foocorp")) - .setMinimumLength(2) + .setMaxValuesAllowed(MAX_SIZE) .setRequired(false) .setSince("6.3"); @@ -86,29 +89,40 @@ public class SearchAction implements OrganizationsWsAction { @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 dbQuery = newOrganizationQueryBuilder() - .setKeys(organizations) - .setMember(userId) - .build(); - + OrganizationQuery dbQuery = buildDbQuery(request); int total = dbClient.organizationDao().countByQuery(dbSession, dbQuery); Paging paging = buildWsPaging(request, total); - List dtos = dbClient.organizationDao().selectByQuery( - dbSession, - dbQuery, - forPage(paging.getPageIndex()).andSize(paging.getPageSize())); - writeResponse(request, response, dtos, paging); + List organizations = dbClient.organizationDao().selectByQuery(dbSession, dbQuery, forPage(paging.getPageIndex()).andSize(paging.getPageSize())); + Set adminOrganizationUuids = searchOrganizationWithAdminPermission(dbSession); + writeResponse(request, response, organizations, adminOrganizationUuids, paging); } } - private void writeResponse(Request request, Response response, List dtos, Paging paging) { - Organizations.SearchWsResponse.Builder responseBuilder = Organizations.SearchWsResponse.newBuilder(); - responseBuilder.setPaging(paging); - Organization.Builder organizationBuilder = Organization.newBuilder(); - dtos.forEach(dto -> responseBuilder.addOrganizations(wsSupport.toOrganization(organizationBuilder, dto))); - writeProtobuf(responseBuilder.build(), request, response); + private OrganizationQuery buildDbQuery(Request request) { + return newOrganizationQueryBuilder() + .setKeys(request.paramAsStrings(PARAM_ORGANIZATIONS)) + .setMember(getUserIdIfFilterOnMembership(request)) + .build(); + } + + private Set searchOrganizationWithAdminPermission(DbSession dbSession) { + Integer userId = userSession.getUserId(); + return userId == null ? emptySet() + : dbClient.organizationDao().selectByPermission(dbSession, userId, ADMINISTER.getKey()).stream().map(OrganizationDto::getUuid).collect(toSet()); + } + + private void writeResponse(Request httpRequest, Response httpResponse, List organizations, Set adminOrganizationUuids, Paging paging) { + Organizations.SearchWsResponse.Builder response = Organizations.SearchWsResponse.newBuilder(); + response.setPaging(paging); + Organization.Builder wsOrganization = Organization.newBuilder(); + organizations + .forEach(o -> { + boolean isAdmin = adminOrganizationUuids.contains(o.getUuid()); + wsOrganization.clear(); + wsOrganization.setIsAdmin(isAdmin); + response.addOrganizations(wsSupport.toOrganization(wsOrganization, o)); + }); + writeProtobuf(response.build(), httpRequest, httpResponse); } private static Paging buildWsPaging(Request request, int total) { @@ -120,23 +134,8 @@ public class SearchAction implements OrganizationsWsAction { } @CheckForNull - private Integer getUserIdIfFilterMembership(Request request) { + private Integer getUserIdIfFilterOnMembership(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) { - checkRequest(organizations.size() <= MAX_SIZE, "Size of '%s' (%d) must be less than %d", PARAM_ORGANIZATIONS, organizations.size(), MAX_SIZE); - } - return organizations; + return (userSession.isLoggedIn() && filterOnAuthenticatedUser) ? userSession.getUserId() : null; } - } diff --git a/server/sonar-server/src/main/resources/org/sonar/server/organization/ws/search-example.json b/server/sonar-server/src/main/resources/org/sonar/server/organization/ws/search-example.json index 84a79a7e84a..481ae34e9ef 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/organization/ws/search-example.json +++ b/server/sonar-server/src/main/resources/org/sonar/server/organization/ws/search-example.json @@ -8,7 +8,8 @@ { "key": "foo-company", "name": "Foo Company", - "guarded": true + "guarded": true, + "isAdmin": false }, { "key": "bar-company", @@ -16,7 +17,8 @@ "description": "The Bar company produces quality software too.", "url": "https://www.bar.com", "avatar": "https://www.bar.com/logo.png", - "guarded": false + "guarded": false, + "isAdmin": true } ] } 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 77adf57e92e..8eedbae42ec 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 @@ -32,9 +32,9 @@ import org.junit.rules.ExpectedException; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; 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.GroupDto; import org.sonar.db.user.UserDto; import org.sonar.server.organization.OrganizationValidationImpl; import org.sonar.server.tester.UserSessionRule; @@ -47,8 +47,10 @@ import org.sonarqube.ws.Organizations.SearchWsResponse; import static java.lang.String.valueOf; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; import static org.sonar.server.organization.ws.SearchAction.PARAM_MEMBER; import static org.sonar.test.JsonAssert.assertJson; @@ -94,6 +96,7 @@ public class SearchActionTest { assertThat(organizations.description()).isEqualTo("Comma-separated list of organization keys"); assertThat(organizations.exampleValue()).isEqualTo("my-org-1,foocorp"); assertThat(organizations.since()).isEqualTo("6.3"); + assertThat(organizations.maxValuesAllowed()).isEqualTo(500); WebService.Param page = action.param("p"); assertThat(page.isRequired()).isFalse(); @@ -113,9 +116,9 @@ public class SearchActionTest { } @Test - public void verify_response_example() throws URISyntaxException, IOException { + public void json_example() throws URISyntaxException, IOException { when(system2.now()).thenReturn(SOME_DATE, SOME_DATE + 1000); - insertOrganization(new OrganizationDto() + OrganizationDto barOrganization = db.organizations().insert(new OrganizationDto() .setUuid(Uuids.UUID_EXAMPLE_02) .setKey("bar-company") .setName("Bar Company") @@ -123,18 +126,44 @@ public class SearchActionTest { .setUrl("https://www.bar.com") .setAvatarUrl("https://www.bar.com/logo.png") .setGuarded(false)); - insertOrganization(new OrganizationDto() + OrganizationDto fooOrganization = db.organizations().insert(new OrganizationDto() .setUuid(Uuids.UUID_EXAMPLE_01) .setKey("foo-company") .setName("Foo Company") .setGuarded(true)); + UserDto user = db.users().insertUser(); + db.organizations().addMember(barOrganization, user); + db.organizations().addMember(fooOrganization, user); + db.users().insertPermissionOnUser(barOrganization, user, ADMINISTER); + userSession.logIn(user).addPermission(ADMINISTER, barOrganization); TestRequest request = ws.newRequest() .setMediaType(MediaTypes.JSON); populateRequest(request, null, 25); - String response = request.execute().getInput(); + String result = request.execute().getInput(); - assertJson(response).isSimilarTo(ws.getDef().responseExampleAsString()); + assertJson(ws.getDef().responseExampleAsString()).isSimilarTo(result); + assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString()); + } + + @Test + public void is_admin_available_for_each_organization() { + OrganizationDto userAdminOrganization = db.organizations().insert(); + OrganizationDto groupAdminOrganization = db.organizations().insert(); + OrganizationDto browseOrganization = db.organizations().insert(); + UserDto user = db.users().insertUser(); + GroupDto group = db.users().insertGroup(groupAdminOrganization); + db.users().insertMember(group, user); + userSession.logIn(user).addPermission(ADMINISTER, userAdminOrganization); + db.users().insertPermissionOnUser(userAdminOrganization, user, ADMINISTER); + db.users().insertPermissionOnGroup(group, ADMINISTER); + + SearchWsResponse result = call(ws.newRequest()); + + assertThat(result.getOrganizationsList()).extracting(Organization::getKey, Organization::getIsAdmin).containsExactlyInAnyOrder( + tuple(userAdminOrganization.getKey(), true), + tuple(browseOrganization.getKey(), false), + tuple(groupAdminOrganization.getKey(), true)); } @Test @@ -150,7 +179,7 @@ public class SearchActionTest { @Test public void request_returns_empty_on_table_with_single_row_when_not_requesting_the_first_page() { when(system2.now()).thenReturn(SOME_DATE); - insertOrganization(ORGANIZATION_DTO); + db.organizations().insert(ORGANIZATION_DTO); assertThat(executeRequestAndReturnList(2, null)).isEmpty(); assertThat(executeRequestAndReturnList(2, 1)).isEmpty(); @@ -162,11 +191,11 @@ public class SearchActionTest { @Test public void request_returns_rows_ordered_by_createdAt_descending_applying_requested_paging() { when(system2.now()).thenReturn(SOME_DATE, SOME_DATE + 1_000, SOME_DATE + 2_000, SOME_DATE + 3_000, SOME_DATE + 5_000); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid3").setKey("key-3")); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid1").setKey("key-1")); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid2").setKey("key-2")); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid5").setKey("key-5")); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid4").setKey("key-4")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid3").setKey("key-3")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid1").setKey("key-1")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid2").setKey("key-2")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid5").setKey("key-5")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid4").setKey("key-4")); assertThat(executeRequestAndReturnList(1, 1)) .extracting(Organization::getKey) @@ -202,11 +231,11 @@ public class SearchActionTest { @Test public void request_returns_only_specified_keys_ordered_by_createdAt_when_filtering_keys() { when(system2.now()).thenReturn(SOME_DATE, SOME_DATE + 1_000, SOME_DATE + 2_000, SOME_DATE + 3_000, SOME_DATE + 5_000); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid3").setKey("key-3")); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid1").setKey("key-1")); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid2").setKey("key-2")); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid5").setKey("key-5")); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid4").setKey("key-4")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid3").setKey("key-3")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid1").setKey("key-1")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid2").setKey("key-2")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid5").setKey("key-5")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid4").setKey("key-4")); assertThat(executeRequestAndReturnList(1, 10, "key-3", "key-1", "key-5")) .extracting(Organization::getKey) @@ -220,11 +249,11 @@ public class SearchActionTest { @Test public void result_is_paginated() { when(system2.now()).thenReturn(SOME_DATE, SOME_DATE + 1_000, SOME_DATE + 2_000, SOME_DATE + 3_000, SOME_DATE + 5_000); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid3").setKey("key-3")); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid1").setKey("key-1")); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid2").setKey("key-2")); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid5").setKey("key-5")); - insertOrganization(ORGANIZATION_DTO.setUuid("uuid4").setKey("key-4")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid3").setKey("key-3")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid1").setKey("key-1")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid2").setKey("key-2")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid5").setKey("key-5")); + db.organizations().insert(ORGANIZATION_DTO.setUuid("uuid4").setKey("key-4")); SearchWsResponse response = call(1, 1, "key-1", "key-3", "key-5"); assertThat(response.getOrganizationsList()).extracting(Organization::getKey).containsOnly("key-5"); @@ -246,7 +275,7 @@ public class SearchActionTest { @Test public void request_returns_empty_when_filtering_on_non_existing_key() { when(system2.now()).thenReturn(SOME_DATE); - insertOrganization(ORGANIZATION_DTO); + db.organizations().insert(ORGANIZATION_DTO); assertThat(executeRequestAndReturnList(1, 10, ORGANIZATION_DTO.getKey())) .extracting(Organization::getKey) @@ -276,12 +305,6 @@ public class SearchActionTest { return request.executeProtobuf(SearchWsResponse.class); } - private void insertOrganization(OrganizationDto dto) { - DbSession dbSession = db.getSession(); - db.getDbClient().organizationDao().insert(dbSession, dto, false); - dbSession.commit(); - } - private SearchWsResponse call(@Nullable Integer page, @Nullable Integer pageSize, String... keys) { TestRequest request = ws.newRequest(); populateRequest(request, page, pageSize, keys); diff --git a/sonar-ws-generated/src/main/protobuf/ws-organizations.proto b/sonar-ws-generated/src/main/protobuf/ws-organizations.proto index f6f2733c772..172bf31d85c 100644 --- a/sonar-ws-generated/src/main/protobuf/ws-organizations.proto +++ b/sonar-ws-generated/src/main/protobuf/ws-organizations.proto @@ -60,6 +60,7 @@ message Organization { optional string url = 4; optional string avatar = 5; optional bool guarded = 6; + optional bool isAdmin = 7; } message User {