Commit 223a4d816aa1c198dbc3b8e252700e183ee709a1

Authored by YunaiV
1 parent 459986bf

fix(system): 禁止禁用或删除超级管理员和租户管理员账号

yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
@@ -46,6 +46,8 @@ public interface ErrorCodeConstants { @@ -46,6 +46,8 @@ public interface ErrorCodeConstants {
46 ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空"); 46 ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空");
47 ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册"); 47 ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册");
48 ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "注册功能已关闭"); 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 // ========== 部门模块 1-002-004-000 ========== 52 // ========== 部门模块 1-002-004-000 ==========
51 ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门"); 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,10 +6,12 @@ import cn.hutool.core.util.ObjUtil;
6 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
8 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 8 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  9 +import cn.iocoder.yudao.framework.common.exception.ErrorCode;
9 import cn.iocoder.yudao.framework.common.exception.ServiceException; 10 import cn.iocoder.yudao.framework.common.exception.ServiceException;
10 import cn.iocoder.yudao.framework.common.pojo.PageResult; 11 import cn.iocoder.yudao.framework.common.pojo.PageResult;
11 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 12 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
12 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 13 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  14 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
13 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; 15 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
14 import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; 16 import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
15 import cn.iocoder.yudao.module.infra.api.config.ConfigApi; 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,14 +24,17 @@ import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserPageReqV
22 import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO; 24 import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO;
23 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; 25 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
24 import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO; 26 import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO;
  27 +import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
25 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; 28 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
26 import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; 29 import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper;
27 import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; 30 import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
  31 +import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
28 import cn.iocoder.yudao.module.system.mq.producer.user.AdminUserProducer; 32 import cn.iocoder.yudao.module.system.mq.producer.user.AdminUserProducer;
29 import cn.iocoder.yudao.module.system.service.dept.DeptService; 33 import cn.iocoder.yudao.module.system.service.dept.DeptService;
30 import cn.iocoder.yudao.module.system.service.dept.PostService; 34 import cn.iocoder.yudao.module.system.service.dept.PostService;
31 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; 35 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
32 import cn.iocoder.yudao.module.system.service.permission.PermissionService; 36 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
  37 +import cn.iocoder.yudao.module.system.service.permission.RoleService;
