Commit d50844246cd44a77380d59c88c46126875c368e9
1 parent
6269f050
【缺陷修复】CRM:上级无法读取下级的数据问题
① CrmPermissionUtils 解决分页过滤 ② CrmPermissionAspect 解决查询详情过滤
Showing
2 changed files
with
62 additions
and
52 deletions
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 | 9 | import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; |
| 10 | 10 | import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; |
| 11 | 11 | import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; |
| 12 | -import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; | |
| 13 | 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 | 16 | import jakarta.annotation.Resource; |
| 15 | 17 | import lombok.extern.slf4j.Slf4j; |
| 16 | 18 | import org.aspectj.lang.JoinPoint; |
| ... | ... | @@ -38,6 +40,9 @@ public class CrmPermissionAspect { |
| 38 | 40 | @Resource |
| 39 | 41 | private CrmPermissionService crmPermissionService; |
| 40 | 42 | |
| 43 | + @Resource | |
| 44 | + private AdminUserApi adminUserApi; | |
| 45 | + | |
| 41 | 46 | @Before("@annotation(crmPermission)") |
| 42 | 47 | public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) { |
| 43 | 48 | // 1.1 获取相关属性值 |
| ... | ... | @@ -65,49 +70,76 @@ public class CrmPermissionAspect { |
| 65 | 70 | if (CrmPermissionUtils.isCrmAdmin()) { |
| 66 | 71 | return; |
| 67 | 72 | } |
| 68 | - // 1.1 没有数据权限的情况 | |
| 73 | + // 特殊:没有数据权限的情况,针对 READ 的特殊处理 | |
| 69 | 74 | if (CollUtil.isEmpty(bizPermissions)) { |
| 70 | - // 公海数据如果没有团队成员大家也因该有读权限才对 | |
| 75 | + // 1.1 公海数据,如果没有团队成员,大家也应该有 READ 权限才对 | |
| 71 | 76 | if (CrmPermissionLevelEnum.isRead(permissionLevel)) { |
| 72 | 77 | return; |
| 73 | 78 | } |
| 74 | 79 | // 没有数据权限的情况下超出了读权限直接报错,避免后面校验空指针 |
| 75 | 80 | throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType)); |
| 76 | 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 | 91 | if (userPermission != null) { |
| 87 | - if (CrmPermissionLevelEnum.isOwner(userPermission.getLevel())) { | |
| 92 | + if (isUserPermissionValid(userPermission, permissionLevel)) { | |
| 88 | 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 | 108 | log.info("[doBefore][userId({}) 要求权限({}) 实际权限({}) 数据校验错误]", // 打个 info 日志,方便后续排查问题、审计 |
| 106 | - getUserId(), permissionLevel, toJsonString(userPermission)); | |
| 109 | + userId, permissionLevel, toJsonString(userPermission)); | |
| 107 | 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 | 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 | 14 | import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties; |
| 15 | 15 | import com.github.yulichang.wrapper.MPJLambdaWrapper; |
| 16 | 16 | |
| 17 | -import java.util.Collection; | |
| 18 | 17 | import java.util.List; |
| 19 | 18 | |
| 20 | 19 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| ... | ... | @@ -50,24 +49,21 @@ public class CrmPermissionUtils { |
| 50 | 49 | Long userId, Integer sceneType) { |
| 51 | 50 | MybatisPlusJoinProperties mybatisPlusJoinProperties = SpringUtil.getBean(MybatisPlusJoinProperties.class); |
| 52 | 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 | 53 | if (CrmSceneTypeEnum.isOwner(sceneType)) { |
| 61 | 54 | query.eq(ownerUserIdField, userId); |
| 62 | 55 | } |
| 63 | - // 2.2 场景二:我参与的数据(我有读或写权限,并且不是负责人) | |
| 56 | + // 场景二:我参与的数据(我有读或写权限,并且不是负责人) | |
| 64 | 57 | if (CrmSceneTypeEnum.isInvolved(sceneType)) { |
| 58 | + if (CrmPermissionUtils.isCrmAdmin()) { // 特殊逻辑:如果是超管,直接查询所有,不过滤数据权限 | |
| 59 | + return; | |
| 60 | + } | |
| 65 | 61 | query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType) |
| 66 | 62 | .eq(CrmPermissionDO::getBizId, bizId) |
| 67 | 63 | .in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel())); |
| 68 | 64 | query.ne(ownerUserIdField, userId); |
| 69 | 65 | } |
| 70 | - // 2.3 场景三:下属负责的数据(下属是负责人) | |
| 66 | + // 场景三:下属负责的数据(下属是负责人) | |
| 71 | 67 | if (CrmSceneTypeEnum.isSubordinate(sceneType)) { |
| 72 | 68 | AdminUserApi adminUserApi = SpringUtil.getBean(AdminUserApi.class); |
| 73 | 69 | List<AdminUserRespDTO> subordinateUsers = adminUserApi.getUserListBySubordinate(userId); |
| ... | ... | @@ -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 | } | ... | ... |