Commit d50844246cd44a77380d59c88c46126875c368e9

Authored by YunaiV
1 parent 6269f050

【缺陷修复】CRM:上级无法读取下级的数据问题

① CrmPermissionUtils 解决分页过滤
② CrmPermissionAspect 解决查询详情过滤
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java
@@ -9,8 +9,10 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; @@ -9,8 +9,10 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
9 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; 9 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
10 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; 10 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
11 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; 11 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
12 -import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;  
13 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; 12 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
  13 +import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
  14 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  15 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
14 import jakarta.annotation.Resource; 16 import jakarta.annotation.Resource;
15 import lombok.extern.slf4j.Slf4j; 17 import lombok.extern.slf4j.Slf4j;
16 import org.aspectj.lang.JoinPoint; 18 import org.aspectj.lang.JoinPoint;
@@ -38,6 +40,9 @@ public class CrmPermissionAspect { @@ -38,6 +40,9 @@ public class CrmPermissionAspect {
38 @Resource 40 @Resource
39 private CrmPermissionService crmPermissionService; 41 private CrmPermissionService crmPermissionService;
40 42
  43 + @Resource
  44 + private AdminUserApi adminUserApi;
  45 +
41 @Before("@annotation(crmPermission)") 46 @Before("@annotation(crmPermission)")
42 public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) { 47 public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) {
43 // 1.1 获取相关属性值 48 // 1.1 获取相关属性值
@@ -65,49 +70,76 @@ public class CrmPermissionAspect { @@ -65,49 +70,76 @@ public class CrmPermissionAspect {
65 if (CrmPermissionUtils.isCrmAdmin()) { 70 if (CrmPermissionUtils.isCrmAdmin()) {
66 return; 71 return;
67 } 72 }
68 - // 1.1 没有数据权限的情况 73 + // 特殊:没有数据权限的情况,针对 READ 的特殊处理
69 if (CollUtil.isEmpty(bizPermissions)) { 74 if (CollUtil.isEmpty(bizPermissions)) {
70 - // 公海数据如果没有团队成员大家也因该有读权限才对 75 + // 1.1 公海数据,如果没有团队成员,大家也应该有 READ 权限才对
71 if (CrmPermissionLevelEnum.isRead(permissionLevel)) { 76 if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
72 return; 77 return;
73 } 78 }
74 // 没有数据权限的情况下超出了读权限直接报错,避免后面校验空指针 79 // 没有数据权限的情况下超出了读权限直接报错,避免后面校验空指针
75 throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType)); 80 throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType));
76 } else { // 1.2 有数据权限但是没有负责人的情况 81 } else { // 1.2 有数据权限但是没有负责人的情况
77 - if (!anyMatch(bizPermissions, item -> CrmPermissionLevelEnum.isOwner(item.getLevel()))) {  
78 - if (CrmPermissionLevelEnum.isRead(permissionLevel)) {  
79 - return;  
80 - } 82 + if (!anyMatch(bizPermissions, item -> CrmPermissionLevelEnum.isOwner(item.getLevel()))
  83 + && CrmPermissionLevelEnum.isRead(permissionLevel)) {
  84 + return;
81 } 85 }
82 } 86 }
83 87
84 - // 2.1 情况一:如果自己是负责人,则默认有所有权限  
85 - CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, permission -> ObjUtil.equal(permission.getUserId(), getUserId())); 88 + // 2. 只考虑自的身权限
  89 + Long userId = getUserId();
  90 + CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, permission -> ObjUtil.equal(permission.getUserId(), userId));
