fix: 收口管理端页面权限与工作流运行授权

- 页面选项接口改用所属页面权限并返回最小数据视图

- 统一校验工作流引用、租户、状态与定时任务执行主体

- 补充聊天记录权限迁移和权限隔离回归测试
This commit is contained in:
2026-08-07 12:51:21 +08:00
parent 6ad004da9b
commit d244a0404d
55 changed files with 3350 additions and 387 deletions

View File

@@ -0,0 +1,169 @@
package tech.easyflow.admin.service.ai;
import com.mybatisflex.core.query.QueryWrapper;
import com.easyagents.flow.core.parser.ChainParser;
import org.mockito.MockedStatic;
import org.testng.Assert;
import org.testng.annotations.Test;
import tech.easyflow.ai.easyagentsflow.service.WorkflowDatacenterContentService;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.plugin.workflow.snapshot.WorkflowPluginSnapshotResolver;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.ai.service.PluginItemService;
import tech.easyflow.ai.service.PluginService;
import tech.easyflow.ai.service.PluginVisibilityService;
import tech.easyflow.ai.service.WorkflowService;
import tech.easyflow.ai.service.WorkflowUsageAuthorizationService;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.datacenter.execution.service.DatacenterDatasetQueryService;
import tech.easyflow.datacenter.meta.entity.DatacenterSource;
import tech.easyflow.datacenter.meta.service.DatacenterDatasetRegistryService;
import tech.easyflow.datacenter.meta.service.DatacenterSourceService;
import tech.easyflow.system.service.ResourceAccessService;
import java.math.BigInteger;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
/**
* {@link WorkflowDesignerOptionService} 工作流引用权限测试。
*/
public class WorkflowDesignerOptionServiceTest {
/**
* 验证客户端提交候选列表之外的模型 ID 时服务端拒绝保存。
*/
@Test
public void shouldRejectModelOutsideSelectableOptions() {
ModelService modelService = mock(ModelService.class);
DocumentCollectionService knowledgeService = mock(DocumentCollectionService.class);
when(modelService.listByIds(any())).thenReturn(List.of());
when(knowledgeService.list(any(QueryWrapper.class))).thenReturn(List.of());
WorkflowDesignerOptionService service = createService(
modelService, knowledgeService, mock(DatacenterSourceService.class));
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(loginAccount());
BusinessException exception = Assert.expectThrows(
BusinessException.class,
() -> service.assertContentReferences(
"{\"nodes\":[{\"type\":\"llmNode\",\"data\":{\"llmId\":\"99\"}}]}")
);
Assert.assertTrue(exception.getMessage().contains("模型"));
}
}
/**
* 验证工作流数据节点不能引用其他租户的数据源。
*/
@Test
public void shouldRejectCrossTenantDataSource() {
ModelService modelService = mock(ModelService.class);
DocumentCollectionService knowledgeService = mock(DocumentCollectionService.class);
DatacenterSourceService sourceService = mock(DatacenterSourceService.class);
when(modelService.listSelectableModels(any(Model.class), eq(false), eq("id"), eq("desc")))
.thenReturn(List.of());
when(knowledgeService.list(any(QueryWrapper.class))).thenReturn(List.of());
DatacenterSource source = new DatacenterSource();
source.setId(BigInteger.valueOf(9));
source.setTenantId(BigInteger.valueOf(200));
when(sourceService.getById(BigInteger.valueOf(9))).thenReturn(source);
WorkflowDesignerOptionService service = createService(
modelService, knowledgeService, sourceService);
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(loginAccount());
BusinessException exception = Assert.expectThrows(
BusinessException.class,
() -> service.assertContentReferences("""
{"nodes":[{"type":"search-dataset-node","data":{
"datasetRef":{"sourceId":"9"}
}}]}
""")
);
Assert.assertTrue(exception.getMessage().contains("数据源"));
}
}
/**
* 验证子流程节点配置拒绝读取其他租户的工作流。
*/
@Test
public void shouldRejectCrossTenantChildWorkflow() {
WorkflowService workflowService = mock(WorkflowService.class);
Workflow workflow = new Workflow();
workflow.setId(BigInteger.valueOf(19));
workflow.setTenantId(BigInteger.valueOf(200));
when(workflowService.getById(BigInteger.valueOf(19))).thenReturn(workflow);
WorkflowDesignerOptionService service = createService(
mock(ModelService.class),
mock(DocumentCollectionService.class),
mock(DatacenterSourceService.class),
workflowService
);
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(loginAccount());
BusinessException exception = Assert.expectThrows(
BusinessException.class,
() -> service.getChildWorkflowNodeData(
BigInteger.valueOf(10),
BigInteger.valueOf(19))
);
Assert.assertTrue(exception.getMessage().contains("子流程"));
}
}
private WorkflowDesignerOptionService createService(
ModelService modelService,
DocumentCollectionService knowledgeService,
DatacenterSourceService sourceService) {
return createService(modelService, knowledgeService, sourceService, mock(WorkflowService.class));
}
private WorkflowDesignerOptionService createService(
ModelService modelService,
DocumentCollectionService knowledgeService,
DatacenterSourceService sourceService,
WorkflowService workflowService) {
ResourceAccessService resourceAccessService = mock(ResourceAccessService.class);
return new WorkflowDesignerOptionService(
modelService,
knowledgeService,
mock(PluginService.class),
mock(PluginItemService.class),
mock(PluginVisibilityService.class),
workflowService,
new WorkflowUsageAuthorizationService(workflowService, resourceAccessService),
mock(WorkflowPluginSnapshotResolver.class),
mock(ChainParser.class),
mock(WorkflowDatacenterContentService.class),
resourceAccessService,
sourceService,
mock(DatacenterDatasetRegistryService.class),
mock(DatacenterDatasetQueryService.class)
);
}
private LoginAccount loginAccount() {
LoginAccount account = new LoginAccount();
account.setId(BigInteger.ONE);
account.setTenantId(BigInteger.valueOf(100));
return account;
}
}