]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8734 Add "organization" parameter to WS api/projects/bulk_delete
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Sun, 5 Feb 2017 20:41:17 +0000 (21:41 +0100)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 7 Feb 2017 13:30:44 +0000 (14:30 +0100)
server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkDeleteAction.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsSupport.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkDeleteActionTest.java

index f85cc5761f8b790b3ee7291a52af044f638d5fdb..d19503c40f123873991568e4c6a85dda9d4ac751 100644 (file)
 package org.sonar.server.project.ws;
 
 import java.util.List;
+import java.util.Optional;
 import javax.annotation.Nullable;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
+import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
-import org.sonar.db.MyBatis;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
 import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.user.UserSession;
 
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
 
 public class BulkDeleteAction implements ProjectsWsAction {
-  private static final String ACTION = "bulk_delete";
 
-  public static final String PARAM_IDS = "ids";
-  public static final String PARAM_KEYS = "keys";
+  private static final String ACTION = "bulk_delete";
+  private static final String PARAM_IDS = "ids";
+  private static final String PARAM_KEYS = "keys";
 
   private final ComponentCleanerService componentCleanerService;
   private final DbClient dbClient;
   private final UserSession userSession;
+  private final ProjectsWsSupport support;
 
-  public BulkDeleteAction(ComponentCleanerService componentCleanerService, DbClient dbClient, UserSession userSession) {
+  public BulkDeleteAction(ComponentCleanerService componentCleanerService, DbClient dbClient, UserSession userSession,
+    ProjectsWsSupport support) {
     this.componentCleanerService = componentCleanerService;
     this.dbClient = dbClient;
     this.userSession = userSession;
+    this.support = support;
   }
 
   @Override
@@ -67,25 +72,39 @@ public class BulkDeleteAction implements ProjectsWsAction {
       .createParam(PARAM_KEYS)
       .setDescription("List of project keys to delete")
       .setExampleValue(KEY_PROJECT_EXAMPLE_001);
+
+    support.addOrganizationParam(action);
   }
 
   @Override
   public void handle(Request request, Response response) throws Exception {
-    userSession.checkLoggedIn().checkIsRoot();
+    userSession.checkLoggedIn();
+
     List<String> uuids = request.paramAsStrings(PARAM_IDS);
     List<String> keys = request.paramAsStrings(PARAM_KEYS);
+    String orgKey = request.param(ProjectsWsSupport.PARAM_ORGANIZATION);
 
-    DbSession dbSession = dbClient.openSession(false);
-    try {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      Optional<OrganizationDto> org = loadOrganizationByKey(dbSession, orgKey);
       List<ComponentDto> projects = searchProjects(dbSession, uuids, keys);
-      componentCleanerService.delete(dbSession, projects);
-    } finally {
-      MyBatis.closeQuietly(dbSession);
+      projects.stream()
+        .filter(p -> !org.isPresent() || org.get().getUuid().equals(p.getOrganizationUuid()))
+        .forEach(p -> componentCleanerService.delete(dbSession, p));
     }
 
     response.noContent();
   }
 
+  private Optional<OrganizationDto> loadOrganizationByKey(DbSession dbSession, @Nullable String orgKey) {
+    if (orgKey == null) {
+      userSession.checkIsRoot();
+      return Optional.empty();
+    }
+    OrganizationDto org = support.getOrganization(dbSession, orgKey);
+    userSession.checkOrganizationPermission(org.getUuid(), GlobalPermissions.SYSTEM_ADMIN);
+    return Optional.of(org);
+  }
+
   private List<ComponentDto> searchProjects(DbSession dbSession, @Nullable List<String> uuids, @Nullable List<String> keys) {
     if (uuids != null) {
       return dbClient.componentDao().selectByUuids(dbSession, uuids);
index 813cb6d101da8ea76bfe46c1db3fff519c2f7d1a..9fa44b65b0d70c2ec18a15ff8e2a2f4d75979e31 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.project.ws;
 
+import org.sonar.api.server.ServerSide;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
@@ -26,6 +27,7 @@ import org.sonar.db.organization.OrganizationDto;
 
 import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
 
+@ServerSide
 public class ProjectsWsSupport {
   public static final String PARAM_ORGANIZATION = "organization";
 
index 7e2a25895771a241608fd66604ae2733028cf79e..813b8f1ff0c1b9e6ceeb33484549f5ba38a936a2 100644 (file)
  */
 package org.sonar.server.project.ws;
 
-import java.util.List;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.mockito.ArgumentCaptor;
 import org.sonar.api.utils.System2;
+import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDbTester;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
 import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.UnauthorizedException;
@@ -40,62 +40,109 @@ import org.sonar.server.ws.WsTester;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.sonar.server.project.ws.BulkDeleteAction.PARAM_IDS;
-import static org.sonar.server.project.ws.BulkDeleteAction.PARAM_KEYS;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 public class BulkDeleteActionTest {
 
   private static final String ACTION = "bulk_delete";
 
-  private System2 system2 = System2.INSTANCE;
-
   @Rule
-  public DbTester db = DbTester.create(system2);
+  public DbTester db = DbTester.create(System2.INSTANCE);
 
   @Rule
-  public UserSessionRule userSessionRule = UserSessionRule.standalone();
+  public UserSessionRule userSession = UserSessionRule.standalone();
 
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-  private ComponentDbTester componentDbTester = new ComponentDbTester(db);
   private ComponentCleanerService componentCleanerService = mock(ComponentCleanerService.class);
   private WsTester ws;
   private DbClient dbClient = db.getDbClient();
+  private BulkDeleteAction underTest = new BulkDeleteAction(componentCleanerService, dbClient, userSession, new ProjectsWsSupport(dbClient));
+  private OrganizationDto org1;
+  private OrganizationDto org2;
 
   @Before
   public void setUp() {
-    ws = new WsTester(new ProjectsWs(
-      new BulkDeleteAction(
-        componentCleanerService,
-        dbClient,
-        userSessionRule)));
+    ws = new WsTester(new ProjectsWs(underTest));
+    org1 = db.organizations().insert();
+    org2 = db.organizations().insert();
+  }
+
+  @Test
+  public void root_deletes_projects_by_uuids_in_all_organizations() throws Exception {
+    userSession.logIn().setRoot();
+    ComponentDto toDeleteInOrg1 = db.components().insertProject(org1);
+    ComponentDto toDeleteInOrg2 = db.components().insertProject(org2);
+    ComponentDto toKeep = db.components().insertProject(org2);
+
+    WsTester.Result result = ws.newPostRequest("api/projects", ACTION)
+      .setParam("ids", toDeleteInOrg1.uuid() + "," + toDeleteInOrg2.uuid())
+      .execute();
+    result.assertNoContent();
+
+    verifyDeleted(toDeleteInOrg1, toDeleteInOrg2);
+  }
+
+  @Test
+  public void root_deletes_projects_by_keys_in_all_organizations() throws Exception {
+    userSession.logIn().setRoot();
+    ComponentDto toDeleteInOrg1 = db.components().insertProject(org1);
+    ComponentDto toDeleteInOrg2 = db.components().insertProject(org2);
+    ComponentDto toKeep = db.components().insertProject(org2);
+
+    WsTester.Result result = ws.newPostRequest("api/projects", ACTION)
+      .setParam("keys", toDeleteInOrg1.key() + "," + toDeleteInOrg2.key())
+      .execute();
+    result.assertNoContent();
+
+    verifyDeleted(toDeleteInOrg1, toDeleteInOrg2);
   }
 
   @Test
-  public void delete_projects_by_uuids() throws Exception {
-    userSessionRule.logIn().setRoot();
-    ComponentDto p1 = componentDbTester.insertProject();
-    ComponentDto p2 = componentDbTester.insertProject();
+  public void projects_that_dont_exist_are_ignored_and_dont_break_bulk_deletion() throws Exception {
+    userSession.logIn().setRoot();
+    ComponentDto toDelete1 = db.components().insertProject(org1);
+    ComponentDto toDelete2 = db.components().insertProject(org1);
 
-    WsTester.Result result = ws.newPostRequest("api/projects", ACTION).setParam(PARAM_IDS, p1.uuid() + "," + p2.uuid()).execute();
+    WsTester.Result result = ws.newPostRequest("api/projects", ACTION)
+      .setParam("keys", toDelete1.key() + ",missing," + toDelete2.key() + ",doesNotExist")
+      .execute();
     result.assertNoContent();
 
-    verifyDeleted(p1, p2);
+    verifyDeleted(toDelete1, toDelete2);
+  }
+
+  @Test
+  public void throw_ForbiddenException_if_organization_administrator_does_not_set_organization_parameter() throws Exception {
+    userSession.logIn().addOrganizationPermission(org1.getUuid(), GlobalPermissions.SYSTEM_ADMIN);
+    ComponentDto project = db.components().insertProject(org1);
+
+    expectedException.expect(ForbiddenException.class);
+    expectedException.expectMessage("Insufficient privileges");
+
+    ws.newPostRequest("api/projects", ACTION)
+      .setParam("keys", project.key())
+      .execute();
+
+    verifyNoDeletions();
   }
 
   @Test
-  public void delete_projects_by_keys() throws Exception {
-    userSessionRule.logIn().setRoot();
-    ComponentDto p1 = componentDbTester.insertProject();
-    ComponentDto p2 = componentDbTester.insertProject();
+  public void organization_administrator_deletes_projects_by_keys_in_his_organization() throws Exception {
+    userSession.logIn().addOrganizationPermission(org1.getUuid(), GlobalPermissions.SYSTEM_ADMIN);
+    ComponentDto toDelete = db.components().insertProject(org1);
+    ComponentDto cantBeDeleted = db.components().insertProject(org2);
 
     WsTester.Result result = ws.newPostRequest("api/projects", ACTION)
-      .setParam(PARAM_KEYS, p1.key() + "," + p2.key()).execute();
+      .setParam("organization", org1.getKey())
+      .setParam("keys", toDelete.key() + "," + cantBeDeleted.key())
+      .execute();
     result.assertNoContent();
 
-    verifyDeleted(p1, p2);
+    verifyDeleted(toDelete);
   }
 
   @Test
@@ -103,23 +150,50 @@ public class BulkDeleteActionTest {
     expectedException.expect(UnauthorizedException.class);
     expectedException.expectMessage("Authentication is required");
 
-    ws.newPostRequest("api/projects", ACTION).setParam(PARAM_IDS, "whatever-the-uuid").execute();
+    ws.newPostRequest("api/projects", ACTION)
+      .setParam("ids", "whatever-the-uuid").execute();
+
+    verifyNoDeletions();
+  }
+
+  @Test
+  public void throw_ForbiddenException_if_param_organization_is_not_set_and_not_root() throws Exception {
+    userSession.logIn().setNonRoot();
+
+    expectedException.expect(ForbiddenException.class);
+    expectedException.expectMessage("Insufficient privileges");
+
+    ws.newPostRequest("api/projects", ACTION)
+      .setParam("ids", "whatever-the-uuid").execute();
+
+    verifyNoDeletions();
   }
 
   @Test
-  public void throw_ForbiddenException_if_not_root_administrator() throws Exception {
-    userSessionRule.logIn().setNonRoot();
+  public void throw_ForbiddenException_if_param_organization_is_set_but_not_organization_administrator() throws Exception {
+    userSession.logIn();
 
     expectedException.expect(ForbiddenException.class);
     expectedException.expectMessage("Insufficient privileges");
 
-    ws.newPostRequest("api/projects", ACTION).setParam(PARAM_IDS, "whatever-the-uuid").execute();
+    ws.newPostRequest("api/projects", ACTION)
+      .setParam("organization", org1.getKey())
+      .setParam("ids", "whatever-the-uuid")
+      .execute();
+
+    verifyNoDeletions();
   }
 
   private void verifyDeleted(ComponentDto... projects) {
-    ArgumentCaptor<List<ComponentDto>> argument = (ArgumentCaptor<List<ComponentDto>>) ((ArgumentCaptor) ArgumentCaptor.forClass(List.class));
-    verify(componentCleanerService).delete(any(DbSession.class), argument.capture());
+    ArgumentCaptor<ComponentDto> argument = ArgumentCaptor.forClass(ComponentDto.class);
+    verify(componentCleanerService, times(projects.length)).delete(any(DbSession.class), argument.capture());
+
+    for (ComponentDto project : projects) {
+      assertThat(argument.getAllValues()).extracting(ComponentDto::uuid).contains(project.uuid());
+    }
+  }
 
-    assertThat(argument.getValue()).containsOnly(projects);
+  private void verifyNoDeletions() {
+    verifyZeroInteractions(componentCleanerService);
   }
 }