fix: adjust admin invite country guard

This commit is contained in:
zhx 2026-06-27 17:42:06 +08:00
parent f16cf81a77
commit 2ef0ac87d5
5 changed files with 184 additions and 13 deletions

View File

@ -55,12 +55,17 @@ public class AgentInviteHostExe {
private final RegisterDeviceGateway registerDeviceGateway;
private final UserProfileAppConvertor userProfileAppConvertor;
private final BdInviteAgentMessageService bdInviteAgentMessageService;
private final TeamInviteGuard teamInviteGuard;
public void execute(AgentInviteHostCmd cmd) {
// 检查权限
boolean teamOwn = teamProfileService.existsOwnUserId(cmd.getReqUserId());
ResponseAssert.isTrue(TeamErrorCode.USER_AGENT_ROLE_INVALID_ERROR, teamOwn);
// 只有APP管理员发起邀请时才强制同国家非管理员保留原邀请规则.
teamInviteGuard.assertSameCountryWhenAdminInviter(cmd.requiredReqUserId(),
cmd.getInviteUserId());
UserProfileDTO userProfile = userProfileAppConvertor.toUserProfileDTO(
userProfileGateway.getByUserId(cmd.getInviteUserId()));
ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_RECORD_INFO, userProfile);

View File

@ -19,7 +19,6 @@ import com.red.circle.other.infra.database.mongo.service.team.bd.BdInviteAgentMe
import com.red.circle.other.infra.database.mongo.service.team.team.TeamApplicationProcessService;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamMemberService;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamProfileService;
import com.red.circle.other.infra.database.rds.service.sys.AdministratorService;
import com.red.circle.other.infra.database.rds.service.team.BusinessDevelopmentBaseInfoService;
import com.red.circle.other.infra.database.rds.service.user.device.LatestMobileDeviceService;
import com.red.circle.other.inner.asserts.user.UserErrorCode;
@ -62,17 +61,21 @@ public class BdAgentInviteExe {
private final BdInviteAgentMessageService bdInviteAgentMessageService;
private final TeamApplicationProcessService teamApplicationProcessService;
private final BusinessDevelopmentBaseInfoService businessDevelopmentBaseInfoService;
private final AdministratorService administratorService;
private final TeamInviteGuard teamInviteGuard;
public void execute(DbInviteAgentCmd cmd) {
ResponseAssert.isFalse(TeamErrorCode.USER_IS_A_ADMIN,
administratorService.existsAdmin(cmd.getInviteUserId()));
teamInviteGuard.assertInviteeIsNotAdminUnlessSelf(cmd.requiredReqUserId(),
cmd.getInviteUserId());
// 权限不足
ResponseAssert.isTrue(CommonErrorCode.INSUFFICIENT_PERMISSION,
businessDevelopmentBaseInfoService.checkBD(cmd.requiredReqUserId()));
// APP管理员发起邀请时只能邀请同国家用户普通BD继续沿用原有区域和余额规则.
boolean adminCountryChecked = teamInviteGuard.assertSameCountryWhenAdminInviter(
cmd.requiredReqUserId(), cmd.getInviteUserId());
// 用户已经是主播
ResponseAssert.isFalse(TeamErrorCode.USER_IS_ALREADY_A_ANCHOR,
teamMemberService.existsMember(cmd.getInviteUserId()));
@ -94,8 +97,9 @@ public class BdAgentInviteExe {
bdInviteAgentMessageService.existsWaitMessage(cmd.requiredReqUserId(),
cmd.getInviteUserId(), MessageInviteGroup.BD_INVITE_TEAM.name()));
// 校验邀请人与被邀请人的区域
checkInvite(cmd);
if (!adminCountryChecked) {
checkInvite(cmd);
}
String messageId = IdWorkerUtils.getIdStr();
bdInviteAgentMessageService.add(new BdInviteAgentMessage()
@ -139,7 +143,7 @@ public class BdAgentInviteExe {
private void checkInvite(DbInviteAgentCmd cmd) {
// 验证BD区域(如BD是代理则按照代理的区域来否则用BD本身指定的区域)与被邀请人区域是不是一致,
// 普通BD保持旧规则同区域直接放行不同区域但被邀请人余额低于100也放行.
Boolean eqRegion = userRegionGateway.checkEqRegion(cmd.requiredReqUserId(),
cmd.getInviteUserId());

View File

@ -15,7 +15,6 @@ import com.red.circle.other.infra.database.mongo.entity.team.bd.BdInviteAgentMes
import com.red.circle.other.infra.database.mongo.entity.team.bd.MessageInviteGroup;
import com.red.circle.other.infra.database.mongo.service.team.bd.BdInviteAgentMessageService;
import com.red.circle.other.infra.database.rds.entity.team.BusinessDevelopmentBaseInfo;
import com.red.circle.other.infra.database.rds.service.sys.AdministratorService;
import com.red.circle.other.infra.database.rds.service.team.BusinessDevelopmentBaseInfoService;
import com.red.circle.other.infra.database.rds.service.team.RoomBdLeadService;
import com.red.circle.other.inner.asserts.user.UserErrorCode;
@ -50,7 +49,7 @@ public class BdLeaderInviteBdExe {
private final UserProfileAppConvertor userProfileAppConvertor;
private final BdInviteAgentMessageService bdInviteAgentMessageService;
private final BusinessDevelopmentBaseInfoService businessDevelopmentBaseInfoService;
private final AdministratorService administratorService;
private final TeamInviteGuard teamInviteGuard;
public void execute(DbLeaderInviteBdCmd cmd) {
@ -65,9 +64,12 @@ public class BdLeaderInviteBdExe {
ResponseAssert.isFalse(TeamErrorCode.USER_IS_ALREADY_A_BD,
businessDevelopmentBaseInfoService.checkBD(cmd.getInviteUserId()));
ResponseAssert.isFalse(TeamErrorCode.USER_IS_A_ADMIN,
administratorService.existsAdmin(cmd.getInviteUserId()));
teamInviteGuard.assertInviteeIsNotAdminUnlessSelf(cmd.requiredReqUserId(),
cmd.getInviteUserId());
// 只有APP管理员发起邀请时才强制同国家非管理员保留原邀请规则.
teamInviteGuard.assertSameCountryWhenAdminInviter(cmd.requiredReqUserId(),
cmd.getInviteUserId());
// 权限不足
BusinessDevelopmentBaseInfo bd = getDevelopmentBaseInfoServiceByUserId(cmd);
@ -117,9 +119,12 @@ public class BdLeaderInviteBdExe {
ResponseAssert.isFalse(TeamErrorCode.USER_IS_ALREADY_A_BDLEADER,
roomBdLeadService.checkBdLeader(cmd.getInviteUserId()));
ResponseAssert.isFalse(TeamErrorCode.USER_IS_A_ADMIN,
administratorService.existsAdmin(cmd.getInviteUserId()));
teamInviteGuard.assertInviteeIsNotAdminUnlessSelf(cmd.requiredReqUserId(),
cmd.getInviteUserId());
// 只有APP管理员发起邀请时才强制同国家非管理员保留原邀请规则.
teamInviteGuard.assertSameCountryWhenAdminInviter(cmd.requiredReqUserId(),
cmd.getInviteUserId());
List<Long> bdUserIds = businessDevelopmentBaseInfoService.listBdUserIdsByLeaderId(cmd.requiredReqUserId());
ResponseAssert.isFalse(CommonErrorCode.INSUFFICIENT_PERMISSION, bdUserIds.contains(cmd.getInviteUserId()));

View File

@ -0,0 +1,75 @@
package com.red.circle.other.app.command.team;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.model.user.UserProfile;
import com.red.circle.other.infra.database.rds.service.sys.AdministratorService;
import com.red.circle.other.inner.asserts.team.TeamErrorCode;
import com.red.circle.other.inner.asserts.user.UserErrorCode;
import com.red.circle.tool.core.text.StringUtils;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* 团队邀请的公共身份和国家校验.
*/
@Component
@RequiredArgsConstructor
public class TeamInviteGuard {
private static final Map<String, String> COUNTRY_CODE_ALIASES = Map.of(
"IA", "ID",
"EY", "EG",
"AB", "AE",
"MB", "MA",
"SP", "SA"
);
private final AdministratorService administratorService;
private final UserProfileGateway userProfileGateway;
public void assertInviteeIsNotAdminUnlessSelf(Long reqUserId, Long inviteUserId) {
if (Objects.equals(reqUserId, inviteUserId)) {
return;
}
ResponseAssert.isFalse(TeamErrorCode.USER_IS_A_ADMIN,
administratorService.existsAdmin(inviteUserId));
}
public boolean assertSameCountryWhenAdminInviter(Long reqUserId, Long inviteUserId) {
if (!isAdminInviteScope(reqUserId, inviteUserId)) {
return false;
}
UserProfile reqUser = userProfileGateway.getByUserId(reqUserId);
UserProfile inviteUser = userProfileGateway.getByUserId(inviteUserId);
ResponseAssert.notNull(UserErrorCode.USER_INFO_NOT_FOUND, reqUser);
ResponseAssert.notNull(UserErrorCode.USER_INFO_NOT_FOUND, inviteUser);
ResponseAssert.isTrue(TeamErrorCode.NOT_INVITE_USER_ERROR, isSameCountry(reqUser, inviteUser));
return true;
}
public boolean isAdminInviteScope(Long reqUserId, Long inviteUserId) {
return !Objects.equals(reqUserId, inviteUserId) && administratorService.existsAdmin(reqUserId);
}
private boolean isSameCountry(UserProfile reqUser, UserProfile inviteUser) {
String reqCountryCode = normalizeCountryCode(reqUser.getCountryCode());
String inviteCountryCode = normalizeCountryCode(inviteUser.getCountryCode());
if (StringUtils.isNotBlank(reqCountryCode) && StringUtils.isNotBlank(inviteCountryCode)) {
return Objects.equals(reqCountryCode, inviteCountryCode);
}
return Objects.nonNull(reqUser.getCountryId())
&& Objects.equals(reqUser.getCountryId(), inviteUser.getCountryId());
}
private String normalizeCountryCode(String countryCode) {
if (StringUtils.isBlank(countryCode)) {
return null;
}
String normalized = countryCode.trim().toUpperCase(Locale.ROOT);
return COUNTRY_CODE_ALIASES.getOrDefault(normalized, normalized);
}
}

View File

@ -0,0 +1,82 @@
package com.red.circle.other.app.command.team;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.model.user.UserProfile;
import com.red.circle.other.infra.database.rds.service.sys.AdministratorService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TeamInviteGuardTest {
@Mock
private AdministratorService administratorService;
@Mock
private UserProfileGateway userProfileGateway;
private TeamInviteGuard teamInviteGuard;
@BeforeEach
void setUp() {
teamInviteGuard = new TeamInviteGuard(administratorService, userProfileGateway);
}
@Test
void shouldNotBlockAdminWhenInviteSelf() {
assertDoesNotThrow(() -> teamInviteGuard.assertInviteeIsNotAdminUnlessSelf(1L, 1L));
verify(administratorService, never()).existsAdmin(1L);
}
@Test
void shouldBlockAdminWhenInviteOtherUser() {
when(administratorService.existsAdmin(2L)).thenReturn(true);
assertThrows(RuntimeException.class,
() -> teamInviteGuard.assertInviteeIsNotAdminUnlessSelf(1L, 2L));
}
@Test
void shouldAllowLegacyAliasCountryCode() {
when(administratorService.existsAdmin(1L)).thenReturn(true);
when(userProfileGateway.getByUserId(1L)).thenReturn(profile(1L, "SP"));
when(userProfileGateway.getByUserId(2L)).thenReturn(profile(2L, "SA"));
assertDoesNotThrow(() -> teamInviteGuard.assertSameCountryWhenAdminInviter(1L, 2L));
}
@Test
void shouldBlockDifferentCountryForAdminInviter() {
when(administratorService.existsAdmin(1L)).thenReturn(true);
when(userProfileGateway.getByUserId(1L)).thenReturn(profile(1L, "BD"));
when(userProfileGateway.getByUserId(2L)).thenReturn(profile(2L, "PK"));
assertThrows(RuntimeException.class,
() -> teamInviteGuard.assertSameCountryWhenAdminInviter(1L, 2L));
}
@Test
void shouldSkipCountryCheckForNonAdminInviter() {
when(administratorService.existsAdmin(1L)).thenReturn(false);
assertDoesNotThrow(() -> teamInviteGuard.assertSameCountryWhenAdminInviter(1L, 2L));
verify(userProfileGateway, never()).getByUserId(1L);
verify(userProfileGateway, never()).getByUserId(2L);
}
private UserProfile profile(Long countryId, String countryCode) {
UserProfile profile = new UserProfile();
profile.setCountryId(countryId);
profile.setCountryCode(countryCode);
return profile;
}
}