]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20532 Add DELETE /github-permission-mappings/{githubRole} endpoint
authorWojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com>
Tue, 26 Sep 2023 13:07:25 +0000 (15:07 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 28 Sep 2023 20:03:11 +0000 (20:03 +0000)
server/sonar-db-dao/src/it/java/org/sonar/db/provisioning/GithubPermissionsMappingDaoIT.java
server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/GithubPermissionsMappingNewValue.java
server/sonar-db-dao/src/main/java/org/sonar/db/provisioning/GithubPermissionsMappingDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/provisioning/GithubPermissionsMappingMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/provisioning/GithubPermissionsMappingMapper.xml
server/sonar-webserver-common/src/it/java/org/sonar/server/common/github/permissions/GithubPermissionsMappingServiceIT.java
server/sonar-webserver-common/src/main/java/org/sonar/server/common/github/permissions/GithubPermissionsMappingService.java
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/permissions/controller/DefaultGithubPermissionsController.java
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/permissions/controller/GithubPermissionsController.java
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/github/permissions/controller/DefaultGithubPermissionsControllerTest.java

index 4751e5d6cedaec25958ed0e9ecb8fc52db276162..437893d1496af436b0e1e393a5d14de4cec8871b 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.db.provisioning;
 
+import java.util.List;
 import java.util.Set;
 import org.junit.Rule;
 import org.junit.Test;
@@ -32,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.sonar.db.audit.model.GithubPermissionsMappingNewValue.ALL_PERMISSIONS;
 
 public class GithubPermissionsMappingDaoIT {
 
@@ -81,6 +83,30 @@ public class GithubPermissionsMappingDaoIT {
     assertThat(newValueCaptor.getValue().getSonarqubePermission()).isEqualTo("SQ_role");
   }
 
+  @Test
+  public void deleteAllPermissionsForRole_deletesGithubPermissionsMappingDto() {
+    List<GithubPermissionsMappingDto> role1Mappings = List.of(
+      new GithubPermissionsMappingDto("1", "GH_role_1", "SQ_role_1"),
+      new GithubPermissionsMappingDto("2", "GH_role_1", "SQ_role_2"),
+      new GithubPermissionsMappingDto("3", "GH_role_1", "SQ_role_3"));
+
+    List<GithubPermissionsMappingDto> role2Mappings = List.of(
+      new GithubPermissionsMappingDto("4", "GH_role_2", "SQ_role_1"),
+      new GithubPermissionsMappingDto("5", "GH_role_2", "SQ_role_2"));
+
+    role1Mappings.forEach(mapping -> underTest.insert(dbSession, mapping));
+    role2Mappings.forEach(mapping -> underTest.insert(dbSession, mapping));
+
+    underTest.deleteAllPermissionsForRole(dbSession, "GH_role_1");
+
+    Set<GithubPermissionsMappingDto> savedGithubPermissionsMappings = underTest.findAll(dbSession);
+    assertThat(savedGithubPermissionsMappings).containsExactlyInAnyOrderElementsOf(role2Mappings);
+
+    verify(auditPersister).deleteGithubPermissionsMapping(eq(dbSession), newValueCaptor.capture());
+    assertThat(newValueCaptor.getValue().getGithubRole()).isEqualTo("GH_role_1");
+    assertThat(newValueCaptor.getValue().getSonarqubePermission()).isEqualTo(ALL_PERMISSIONS);
+  }
+
   @Test
   public void findAll_shouldReturnAllGithubOrganizationGroup() {
     GithubPermissionsMappingDto mapping1 = new GithubPermissionsMappingDto(MAPPING_UUID, "GH_role", "SQ_role");
index 9764e63b7ff75b0b4c010f05290f746553bcc88d..39c3d742742ca6f89784774765bc04f5004d1f09 100644 (file)
@@ -23,6 +23,8 @@ import com.google.common.annotations.VisibleForTesting;
 
 public class GithubPermissionsMappingNewValue extends NewValue {
 
+  @VisibleForTesting
+  public static final String ALL_PERMISSIONS = "all";
   private final String githubRole;
   private final String sonarqubePermission;
 
@@ -31,6 +33,10 @@ public class GithubPermissionsMappingNewValue extends NewValue {
     this.sonarqubePermission = sonarqubePermission;
   }
 
+  public static GithubPermissionsMappingNewValue withAllPermissions(String githubRole) {
+    return new GithubPermissionsMappingNewValue(githubRole, ALL_PERMISSIONS);
+  }
+
   @VisibleForTesting
   public String getGithubRole() {
     return githubRole;
index 18c4784d940997f8a2d5f3844d18986d2015fdc1..1bf4d9147d93c7287ba809a50a6e7c79f79ff267 100644 (file)
@@ -51,6 +51,11 @@ public class GithubPermissionsMappingDao implements Dao {
     auditPersister.deleteGithubPermissionsMapping(dbSession, toNewValueForAuditLogs(githubRole, sonarqubePermission));
   }
 
+  public void deleteAllPermissionsForRole(DbSession dbSession, String githubRole) {
+    mapper(dbSession).deleteAllPermissionsForRole(githubRole);
+    auditPersister.deleteGithubPermissionsMapping(dbSession, GithubPermissionsMappingNewValue.withAllPermissions(githubRole));
+  }
+
   private static GithubPermissionsMappingNewValue toNewValueForAuditLogs(String githubRole, String sonarqubePermission) {
     return new GithubPermissionsMappingNewValue(githubRole, sonarqubePermission);
   }
index bb2eb7fb114c387bf1ebab75758aeeab30f5751c..faa56070f67a04d5e187de733e221b4c2bd130c6 100644 (file)
@@ -31,4 +31,6 @@ public interface GithubPermissionsMappingMapper {
   void insert(GithubPermissionsMappingDto githubPermissionsMappingDto);
 
   void delete(@Param("githubRole") String githubRole, @Param("sonarqubePermission") String sonarqubePermission);
+
+  void deleteAllPermissionsForRole(String githubRole);
 }
index 9a813f6d050b4c78eaba5be7a8e5bd278d6a8182..1ef0f24582dc911a4d52e12e55c8c3561ed5a84c 100644 (file)
     where github_role = #{githubRole,jdbcType=VARCHAR} AND sonarqube_permission = #{sonarqubePermission,jdbcType=VARCHAR}
   </delete>
 
+  <delete id="deleteAllPermissionsForRole" parameterType="GithubPermissionsMapping">
+    delete from github_perms_mapping
+    where github_role = #{githubRole,jdbcType=VARCHAR}
+  </delete>
+
   <select id="selectAll" resultType="GithubPermissionsMapping">
     SELECT
       <include refid="githubPermissionsMappingColumns"/>
index a2480a3728a1008657b451a27da22f356a6162fd..58a204505066a80a84268db85c025e3e91daa6f1 100644 (file)
@@ -32,8 +32,10 @@ import org.sonar.db.audit.AuditPersister;
 import org.sonar.db.provisioning.GithubPermissionsMappingDao;
 import org.sonar.db.provisioning.GithubPermissionsMappingDto;
 import org.sonar.server.common.permission.Operation;
+import org.sonar.server.exceptions.NotFoundException;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.Mockito.mock;
 import static org.sonar.server.common.github.permissions.GithubPermissionsMappingService.ADMIN_GITHUB_ROLE;
 import static org.sonar.server.common.github.permissions.GithubPermissionsMappingService.MAINTAIN_GITHUB_ROLE;
@@ -213,4 +215,41 @@ public class GithubPermissionsMappingServiceIT {
     assertThat(actualPermissionsMapping).isEqualTo(expectedPermissionsMapping);
   }
 
+  @Test
+  public void deletePermissionMappings_whenTryingToDeleteForBaseRole_shouldThrow() {
+    assertThatThrownBy(() -> underTest.deletePermissionMappings(READ_GITHUB_ROLE))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Deleting permission mapping for GitHub base role '" + READ_GITHUB_ROLE + "' is not allowed.");
+  }
+
+  @Test
+  public void deletePermissionMappings_whenNoMappingsExistForGithubRole_shouldThrow() {
+    assertThatThrownBy(() -> underTest.deletePermissionMappings(CUSTOM_ROLE_NAME))
+      .isInstanceOf(NotFoundException.class)
+      .hasMessage("Role '" + CUSTOM_ROLE_NAME + "' not found.");
+  }
+
+  @Test
+  public void deletePermissionMappings_whenTryingToDeleteForCustomRole_shouldDeleteMapping() {
+    Map<String, Set<String>> githubRolesToSqPermissions = Map.of(
+      READ_GITHUB_ROLE, Set.of("user", "codeviewer"),
+      WRITE_GITHUB_ROLE, Set.of("user", "codeviewer", "issueadmin", "securityhotspotadmin", "admin", "scan"),
+      CUSTOM_ROLE_NAME, Set.of("user", "codeviewer", "scan"),
+      "customRole2", Set.of("user", "codeviewer"));
+
+    persistGithubPermissionsMapping(githubRolesToSqPermissions);
+    underTest.deletePermissionMappings("customRole2");
+
+    List<GithubPermissionsMapping> allPermissionMappings = underTest.getPermissionsMapping();
+
+    assertThat(allPermissionMappings)
+      .containsExactlyInAnyOrder(
+        new GithubPermissionsMapping(READ_GITHUB_ROLE, true, new SonarqubePermissions(true, true, false, false, false, false)),
+        new GithubPermissionsMapping(WRITE_GITHUB_ROLE, true, new SonarqubePermissions(true, true, true, true, true, true)),
+        new GithubPermissionsMapping(TRIAGE_GITHUB_ROLE, true, NO_SQ_PERMISSIONS),
+        new GithubPermissionsMapping(MAINTAIN_GITHUB_ROLE, true, NO_SQ_PERMISSIONS),
+        new GithubPermissionsMapping(ADMIN_GITHUB_ROLE, true, NO_SQ_PERMISSIONS),
+        new GithubPermissionsMapping(CUSTOM_ROLE_NAME, false, new SonarqubePermissions(true, true, false, false, false, true)));
+  }
+
 }
index ed338fb00dd10d6559ee240d8f2885e23e4aafcd..68a4b884ce760e165a59b6786ff1abf0463b61dc 100644 (file)
@@ -30,9 +30,11 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.provisioning.GithubPermissionsMappingDao;
 import org.sonar.db.provisioning.GithubPermissionsMappingDto;
+import org.sonar.server.exceptions.NotFoundException;
 
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.toSet;
+import static org.sonar.api.utils.Preconditions.checkArgument;
 import static org.sonar.server.common.permission.Operation.ADD;
 import static org.sonar.server.common.permission.Operation.REMOVE;
 
@@ -56,8 +58,7 @@ public class GithubPermissionsMappingService {
     UserRole.ISSUE_ADMIN, builder -> builder.issueAdmin(true),
     UserRole.SECURITYHOTSPOT_ADMIN, builder -> builder.securityHotspotAdmin(true),
     UserRole.ADMIN, builder -> builder.admin(true),
-    UserRole.SCAN, builder -> builder.scan(true)
-  );
+    UserRole.SCAN, builder -> builder.scan(true));
 
   private final DbClient dbClient;
   private final GithubPermissionsMappingDao githubPermissionsMappingDao;
@@ -107,6 +108,18 @@ public class GithubPermissionsMappingService {
     }
   }
 
+  public void deletePermissionMappings(String githubRole) {
+    checkArgument(!GITHUB_BASE_ROLES.contains(githubRole), "Deleting permission mapping for GitHub base role '" + githubRole + "' is not allowed.");
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      Set<GithubPermissionsMappingDto> existingPermissions = githubPermissionsMappingDao.findAllForGithubRole(dbSession, githubRole);
+      if (existingPermissions.isEmpty()) {
+        throw new NotFoundException("Role '" + githubRole + "' not found.");
+      }
+      githubPermissionsMappingDao.deleteAllPermissionsForRole(dbSession, githubRole);
+      dbSession.commit();
+    }
+  }
+
   private void updatePermissionsMappings(DbSession dbSession, String githubRole, List<PermissionMappingChange> permissionChanges) {
     Set<String> currentPermissionsForRole = getSqPermissionsForGithubRole(dbSession, githubRole);
     removePermissions(dbSession, permissionChanges, currentPermissionsForRole);
@@ -135,8 +148,7 @@ public class GithubPermissionsMappingService {
       .filter(permissionMappingChange -> ADD.equals(permissionMappingChange.operation()))
       .filter(permissionMappingChange -> !currentPermissionsForRole.contains(permissionMappingChange.sonarqubePermission()))
       .forEach(
-        mapping -> githubPermissionsMappingDao.insert(dbSession, new GithubPermissionsMappingDto(uuidFactory.create(), mapping.githubRole(), mapping.sonarqubePermission()))
-      );
+        mapping -> githubPermissionsMappingDao.insert(dbSession, new GithubPermissionsMappingDto(uuidFactory.create(), mapping.githubRole(), mapping.sonarqubePermission())));
   }
 
   private static SonarqubePermissions getSonarqubePermissions(Set<GithubPermissionsMappingDto> githubPermissionsMappingDtos) {
index 72ebab94a8c860ad553709adfef7222fa5094081..c6148669832202c8fb3def34911c8cb7fd3c5a7b 100644 (file)
@@ -78,6 +78,12 @@ public class DefaultGithubPermissionsController implements GithubPermissionsCont
 
   }
 
+  @Override
+  public void deleteMapping(String githubRole) {
+    userSession.checkIsSystemAdministrator();
+    githubPermissionsMappingService.deletePermissionMappings(githubRole);
+  }
+
   private static PermissionMappingChange toPermissionMappingChange(String githubRole, String sonarqubePermission, boolean shouldAddPermission) {
     return new PermissionMappingChange(githubRole, sonarqubePermission, shouldAddPermission ? Operation.ADD : Operation.REMOVE);
   }
@@ -93,8 +99,7 @@ public class DefaultGithubPermissionsController implements GithubPermissionsCont
       githubPermissionsMapping.roleName(),
       githubPermissionsMapping.roleName(),
       githubPermissionsMapping.isBaseRole(),
-      githubPermissionsMapping.permissions()
-    );
+      githubPermissionsMapping.permissions());
   }
 
 }
