Commit 8d94c7c1054e972bbfc9f3d12f871dbadbcc62df

Authored by YunaiV
1 parent 85f2928a

fix(crm): 校验转移后旧负责人保留权限级别

yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -76,6 +76,7 @@ public interface ErrorCodeConstants { @@ -76,6 +76,7 @@ public interface ErrorCodeConstants {
76 ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人"); 76 ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人");
77 ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限"); 77 ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限");
78 ErrorCode CRM_PERMISSION_CREATE_FAIL_EXISTS = new ErrorCode(1_020_007_009, "同时添加数据权限失败,原因:用户【{}】已有模块【{}】数据【{}】的【{}】权限"); 78 ErrorCode CRM_PERMISSION_CREATE_FAIL_EXISTS = new ErrorCode(1_020_007_009, "同时添加数据权限失败,原因:用户【{}】已有模块【{}】数据【{}】的【{}】权限");
  79 + ErrorCode CRM_PERMISSION_MODEL_TRANSFER_FAIL_OLD_OWNER_PERMISSION_LEVEL_ERROR = new ErrorCode(1_020_007_010, "{}操作失败,原因:老负责人的权限级别只能是只读或读写");
79 80
80 // ========== 产品 1_020_008_000 ========== 81 // ========== 产品 1_020_008_000 ==========
81 ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在"); 82 ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在");
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 @@ -34,6 +34,8 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
34 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 34 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
35 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; 35 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
36 import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isOwner; 36 import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isOwner;
  37 +import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isRead;
  38 +import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isWrite;
