From 37f86b18f2a715c0b97ff56cb242dc6a08e46bbd Mon Sep 17 00:00:00 2001 From: Claude <209825114+claude[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 15:46:17 +0800 Subject: [PATCH] fix(security): enforce gateway user and command tenant scope --- .../data/biz/PointValueCommandService.java | 26 +++++++++++- .../impl/PointValueCommandServiceImpl.java | 42 ++++++++++++++++++- .../PointValueCommandController.java | 12 +++--- .../service/impl/FilterServiceImpl.java | 9 ++-- 4 files changed, 76 insertions(+), 13 deletions(-) diff --git a/dc3-common/dc3-common-data/src/main/java/io/github/pnoker/common/data/biz/PointValueCommandService.java b/dc3-common/dc3-common-data/src/main/java/io/github/pnoker/common/data/biz/PointValueCommandService.java index 2fd07fc7d..de0710d43 100644 --- a/dc3-common/dc3-common-data/src/main/java/io/github/pnoker/common/data/biz/PointValueCommandService.java +++ b/dc3-common/dc3-common-data/src/main/java/io/github/pnoker/common/data/biz/PointValueCommandService.java @@ -32,15 +32,37 @@ public interface PointValueCommandService { /** * Read command * + * @param tenantId current tenant id * @param entityVO PointValueReadVO */ - void read(PointValueReadVO entityVO); + void read(Long tenantId, PointValueReadVO entityVO); + + /** + * Read command from trusted internal callers that do not carry request tenant + * context. + * + * @param entityVO PointValueReadVO + */ + default void read(PointValueReadVO entityVO) { + read(null, entityVO); + } /** * Write command * + * @param tenantId current tenant id * @param entityVO PointValueWriteVO */ - void write(PointValueWriteVO entityVO); + void write(Long tenantId, PointValueWriteVO entityVO); + + /** + * Write command from trusted internal callers that do not carry request tenant + * context. + * + * @param entityVO PointValueWriteVO + */ + default void write(PointValueWriteVO entityVO) { + write(null, entityVO); + } } diff --git a/dc3-common/dc3-common-data/src/main/java/io/github/pnoker/common/data/biz/impl/PointValueCommandServiceImpl.java b/dc3-common/dc3-common-data/src/main/java/io/github/pnoker/common/data/biz/impl/PointValueCommandServiceImpl.java index da1d0722c..3b93aaf46 100644 --- a/dc3-common/dc3-common-data/src/main/java/io/github/pnoker/common/data/biz/impl/PointValueCommandServiceImpl.java +++ b/dc3-common/dc3-common-data/src/main/java/io/github/pnoker/common/data/biz/impl/PointValueCommandServiceImpl.java @@ -17,14 +17,21 @@ package io.github.pnoker.common.data.biz.impl; +import io.github.pnoker.common.constant.common.ExceptionConstant; import io.github.pnoker.common.constant.driver.RabbitConstant; import io.github.pnoker.common.data.biz.PointValueCommandService; import io.github.pnoker.common.data.entity.vo.PointValueReadVO; import io.github.pnoker.common.data.entity.vo.PointValueWriteVO; import io.github.pnoker.common.entity.dto.DeviceCommandDTO; import io.github.pnoker.common.enums.DeviceCommandTypeEnum; +import io.github.pnoker.common.exception.NotFoundException; +import io.github.pnoker.common.exception.UnAuthorizedException; +import io.github.pnoker.common.facade.api.DeviceFacade; import io.github.pnoker.common.facade.api.DriverFacade; +import io.github.pnoker.common.facade.api.PointFacade; +import io.github.pnoker.common.facade.entity.bo.FacadeDeviceBO; import io.github.pnoker.common.facade.entity.bo.FacadeDriverBO; +import io.github.pnoker.common.facade.entity.bo.FacadePointBO; import io.github.pnoker.common.utils.JsonUtil; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -42,14 +49,22 @@ import java.util.Objects; @Service public class PointValueCommandServiceImpl implements PointValueCommandService { + @Resource + private DeviceFacade deviceFacade; + @Resource private DriverFacade driverFacade; + @Resource + private PointFacade pointFacade; + @Resource private RabbitTemplate rabbitTemplate; @Override - public void read(PointValueReadVO entityVO) { + public void read(Long tenantId, PointValueReadVO entityVO) { + validateCommandScope(tenantId, entityVO.getDeviceId(), entityVO.getPointId()); + FacadeDriverBO driver = driverFacade.selectByDeviceId(entityVO.getDeviceId()); if (Objects.isNull(driver)) { return; @@ -64,7 +79,9 @@ public class PointValueCommandServiceImpl implements PointValueCommandService { } @Override - public void write(PointValueWriteVO entityVO) { + public void write(Long tenantId, PointValueWriteVO entityVO) { + validateCommandScope(tenantId, entityVO.getDeviceId(), entityVO.getPointId()); + FacadeDriverBO driver = driverFacade.selectByDeviceId(entityVO.getDeviceId()); if (Objects.isNull(driver)) { return; @@ -78,4 +95,25 @@ public class PointValueCommandServiceImpl implements PointValueCommandService { RabbitConstant.ROUTING_DEVICE_COMMAND_PREFIX + driver.getServiceName(), deviceCommandDTO); } + private void validateCommandScope(Long tenantId, Long deviceId, Long pointId) { + FacadeDeviceBO device = deviceFacade.selectById(deviceId); + if (Objects.isNull(device)) { + throw new NotFoundException("Device does not exist"); + } + if (Objects.nonNull(tenantId) && !tenantId.equals(device.getTenantId())) { + throw new UnAuthorizedException(ExceptionConstant.NO_AVAILABLE_AUTH); + } + + FacadePointBO point = pointFacade.selectById(pointId); + if (Objects.isNull(point)) { + throw new NotFoundException("Point does not exist"); + } + if (Objects.nonNull(tenantId) && !tenantId.equals(point.getTenantId())) { + throw new UnAuthorizedException(ExceptionConstant.NO_AVAILABLE_AUTH); + } + if (Objects.isNull(device.getProfileIds()) || !device.getProfileIds().contains(point.getProfileId())) { + throw new UnAuthorizedException(ExceptionConstant.NO_AVAILABLE_AUTH); + } + } + } diff --git a/dc3-common/dc3-common-data/src/main/java/io/github/pnoker/common/data/controller/PointValueCommandController.java b/dc3-common/dc3-common-data/src/main/java/io/github/pnoker/common/data/controller/PointValueCommandController.java index fc0bf5552..a91ff06c1 100644 --- a/dc3-common/dc3-common-data/src/main/java/io/github/pnoker/common/data/controller/PointValueCommandController.java +++ b/dc3-common/dc3-common-data/src/main/java/io/github/pnoker/common/data/controller/PointValueCommandController.java @@ -57,10 +57,10 @@ public class PointValueCommandController implements BaseController { */ @PostMapping("/read") public Mono> read(@Validated @RequestBody PointValueReadVO entityVO) { - return async(() -> { - pointValueCommandService.read(entityVO); + return getTenantId().flatMap(tenantId -> async(() -> { + pointValueCommandService.read(tenantId, entityVO); return R.ok(); - }); + })); } /** @@ -71,10 +71,10 @@ public class PointValueCommandController implements BaseController { */ @PostMapping("/write") public Mono> write(@Validated @RequestBody PointValueWriteVO entityVO) { - return async(() -> { - pointValueCommandService.write(entityVO); + return getTenantId().flatMap(tenantId -> async(() -> { + pointValueCommandService.write(tenantId, entityVO); return R.ok(); - }); + })); } } diff --git a/dc3-common/dc3-common-gateway/src/main/java/io/github/pnoker/common/gateway/service/impl/FilterServiceImpl.java b/dc3-common/dc3-common-gateway/src/main/java/io/github/pnoker/common/gateway/service/impl/FilterServiceImpl.java index ea6ca9a01..4bfae1ebf 100644 --- a/dc3-common/dc3-common-gateway/src/main/java/io/github/pnoker/common/gateway/service/impl/FilterServiceImpl.java +++ b/dc3-common/dc3-common-gateway/src/main/java/io/github/pnoker/common/gateway/service/impl/FilterServiceImpl.java @@ -112,9 +112,12 @@ public class FilterServiceImpl implements FilterService { @Override public RequestHeader.UserHeader getUser(FacadeUserLoginBO userLogin, FacadeTenantBO tenant) { - // Preserves the existing (surprising) behavior: lookup UserApi by UserLogin.id, - // not UserLogin.userId. Changing that belongs in a separate bug-fix PR. - FacadeUserBO user = userCache.get(userLogin.getId(), id -> Optional.ofNullable(userFacade.selectById(id))) + Long userId = userLogin.getUserId(); + if (Objects.isNull(userId)) { + throw new UnAuthorizedException(RequestConstant.Message.INVALID_REQUEST); + } + + FacadeUserBO user = userCache.get(userId, id -> Optional.ofNullable(userFacade.selectById(id))) .orElse(null); if (Objects.isNull(user)) { throw new UnAuthorizedException(RequestConstant.Message.INVALID_REQUEST);