86 if (userPermission != null) { 91 if (userPermission != null) {
87 - if (CrmPermissionLevelEnum.isOwner(userPermission.getLevel())) { 92 + if (isUserPermissionValid(userPermission, permissionLevel)) {
88 return; 93 return;
89 } 94 }
90 - // 2.2 情况二:校验自己是否有读权限  
91 - if (CrmPermissionLevelEnum.isRead(permissionLevel)) {  
92 - if (CrmPermissionLevelEnum.isRead(userPermission.getLevel()) // 校验当前用户是否有读权限  
93 - || CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限  
94 - return;  
95 - }  
96 - }  
97 - // 2.3 情况三:校验自己是否有写权限  
98 - if (CrmPermissionLevelEnum.isWrite(permissionLevel)) {  
99 - if (CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限  
100 - return;  
101 - } 95 + }
  96 +
  97 + // 3. 考虑下级的权限
  98 + List<AdminUserRespDTO> subordinateUserIds = adminUserApi.getUserListBySubordinate(userId);
  99 + for (Long subordinateUserId : convertSet(subordinateUserIds, AdminUserRespDTO::getId)) {
  100 + CrmPermissionDO subordinatePermission = CollUtil.findOne(bizPermissions,
  101 + permission -> ObjUtil.equal(permission.getUserId(), subordinateUserId));
  102 + if (subordinatePermission != null && isUserPermissionValid(subordinatePermission, permissionLevel)) {
  103 + return;
102 } 104 }
103 } 105 }
104 - // 2.4 没有权限,抛出异常 106 +
  107 + // 4. 没有权限,抛出异常
