fix: policy always checked regardless of caller-provided floors

Redesign addVisitor four-phase flow:
- Phase1: ALWAYS query person detail (orgIds for policy lookup)
- Phase2: candidate = caller floors or org floorList
- Phase3: ALWAYS check policy; intersect candidate with allow
- Phase4: empty set validation
Fixes UC-02 bypass: policy was entirely skipped when caller
provided floorIds. Now policy always constrains.
Bump v2.0.19
This commit is contained in:
反编译工作区
2026-05-05 19:47:01 +08:00
parent c5febc9905
commit f7da04caea
42 changed files with 2584 additions and 43 deletions
@@ -173,51 +173,64 @@ public class PersonRuleServiceImpl extends AbstractAcsPassService implements Per
@CloudwalkParamsValidate
public CloudwalkResult<Boolean> addVisitor(AcsPersonAddVisitorParam param, CloudwalkCallContext context)
throws ServiceException {
this.logger.info("根据被访人添加访客派梯权限开始,AcsPersonAddVisitorParam=[{}], CloudwalkCallContext=[{}]",
JSONObject.toJSONString(param), JSONObject.toJSONString(context));
this.logger.info("根据被访人添加访客派梯权限开始,businessId={} personId={} visitorId={} requestFloorSize={}",
context.getCompany().getCompanyId(), param.getPersonId(), param.getVisitorId(),
Integer.valueOf(param.getFloorIds() == null ? 0 : param.getFloorIds().size()));
try {
boolean callerProvidedFloors = !CollectionUtils.isEmpty(param.getFloorIds());
if (!callerProvidedFloors) {
PersonDetailParam detailParam = new PersonDetailParam();
detailParam.setId(param.getPersonId());
detailParam.setBusinessId(context.getCompany().getCompanyId());
CloudwalkResult<PersonResult> detail = this.personService.detail(detailParam, context);
if (detail == null || !detail.isSuccess()) {
String code = detail != null ? detail.getCode() : "76260531";
String msg = detail != null ? detail.getMessage() : getMessage("76260531");
return CloudwalkResult.fail(code, msg);
}
PersonResult personResult = (PersonResult)detail.getData();
if (personResult == null) {
return CloudwalkResult.fail("76260531", getMessage("76260531"));
}
List<String> hostFloors = personResult.getFloorList();
if (CollectionUtils.isEmpty(hostFloors)) {
return CloudwalkResult.fail("76260531", getMessage("76260531"));
}
List<String> effectiveFloors;
TenantVisitorFloorPolicyDto policy = findPolicyByOrgIds(personResult.getOrganizationIds());
if (policy != null) {
effectiveFloors = resolveEffectiveFloors(callerProvidedFloors ? param.getFloorIds() : hostFloors,
hostFloors, policy, param.getPersonId());
} else {
effectiveFloors = hostFloors;
Set<String> hostSet = new HashSet<>(hostFloors);
List<String> outliers =
param.getFloorIds().stream().filter(f -> !hostSet.contains(f)).collect(Collectors.toList());
if (!outliers.isEmpty()) {
this.logger.warn("UC-02 传入非被访人授权楼层 businessId={} personId={} outliers={}",
context.getCompany().getCompanyId(), param.getPersonId(), outliers);
}
}
if (CollectionUtils.isEmpty(effectiveFloors)) {
return CloudwalkResult.fail("76260531", getMessage("76260531"));
}
param.setFloorIds(effectiveFloors);
// === 阶段1:查询被访人组织信息(ALWAYS,用于策略获取)===
PersonDetailParam detailParam = new PersonDetailParam();
detailParam.setId(param.getPersonId());
detailParam.setBusinessId(context.getCompany().getCompanyId());
CloudwalkResult<PersonResult> detailResult = this.personService.detail(detailParam, context);
if (detailResult == null || !detailResult.isSuccess()) {
String code = detailResult != null ? detailResult.getCode() : "76260531";
String msg = detailResult != null ? detailResult.getMessage() : getMessage("76260531");
return CloudwalkResult.fail(code, msg);
}
if (CollectionUtils.isEmpty(param.getFloorIds())) {
PersonResult personResult = (PersonResult)detailResult.getData();
if (personResult == null) {
this.logger.warn("被访人信息为空 personId={}", param.getPersonId());
return CloudwalkResult.fail("76260531", getMessage("76260531"));
}
// === 阶段2:确定候选楼层 candidate ===
List<String> candidate;
boolean callerProvidedFloors = !CollectionUtils.isEmpty(param.getFloorIds());
if (callerProvidedFloors) {
candidate = param.getFloorIds();
this.logger.info("UC-02:调用方已指定楼层,候选楼层为 {}", candidate);
} else {
candidate = personResult.getFloorList();
if (CollectionUtils.isEmpty(candidate)) {
this.logger.warn("UC-01:被访人无授权楼层 personId={}", param.getPersonId());
return CloudwalkResult.fail("76260531", getMessage("76260531"));
}
this.logger.info("UC-01:调用方未指定楼层,取被访人默认楼层为 {}", candidate);
}
// === 阶段3:ALWAYS 查询策略,有策略且生效则求交 ===
List<String> orgIds = personResult.getOrganizationIds();
this.logger.info("被访人所属组织 orgIds={}", orgIds);
TenantVisitorFloorPolicyDto policy = findPolicyByOrgIds(orgIds);
List<String> effective;
if (policy != null) {
this.logger.info("找到启用策略 policyId={} orgId={} allow={}", policy.getId(), policy.getOrgId(),
policy.getAllowZoneIds());
effective = resolveEffectiveFloors(candidate, candidate, policy, param.getPersonId());
this.logger.info("策略求交后最终楼层 effective={}", effective);
} else {
this.logger.info("未找到启用策略,使用候选楼层原值 {}", candidate);
effective = candidate;
}
// === 阶段4:空集校验 ===
if (CollectionUtils.isEmpty(effective)) {
this.logger.warn("无可用楼层 businessId={} personId={} visitorId={} callerProvided={} candidate={}",
context.getCompany().getCompanyId(), param.getPersonId(), param.getVisitorId(),
Boolean.valueOf(callerProvidedFloors), candidate);
return CloudwalkResult.fail("76260531", getMessage("76260531"));
}
param.setFloorIds(effective);
ZoneQueryParam zoneQueryParam = new ZoneQueryParam();
zoneQueryParam.setId(param.getFloorIds().get(0));
zoneQueryParam.setRowsOfPage(10);
@@ -310,8 +323,9 @@ public class PersonRuleServiceImpl extends AbstractAcsPassService implements Per
policy.getOrgId(), policy.getId(), personId, unknownAllow, hostFloors);
throw new ServiceException("76260533", "策略配置了被访人无权访问的楼层,请联系管理员");
}
this.logger.info("策略生效 orgId={} policyId={} v={} allowSize={} hostSize={}", policy.getOrgId(), policy.getId(),
policy.getPolicyVersion(), Integer.valueOf(allow.size()), Integer.valueOf(hostFloors.size()));
this.logger.info("策略生效 orgId={} policyId={} v={} allowSize={} hostSize={} intersected={}", policy.getOrgId(),
policy.getId(), policy.getPolicyVersion(), Integer.valueOf(allow.size()), Integer.valueOf(hostFloors.size()),
allow.stream().filter(hostSet::contains).collect(Collectors.toList()));
return allow;
}