index 51bd43f62c5b7b266adf6e688cdb0c86272ed0be..fddb261ab60bc3385b45238865d752795ec4dfd6 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.server.v2.api.github.permissions.request.GithubPermissionMappin
 import org.sonar.server.v2.api.github.permissions.response.GithubPermissionsMappingRestResponse;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PatchMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -51,4 +52,9 @@ public interface GithubPermissionsController {
   @Operation(summary = "Update a single Github permission mapping", description = "Update a single Github permission mapping")
   RestGithubPermissionsMapping updateMapping(@PathVariable("githubRole") String githubRole, @Valid @RequestBody GithubPermissionMappingUpdateRequest request);
 
+  @DeleteMapping(path = "/{githubRole}")
+  @ResponseStatus(HttpStatus.NO_CONTENT)
+  @Operation(summary = "Delete a single Github permission mapping", description = "Delete a single Github permission mapping")
+  void deleteMapping(@PathVariable("githubRole") String githubRole);
+
 }
index 7bc542a0489fe1187bfc0ae0375dee186d9dd539..dd0282d50798e1e0b7ee3c7dd5ff6008801161de 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.server.common.github.permissions.GithubPermissionsMappingServic
 import org.sonar.server.common.github.permissions.PermissionMappingChange;
 import org.sonar.server.common.github.permissions.SonarqubePermissions;
 import org.sonar.server.common.permission.Operation;
