*/
package org.sonar.db.provisioning;
+import java.util.List;
import java.util.Set;
import org.junit.Rule;
import org.junit.Test;
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 {
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");
public class GithubPermissionsMappingNewValue extends NewValue {
+ @VisibleForTesting
+ public static final String ALL_PERMISSIONS = "all";
private final String githubRole;
private final String sonarqubePermission;
this.sonarqubePermission = sonarqubePermission;
}
+ public static GithubPermissionsMappingNewValue withAllPermissions(String githubRole) {
+ return new GithubPermissionsMappingNewValue(githubRole, ALL_PERMISSIONS);
+ }
+
@VisibleForTesting
public String getGithubRole() {
return githubRole;
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);
}
void insert(GithubPermissionsMappingDto githubPermissionsMappingDto);
void delete(@Param("githubRole") String githubRole, @Param("sonarqubePermission") String sonarqubePermission);
+
+ void deleteAllPermissionsForRole(String githubRole);
}
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"/>
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;
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)));
+ }
+
}
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;
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;
}
}
+ 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);
.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) {
}
+ @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);
}
githubPermissionsMapping.roleName(),
githubPermissionsMapping.roleName(),
githubPermissionsMapping.isBaseRole(),
- githubPermissionsMapping.permissions()
- );
+ githubPermissionsMapping.permissions());
}
}
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;
@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);
+
}
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;
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;
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\"}"));
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);
.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);
}
}