From 8d94c7c1054e972bbfc9f3d12f871dbadbcc62df Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 28 Oct 2026 08:51:53 +0800 Subject: [PATCH] fix(crm): 校验转移后旧负责人保留权限级别 --- yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java | 1 + yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java | 13 ++++++++++--- yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImplTest.java | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java index 3b3143a..f566e82 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java @@ -76,6 +76,7 @@ public interface ErrorCodeConstants { ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人"); ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限"); ErrorCode CRM_PERMISSION_CREATE_FAIL_EXISTS = new ErrorCode(1_020_007_009, "同时添加数据权限失败,原因:用户【{}】已有模块【{}】数据【{}】的【{}】权限"); + ErrorCode CRM_PERMISSION_MODEL_TRANSFER_FAIL_OLD_OWNER_PERMISSION_LEVEL_ERROR = new ErrorCode(1_020_007_010, "{}操作失败,原因:老负责人的权限级别只能是只读或读写"); // ========== 产品 1_020_008_000 ========== ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在"); diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java index dd9d65f..9a41b52 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java @@ -34,6 +34,8 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isOwner; +import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isRead; +import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isWrite; /** * CRM 数据权限 Service 接口实现类 @@ -216,17 +218,22 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { if (!owner && !CrmPermissionUtils.isCrmAdmin()) { throw exception(CRM_PERMISSION_DENIED, bizTypeName); } - // 1.2 获得老负责人的权限:操作人是负责人时,就是操作人的权限;否则(CRM 管理员),是数据实际的负责人,而不是管理员自己的团队权限 + // 1.2 校验老负责人的权限级别:为空时表示移除,否则只能是只读或读写,避免出现多个负责人 + Integer oldOwnerPermissionLevel = transferReqBO.getOldOwnerPermissionLevel(); + if (oldOwnerPermissionLevel != null && !isRead(oldOwnerPermissionLevel) && !isWrite(oldOwnerPermissionLevel)) { + throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OLD_OWNER_PERMISSION_LEVEL_ERROR, bizTypeName); + } + // 1.3 获得老负责人的权限:操作人是负责人时,就是操作人的权限;否则(CRM 管理员),是数据实际的负责人,而不是管理员自己的团队权限 CrmPermissionDO oldOwnerPermission = owner ? userPermission : CollUtil.findOne(permissions, item -> isOwner(item.getLevel())); if (oldOwnerPermission == null) { // 数据缺少负责人时,在修改任何权限前失败,避免部分写入 throw exception(CRM_PERMISSION_NOT_EXISTS); } - // 1.3 校验转移对象是否已经是该负责人 + // 1.4 校验转移对象是否已经是该负责人 if (ObjUtil.equal(transferReqBO.getNewOwnerUserId(), oldOwnerPermission.getUserId())) { throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS, bizTypeName); } - // 1.4 校验新负责人是否存在 + // 1.5 校验新负责人是否存在 adminUserApi.validateUserList(Collections.singletonList(transferReqBO.getNewOwnerUserId())); // 2. 修改新负责人的权限:已在团队中时,提升为负责人,不重复新增 diff --git a/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImplTest.java b/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImplTest.java index 60beb6e..d01e836 100644 --- a/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImplTest.java +++ b/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImplTest.java @@ -15,6 +15,8 @@ import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; @@ -246,6 +248,78 @@ public class CrmPermissionServiceImplTest extends BaseMockitoUnitTest { assertNoPermissionWrite(); } + // ========== 老负责人的权限级别 ========== + + @Test + public void testTransferPermission_ownerKeepOldOwnerRead() { + // mock 数据:当前用户是负责人,新负责人不在团队中 + mockPermissions(buildPermission(LOGIN_USER_PERMISSION_ID, LOGIN_USER_ID, CrmPermissionLevelEnum.OWNER)); + + // 调用:老负责人保留为 READ + permissionService.transferPermission(buildTransferReqBO(NEW_OWNER_USER_ID, CrmPermissionLevelEnum.READ.getLevel())); + // 断言:新增新负责人;老负责人(当前用户)改为 READ + verify(permissionMapper).selectByBizTypeAndBizId(BIZ_TYPE, BIZ_ID); + verifyNewOwnerInserted(); + verifyLevelUpdated(LOGIN_USER_PERMISSION_ID, CrmPermissionLevelEnum.READ); + verifyNoMoreInteractions(permissionMapper); + } + + @ParameterizedTest + @ValueSource(ints = {1, 0, -1, 4, Integer.MAX_VALUE, Integer.MIN_VALUE}) // 1 是 OWNER,其它是不存在的级别 + public void testTransferPermission_ownerInvalidOldOwnerPermissionLevel(int oldOwnerPermissionLevel) { + // mock 数据:当前用户是负责人 + mockPermissions(buildPermission(LOGIN_USER_PERMISSION_ID, LOGIN_USER_ID, CrmPermissionLevelEnum.OWNER)); + + // 调用,并断言异常:在修改任何权限前失败 + assertServiceException(() -> permissionService.transferPermission(buildTransferReqBO(NEW_OWNER_USER_ID, oldOwnerPermissionLevel)), + CRM_PERMISSION_MODEL_TRANSFER_FAIL_OLD_OWNER_PERMISSION_LEVEL_ERROR, BIZ_TYPE_NAME); + assertNoPermissionWrite(); + verifyNoInteractions(adminUserApi); + } + + @ParameterizedTest + @ValueSource(ints = {1, 0, -1, 4, Integer.MAX_VALUE, Integer.MIN_VALUE}) // 1 是 OWNER,其它是不存在的级别 + public void testTransferPermission_crmAdminInvalidOldOwnerPermissionLevel(int oldOwnerPermissionLevel) { + // mock 数据:CRM 管理员没有权限,数据的负责人是其他人(修复前:老负责人被更新回 OWNER,留下两个负责人) + mockPermissions(buildPermission(OWNER_PERMISSION_ID, OWNER_USER_ID, CrmPermissionLevelEnum.OWNER)); + mockCrmAdmin(true); + + // 调用,并断言异常:在修改任何权限前失败 + assertServiceException(() -> permissionService.transferPermission(buildTransferReqBO(NEW_OWNER_USER_ID, oldOwnerPermissionLevel)), + CRM_PERMISSION_MODEL_TRANSFER_FAIL_OLD_OWNER_PERMISSION_LEVEL_ERROR, BIZ_TYPE_NAME); + assertNoPermissionWrite(); + verifyNoInteractions(adminUserApi); + } + + @Test + public void testTransferPermission_teamMemberDeniedBeforeOldOwnerPermissionLevel() { + // mock 数据:当前用户只有 WRITE 团队权限,不是 CRM 管理员 + mockPermissions(buildPermission(OWNER_PERMISSION_ID, OWNER_USER_ID, CrmPermissionLevelEnum.OWNER), + buildPermission(LOGIN_USER_PERMISSION_ID, LOGIN_USER_ID, CrmPermissionLevelEnum.WRITE)); + mockCrmAdmin(false); + + // 调用,并断言异常:先校验数据权限,再校验老负责人的权限级别 + assertServiceException(() -> permissionService.transferPermission(buildTransferReqBO(NEW_OWNER_USER_ID, + CrmPermissionLevelEnum.OWNER.getLevel())), + CRM_PERMISSION_DENIED, BIZ_TYPE_NAME); + assertNoPermissionWrite(); + verifyNoInteractions(adminUserApi); + } + + @Test + public void testTransferPermission_crmAdminOldOwnerPermissionLevelBeforeOwnerNotExists() { + // mock 数据:CRM 管理员有 WRITE 团队权限,但数据缺少负责人 + mockPermissions(buildPermission(LOGIN_USER_PERMISSION_ID, LOGIN_USER_ID, CrmPermissionLevelEnum.WRITE)); + mockCrmAdmin(true); + + // 调用,并断言异常:先校验老负责人的权限级别(请求参数),再校验数据的负责人 + assertServiceException(() -> permissionService.transferPermission(buildTransferReqBO(NEW_OWNER_USER_ID, + CrmPermissionLevelEnum.OWNER.getLevel())), + CRM_PERMISSION_MODEL_TRANSFER_FAIL_OLD_OWNER_PERMISSION_LEVEL_ERROR, BIZ_TYPE_NAME); + assertNoPermissionWrite(); + verifyNoInteractions(adminUserApi); + } + private void mockCrmAdmin(boolean crmAdmin) { springUtilMockedStatic.when(() -> SpringUtil.getBean(PermissionCommonApi.class)).thenReturn(permissionCommonApi); when(permissionCommonApi.hasAnyRoles(LOGIN_USER_ID, RoleCodeEnum.CRM_ADMIN.getCode())).thenReturn(crmAdmin); -- libgit2 0.21.4