+import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.v2.api.ControllerTester;
 import org.sonar.server.v2.api.github.permissions.model.RestGithubPermissionsMapping;
@@ -39,11 +40,14 @@ import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.sonar.server.common.github.permissions.GithubPermissionsMappingService.READ_GITHUB_ROLE;
 import static org.sonar.server.v2.WebApiEndpoints.GITHUB_PERMISSIONS_ENDPOINT;
 import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -101,15 +105,15 @@ public class DefaultGithubPermissionsControllerTest {
     userSession.logIn().setNonSystemAdministrator();
 
     mockMvc.perform(
-        patch(GITHUB_PERMISSIONS_ENDPOINT + "/" + GITHUB_ROLE)
-          .contentType(JSON_MERGE_PATCH_CONTENT_TYPE)
-          .content("""
-            {
-                "user": true,
-                "codeViewer": false,
-                "admin": true
-            }
-            """))
+      patch(GITHUB_PERMISSIONS_ENDPOINT + "/" + GITHUB_ROLE)
+        .contentType(JSON_MERGE_PATCH_CONTENT_TYPE)
+        .content("""
+          {
+              "user": true,
+              "codeViewer": false,
+              "admin": true
+          }
+          """))
       .andExpectAll(
         status().isForbidden(),
         content().json("{\"message\":\"Insufficient privileges\"}"));
