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

① CrmPermissionUtils 解决分页过滤
② CrmPermissionAspect 解决查询详情过滤
This commit is contained in:
YunaiV 2024-10-02 11:13:21 +08:00
parent 6269f050eb
commit d50844246c
2 changed files with 62 additions and 52 deletions

View File

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

View File

@ -14,7 +14,6 @@ import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties; import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties;
import com.github.yulichang.wrapper.MPJLambdaWrapper; import com.github.yulichang.wrapper.MPJLambdaWrapper;
import java.util.Collection;
import java.util.List; import java.util.List;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@ -50,24 +49,21 @@ public class CrmPermissionUtils {
Long userId, Integer sceneType) { Long userId, Integer sceneType) {
MybatisPlusJoinProperties mybatisPlusJoinProperties = SpringUtil.getBean(MybatisPlusJoinProperties.class); MybatisPlusJoinProperties mybatisPlusJoinProperties = SpringUtil.getBean(MybatisPlusJoinProperties.class);
final String ownerUserIdField = mybatisPlusJoinProperties.getTableAlias() + ".owner_user_id"; 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)) { if (CrmSceneTypeEnum.isOwner(sceneType)) {
query.eq(ownerUserIdField, userId); query.eq(ownerUserIdField, userId);
} }
// 2.2 场景二我参与的数据我有读或写权限并且不是负责人 // 场景二我参与的数据我有读或写权限并且不是负责人
if (CrmSceneTypeEnum.isInvolved(sceneType)) { if (CrmSceneTypeEnum.isInvolved(sceneType)) {
if (CrmPermissionUtils.isCrmAdmin()) { // 特殊逻辑如果是超管直接查询所有不过滤数据权限
return;
}
query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType) query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)
.eq(CrmPermissionDO::getBizId, bizId) .eq(CrmPermissionDO::getBizId, bizId)
.in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel())); .in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel()));
query.ne(ownerUserIdField, userId); query.ne(ownerUserIdField, userId);
} }
// 2.3 场景三下属负责的数据下属是负责人 // 场景三下属负责的数据下属是负责人
if (CrmSceneTypeEnum.isSubordinate(sceneType)) { if (CrmSceneTypeEnum.isSubordinate(sceneType)) {
AdminUserApi adminUserApi = SpringUtil.getBean(AdminUserApi.class); AdminUserApi adminUserApi = SpringUtil.getBean(AdminUserApi.class);
List<AdminUserRespDTO> subordinateUsers = adminUserApi.getUserListBySubordinate(userId); 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));
}
} }