33 import cn.iocoder.yudao.module.system.service.tenant.TenantService; 38 import cn.iocoder.yudao.module.system.service.tenant.TenantService;
34 import com.google.common.annotations.VisibleForTesting; 39 import com.google.common.annotations.VisibleForTesting;
35 import com.mzt.logapi.context.LogRecordContext; 40 import com.mzt.logapi.context.LogRecordContext;
@@ -75,6 +80,8 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -75,6 +80,8 @@ public class AdminUserServiceImpl implements AdminUserService {
75 @Resource 80 @Resource
76 private PermissionService permissionService; 81 private PermissionService permissionService;
77 @Resource 82 @Resource
  83 + private RoleService roleService;
  84 + @Resource
78 private PasswordEncoder passwordEncoder; 85 private PasswordEncoder passwordEncoder;
79 @Resource 86 @Resource
80 @Lazy // 延迟,避免循环依赖报错 87 @Lazy // 延迟,避免循环依赖报错
@@ -250,15 +257,20 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -250,15 +257,20 @@ public class AdminUserServiceImpl implements AdminUserService {
250 257
251 @Override 258 @Override
252 public void updateUserStatus(Long id, Integer status) { 259 public void updateUserStatus(Long id, Integer status) {
253 - // 校验用户存在 260 + // 1.1 校验用户存在
254 validateUserExists(id); 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 AdminUserDO updateObj = new AdminUserDO(); 268 AdminUserDO updateObj = new AdminUserDO();
257 updateObj.setId(id); 269 updateObj.setId(id);
258 updateObj.setStatus(status); 270 updateObj.setStatus(status);
259 userMapper.updateById(updateObj); 271 userMapper.updateById(updateObj);
260 272
261 - // 如果是禁用用户,则删除其 Token 信息 273 + // 3. 如果是禁用用户,则删除其 Token 信息
262 if (CommonStatusEnum.isDisable(status)) { 274 if (CommonStatusEnum.isDisable(status)) {
263 oauth2TokenService.removeAccessToken(id, UserTypeEnum.ADMIN.getValue()); 275 oauth2TokenService.removeAccessToken(id, UserTypeEnum.ADMIN.getValue());
264 } 276 }
@@ -269,8 +281,10 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -269,8 +281,10 @@ public class AdminUserServiceImpl implements AdminUserService {
269 @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_DELETE_SUB_TYPE, bizNo = "{{#id}}", 281 @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_DELETE_SUB_TYPE, bizNo = "{{#id}}",
270 success = SYSTEM_USER_DELETE_SUCCESS) 282 success = SYSTEM_USER_DELETE_SUCCESS)
271 public void deleteUser(Long id) { 283 public void deleteUser(Long id) {
272 - // 1. 校验用户存在 284 + // 1.1 校验用户存在
273 AdminUserDO user = validateUserExists(id); 285 AdminUserDO user = validateUserExists(id);
  286 + // 1.2 管理员不能删除
  287 + validateUserNotAdmin(id, USER_ADMIN_NOT_ALLOW_DELETE);
274 288
275 // 2.1 删除用户 289 // 2.1 删除用户
276 userMapper.deleteById(id); 290 userMapper.deleteById(id);
@@ -286,16 +300,32 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -286,16 +300,32 @@ public class AdminUserServiceImpl implements AdminUserService {
286 @Override 300 @Override
287 @Transactional(rollbackFor = Exception.class) 301 @Transactional(rollbackFor = Exception.class)
288 public void deleteUserList(List<Long> ids) { 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 userMapper.deleteByIds(ids); 307 userMapper.deleteByIds(ids);
291 308
292 - // 2. 批量删除用户关联数据 309 + // 3. 批量删除用户关联数据
293 ids.forEach(id -> { 310 ids.forEach(id -> {
294 permissionService.processUserDeleted(id); 311 permissionService.processUserDeleted(id);
295 userPostMapper.deleteByUserId(id); 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 @Override 329 @Override
300 public AdminUserDO getUserByUsername(String username) { 330 public AdminUserDO getUserByUsername(String username) {
301 return userMapper.selectByUsername(username); 331 return userMapper.selectByUsername(username);
@@ -562,6 +592,16 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -562,6 +592,16 @@ public class AdminUserServiceImpl implements AdminUserService {
562 respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg()); 592 respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg());
563 return; 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 AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class); 605 AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class);
566 updateUser.setId(existUser.getId()); 606 updateUser.setId(existUser.getId());
567 userMapper.updateById(updateUser); 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,16 +19,19 @@ import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqV
19 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; 19 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
20 import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO; 20 import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO;
21 import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO; 21 import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO;
  22 +import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
22 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO; 23 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
23 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; 24 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
24 import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; 25 import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper;
25 import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; 26 import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
26 import cn.iocoder.yudao.module.system.enums.common.SexEnum; 27 import cn.iocoder.yudao.module.system.enums.common.SexEnum;
  28 +import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
27 import cn.iocoder.yudao.module.system.mq.producer.user.AdminUserProducer; 29 import cn.iocoder.yudao.module.system.mq.producer.user.AdminUserProducer;
28 import cn.iocoder.yudao.module.system.service.dept.DeptService; 30 import cn.iocoder.yudao.module.system.service.dept.DeptService;
29 import cn.iocoder.yudao.module.system.service.dept.PostService; 31 import cn.iocoder.yudao.module.system.service.dept.PostService;
30 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; 32 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
31 import cn.iocoder.yudao.module.system.service.permission.PermissionService; 33 import cn.iocoder.yudao.module.system.service.permission.PermissionService;
  34 +import cn.iocoder.yudao.module.system.service.permission.RoleService;
32 import cn.iocoder.yudao.module.system.service.tenant.TenantService; 35 import cn.iocoder.yudao.module.system.service.tenant.TenantService;
33 import jakarta.annotation.Resource; 36 import jakarta.annotation.Resource;
34 import org.junit.jupiter.api.BeforeEach; 37 import org.junit.jupiter.api.BeforeEach;
@@ -78,6 +81,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { @@ -78,6 +81,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
78 @MockitoBean 81 @MockitoBean
79 private PermissionService permissionService; 82 private PermissionService permissionService;
80 @MockitoBean 83 @MockitoBean
  84 + private RoleService roleService;
  85 + @MockitoBean
81 private PasswordEncoder passwordEncoder; 86 private PasswordEncoder passwordEncoder;
82 @MockitoBean 87 @MockitoBean
83 private TenantService tenantService; 88 private TenantService tenantService;
@@ -290,6 +295,35 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { @@ -290,6 +295,35 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
290 } 295 }
291 296
292 @Test 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 public void testDeleteUser_success(){ 327 public void testDeleteUser_success(){
294 // mock 数据 328 // mock 数据
295 AdminUserDO dbUser = randomAdminUserDO(); 329 AdminUserDO dbUser = randomAdminUserDO();
@@ -306,6 +340,43 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { @@ -306,6 +340,43 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
306 } 340 }
307 341
308 @Test 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 public void testGetUserByUsername() { 380 public void testGetUserByUsername() {
310 // mock 数据 381 // mock 数据
311 AdminUserDO dbUser = randomAdminUserDO(); 382 AdminUserDO dbUser = randomAdminUserDO();
@@ -533,6 +604,29 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { @@ -533,6 +604,29 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
533 } 604 }
534 605
535 @Test 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 public void testValidateUserExists_notExists() { 630 public void testValidateUserExists_notExists() {
537 assertServiceException(() -> userService.validateUserExists(randomLongId()), USER_NOT_EXISTS); 631 assertServiceException(() -> userService.validateUserExists(randomLongId()), USER_NOT_EXISTS);
538 } 632 }