修复系统管理后台权限配置校验

This commit is contained in:
andy
2026-07-10 18:57:06 +08:00
parent aa5783ba61
commit bbd4ea22df
4 changed files with 117 additions and 10 deletions

View File

@@ -1,5 +1,6 @@
package cn.nianxx.thhotel.platform.access.service.impl; package cn.nianxx.thhotel.platform.access.service.impl;
import cn.nianxx.thhotel.platform.access.common.enums.PlatformPermissionStatus;
import cn.nianxx.thhotel.platform.access.common.enums.PlatformRoleStatus; import cn.nianxx.thhotel.platform.access.common.enums.PlatformRoleStatus;
import cn.nianxx.thhotel.platform.access.common.request.AdminRoleCreateRequest; import cn.nianxx.thhotel.platform.access.common.request.AdminRoleCreateRequest;
import cn.nianxx.thhotel.platform.access.common.request.AdminRolePermissionAssignmentRequest; import cn.nianxx.thhotel.platform.access.common.request.AdminRolePermissionAssignmentRequest;
@@ -315,12 +316,13 @@ public class AdminAccessManagementServiceImpl implements AdminAccessManagementSe
if (permissionIds == null || permissionIds.isEmpty()) { if (permissionIds == null || permissionIds.isEmpty()) {
return; return;
} }
Set<Long> existingPermissionIds = accessRepository.listAllPermissions().stream() Set<Long> activePermissionIds = accessRepository.listAllPermissions().stream()
.filter(permission -> PlatformPermissionStatus.ACTIVE.name().equals(permission.getPermissionStatus()))
.map(PlatformPermissionEntity::getId) .map(PlatformPermissionEntity::getId)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
if (!existingPermissionIds.containsAll(permissionIds)) { if (!activePermissionIds.containsAll(permissionIds)) {
throw invalidRequest("存在不存在的权限 ID。"); throw invalidRequest("存在不存在或未启用的权限 ID。");
} }
} }

View File

@@ -1,5 +1,6 @@
package cn.nianxx.thhotel.platform.identity.service.impl; package cn.nianxx.thhotel.platform.identity.service.impl;
import cn.nianxx.thhotel.platform.access.common.enums.PlatformRoleStatus;
import cn.nianxx.thhotel.platform.access.domain.PlatformRoleEntity; import cn.nianxx.thhotel.platform.access.domain.PlatformRoleEntity;
import cn.nianxx.thhotel.platform.access.domain.PlatformUserRoleEntity; import cn.nianxx.thhotel.platform.access.domain.PlatformUserRoleEntity;
import cn.nianxx.thhotel.platform.access.repository.PlatformAccessRepository; import cn.nianxx.thhotel.platform.access.repository.PlatformAccessRepository;
@@ -479,11 +480,12 @@ public class AdminUserManagementServiceImpl implements AdminUserManagementServic
if (roleIds == null || roleIds.isEmpty()) { if (roleIds == null || roleIds.isEmpty()) {
return; return;
} }
Set<Long> existingRoleIds = accessRepository.listRolesByIds(roleIds).stream() Set<Long> activeRoleIds = accessRepository.listRolesByIds(roleIds).stream()
.filter(role -> PlatformRoleStatus.ACTIVE.name().equals(role.getRoleStatus()))
.map(PlatformRoleEntity::getId) .map(PlatformRoleEntity::getId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
if (!existingRoleIds.containsAll(roleIds)) { if (!activeRoleIds.containsAll(roleIds)) {
throw invalidRequest("存在不存在的角色 ID。"); throw invalidRequest("存在不存在或未启用的角色 ID。");
} }
} }

View File

