Преглед изворни кода

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

① CrmPermissionUtils 解决分页过滤
② CrmPermissionAspect 解决查询详情过滤
YunaiV пре 8 месеци
родитељ
комит
d50844246c

+ 56 - 24
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;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
-import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
+import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.aspectj.lang.JoinPoint;
@@ -38,6 +40,9 @@ public class CrmPermissionAspect {
     @Resource
     private CrmPermissionService crmPermissionService;
 
+    @Resource
+    private AdminUserApi adminUserApi;
+
     @Before("@annotation(crmPermission)")
     public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) {
         // 1.1 获取相关属性值
@@ -65,48 +70,75 @@ public class CrmPermissionAspect {
         if (CrmPermissionUtils.isCrmAdmin()) {
             return;
         }
-        // 1.1 没有数据权限的情况
+        // 特殊:没有数据权限的情况,针对 READ 的特殊处理
         if (CollUtil.isEmpty(bizPermissions)) {
-            // 公海数据如果没有团队成员大家也因该有读权限才对
+            // 1.1 公海数据,如果没有团队成员,大家也应该有 READ 权限才对
             if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
                 return;
             }
             // 没有数据权限的情况下超出了读权限直接报错,避免后面校验空指针
             throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType));
         } else { // 1.2 有数据权限但是没有负责人的情况
-            if (!anyMatch(bizPermissions, item -> CrmPermissionLevelEnum.isOwner(item.getLevel()))) {
-                if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
-                    return;
-                }
+            if (!anyMatch(bizPermissions, item -> CrmPermissionLevelEnum.isOwner(item.getLevel()))
+                && CrmPermissionLevelEnum.isRead(permissionLevel)) {
+                return;
             }
         }
 
-        // 2.1 情况一:如果自己是负责人,则默认有所有权限
-        CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, permission -> ObjUtil.equal(permission.getUserId(), getUserId()));
+        // 2. 只考虑自的身权限
+        Long userId = getUserId();
+        CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, permission -> ObjUtil.equal(permission.getUserId(), userId));
         if (userPermission != null) {
-            if (CrmPermissionLevelEnum.isOwner(userPermission.getLevel())) {
+            if (isUserPermissionValid(userPermission, permissionLevel)) {
                 return;
             }
-            // 2.2 情况二:校验自己是否有读权限
-            if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
-                if (CrmPermissionLevelEnum.isRead(userPermission.getLevel()) // 校验当前用户是否有读权限
-                        || CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限
-                    return;
-                }
-            }
-            // 2.3 情况三:校验自己是否有写权限
-            if (CrmPermissionLevelEnum.isWrite(permissionLevel)) {
-                if (CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限
-                    return;
-                }
+        }
+
+        // 3. 考虑下级的权限
+        List<AdminUserRespDTO> subordinateUserIds = adminUserApi.getUserListBySubordinate(userId);
+        for (Long subordinateUserId : convertSet(subordinateUserIds, AdminUserRespDTO::getId)) {
+            CrmPermissionDO subordinatePermission = CollUtil.findOne(bizPermissions,
+                    permission -> ObjUtil.equal(permission.getUserId(), subordinateUserId));
+            if (subordinatePermission != null && isUserPermissionValid(subordinatePermission, permissionLevel)) {
+                return;
             }
         }
-        // 2.4 没有权限,抛出异常
+
+        // 4. 没有权限,抛出异常
         log.info("[doBefore][userId({}) 要求权限({}) 实际权限({}) 数据校验错误]", // 打个 info 日志,方便后续排查问题、审计
-                getUserId(), permissionLevel, toJsonString(userPermission));
+                userId, permissionLevel, toJsonString(userPermission));
         throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType));
     }
 
