Commit c0168a8b104fd0874d1efe6915c7d4ddc0121569
1 parent
9ecd3ed9
fix(system): 批量删除部门时整批校验数据权限
批删前检查所有部门的数据范围,任一越权则整批拒绝。 userId 由 Controller 显式传入,保留授权跨租户和忽略数据权限行为。 其他写操作保持原有行为;23 个服务测试与 9 项 MySQL 写入检查通过。 参考:https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1582
Showing
5 changed files
with
206 additions
and
5 deletions
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/DeptController.java
| @@ -21,6 +21,7 @@ import jakarta.validation.Valid; | @@ -21,6 +21,7 @@ import jakarta.validation.Valid; | ||
| 21 | import java.util.List; | 21 | import java.util.List; |
| 22 | 22 | ||
| 23 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 23 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 24 | +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | ||
| 24 | 25 | ||
| 25 | @Tag(name = "管理后台 - 部门") | 26 | @Tag(name = "管理后台 - 部门") |
| 26 | @RestController | 27 | @RestController |
| @@ -61,7 +62,7 @@ public class DeptController { | @@ -61,7 +62,7 @@ public class DeptController { | ||
| 61 | @Parameter(name = "ids", description = "编号列表", required = true) | 62 | @Parameter(name = "ids", description = "编号列表", required = true) |
| 62 | @PreAuthorize("@ss.hasPermission('system:dept:delete')") | 63 | @PreAuthorize("@ss.hasPermission('system:dept:delete')") |
| 63 | public CommonResult<Boolean> deleteDeptList(@RequestParam("ids") List<Long> ids) { | 64 | public CommonResult<Boolean> deleteDeptList(@RequestParam("ids") List<Long> ids) { |
| 64 | - deptService.deleteDeptList(ids); | 65 | + deptService.deleteDeptList(ids, getLoginUserId()); |
| 65 | return success(true); | 66 | return success(true); |
| 66 | } | 67 | } |
| 67 | 68 |
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
| @@ -57,6 +57,7 @@ public interface ErrorCodeConstants { | @@ -57,6 +57,7 @@ public interface ErrorCodeConstants { | ||
| 57 | ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1_002_004_004, "不能设置自己为父部门"); | 57 | ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1_002_004_004, "不能设置自己为父部门"); |
| 58 | ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1_002_004_006, "部门({})不处于开启状态,不允许选择"); | 58 | ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1_002_004_006, "部门({})不处于开启状态,不允许选择"); |
| 59 | ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1_002_004_007, "不能设置自己的子部门为父部门"); | 59 | ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1_002_004_007, "不能设置自己的子部门为父部门"); |
| 60 | + ErrorCode DEPT_NOT_IN_SCOPE = new ErrorCode(1_002_004_008, "部门({})不在数据权限范围内,禁止操作"); | ||
| 60 | 61 | ||
| 61 | // ========== 岗位模块 1-002-005-000 ========== | 62 | // ========== 岗位模块 1-002-005-000 ========== |
| 62 | ErrorCode POST_NOT_FOUND = new ErrorCode(1_002_005_000, "当前岗位不存在"); | 63 | ErrorCode POST_NOT_FOUND = new ErrorCode(1_002_005_000, "当前岗位不存在"); |
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java
| @@ -40,8 +40,9 @@ public interface DeptService { | @@ -40,8 +40,9 @@ public interface DeptService { | ||
| 40 | * 批量删除部门 | 40 | * 批量删除部门 |
| 41 | * | 41 | * |
| 42 | * @param ids 部门编号数组 | 42 | * @param ids 部门编号数组 |
| 43 | + * @param userId 操作的用户编号,用于校验数据权限 | ||
| 43 | */ | 44 | */ |
| 44 | - void deleteDeptList(List<Long> ids); | 45 | + void deleteDeptList(List<Long> ids, Long userId); |
| 45 | 46 | ||
| 46 | /** | 47 | /** |
| 47 | * 获得部门信息 | 48 | * 获得部门信息 |
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
| @@ -2,18 +2,22 @@ package cn.iocoder.yudao.module.system.service.dept; | @@ -2,18 +2,22 @@ package cn.iocoder.yudao.module.system.service.dept; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.hutool.core.util.ObjectUtil; | 4 | import cn.hutool.core.util.ObjectUtil; |
| 5 | +import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; | ||
| 5 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 6 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 6 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 7 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 7 | import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; | 8 | import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; |
| 9 | +import cn.iocoder.yudao.framework.datapermission.core.aop.DataPermissionContextHolder; | ||
| 8 | import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO; | 10 | import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO; |
| 9 | import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO; | 11 | import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO; |
| 10 | import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; | 12 | import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; |
| 11 | import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper; | 13 | import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper; |
| 12 | import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants; | 14 | import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants; |
| 15 | +import cn.iocoder.yudao.module.system.service.permission.PermissionService; | ||
| 13 | import com.google.common.annotations.VisibleForTesting; | 16 | import com.google.common.annotations.VisibleForTesting; |
| 14 | import lombok.extern.slf4j.Slf4j; | 17 | import lombok.extern.slf4j.Slf4j; |
| 15 | import org.springframework.cache.annotation.CacheEvict; | 18 | import org.springframework.cache.annotation.CacheEvict; |
| 16 | import org.springframework.cache.annotation.Cacheable; | 19 | import org.springframework.cache.annotation.Cacheable; |
| 20 | +import org.springframework.context.annotation.Lazy; | ||
| 17 | import org.springframework.stereotype.Service; | 21 | import org.springframework.stereotype.Service; |
| 18 | import org.springframework.validation.annotation.Validated; | 22 | import org.springframework.validation.annotation.Validated; |
| 19 | 23 | ||
| @@ -22,6 +26,7 @@ import java.util.*; | @@ -22,6 +26,7 @@ import java.util.*; | ||
| 22 | 26 | ||
| 23 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 27 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 24 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | 28 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| 29 | +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.skipPermissionCheck; | ||
| 25 | import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; | 30 | import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; |
| 26 | 31 | ||
| 27 | /** | 32 | /** |
| @@ -37,6 +42,10 @@ public class DeptServiceImpl implements DeptService { | @@ -37,6 +42,10 @@ public class DeptServiceImpl implements DeptService { | ||
| 37 | @Resource | 42 | @Resource |
| 38 | private DeptMapper deptMapper; | 43 | private DeptMapper deptMapper; |
| 39 | 44 | ||
| 45 | + @Resource | ||
| 46 | + @Lazy // 延迟,避免循环依赖报错 | ||
| 47 | + private PermissionService permissionService; | ||
| 48 | + | ||
| 40 | @Override | 49 | @Override |
| 41 | @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, | 50 | @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, |
| 42 | allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存 | 51 | allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存 |
| @@ -91,7 +100,9 @@ public class DeptServiceImpl implements DeptService { | @@ -91,7 +100,9 @@ public class DeptServiceImpl implements DeptService { | ||
| 91 | @Override | 100 | @Override |
| 92 | @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, | 101 | @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, |
| 93 | allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存 | 102 | allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存 |
| 94 | - public void deleteDeptList(List<Long> ids) { | 103 | + public void deleteDeptList(List<Long> ids, Long userId) { |
| 104 | + // 校验数据权限 | ||
| 105 | + validateDeptListDataScope(ids, userId); | ||
| 95 | // 校验是否有子部门 | 106 | // 校验是否有子部门 |
| 96 | for (Long id : ids) { | 107 | for (Long id : ids) { |
| 97 | if (deptMapper.selectCountByParentId(id) > 0) { | 108 | if (deptMapper.selectCountByParentId(id) > 0) { |
| @@ -103,6 +114,41 @@ public class DeptServiceImpl implements DeptService { | @@ -103,6 +114,41 @@ public class DeptServiceImpl implements DeptService { | ||
| 103 | deptMapper.deleteByIds(ids); | 114 | deptMapper.deleteByIds(ids); |
| 104 | } | 115 | } |
| 105 | 116 | ||
| 117 | + /** | ||
| 118 | + * 校验部门们是否都在用户的数据权限范围内,任一不在则整体拒绝 | ||
| 119 | + * | ||
| 120 | + * 原因:数据权限插件只会删除范围内的部门,导致批量删除时“部分成功” | ||
| 121 | + * 相关 PR:https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1582 | ||
| 122 | + * | ||
| 123 | + * @param ids 部门编号数组 | ||
| 124 | + * @param userId 用户编号 | ||
| 125 | + */ | ||
| 126 | + @VisibleForTesting | ||
| 127 | + void validateDeptListDataScope(Collection<Long> ids, Long userId) { | ||
| 128 | + if (CollUtil.isEmpty(ids)) { | ||
| 129 | + return; | ||
| 130 | + } | ||
| 131 | + // 特殊:跨租户访问、忽略数据权限时,与数据权限插件保持一致,无需校验 | ||
| 132 | + if (skipPermissionCheck()) { | ||
| 133 | + return; | ||
| 134 | + } | ||
| 135 | + DataPermission dataPermission = DataPermissionContextHolder.get(); | ||
| 136 | + if (dataPermission != null && !dataPermission.enable()) { | ||
| 137 | + return; | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + // 校验每个部门:与 DeptDataPermissionRule 对部门表的过滤保持一致 | ||
| 141 | + DeptDataPermissionRespDTO deptDataPermission = permissionService.getDeptDataPermission(userId); | ||
| 142 | + if (Boolean.TRUE.equals(deptDataPermission.getAll())) { | ||
| 143 | + return; | ||
| 144 | + } | ||
| 145 | + for (Long id : ids) { | ||
| 146 | + if (!CollUtil.contains(deptDataPermission.getDeptIds(), id)) { | ||
| 147 | + throw exception(DEPT_NOT_IN_SCOPE, id); | ||
| 148 | + } | ||
| 149 | + } | ||
| 150 | + } | ||
| 151 | + | ||
| 106 | @VisibleForTesting | 152 | @VisibleForTesting |
| 107 | void validateDeptExists(Long id) { | 153 | void validateDeptExists(Long id) { |
| 108 | if (id == null) { | 154 | if (id == null) { |
yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImplTest.java
| 1 | package cn.iocoder.yudao.module.system.service.dept; | 1 | package cn.iocoder.yudao.module.system.service.dept; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; | ||
| 3 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 5 | +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||
| 4 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | 6 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| 7 | +import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; | ||
| 8 | +import cn.iocoder.yudao.framework.security.core.LoginUser; | ||
| 9 | +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | ||
| 5 | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | 10 | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; |
| 6 | import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO; | 11 | import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO; |
| 7 | import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO; | 12 | import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO; |
| 8 | import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; | 13 | import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; |
| 9 | import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper; | 14 | import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper; |
| 15 | +import cn.iocoder.yudao.module.system.service.permission.PermissionService; | ||
| 10 | import org.junit.jupiter.api.Test; | 16 | import org.junit.jupiter.api.Test; |
| 11 | import org.springframework.context.annotation.Import; | 17 | import org.springframework.context.annotation.Import; |
| 18 | +import org.springframework.mock.web.MockHttpServletRequest; | ||
| 19 | +import org.springframework.security.core.context.SecurityContextHolder; | ||
| 20 | +import org.springframework.test.context.bean.override.mockito.MockitoBean; | ||
| 12 | 21 | ||
| 13 | import jakarta.annotation.Resource; | 22 | import jakarta.annotation.Resource; |
| 14 | import java.util.Arrays; | 23 | import java.util.Arrays; |
| 15 | import java.util.List; | 24 | import java.util.List; |
| 16 | import java.util.Set; | 25 | import java.util.Set; |
| 17 | 26 | ||
| 27 | +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; | ||
| 18 | import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; | 28 | import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; |
| 19 | import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; | 29 | import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; |
| 20 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; | 30 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; |
| 21 | import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; | 31 | import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; |
| 22 | import static java.util.Collections.singletonList; | 32 | import static java.util.Collections.singletonList; |
| 23 | import static org.junit.jupiter.api.Assertions.*; | 33 | import static org.junit.jupiter.api.Assertions.*; |
| 34 | +import static org.mockito.ArgumentMatchers.anyLong; | ||
| 35 | +import static org.mockito.ArgumentMatchers.eq; | ||
| 36 | +import static org.mockito.Mockito.*; | ||
| 24 | 37 | ||
| 25 | /** | 38 | /** |
| 26 | * {@link DeptServiceImpl} 的单元测试类 | 39 | * {@link DeptServiceImpl} 的单元测试类 |
| @@ -35,6 +48,9 @@ public class DeptServiceImplTest extends BaseDbUnitTest { | @@ -35,6 +48,9 @@ public class DeptServiceImplTest extends BaseDbUnitTest { | ||
| 35 | @Resource | 48 | @Resource |
| 36 | private DeptMapper deptMapper; | 49 | private DeptMapper deptMapper; |
| 37 | 50 | ||
| 51 | + @MockitoBean | ||
| 52 | + private PermissionService permissionService; | ||
| 53 | + | ||
| 38 | @Test | 54 | @Test |
| 39 | public void testCreateDept() { | 55 | public void testCreateDept() { |
| 40 | // 准备参数 | 56 | // 准备参数 |
| @@ -113,12 +129,19 @@ public class DeptServiceImplTest extends BaseDbUnitTest { | @@ -113,12 +129,19 @@ public class DeptServiceImplTest extends BaseDbUnitTest { | ||
| 113 | deptMapper.insert(deptDO2); | 129 | deptMapper.insert(deptDO2); |
| 114 | // 准备参数 | 130 | // 准备参数 |
| 115 | List<Long> ids = Arrays.asList(deptDO1.getId(), deptDO2.getId()); | 131 | List<Long> ids = Arrays.asList(deptDO1.getId(), deptDO2.getId()); |
| 132 | + Long userId = randomLongId(); | ||
| 133 | + // mock 方法(数据权限包含全部部门) | ||
| 134 | + DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO(); | ||
| 135 | + deptDataPermission.setDeptIds(asSet(deptDO1.getId(), deptDO2.getId())); | ||
| 136 | + when(permissionService.getDeptDataPermission(eq(userId))).thenReturn(deptDataPermission); | ||
| 116 | 137 | ||
| 117 | // 调用 | 138 | // 调用 |
| 118 | - deptService.deleteDeptList(ids); | 139 | + deptService.deleteDeptList(ids, userId); |
| 119 | // 校验数据不存在了 | 140 | // 校验数据不存在了 |
| 120 | assertNull(deptMapper.selectById(deptDO1.getId())); | 141 | assertNull(deptMapper.selectById(deptDO1.getId())); |
| 121 | assertNull(deptMapper.selectById(deptDO2.getId())); | 142 | assertNull(deptMapper.selectById(deptDO2.getId())); |
| 143 | + // 校验使用传入的用户编号,获取数据权限 | ||
| 144 | + verify(permissionService).getDeptDataPermission(eq(userId)); | ||
| 122 | } | 145 | } |
| 123 | 146 | ||
| 124 | @Test | 147 | @Test |
| @@ -136,9 +159,138 @@ public class DeptServiceImplTest extends BaseDbUnitTest { | @@ -136,9 +159,138 @@ public class DeptServiceImplTest extends BaseDbUnitTest { | ||
| 136 | 159 | ||
| 137 | // 准备参数(包含有子部门的 parentDept) | 160 | // 准备参数(包含有子部门的 parentDept) |
| 138 | List<Long> ids = Arrays.asList(parentDept.getId(), anotherDept.getId()); | 161 | List<Long> ids = Arrays.asList(parentDept.getId(), anotherDept.getId()); |
| 162 | + Long userId = randomLongId(); | ||
| 163 | + // mock 方法(数据权限为全部) | ||
| 164 | + DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO(); | ||
| 165 | + deptDataPermission.setAll(true); | ||
| 166 | + when(permissionService.getDeptDataPermission(eq(userId))).thenReturn(deptDataPermission); | ||
| 167 | + | ||
| 168 | + // 调用, 并断言异常 | ||
| 169 | + assertServiceException(() -> deptService.deleteDeptList(ids, userId), DEPT_EXITS_CHILDREN); | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + @Test | ||
| 173 | + public void testDeleteDeptList_notInScope() { | ||
| 174 | + // mock 数据 | ||
| 175 | + DeptDO deptDO1 = randomPojo(DeptDO.class); | ||
| 176 | + deptMapper.insert(deptDO1); | ||
| 177 | + DeptDO deptDO2 = randomPojo(DeptDO.class); | ||
| 178 | + deptMapper.insert(deptDO2); | ||
| 179 | + // 准备参数 | ||
| 180 | + List<Long> ids = Arrays.asList(deptDO1.getId(), deptDO2.getId()); | ||
| 181 | + Long userId = randomLongId(); | ||
| 182 | + // mock 方法(数据权限只包含 deptDO1) | ||
| 183 | + DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO(); | ||
| 184 | + deptDataPermission.setDeptIds(asSet(deptDO1.getId())); | ||
| 185 | + when(permissionService.getDeptDataPermission(eq(userId))).thenReturn(deptDataPermission); | ||
| 139 | 186 | ||
| 140 | // 调用, 并断言异常 | 187 | // 调用, 并断言异常 |
| 141 | - assertServiceException(() -> deptService.deleteDeptList(ids), DEPT_EXITS_CHILDREN); | 188 | + assertServiceException(() -> deptService.deleteDeptList(ids, userId), DEPT_NOT_IN_SCOPE, deptDO2.getId()); |
| 189 | + // 校验整体拒绝,范围内的部门也未被删除 | ||
| 190 | + assertNotNull(deptMapper.selectById(deptDO1.getId())); | ||
| 191 | + assertNotNull(deptMapper.selectById(deptDO2.getId())); | ||
| 192 | + } | ||
| 193 | + | ||
| 194 | + @Test | ||
| 195 | + public void testDeleteDeptList_selfScope() { | ||
| 196 | + // mock 数据 | ||
| 197 | + DeptDO deptDO = randomPojo(DeptDO.class); | ||
| 198 | + deptMapper.insert(deptDO); | ||
| 199 | + // 准备参数 | ||
| 200 | + List<Long> ids = singletonList(deptDO.getId()); | ||
| 201 | + Long userId = randomLongId(); | ||
| 202 | + // mock 方法(数据权限为仅本人,部门表无对应的用户字段,无法操作任何部门) | ||
| 203 | + DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO(); | ||
| 204 | + deptDataPermission.setSelf(true); | ||
| 205 | + when(permissionService.getDeptDataPermission(eq(userId))).thenReturn(deptDataPermission); | ||
| 206 | + | ||
| 207 | + // 调用, 并断言异常 | ||
| 208 | + assertServiceException(() -> deptService.deleteDeptList(ids, userId), DEPT_NOT_IN_SCOPE, deptDO.getId()); | ||
| 209 | + assertNotNull(deptMapper.selectById(deptDO.getId())); | ||
| 210 | + } | ||
| 211 | + | ||
| 212 | + @Test | ||
| 213 | + public void testDeleteDeptList_allScope() { | ||
| 214 | + // mock 数据 | ||
| 215 | + DeptDO deptDO1 = randomPojo(DeptDO.class); | ||
| 216 | + deptMapper.insert(deptDO1); | ||
| 217 | + DeptDO deptDO2 = randomPojo(DeptDO.class); | ||
| 218 | + deptMapper.insert(deptDO2); | ||
| 219 | + // 准备参数 | ||
| 220 | + List<Long> ids = Arrays.asList(deptDO1.getId(), deptDO2.getId()); | ||
| 221 | + Long userId = randomLongId(); | ||
| 222 | + // mock 方法(数据权限为全部,deptIds 为空) | ||
| 223 | + DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO(); | ||
| 224 | + deptDataPermission.setAll(true); | ||
| 225 | + when(permissionService.getDeptDataPermission(eq(userId))).thenReturn(deptDataPermission); | ||
| 226 | + | ||
| 227 | + // 调用 | ||
| 228 | + deptService.deleteDeptList(ids, userId); | ||
| 229 | + // 校验数据不存在了 | ||
| 230 | + assertNull(deptMapper.selectById(deptDO1.getId())); | ||
| 231 | + assertNull(deptMapper.selectById(deptDO2.getId())); | ||
| 232 | + } | ||
| 233 | + | ||
| 234 | + @Test | ||
| 235 | + public void testDeleteDeptList_duplicateIds() { | ||
| 236 | + // mock 数据 | ||
| 237 | + DeptDO deptDO1 = randomPojo(DeptDO.class); | ||
| 238 | + deptMapper.insert(deptDO1); | ||
| 239 | + DeptDO deptDO2 = randomPojo(DeptDO.class); | ||
| 240 | + deptMapper.insert(deptDO2); | ||
| 241 | + // 准备参数(deptDO1 重复) | ||
| 242 | + List<Long> ids = Arrays.asList(deptDO1.getId(), deptDO1.getId(), deptDO2.getId()); | ||
| 243 | + Long userId = randomLongId(); | ||
| 244 | + // mock 方法 | ||
| 245 | + DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO(); | ||
| 246 | + deptDataPermission.setDeptIds(asSet(deptDO1.getId(), deptDO2.getId())); | ||
| 247 | + when(permissionService.getDeptDataPermission(eq(userId))).thenReturn(deptDataPermission); | ||
| 248 | + | ||
| 249 | + // 调用 | ||
| 250 | + deptService.deleteDeptList(ids, userId); | ||
| 251 | + // 校验数据不存在了 | ||
| 252 | + assertNull(deptMapper.selectById(deptDO1.getId())); | ||
| 253 | + assertNull(deptMapper.selectById(deptDO2.getId())); | ||
| 254 | + } | ||
| 255 | + | ||
| 256 | + @Test | ||
| 257 | + public void testDeleteDeptList_visitTenant() { | ||
| 258 | + // mock 数据 | ||
| 259 | + DeptDO deptDO = randomPojo(DeptDO.class); | ||
| 260 | + deptMapper.insert(deptDO); | ||
| 261 | + // 准备参数 | ||
| 262 | + List<Long> ids = singletonList(deptDO.getId()); | ||
| 263 | + Long userId = randomLongId(); | ||
| 264 | + // mock 登录用户(跨租户访问) | ||
| 265 | + LoginUser loginUser = new LoginUser().setId(userId).setUserType(UserTypeEnum.ADMIN.getValue()) | ||
| 266 | + .setTenantId(1L).setVisitTenantId(2L); | ||
| 267 | + SecurityFrameworkUtils.setLoginUser(loginUser, new MockHttpServletRequest()); | ||
| 268 | + | ||
| 269 | + try { | ||
| 270 | + // 调用 | ||
| 271 | + deptService.deleteDeptList(ids, userId); | ||
| 272 | + } finally { | ||
| 273 | + SecurityContextHolder.clearContext(); | ||
| 274 | + } | ||
| 275 | + // 校验数据不存在了,且未获取目标租户的数据权限 | ||
| 276 | + assertNull(deptMapper.selectById(deptDO.getId())); | ||
| 277 | + verify(permissionService, never()).getDeptDataPermission(anyLong()); | ||
| 278 | + } | ||
| 279 | + | ||
| 280 | + @Test | ||
| 281 | + public void testDeleteDeptList_ignoreDataPermission() { | ||
| 282 | + // mock 数据 | ||
| 283 | + DeptDO deptDO = randomPojo(DeptDO.class); | ||
| 284 | + deptMapper.insert(deptDO); | ||
| 285 | + // 准备参数 | ||
| 286 | + List<Long> ids = singletonList(deptDO.getId()); | ||
| 287 | + Long userId = randomLongId(); | ||
| 288 | + | ||
| 289 | + // 调用(忽略数据权限) | ||
| 290 | + DataPermissionUtils.executeIgnore(() -> deptService.deleteDeptList(ids, userId)); | ||
| 291 | + // 校验数据不存在了 | ||
| 292 | + assertNull(deptMapper.selectById(deptDO.getId())); | ||
| 293 | + verify(permissionService, never()).getDeptDataPermission(anyLong()); | ||
| 142 | } | 294 | } |
| 143 | 295 | ||
| 144 | @Test | 296 | @Test |