Commit 223a4d816aa1c198dbc3b8e252700e183ee709a1
1 parent
459986bf
fix(system): 禁止禁用或删除超级管理员和租户管理员账号
Showing
3 changed files
with
142 additions
and
6 deletions
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
| ... | ... | @@ -46,6 +46,8 @@ public interface ErrorCodeConstants { |
| 46 | 46 | ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空"); |
| 47 | 47 | ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册"); |
| 48 | 48 | ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "注册功能已关闭"); |
| 49 | + ErrorCode USER_ADMIN_NOT_ALLOW_DISABLE = new ErrorCode(1_002_003_012, "超级管理员或租户管理员账号不能禁用"); | |
| 50 | + ErrorCode USER_ADMIN_NOT_ALLOW_DELETE = new ErrorCode(1_002_003_013, "超级管理员或租户管理员账号不能删除"); | |
| 49 | 51 | |
| 50 | 52 | // ========== 部门模块 1-002-004-000 ========== |
| 51 | 53 | ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门"); | ... | ... |
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
| ... | ... | @@ -6,10 +6,12 @@ import cn.hutool.core.util.ObjUtil; |
| 6 | 6 | import cn.hutool.core.util.StrUtil; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; |
| 9 | +import cn.iocoder.yudao.framework.common.exception.ErrorCode; | |
| 9 | 10 | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| 10 | 11 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 11 | 12 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 12 | 13 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 14 | +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | |
| 13 | 15 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; |
| 14 | 16 | import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; |
| 15 | 17 | import cn.iocoder.yudao.module.infra.api.config.ConfigApi; |
| ... | ... | @@ -22,14 +24,17 @@ import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserPageReqV |
| 22 | 24 | import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO; |
| 23 | 25 | import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; |
| 24 | 26 | import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO; |
| 27 | +import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; | |
| 25 | 28 | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; |
| 26 | 29 | import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; |
| 27 | 30 | import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; |
| 31 | +import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum; | |
| 28 | 32 | import cn.iocoder.yudao.module.system.mq.producer.user.AdminUserProducer; |
| 29 | 33 | import cn.iocoder.yudao.module.system.service.dept.DeptService; |
| 30 | 34 | import cn.iocoder.yudao.module.system.service.dept.PostService; |
| 31 | 35 | import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; |
| 32 | 36 | import cn.iocoder.yudao.module.system.service.permission.PermissionService; |
| 37 | +import cn.iocoder.yudao.module.system.service.permission.RoleService; | |
| 33 | 38 | import cn.iocoder.yudao.module.system.service.tenant.TenantService; |
| 34 | 39 | import com.google.common.annotations.VisibleForTesting; |
| 35 | 40 | import com.mzt.logapi.context.LogRecordContext; |
| ... | ... | @@ -75,6 +80,8 @@ public class AdminUserServiceImpl implements AdminUserService { |
| 75 | 80 | @Resource |
| 76 | 81 | private PermissionService permissionService; |
| 77 | 82 | @Resource |
| 83 | + private RoleService roleService; | |
| 84 | + @Resource | |
| 78 | 85 | private PasswordEncoder passwordEncoder; |
| 79 | 86 | @Resource |
| 80 | 87 | @Lazy // 延迟,避免循环依赖报错 |
| ... | ... | @@ -250,15 +257,20 @@ public class AdminUserServiceImpl implements AdminUserService { |
| 250 | 257 | |
| 251 | 258 | @Override |
| 252 | 259 | public void updateUserStatus(Long id, Integer status) { |
| 253 | - // 校验用户存在 | |
| 260 | + // 1.1 校验用户存在 | |
| 254 | 261 | validateUserExists(id); |
| 255 | - // 更新状态 | |
| 262 | + // 1.2 管理员不能禁用 | |
| 263 | + if (CommonStatusEnum.isDisable(status)) { | |
| 264 | + validateUserNotAdmin(id, USER_ADMIN_NOT_ALLOW_DISABLE); | |
| 265 | + } | |
| 266 | + | |
| 267 | + // 2. 更新状态 | |
| 256 | 268 | AdminUserDO updateObj = new AdminUserDO(); |
| 257 | 269 | updateObj.setId(id); |
| 258 | 270 | updateObj.setStatus(status); |
| 259 | 271 | userMapper.updateById(updateObj); |
| 260 | 272 | |
| 261 | - // 如果是禁用用户,则删除其 Token 信息 | |
| 273 | + // 3. 如果是禁用用户,则删除其 Token 信息 | |
| 262 | 274 | if (CommonStatusEnum.isDisable(status)) { |
| 263 | 275 | oauth2TokenService.removeAccessToken(id, UserTypeEnum.ADMIN.getValue()); |
| 264 | 276 | } |
| ... | ... | @@ -269,8 +281,10 @@ public class AdminUserServiceImpl implements AdminUserService { |
| 269 | 281 | @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_DELETE_SUB_TYPE, bizNo = "{{#id}}", |
| 270 | 282 | success = SYSTEM_USER_DELETE_SUCCESS) |
| 271 | 283 | public void deleteUser(Long id) { |
| 272 | - // 1. 校验用户存在 | |
| 284 | + // 1.1 校验用户存在 | |
| 273 | 285 | AdminUserDO user = validateUserExists(id); |
| 286 | + // 1.2 管理员不能删除 | |
| 287 | + validateUserNotAdmin(id, USER_ADMIN_NOT_ALLOW_DELETE); | |
| 274 | 288 | |
| 275 | 289 | // 2.1 删除用户 |
| 276 | 290 | userMapper.deleteById(id); |
| ... | ... | @@ -286,16 +300,32 @@ public class AdminUserServiceImpl implements AdminUserService { |
| 286 | 300 | @Override |
| 287 | 301 | @Transactional(rollbackFor = Exception.class) |
| 288 | 302 | public void deleteUserList(List<Long> ids) { |
| 289 | - // 1. 批量删除用户 | |
| 303 | + // 1. 校验不能删除管理员 | |
| 304 | + ids.forEach(id -> validateUserNotAdmin(id, USER_ADMIN_NOT_ALLOW_DELETE)); | |
| 305 | + | |
| 306 | + // 2. 批量删除用户 | |
| 290 | 307 | userMapper.deleteByIds(ids); |
| 291 | 308 | |
| 292 | - // 2. 批量删除用户关联数据 | |
| 309 | + // 3. 批量删除用户关联数据 | |
| 293 | 310 | ids.forEach(id -> { |
| 294 | 311 | permissionService.processUserDeleted(id); |
| 295 | 312 | userPostMapper.deleteByUserId(id); |
| 296 | 313 | }); |
| 297 | 314 | } |
| 298 | 315 | |
| 316 | + private void validateUserNotAdmin(Long id, ErrorCode errorCode) { | |
| 317 | + Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(id); | |
| 318 | + if (CollUtil.isEmpty(roleIds)) { | |
| 319 | + return; | |
| 320 | + } | |
| 321 | + for (RoleDO role : roleService.getRoleList(roleIds)) { | |
| 322 | + if (ObjectUtils.equalsAny(role.getCode(), | |
| 323 | + RoleCodeEnum.SUPER_ADMIN.getCode(), RoleCodeEnum.TENANT_ADMIN.getCode())) { | |
| 324 | + throw exception(errorCode); | |
| 325 | + } | |
| 326 | + } | |
| 327 | + } | |
| 328 | + | |
| 299 | 329 | @Override |
| 300 | 330 | public AdminUserDO getUserByUsername(String username) { |
| 301 | 331 | return userMapper.selectByUsername(username); |
| ... | ... | @@ -562,6 +592,16 @@ public class AdminUserServiceImpl implements AdminUserService { |
| 562 | 592 | respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg()); |
| 563 | 593 | return; |
| 564 | 594 | } |
| 595 | + // 2.2.3 校验管理员不能禁用 | |
| 596 | + if (CommonStatusEnum.isDisable(importUser.getStatus())) { | |
| 597 | + try { | |
| 598 | + validateUserNotAdmin(existUser.getId(), USER_ADMIN_NOT_ALLOW_DISABLE); | |
| 599 | + } catch (ServiceException ex) { | |
| 600 | + respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage()); | |
| 601 | + return; | |
| 602 | + } | |
| 603 | + } | |
| 604 | + // 2.2.4 更新用户 | |
| 565 | 605 | AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class); |
| 566 | 606 | updateUser.setId(existUser.getId()); |
| 567 | 607 | userMapper.updateById(updateUser); | ... | ... |
yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java
| ... | ... | @@ -19,16 +19,19 @@ import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqV |
| 19 | 19 | import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; |
| 20 | 20 | import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO; |
| 21 | 21 | import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO; |
| 22 | +import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; | |
| 22 | 23 | import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO; |
| 23 | 24 | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; |
| 24 | 25 | import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; |
| 25 | 26 | import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; |
| 26 | 27 | import cn.iocoder.yudao.module.system.enums.common.SexEnum; |
| 28 | +import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum; | |
| 27 | 29 | import cn.iocoder.yudao.module.system.mq.producer.user.AdminUserProducer; |
| 28 | 30 | import cn.iocoder.yudao.module.system.service.dept.DeptService; |
| 29 | 31 | import cn.iocoder.yudao.module.system.service.dept.PostService; |
| 30 | 32 | import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; |
| 31 | 33 | import cn.iocoder.yudao.module.system.service.permission.PermissionService; |
| 34 | +import cn.iocoder.yudao.module.system.service.permission.RoleService; | |
| 32 | 35 | import cn.iocoder.yudao.module.system.service.tenant.TenantService; |
| 33 | 36 | import jakarta.annotation.Resource; |
| 34 | 37 | import org.junit.jupiter.api.BeforeEach; |
| ... | ... | @@ -78,6 +81,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { |
| 78 | 81 | @MockitoBean |
| 79 | 82 | private PermissionService permissionService; |
| 80 | 83 | @MockitoBean |
| 84 | + private RoleService roleService; | |
| 85 | + @MockitoBean | |
| 81 | 86 | private PasswordEncoder passwordEncoder; |
| 82 | 87 | @MockitoBean |
| 83 | 88 | private TenantService tenantService; |
| ... | ... | @@ -290,6 +295,35 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { |
| 290 | 295 | } |
| 291 | 296 | |
| 292 | 297 | @Test |
| 298 | + public void testUpdateUserStatus_adminCannotDisable() { | |
| 299 | + for (RoleCodeEnum roleCode : new RoleCodeEnum[]{RoleCodeEnum.SUPER_ADMIN, RoleCodeEnum.TENANT_ADMIN}) { | |
| 300 | + // mock 数据 | |
| 301 | + AdminUserDO dbUser = randomAdminUserDO(o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())); | |
| 302 | + userMapper.insert(dbUser); | |
| 303 | + mockUserRole(dbUser.getId(), roleCode); | |
| 304 | + | |
| 305 | + // 调用,并断言异常 | |
| 306 | + assertServiceException(() -> userService.updateUserStatus(dbUser.getId(), CommonStatusEnum.DISABLE.getStatus()), | |
| 307 | + USER_ADMIN_NOT_ALLOW_DISABLE); | |
| 308 | + assertEquals(CommonStatusEnum.ENABLE.getStatus(), userMapper.selectById(dbUser.getId()).getStatus()); | |
| 309 | + } | |
| 310 | + verify(oauth2TokenService, never()).removeAccessToken(anyLong(), anyInt()); | |
| 311 | + } | |
| 312 | + | |
| 313 | + @Test | |
| 314 | + public void testUpdateUserStatus_adminCanEnable() { | |
| 315 | + // mock 数据 | |
| 316 | + AdminUserDO dbUser = randomAdminUserDO(o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())); | |
| 317 | + userMapper.insert(dbUser); | |
| 318 | + mockUserRole(dbUser.getId(), RoleCodeEnum.TENANT_ADMIN); | |
| 319 | + | |
| 320 | + // 调用 | |
| 321 | + userService.updateUserStatus(dbUser.getId(), CommonStatusEnum.ENABLE.getStatus()); | |
| 322 | + // 断言 | |
| 323 | + assertEquals(CommonStatusEnum.ENABLE.getStatus(), userMapper.selectById(dbUser.getId()).getStatus()); | |
| 324 | + } | |
| 325 | + | |
| 326 | + @Test | |
| 293 | 327 | public void testDeleteUser_success(){ |
| 294 | 328 | // mock 数据 |
| 295 | 329 | AdminUserDO dbUser = randomAdminUserDO(); |
| ... | ... | @@ -306,6 +340,43 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { |
| 306 | 340 | } |
| 307 | 341 | |
| 308 | 342 | @Test |
| 343 | + public void testDeleteUser_adminCannotDelete() { | |
| 344 | + for (RoleCodeEnum roleCode : new RoleCodeEnum[]{RoleCodeEnum.SUPER_ADMIN, RoleCodeEnum.TENANT_ADMIN}) { | |
| 345 | + // mock 数据 | |
| 346 | + AdminUserDO dbUser = randomAdminUserDO(); | |
| 347 | + userMapper.insert(dbUser); | |
| 348 | + mockUserRole(dbUser.getId(), roleCode); | |
| 349 | + | |
| 350 | + // 调用,并断言异常 | |
| 351 | + assertServiceException(() -> userService.deleteUser(dbUser.getId()), USER_ADMIN_NOT_ALLOW_DELETE); | |
| 352 | + assertNotNull(userMapper.selectById(dbUser.getId())); | |
| 353 | + } | |
| 354 | + } | |
| 355 | + | |
| 356 | + @Test | |
| 357 | + public void testDeleteUserList_adminCannotDelete() { | |
| 358 | + // mock 数据 | |
| 359 | + AdminUserDO ordinaryUser = randomAdminUserDO(); | |
| 360 | + userMapper.insert(ordinaryUser); | |
| 361 | + AdminUserDO adminUser = randomAdminUserDO(); | |
| 362 | + userMapper.insert(adminUser); | |
| 363 | + mockUserRole(adminUser.getId(), RoleCodeEnum.TENANT_ADMIN); | |
| 364 | + | |
| 365 | + // 调用,并断言整批未删除 | |
| 366 | + assertServiceException(() -> userService.deleteUserList(List.of(ordinaryUser.getId(), adminUser.getId())), | |
| 367 | + USER_ADMIN_NOT_ALLOW_DELETE); | |
| 368 | + assertNotNull(userMapper.selectById(ordinaryUser.getId())); | |
| 369 | + assertNotNull(userMapper.selectById(adminUser.getId())); | |
| 370 | + } | |
| 371 | + | |
| 372 | + private void mockUserRole(Long userId, RoleCodeEnum roleCode) { | |
| 373 | + Long roleId = 100L; | |
| 374 | + when(permissionService.getUserRoleIdListByUserId(userId)).thenReturn(asSet(roleId)); | |
| 375 | + when(roleService.getRoleList(asSet(roleId))).thenReturn(singletonList( | |
| 376 | + randomPojo(RoleDO.class, role -> role.setCode(roleCode.getCode())))); | |
| 377 | + } | |
| 378 | + | |
| 379 | + @Test | |
| 309 | 380 | public void testGetUserByUsername() { |
| 310 | 381 | // mock 数据 |
| 311 | 382 | AdminUserDO dbUser = randomAdminUserDO(); |
| ... | ... | @@ -533,6 +604,29 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { |
| 533 | 604 | } |
| 534 | 605 | |
| 535 | 606 | @Test |
| 607 | + public void testImportUserList_adminCannotDisable() { | |
| 608 | + // mock 数据 | |
| 609 | + AdminUserDO dbUser = randomAdminUserDO(o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())); | |
| 610 | + userMapper.insert(dbUser); | |
| 611 | + mockUserRole(dbUser.getId(), RoleCodeEnum.TENANT_ADMIN); | |
| 612 | + // 准备参数 | |
| 613 | + UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> { | |
| 614 | + o.setUsername(dbUser.getUsername()); | |
| 615 | + o.setStatus(CommonStatusEnum.DISABLE.getStatus()); | |
| 616 | + o.setSex(randomEle(SexEnum.values()).getSex()); | |
| 617 | + o.setEmail(randomEmail()); | |
| 618 | + o.setMobile(randomMobile()); | |
| 619 | + }); | |
| 620 | + | |
| 621 | + // 调用 | |
| 622 | + UserImportRespVO respVO = userService.importUserList(singletonList(importUser), true); | |
| 623 | + // 断言 | |
| 624 | + assertEquals(USER_ADMIN_NOT_ALLOW_DISABLE.getMsg(), respVO.getFailureUsernames().get(importUser.getUsername())); | |
| 625 | + assertTrue(respVO.getUpdateUsernames().isEmpty()); | |
| 626 | + assertEquals(CommonStatusEnum.ENABLE.getStatus(), userMapper.selectById(dbUser.getId()).getStatus()); | |
| 627 | + } | |
| 628 | + | |
| 629 | + @Test | |
| 536 | 630 | public void testValidateUserExists_notExists() { |
| 537 | 631 | assertServiceException(() -> userService.validateUserExists(randomLongId()), USER_NOT_EXISTS); |
| 538 | 632 | } | ... | ... |