+    /**
+     * 校验用户权限是否有效
+     *
+     * @param userPermission   用户拥有的权限
+     * @param permissionLevel  需要的权限级别
+     * @return 是否有效
+     */
+    @SuppressWarnings("RedundantIfStatement")
+    private boolean isUserPermissionValid(CrmPermissionDO userPermission, Integer permissionLevel) {
+        // 2.1 情况一:如果自己是负责人,则默认有所有权限
+        if (CrmPermissionLevelEnum.isOwner(userPermission.getLevel())) {
+            return true;
+        }
+        // 2.2 情况二:校验自己是否有读权限
+        if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
+            if (CrmPermissionLevelEnum.isRead(userPermission.getLevel()) // 校验当前用户是否有读权限
+                    || CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限
+                return true;
+            }
+        }
+        // 2.3 情况三:校验自己是否有写权限
+        if (CrmPermissionLevelEnum.isWrite(permissionLevel)) {
+            if (CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * 获得用户编号
      *

+ 6 - 28
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;
 import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties;
 import com.github.yulichang.wrapper.MPJLambdaWrapper;
 
-import java.util.Collection;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@@ -50,24 +49,21 @@ public class CrmPermissionUtils {
                                                                                     Long userId, Integer sceneType) {
         MybatisPlusJoinProperties mybatisPlusJoinProperties = SpringUtil.getBean(MybatisPlusJoinProperties.class);
         final String ownerUserIdField = mybatisPlusJoinProperties.getTableAlias() + ".owner_user_id";
-        // 1. 构建数据权限连表条件
-        if (!CrmPermissionUtils.isCrmAdmin()) { // 管理员,公海不需要数据权限
-            query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)
-                    .eq(CrmPermissionDO::getBizId, bizId) // 只能使用 SFunction 如果传 id 解析出来的 sql 不对
-                    .eq(CrmPermissionDO::getUserId, userId));
-        }
-        // 2.1 场景一:我负责的数据
+        // 场景一:我负责的数据
         if (CrmSceneTypeEnum.isOwner(sceneType)) {
             query.eq(ownerUserIdField, userId);
         }
-        // 2.2 场景二:我参与的数据(我有读或写权限,并且不是负责人)
+        // 场景二:我参与的数据(我有读或写权限,并且不是负责人)
         if (CrmSceneTypeEnum.isInvolved(sceneType)) {
+            if (CrmPermissionUtils.isCrmAdmin()) { // 特殊逻辑:如果是超管,直接查询所有,不过滤数据权限
+                return;
+            }
             query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)
                     .eq(CrmPermissionDO::getBizId, bizId)
                     .in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel()));
             query.ne(ownerUserIdField, userId);
         }
-        // 2.3 场景三:下属负责的数据(下属是负责人)
+        // 场景三:下属负责的数据(下属是负责人)
         if (CrmSceneTypeEnum.isSubordinate(sceneType)) {
             AdminUserApi adminUserApi = SpringUtil.getBean(AdminUserApi.class);
             List<AdminUserRespDTO> subordinateUsers = adminUserApi.getUserListBySubordinate(userId);
@@ -79,22 +75,4 @@ public class CrmPermissionUtils {
         }
     }
 
-    /**
-     * 构造 CRM 数据类型【批量】数据查询条件
-     *
-     * @param query   连表查询对象
-     * @param bizType 数据类型 {@link CrmBizTypeEnum}
-     * @param bizIds  数据编号
-     * @param userId  用户编号
-     */
-    public static <T extends MPJLambdaWrapper<?>> void appendPermissionCondition(T query,
-                                                                                 Integer bizType, Collection<Long> bizIds, Long userId) {
-        if (isCrmAdmin()) {// 管理员不需要数据权限
-            return;
-        }
-        query.innerJoin(CrmPermissionDO.class, on ->
-                on.eq(CrmPermissionDO::getBizType, bizType).in(CrmPermissionDO::getBizId, bizIds)
-                        .eq(CollUtil.isNotEmpty(bizIds), CrmPermissionDO::getUserId, userId));
-    }
-
 }