37 39
38 /** 40 /**
39 * CRM 数据权限 Service 接口实现类 41 * CRM 数据权限 Service 接口实现类
@@ -216,17 +218,22 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { @@ -216,17 +218,22 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
216 if (!owner && !CrmPermissionUtils.isCrmAdmin()) { 218 if (!owner && !CrmPermissionUtils.isCrmAdmin()) {
217 throw exception(CRM_PERMISSION_DENIED, bizTypeName); 219 throw exception(CRM_PERMISSION_DENIED, bizTypeName);
218 } 220 }
219 - // 1.2 获得老负责人的权限:操作人是负责人时,就是操作人的权限;否则(CRM 管理员),是数据实际的负责人,而不是管理员自己的团队权限 221 + // 1.2 校验老负责人的权限级别:为空时表示移除,否则只能是只读或读写,避免出现多个负责人
  222 + Integer oldOwnerPermissionLevel = transferReqBO.getOldOwnerPermissionLevel();
  223 + if (oldOwnerPermissionLevel != null && !isRead(oldOwnerPermissionLevel) && !isWrite(oldOwnerPermissionLevel)) {
  224 + throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OLD_OWNER_PERMISSION_LEVEL_ERROR, bizTypeName);
  225 + }
  226 + // 1.3 获得老负责人的权限:操作人是负责人时,就是操作人的权限;否则(CRM 管理员),是数据实际的负责人,而不是管理员自己的团队权限
220 CrmPermissionDO oldOwnerPermission = owner ? userPermission 227 CrmPermissionDO oldOwnerPermission = owner ? userPermission
221 : CollUtil.findOne(permissions, item -> isOwner(item.getLevel())); 228 : CollUtil.findOne(permissions, item -> isOwner(item.getLevel()));
222 if (oldOwnerPermission == null) { // 数据缺少负责人时,在修改任何权限前失败,避免部分写入 229 if (oldOwnerPermission == null) { // 数据缺少负责人时,在修改任何权限前失败,避免部分写入
223 throw exception(CRM_PERMISSION_NOT_EXISTS); 230 throw exception(CRM_PERMISSION_NOT_EXISTS);
224 } 231 }
225 - // 1.3 校验转移对象是否已经是该负责人 232 + // 1.4 校验转移对象是否已经是该负责人
226 if (ObjUtil.equal(transferReqBO.getNewOwnerUserId(), oldOwnerPermission.getUserId())) { 233 if (ObjUtil.equal(transferReqBO.getNewOwnerUserId(), oldOwnerPermission.getUserId())) {
227 throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS, bizTypeName); 234 throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS, bizTypeName);
228 } 235 }
229 - // 1.4 校验新负责人是否存在 236 + // 1.5 校验新负责人是否存在
230 adminUserApi.validateUserList(Collections.singletonList(transferReqBO.getNewOwnerUserId())); 237 adminUserApi.validateUserList(Collections.singletonList(transferReqBO.getNewOwnerUserId()));
231 238
232 // 2. 修改新负责人的权限:已在团队中时,提升为负责人,不重复新增 239 // 2. 修改新负责人的权限:已在团队中时,提升为负责人,不重复新增
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; @@ -15,6 +15,8 @@ import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
15 import org.junit.jupiter.api.AfterEach; 15 import org.junit.jupiter.api.AfterEach;
16 import org.junit.jupiter.api.BeforeEach; 16 import org.junit.jupiter.api.BeforeEach;
17 import org.junit.jupiter.api.Test; 17 import org.junit.jupiter.api.Test;
  18 +import org.junit.jupiter.params.ParameterizedTest;
  19 +import org.junit.jupiter.params.provider.ValueSource;
18 import org.mockito.InjectMocks; 20 import org.mockito.InjectMocks;
19 import org.mockito.Mock; 21 import org.mockito.Mock;
20 import org.mockito.MockedStatic; 22 import org.mockito.MockedStatic;
@@ -246,6 +248,78 @@ public class CrmPermissionServiceImplTest extends BaseMockitoUnitTest { @@ -246,6 +248,78 @@ public class CrmPermissionServiceImplTest extends BaseMockitoUnitTest {
246 assertNoPermissionWrite(); 248 assertNoPermissionWrite();
247 } 249 }
248 250
  251 + // ========== 老负责人的权限级别 ==========
  252 +
  253 + @Test
  254 + public void testTransferPermission_ownerKeepOldOwnerRead() {
  255 + // mock 数据:当前用户是负责人,新负责人不在团队中
  256 + mockPermissions(buildPermission(LOGIN_USER_PERMISSION_ID, LOGIN_USER_ID, CrmPermissionLevelEnum.OWNER));
  257 +
  258 + // 调用:老负责人保留为 READ
  259 + permissionService.transferPermission(buildTransferReqBO(NEW_OWNER_USER_ID, CrmPermissionLevelEnum.READ.getLevel()));
  260 + // 断言:新增新负责人;老负责人(当前用户)改为 READ
  261 + verify(permissionMapper).selectByBizTypeAndBizId(BIZ_TYPE, BIZ_ID);
  262 + verifyNewOwnerInserted();
  263 + verifyLevelUpdated(LOGIN_USER_PERMISSION_ID, CrmPermissionLevelEnum.READ);
  264 + verifyNoMoreInteractions(permissionMapper);
  265 + }
  266 +
  267 + @ParameterizedTest
  268 + @ValueSource(ints = {1, 0, -1, 4, Integer.MAX_VALUE, Integer.MIN_VALUE}) // 1 是 OWNER,其它是不存在的级别
  269 + public void testTransferPermission_ownerInvalidOldOwnerPermissionLevel(int oldOwnerPermissionLevel) {
  270 + // mock 数据:当前用户是负责人
  271 + mockPermissions(buildPermission(LOGIN_USER_PERMISSION_ID, LOGIN_USER_ID, CrmPermissionLevelEnum.OWNER));
  272 +
  273 + // 调用,并断言异常:在修改任何权限前失败
  274 + assertServiceException(() -> permissionService.transferPermission(buildTransferReqBO(NEW_OWNER_USER_ID, oldOwnerPermissionLevel)),
  275 + CRM_PERMISSION_MODEL_TRANSFER_FAIL_OLD_OWNER_PERMISSION_LEVEL_ERROR, BIZ_TYPE_NAME);
  276 + assertNoPermissionWrite();
  277 + verifyNoInteractions(adminUserApi);
  278 + }
  279 +
  280 + @ParameterizedTest
  281 + @ValueSource(ints = {1, 0, -1, 4, Integer.MAX_VALUE, Integer.MIN_VALUE}) // 1 是 OWNER,其它是不存在的级别
  282 + public void testTransferPermission_crmAdminInvalidOldOwnerPermissionLevel(int oldOwnerPermissionLevel) {
  283 + // mock 数据:CRM 管理员没有权限,数据的负责人是其他人(修复前:老负责人被更新回 OWNER,留下两个负责人)
  284 + mockPermissions(buildPermission(OWNER_PERMISSION_ID, OWNER_USER_ID, CrmPermissionLevelEnum.OWNER));
  285 + mockCrmAdmin(true);
  286 +
  287 + // 调用,并断言异常:在修改任何权限前失败
  288 + assertServiceException(() -> permissionService.transferPermission(buildTransferReqBO(NEW_OWNER_USER_ID, oldOwnerPermissionLevel)),
  289 + CRM_PERMISSION_MODEL_TRANSFER_FAIL_OLD_OWNER_PERMISSION_LEVEL_ERROR, BIZ_TYPE_NAME);
  290 + assertNoPermissionWrite();
  291 + verifyNoInteractions(adminUserApi);
  292 + }
  293 +
  294 + @Test
  295 + public void testTransferPermission_teamMemberDeniedBeforeOldOwnerPermissionLevel() {
  296 + // mock 数据:当前用户只有 WRITE 团队权限,不是 CRM 管理员
  297 + mockPermissions(buildPermission(OWNER_PERMISSION_ID, OWNER_USER_ID, CrmPermissionLevelEnum.OWNER),
  298 + buildPermission(LOGIN_USER_PERMISSION_ID, LOGIN_USER_ID, CrmPermissionLevelEnum.WRITE));
  299 + mockCrmAdmin(false);
  300 +
  301 + // 调用,并断言异常:先校验数据权限,再校验老负责人的权限级别
  302 + assertServiceException(() -> permissionService.transferPermission(buildTransferReqBO(NEW_OWNER_USER_ID,
  303 + CrmPermissionLevelEnum.OWNER.getLevel())),
  304 + CRM_PERMISSION_DENIED, BIZ_TYPE_NAME);
  305 + assertNoPermissionWrite();
  306 + verifyNoInteractions(adminUserApi);
  307 + }
  308 +
  309 + @Test
  310 + public void testTransferPermission_crmAdminOldOwnerPermissionLevelBeforeOwnerNotExists() {
  311 + // mock 数据:CRM 管理员有 WRITE 团队权限,但数据缺少负责人
  312 + mockPermissions(buildPermission(LOGIN_USER_PERMISSION_ID, LOGIN_USER_ID, CrmPermissionLevelEnum.WRITE));
  313 + mockCrmAdmin(true);
  314 +
  315 + // 调用,并断言异常:先校验老负责人的权限级别(请求参数),再校验数据的负责人
  316 + assertServiceException(() -> permissionService.transferPermission(buildTransferReqBO(NEW_OWNER_USER_ID,
  317 + CrmPermissionLevelEnum.OWNER.getLevel())),
  318 + CRM_PERMISSION_MODEL_TRANSFER_FAIL_OLD_OWNER_PERMISSION_LEVEL_ERROR, BIZ_TYPE_NAME);
  319 + assertNoPermissionWrite();
  320 + verifyNoInteractions(adminUserApi);
  321 + }
  322 +
249 private void mockCrmAdmin(boolean crmAdmin) { 323 private void mockCrmAdmin(boolean crmAdmin) {
250 springUtilMockedStatic.when(() -> SpringUtil.getBean(PermissionCommonApi.class)).thenReturn(permissionCommonApi); 324 springUtilMockedStatic.when(() -> SpringUtil.getBean(PermissionCommonApi.class)).thenReturn(permissionCommonApi);
251 when(permissionCommonApi.hasAnyRoles(LOGIN_USER_ID, RoleCodeEnum.CRM_ADMIN.getCode())).thenReturn(crmAdmin); 325 when(permissionCommonApi.hasAnyRoles(LOGIN_USER_ID, RoleCodeEnum.CRM_ADMIN.getCode())).thenReturn(crmAdmin);