@@ -1,5 +1,6 @@
package cn.nianxx.thhotel.platform.navigation.service.impl; package cn.nianxx.thhotel.platform.navigation.service.impl;
import cn.nianxx.thhotel.platform.access.common.enums.PlatformPermissionStatus;
import cn.nianxx.thhotel.platform.access.repository.PlatformAccessRepository; import cn.nianxx.thhotel.platform.access.repository.PlatformAccessRepository;
import cn.nianxx.thhotel.platform.audit.common.dto.PlatformAdminAuditLogDraft; import cn.nianxx.thhotel.platform.audit.common.dto.PlatformAdminAuditLogDraft;
import cn.nianxx.thhotel.platform.audit.service.PlatformAdminAuditLogService; import cn.nianxx.thhotel.platform.audit.service.PlatformAdminAuditLogService;
@@ -44,7 +45,8 @@ public class AdminMenuManagementServiceImpl implements AdminMenuManagementServic
"/system/users", "/system/users",
"/system/roles", "/system/roles",
"/system/menus", "/system/menus",
"/system/hotels"); "/system/hotels",
"/system/audits");
private final PlatformNavigationRepository navigationRepository; private final PlatformNavigationRepository navigationRepository;
private final PlatformAccessRepository accessRepository; private final PlatformAccessRepository accessRepository;
@@ -231,8 +233,10 @@ public class AdminMenuManagementServiceImpl implements AdminMenuManagementServic
menu.setIconKey(trimToNull(iconKey)); menu.setIconKey(trimToNull(iconKey));
String normalizedPermissionCode = trimToNull(permissionCode); String normalizedPermissionCode = trimToNull(permissionCode);
if (normalizedPermissionCode != null if (normalizedPermissionCode != null
&& accessRepository.findPermissionByCode(normalizedPermissionCode).isEmpty()) { && accessRepository.findPermissionByCode(normalizedPermissionCode)
throw invalidRequest("菜单绑定的权限码不存在。"); .filter(permission -> PlatformPermissionStatus.ACTIVE.name().equals(permission.getPermissionStatus()))
.isEmpty()) {
throw invalidRequest("菜单绑定的权限码不存在或未启用。");
} }
menu.setPermissionCode(normalizedPermissionCode); menu.setPermissionCode(normalizedPermissionCode);
menu.setSortOrder(sortOrder == null ? 0 : sortOrder); menu.setSortOrder(sortOrder == null ? 0 : sortOrder);

View File