105 log.info("[doBefore][userId({}) 要求权限({}) 实际权限({}) 数据校验错误]", // 打个 info 日志,方便后续排查问题、审计 108 log.info("[doBefore][userId({}) 要求权限({}) 实际权限({}) 数据校验错误]", // 打个 info 日志,方便后续排查问题、审计
106 - getUserId(), permissionLevel, toJsonString(userPermission)); 109 + userId, permissionLevel, toJsonString(userPermission));
107 throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType)); 110 throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType));
108 } 111 }
109 112
110 /** 113 /**
  114 + * 校验用户权限是否有效
  115 + *
  116 + * @param userPermission 用户拥有的权限
  117 + * @param permissionLevel 需要的权限级别
  118 + * @return 是否有效
  119 + */
  120 + @SuppressWarnings("RedundantIfStatement")
  121 + private boolean isUserPermissionValid(CrmPermissionDO userPermission, Integer permissionLevel) {
  122 + // 2.1 情况一:如果自己是负责人,则默认有所有权限
  123 + if (CrmPermissionLevelEnum.isOwner(userPermission.getLevel())) {
  124 + return true;
  125 + }
  126 + // 2.2 情况二:校验自己是否有读权限
  127 + if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
  128 + if (CrmPermissionLevelEnum.isRead(userPermission.getLevel()) // 校验当前用户是否有读权限
  129 + || CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限
  130 + return true;
  131 + }
  132 + }
  133 + // 2.3 情况三:校验自己是否有写权限
  134 + if (CrmPermissionLevelEnum.isWrite(permissionLevel)) {
  135 + if (CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限
  136 + return true;
  137 + }
  138 + }
  139 + return false;
  140 + }
  141 +
  142 + /**
111 * 获得用户编号 143 * 获得用户编号
112 * 144 *
113 * @return 用户编号 145 * @return 用户编号
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java
@@ -14,7 +14,6 @@ import com.baomidou.mybatisplus.core.toolkit.support.SFunction; @@ -14,7 +14,6 @@ import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
14 import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties; 14 import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties;
15 import com.github.yulichang.wrapper.MPJLambdaWrapper; 15 import com.github.yulichang.wrapper.MPJLambdaWrapper;
16 16
17 -import java.util.Collection;  
18 import java.util.List; 17 import java.util.List;
19 18
20 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 19 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@@ -50,24 +49,21 @@ public class CrmPermissionUtils { @@ -50,24 +49,21 @@ public class CrmPermissionUtils {
50 Long userId, Integer sceneType) { 49 Long userId, Integer sceneType) {
51 MybatisPlusJoinProperties mybatisPlusJoinProperties = SpringUtil.getBean(MybatisPlusJoinProperties.class); 50 MybatisPlusJoinProperties mybatisPlusJoinProperties = SpringUtil.getBean(MybatisPlusJoinProperties.class);
52 final String ownerUserIdField = mybatisPlusJoinProperties.getTableAlias() + ".owner_user_id"; 51 final String ownerUserIdField = mybatisPlusJoinProperties.getTableAlias() + ".owner_user_id";
53 - // 1. 构建数据权限连表条件  
54 - if (!CrmPermissionUtils.isCrmAdmin()) { // 管理员,公海不需要数据权限  
55 - query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)  
56 - .eq(CrmPermissionDO::getBizId, bizId) // 只能使用 SFunction 如果传 id 解析出来的 sql 不对  
57 - .eq(CrmPermissionDO::getUserId, userId));  
58 - }  
59 - // 2.1 场景一:我负责的数据 52 + // 场景一:我负责的数据
60 if (CrmSceneTypeEnum.isOwner(sceneType)) { 53 if (CrmSceneTypeEnum.isOwner(sceneType)) {
61 query.eq(ownerUserIdField, userId); 54 query.eq(ownerUserIdField, userId);
62 } 55 }
63 - // 2.2 场景二:我参与的数据(我有读或写权限,并且不是负责人) 56 + // 场景二:我参与的数据(我有读或写权限,并且不是负责人)
64 if (CrmSceneTypeEnum.isInvolved(sceneType)) { 57 if (CrmSceneTypeEnum.isInvolved(sceneType)) {
  58 + if (CrmPermissionUtils.isCrmAdmin()) { // 特殊逻辑:如果是超管,直接查询所有,不过滤数据权限
  59 + return;
  60 + }
65 query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType) 61 query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)
66 .eq(CrmPermissionDO::getBizId, bizId) 62 .eq(CrmPermissionDO::getBizId, bizId)
67 .in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel())); 63 .in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel()));
68 query.ne(ownerUserIdField, userId); 64 query.ne(ownerUserIdField, userId);
69 } 65 }
70 - // 2.3 场景三:下属负责的数据(下属是负责人) 66 + // 场景三:下属负责的数据(下属是负责人)
71 if (CrmSceneTypeEnum.isSubordinate(sceneType)) { 67 if (CrmSceneTypeEnum.isSubordinate(sceneType)) {
72 AdminUserApi adminUserApi = SpringUtil.getBean(AdminUserApi.class); 68 AdminUserApi adminUserApi = SpringUtil.getBean(AdminUserApi.class);
73 List<AdminUserRespDTO> subordinateUsers = adminUserApi.getUserListBySubordinate(userId); 69 List<AdminUserRespDTO> subordinateUsers = adminUserApi.getUserListBySubordinate(userId);
@@ -79,22 +75,4 @@ public class CrmPermissionUtils { @@ -79,22 +75,4 @@ public class CrmPermissionUtils {
79 } 75 }
80 } 76 }
81 77
82 - /**  
83 - * 构造 CRM 数据类型【批量】数据查询条件  
84 - *  
85 - * @param query 连表查询对象  
86 - * @param bizType 数据类型 {@link CrmBizTypeEnum}  
87 - * @param bizIds 数据编号  
88 - * @param userId 用户编号  
89 - */  
90 - public static <T extends MPJLambdaWrapper<?>> void appendPermissionCondition(T query,  
91 - Integer bizType, Collection<Long> bizIds, Long userId) {  
92 - if (isCrmAdmin()) {// 管理员不需要数据权限  
93 - return;  
94 - }  
95 - query.innerJoin(CrmPermissionDO.class, on ->  
96 - on.eq(CrmPermissionDO::getBizType, bizType).in(CrmPermissionDO::getBizId, bizIds)  
97 - .eq(CollUtil.isNotEmpty(bizIds), CrmPermissionDO::getUserId, userId));  
98 - }  
99 -  
100 } 78 }