fix(security): enforce gateway user and command tenant scope

This commit is contained in:
Claude
2026-05-09 18:49:51 +08:00
parent d1a016a8ce
commit 37f86b18f2
4 changed files with 76 additions and 13 deletions
@@ -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);
}
}
@@ -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);
}
}
}
@@ -57,10 +57,10 @@ public class PointValueCommandController implements BaseController {
*/
@PostMapping("/read")
public Mono<R<Boolean>> 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<R<Boolean>> write(@Validated @RequestBody PointValueWriteVO entityVO) {
return async(() -> {
pointValueCommandService.write(entityVO);
return getTenantId().flatMap(tenantId -> async(() -> {
pointValueCommandService.write(tenantId, entityVO);
return R.ok();
});
}));
}
}
@@ -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);