fix: 收口管理端页面权限与工作流运行授权
- 页面选项接口改用所属页面权限并返回最小数据视图 - 统一校验工作流引用、租户、状态与定时任务执行主体 - 补充聊天记录权限迁移和权限隔离回归测试
This commit is contained in:
@@ -3,36 +3,31 @@ package tech.easyflow.ai.config;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import tech.easyflow.ai.mapper.*;
|
||||
import tech.easyflow.ai.mapper.BotCategoryMapper;
|
||||
import tech.easyflow.common.util.SpringContextUtil;
|
||||
import tech.easyflow.common.dict.DictManager;
|
||||
import tech.easyflow.common.dict.loader.DbDataLoader;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 注册仍由 Bot 兼容页面使用的数据库字典。
|
||||
*/
|
||||
@Configuration
|
||||
public class AiDictAutoConfig {
|
||||
|
||||
@Resource
|
||||
private WorkflowMapper workflowMapper;
|
||||
@Resource
|
||||
private WorkflowCategoryMapper workflowCategoryMapper;
|
||||
/** Bot 分类字典数据访问器。 */
|
||||
@Resource
|
||||
private BotCategoryMapper botCategoryMapper;
|
||||
@Resource
|
||||
private ResourceCategoryMapper resourceCategoryMapper;
|
||||
@Resource
|
||||
private DocumentCollectionCategoryMapper documentCollectionCategoryMapper;
|
||||
|
||||
/**
|
||||
* 应用启动完成后注册 Bot 兼容字典。
|
||||
*/
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void onApplicationStartup() {
|
||||
|
||||
DictManager dictManager = SpringContextUtil.getBean(DictManager.class);
|
||||
dictManager.putLoader(new DbDataLoader<>("aiWorkFlow", workflowMapper, "id", "title", null, null, false));
|
||||
dictManager.putLoader(new DbDataLoader<>("aiWorkFlowCategory", workflowCategoryMapper, "id", "category_name", null, null, false));
|
||||
dictManager.putLoader(new DbDataLoader<>("aiBotCategory", botCategoryMapper, "id", "category_name", null, null, false));
|
||||
dictManager.putLoader(new DbDataLoader<>("aiResourceCategory", resourceCategoryMapper, "id", "category_name", null, null, false));
|
||||
dictManager.putLoader(new DbDataLoader<>("aiDocumentCollectionCategory", documentCollectionCategoryMapper, "id", "category_name", null, null, false));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package tech.easyflow.ai.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.easyflow.ai.entity.Workflow;
|
||||
import tech.easyflow.common.constant.enums.EnumDataStatus;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.enums.CategoryResourceType;
|
||||
import tech.easyflow.system.enums.ResourceAction;
|
||||
import tech.easyflow.system.service.ResourceAccessService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 工作流使用权限校验服务。
|
||||
*
|
||||
* <p>统一封装工作流存在性、租户、启用状态和资源使用权限校验,供页面能力和后台任务复用。</p>
|
||||
*/
|
||||
@Service
|
||||
public class WorkflowUsageAuthorizationService {
|
||||
|
||||
/** 工作流服务。 */
|
||||
private final WorkflowService workflowService;
|
||||
|
||||
/** 资源访问控制服务。 */
|
||||
private final ResourceAccessService resourceAccessService;
|
||||
|
||||
/**
|
||||
* 创建工作流使用权限校验服务。
|
||||
*
|
||||
* @param workflowService 工作流服务
|
||||
* @param resourceAccessService 资源访问控制服务
|
||||
*/
|
||||
public WorkflowUsageAuthorizationService(
|
||||
WorkflowService workflowService,
|
||||
ResourceAccessService resourceAccessService) {
|
||||
this.workflowService = workflowService;
|
||||
this.resourceAccessService = resourceAccessService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前账号可使用的启用工作流。
|
||||
*
|
||||
* @param workflowId 工作流 ID
|
||||
* @param account 使用工作流的账号
|
||||
* @param denyMessage 校验失败提示
|
||||
* @return 可使用的工作流
|
||||
* @throws BusinessException 工作流不存在、未启用、跨租户或无使用权限时抛出
|
||||
*/
|
||||
public Workflow requireUsableWorkflow(
|
||||
BigInteger workflowId,
|
||||
LoginAccount account,
|
||||
String denyMessage) {
|
||||
String message = denyMessage == null || denyMessage.isBlank()
|
||||
? "工作流不存在、已禁用或无权使用"
|
||||
: denyMessage;
|
||||
if (workflowId == null || account == null || account.getId() == null
|
||||
|| account.getTenantId() == null) {
|
||||
throw new BusinessException(403, 403, message);
|
||||
}
|
||||
Workflow workflow = workflowService.getById(workflowId);
|
||||
boolean usable = workflow != null
|
||||
&& Objects.equals(workflow.getTenantId(), account.getTenantId())
|
||||
&& EnumDataStatus.AVAILABLE.getCode().equals(workflow.getStatus())
|
||||
&& resourceAccessService.canAccess(
|
||||
account,
|
||||
CategoryResourceType.WORKFLOW,
|
||||
workflow,
|
||||
ResourceAction.USE);
|
||||
if (!usable) {
|
||||
throw new BusinessException(403, 403, message);
|
||||
}
|
||||
return workflow;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,14 @@ public final class WorkflowSharePolicy {
|
||||
private static final Set<String> ALLOWED_REQUESTS = Set.of(
|
||||
permissionKey("GET", "/api/v1/workflow/detail", ResourceAction.READ),
|
||||
permissionKey("GET", "/api/v1/workflow/getRunningParameters", ResourceAction.READ),
|
||||
permissionKey("GET", "/api/v1/workflow/designer/options", ResourceAction.READ),
|
||||
permissionKey("GET", "/api/v1/workflow/designer/plugins", ResourceAction.READ),
|
||||
permissionKey("GET", "/api/v1/workflow/designer/pluginTinyFlow", ResourceAction.READ),
|
||||
permissionKey("GET", "/api/v1/workflow/designer/childWorkflow", ResourceAction.READ),
|
||||
permissionKey("GET", "/api/v1/workflow/designer/dataSources", ResourceAction.READ),
|
||||
permissionKey("GET", "/api/v1/workflow/designer/catalogs", ResourceAction.READ),
|
||||
permissionKey("GET", "/api/v1/workflow/designer/managedTables", ResourceAction.READ),
|
||||
permissionKey("GET", "/api/v1/workflow/designer/schema", ResourceAction.READ),
|
||||
permissionKey("POST", "/api/v1/workflow/update", ResourceAction.MANAGE),
|
||||
permissionKey("POST", "/api/v1/workflow/check", ResourceAction.MANAGE),
|
||||
permissionKey("POST", "/api/v1/workflow/singleRun", ResourceAction.USE),
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package tech.easyflow.ai.service;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import tech.easyflow.ai.entity.Workflow;
|
||||
import tech.easyflow.common.constant.enums.EnumDataStatus;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.enums.CategoryResourceType;
|
||||
import tech.easyflow.system.enums.ResourceAction;
|
||||
import tech.easyflow.system.service.ResourceAccessService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link WorkflowUsageAuthorizationService} 工作流使用权限校验测试。
|
||||
*/
|
||||
public class WorkflowUsageAuthorizationServiceTest {
|
||||
|
||||
/**
|
||||
* 验证禁用工作流即使资源权限允许也不能被使用。
|
||||
*/
|
||||
@Test
|
||||
public void shouldRejectDisabledWorkflow() {
|
||||
BigInteger workflowId = BigInteger.valueOf(101);
|
||||
WorkflowService workflowService = mock(WorkflowService.class);
|
||||
ResourceAccessService resourceAccessService = mock(ResourceAccessService.class);
|
||||
Workflow workflow = workflow(workflowId, BigInteger.TEN, EnumDataStatus.UNAVAILABLE.getCode());
|
||||
LoginAccount account = account(BigInteger.ONE, BigInteger.TEN);
|
||||
when(workflowService.getById(workflowId)).thenReturn(workflow);
|
||||
when(resourceAccessService.canAccess(
|
||||
account,
|
||||
CategoryResourceType.WORKFLOW,
|
||||
workflow,
|
||||
ResourceAction.USE)).thenReturn(true);
|
||||
WorkflowUsageAuthorizationService service =
|
||||
new WorkflowUsageAuthorizationService(workflowService, resourceAccessService);
|
||||
|
||||
BusinessException exception = Assert.assertThrows(
|
||||
BusinessException.class,
|
||||
() -> service.requireUsableWorkflow(workflowId, account, "工作流不可用")
|
||||
);
|
||||
|
||||
Assert.assertEquals(exception.getMessage(), "工作流不可用");
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证工作流与账号租户不一致时拒绝使用。
|
||||
*/
|
||||
@Test
|
||||
public void shouldRejectCrossTenantWorkflow() {
|
||||
BigInteger workflowId = BigInteger.valueOf(102);
|
||||
WorkflowService workflowService = mock(WorkflowService.class);
|
||||
ResourceAccessService resourceAccessService = mock(ResourceAccessService.class);
|
||||
Workflow workflow = workflow(
|
||||
workflowId,
|
||||
BigInteger.valueOf(20),
|
||||
EnumDataStatus.AVAILABLE.getCode());
|
||||
LoginAccount account = account(BigInteger.ONE, BigInteger.TEN);
|
||||
when(workflowService.getById(workflowId)).thenReturn(workflow);
|
||||
WorkflowUsageAuthorizationService service =
|
||||
new WorkflowUsageAuthorizationService(workflowService, resourceAccessService);
|
||||
|
||||
Assert.assertThrows(
|
||||
BusinessException.class,
|
||||
() -> service.requireUsableWorkflow(workflowId, account, "工作流不可用")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证启用、同租户且具有使用权限的工作流可以返回。
|
||||
*/
|
||||
@Test
|
||||
public void shouldReturnUsableWorkflow() {
|
||||
BigInteger workflowId = BigInteger.valueOf(103);
|
||||
WorkflowService workflowService = mock(WorkflowService.class);
|
||||
ResourceAccessService resourceAccessService = mock(ResourceAccessService.class);
|
||||
Workflow workflow = workflow(
|
||||
workflowId,
|
||||
BigInteger.TEN,
|
||||
EnumDataStatus.AVAILABLE.getCode());
|
||||
LoginAccount account = account(BigInteger.ONE, BigInteger.TEN);
|
||||
when(workflowService.getById(workflowId)).thenReturn(workflow);
|
||||
when(resourceAccessService.canAccess(
|
||||
account,
|
||||
CategoryResourceType.WORKFLOW,
|
||||
workflow,
|
||||
ResourceAction.USE)).thenReturn(true);
|
||||
WorkflowUsageAuthorizationService service =
|
||||
new WorkflowUsageAuthorizationService(workflowService, resourceAccessService);
|
||||
|
||||
Workflow result = service.requireUsableWorkflow(workflowId, account, "工作流不可用");
|
||||
|
||||
Assert.assertSame(result, workflow);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建工作流测试数据。
|
||||
*
|
||||
* @param id 工作流 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @param status 工作流状态
|
||||
* @return 工作流
|
||||
*/
|
||||
private Workflow workflow(BigInteger id, BigInteger tenantId, Integer status) {
|
||||
Workflow workflow = new Workflow();
|
||||
workflow.setId(id);
|
||||
workflow.setTenantId(tenantId);
|
||||
workflow.setStatus(status);
|
||||
return workflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建登录账号测试数据。
|
||||
*
|
||||
* @param id 账号 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @return 登录账号
|
||||
*/
|
||||
private LoginAccount account(BigInteger id, BigInteger tenantId) {
|
||||
LoginAccount account = new LoginAccount();
|
||||
account.setId(id);
|
||||
account.setTenantId(tenantId);
|
||||
return account;
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,11 @@ public class WorkflowSharePolicyTest {
|
||||
"/api/v1/workflow/detail",
|
||||
ResourceAction.READ
|
||||
));
|
||||
Assert.assertTrue(WorkflowSharePolicy.isAllowedRequest(
|
||||
"GET",
|
||||
"/api/v1/workflow/designer/childWorkflow",
|
||||
ResourceAction.READ
|
||||
));
|
||||
Assert.assertTrue(WorkflowSharePolicy.isAllowedRequest(
|
||||
"POST",
|
||||
"/api/v1/workflow/update",
|
||||
|
||||
Reference in New Issue
Block a user