@@ -123,23 +127,24 @@ public class DefaultGithubPermissionsControllerTest {
     when(githubPermissionsMappingService.getPermissionsMappingForGithubRole(GITHUB_ROLE)).thenReturn(updatedRolePermissions);
 
     MvcResult mvcResult = mockMvc.perform(
-        patch(GITHUB_PERMISSIONS_ENDPOINT + "/" + GITHUB_ROLE)
-          .contentType(JSON_MERGE_PATCH_CONTENT_TYPE)
-          .content("""
-            {
-              "permissions": {
-                "user": true,
-                "codeViewer": false,
-                "admin": true
-              }
+      patch(GITHUB_PERMISSIONS_ENDPOINT + "/" + GITHUB_ROLE)
+        .contentType(JSON_MERGE_PATCH_CONTENT_TYPE)
+        .content("""
+          {
+            "permissions": {
+              "user": true,
+              "codeViewer": false,
+              "admin": true
             }
-            """))
+          }
+          """))
       .andExpect(status().isOk())
       .andReturn();
 
     RestGithubPermissionsMapping response = gson.fromJson(mvcResult.getResponse().getContentAsString(), RestGithubPermissionsMapping.class);
 
-    RestGithubPermissionsMapping expectedResponse = new RestGithubPermissionsMapping(GITHUB_ROLE, GITHUB_ROLE, false, new SonarqubePermissions(true, false, false, true, true, false));
+    RestGithubPermissionsMapping expectedResponse = new RestGithubPermissionsMapping(GITHUB_ROLE, GITHUB_ROLE, false,
+      new SonarqubePermissions(true, false, false, true, true, false));
     assertThat(response).isEqualTo(expectedResponse);
 
     ArgumentCaptor<Set<PermissionMappingChange>> permissionMappingChangesCaptor = ArgumentCaptor.forClass(Set.class);
@@ -148,8 +153,50 @@ public class DefaultGithubPermissionsControllerTest {
       .containsExactlyInAnyOrder(
         new PermissionMappingChange(GITHUB_ROLE, "codeviewer", Operation.REMOVE),
         new PermissionMappingChange(GITHUB_ROLE, "user", Operation.ADD),
-        new PermissionMappingChange(GITHUB_ROLE, "admin", Operation.ADD)
-      );
+        new PermissionMappingChange(GITHUB_ROLE, "admin", Operation.ADD));
+  }
+
+  @Test
+  public void deleteMapping_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
+    userSession.logIn().setNonSystemAdministrator();
+    mockMvc
+      .perform(delete(GITHUB_PERMISSIONS_ENDPOINT + "/" + READ_GITHUB_ROLE))
+      .andExpectAll(
+        status().isForbidden(),
+        content().json("{\"message\":\"Insufficient privileges\"}"));
+  }
+
+  @Test
+  public void deleteMapping_whenTryingToDeleteBaseRole_shouldReturnBadRequest() throws Exception {
+    userSession.logIn().setSystemAdministrator();
+    doThrow(new IllegalArgumentException("Bad request")).when(githubPermissionsMappingService).deletePermissionMappings(READ_GITHUB_ROLE);
+    mockMvc
+      .perform(delete(GITHUB_PERMISSIONS_ENDPOINT + "/" + READ_GITHUB_ROLE))
+      .andExpectAll(
+        status().isBadRequest(),
+        content().json("{\"message\":\"Bad request\"}"));
+  }
+
+  @Test
+  public void deleteMapping_whenNoMappingsExistForACustomRole_shouldReturnNotFound() throws Exception {
+    userSession.logIn().setSystemAdministrator();
+    doThrow(new NotFoundException("Role not found")).when(githubPermissionsMappingService).deletePermissionMappings(READ_GITHUB_ROLE);
+    mockMvc
+      .perform(delete(GITHUB_PERMISSIONS_ENDPOINT + "/" + READ_GITHUB_ROLE))
+      .andExpectAll(
+        status().isNotFound(),
+        content().json("{\"message\":\"Role not found\"}"));
+  }
+
+  @Test
+  public void deleteMapping_whenTryingToDeleteCustomRole_shouldReturnNoContent() throws Exception {
+    userSession.logIn().setSystemAdministrator();
+    mockMvc
+      .perform(delete(GITHUB_PERMISSIONS_ENDPOINT + "/" + GITHUB_ROLE))
+      .andExpect(
+        status().isNoContent());
+
+    verify(githubPermissionsMappingService).deletePermissionMappings(GITHUB_ROLE);
   }
 
 }