@@ -9,7 +9,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import cn.nianxx.thhotel.ThHotelApplication; import cn.nianxx.thhotel.ThHotelApplication;
import cn.nianxx.thhotel.platform.access.common.enums.PlatformPermissionStatus;
import cn.nianxx.thhotel.platform.access.common.enums.PlatformRoleCode; import cn.nianxx.thhotel.platform.access.common.enums.PlatformRoleCode;
import cn.nianxx.thhotel.platform.access.common.enums.PlatformRoleStatus;
import cn.nianxx.thhotel.platform.access.domain.PlatformPermissionEntity; import cn.nianxx.thhotel.platform.access.domain.PlatformPermissionEntity;
import cn.nianxx.thhotel.platform.access.domain.PlatformRoleEntity; import cn.nianxx.thhotel.platform.access.domain.PlatformRoleEntity;
import cn.nianxx.thhotel.platform.access.repository.PlatformAccessRepository; import cn.nianxx.thhotel.platform.access.repository.PlatformAccessRepository;
@@ -264,6 +266,58 @@ class AdminReadonlyControllerTest {
.andExpect(status().isUnauthorized()); .andExpect(status().isUnauthorized());
} }
@Test
void shouldRejectDisabledRoleAssignmentToUser() throws Exception {
String token = tokenFrom(login("m006-admin", "Admin@123456"));
PlatformRoleEntity viewerRole = accessRepository.findRoleByCode(PlatformRoleCode.RESERVATION_VIEWER.name())
.orElseThrow();
String username = "m006-role-user-" + System.nanoTime();
MvcResult createUserResult = mockMvc.perform(post("/api/admin/users")
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"username": "%s",
"initial_password": "User@123456",
"display_name": "角色校验用户",
"user_status": "ACTIVE",
"role_ids": ["%s"],
"hotel_ids": ["HOTEL-TEST"],
"default_hotel_id": "HOTEL-TEST"
}
""".formatted(username, viewerRole.getId())))
.andExpect(status().isOk())
.andReturn();
String userId = objectMapper.readTree(createUserResult.getResponse().getContentAsString()).path("id").asText();
MvcResult disabledRoleResult = mockMvc.perform(post("/api/admin/roles")
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"role_code": "%s",
"role_name": "M006 停用角色",
"role_status": "DISABLED"
}
""".formatted("M006_DISABLED_ROLE_" + System.nanoTime())))
.andExpect(status().isOk())
.andExpect(jsonPath("$.role_status").value(PlatformRoleStatus.DISABLED.name()))
.andReturn();
String disabledRoleId = objectMapper.readTree(disabledRoleResult.getResponse().getContentAsString())
.path("id").asText();
mockMvc.perform(put("/api/admin/users/{userId}/roles", userId)
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"role_ids": ["%s"]
}
""".formatted(disabledRoleId)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.error_code").value("ADMIN_INVALID_REQUEST"));
}
@Test @Test
void shouldManageCustomRoleAndRejectBuiltinRoleMutation() throws Exception { void shouldManageCustomRoleAndRejectBuiltinRoleMutation() throws Exception {
String token = tokenFrom(login("m006-admin", "Admin@123456")); String token = tokenFrom(login("m006-admin", "Admin@123456"));
@@ -297,6 +351,18 @@ class AdminReadonlyControllerTest {
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.permissions[0].permission_code").value("SYSTEM_USER_MANAGE")); .andExpect(jsonPath("$.permissions[0].permission_code").value("SYSTEM_USER_MANAGE"));
PlatformPermissionEntity disabledPermission = createDisabledPermission("M006_DISABLED_PERMISSION_");
mockMvc.perform(put("/api/admin/roles/{roleId}/permissions", roleId)
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"permission_ids": ["%s"]
}
""".formatted(disabledPermission.getId())))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.error_code").value("ADMIN_INVALID_REQUEST"));
PlatformRoleEntity builtinRole = accessRepository.findRoleByCode(PlatformRoleCode.SYSTEM_ADMIN.name()) PlatformRoleEntity builtinRole = accessRepository.findRoleByCode(PlatformRoleCode.SYSTEM_ADMIN.name())
.orElseThrow(); .orElseThrow();
mockMvc.perform(put("/api/admin/roles/{roleId}", builtinRole.getId().toString()) mockMvc.perform(put("/api/admin/roles/{roleId}", builtinRole.getId().toString())
@@ -344,7 +410,7 @@ class AdminReadonlyControllerTest {
{ {
"menu_name": "未来菜单更新", "menu_name": "未来菜单更新",
"menu_type": "PAGE", "menu_type": "PAGE",
"route_path": "/system/users", "route_path": "/system/audits",
"permission_code": "SYSTEM_USER_MANAGE", "permission_code": "SYSTEM_USER_MANAGE",
"visible": true, "visible": true,
"menu_status": "ACTIVE" "menu_status": "ACTIVE"
@@ -353,6 +419,25 @@ class AdminReadonlyControllerTest {
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.menu_code").value(menuCode)) .andExpect(jsonPath("$.menu_code").value(menuCode))
.andExpect(jsonPath("$.known_route").value(true)); .andExpect(jsonPath("$.known_route").value(true));
PlatformPermissionEntity disabledPermission = createDisabledPermission("M006_MENU_DISABLED_PERMISSION_");
mockMvc.perform(post("/api/admin/menus")
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"menu_code": "%s",
"menu_name": "停用权限菜单",
"menu_type": "PAGE",
"route_path": "/system/users",
"permission_code": "%s",
"visible": true,
"menu_status": "ACTIVE"
}
""".formatted("M006_MENU_DISABLED_" + System.nanoTime(),
disabledPermission.getPermissionCode())))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.error_code").value("ADMIN_INVALID_REQUEST"));
} }
@Test @Test
@@ -415,4 +500,18 @@ class AdminReadonlyControllerTest {
JsonNode json = objectMapper.readTree(result.getResponse().getContentAsString()); JsonNode json = objectMapper.readTree(result.getResponse().getContentAsString());
return json.path("access_token").asText(); return json.path("access_token").asText();
} }
private PlatformPermissionEntity createDisabledPermission(String codePrefix) {
LocalDateTime now = LocalDateTime.now();
PlatformPermissionEntity permission = new PlatformPermissionEntity();
permission.setPermissionCode(codePrefix + System.nanoTime());
permission.setPermissionName("M006 停用权限");
permission.setPermissionGroup("SYSTEM");
permission.setPermissionStatus(PlatformPermissionStatus.DISABLED.name());
permission.setSystemBuiltin(false);
permission.setCreatedAt(now);
permission.setUpdatedAt(now);
accessRepository.insertPermission(permission);
return permission;
}
} }