fix: 收口管理端页面权限与工作流运行授权
- 页面选项接口改用所属页面权限并返回最小数据视图 - 统一校验工作流引用、租户、状态与定时任务执行主体 - 补充聊天记录权限迁移和权限隔离回归测试
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
package tech.easyflow.job.service;
|
||||
|
||||
import com.easyagents.flow.core.chain.runtime.ChainExecutor;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import tech.easyflow.ai.service.WorkflowUsageAuthorizationService;
|
||||
import tech.easyflow.common.constant.Constants;
|
||||
import tech.easyflow.common.constant.enums.EnumDataStatus;
|
||||
import tech.easyflow.common.constant.enums.EnumJobStatus;
|
||||
import tech.easyflow.common.constant.enums.EnumJobType;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.job.entity.SysJob;
|
||||
import tech.easyflow.job.job.JobConstant;
|
||||
import tech.easyflow.system.entity.SysAccount;
|
||||
import tech.easyflow.system.service.SysAccountService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link WorkflowJobExecutionService} 运行时授权测试。
|
||||
*/
|
||||
public class WorkflowJobExecutionServiceTest {
|
||||
|
||||
/**
|
||||
* 验证每次触发都会按数据库当前状态授权,并将任务创建账号注入工作流参数。
|
||||
*/
|
||||
@Test
|
||||
public void shouldReauthorizeAndRestoreServerControlledOwner() {
|
||||
BigInteger jobId = BigInteger.valueOf(101);
|
||||
BigInteger tenantId = BigInteger.valueOf(201);
|
||||
BigInteger accountId = BigInteger.valueOf(301);
|
||||
BigInteger workflowId = BigInteger.valueOf(401);
|
||||
SysJobService jobService = mock(SysJobService.class);
|
||||
SysAccountService accountService = mock(SysAccountService.class);
|
||||
WorkflowUsageAuthorizationService authorizationService =
|
||||
mock(WorkflowUsageAuthorizationService.class);
|
||||
ChainExecutor chainExecutor = mock(ChainExecutor.class);
|
||||
SysJob currentJob = workflowJob(jobId, tenantId, accountId, workflowId);
|
||||
SysAccount account = account(accountId, tenantId, EnumDataStatus.AVAILABLE.getCode());
|
||||
when(jobService.getById(jobId)).thenReturn(currentJob);
|
||||
when(accountService.getById(accountId)).thenReturn(account);
|
||||
Map<String, Object> executionResult = Map.of("status", "done");
|
||||
when(chainExecutor.execute(eq(workflowId.toString()), anyMap()))
|
||||
.thenReturn(executionResult);
|
||||
WorkflowJobExecutionService service = new WorkflowJobExecutionService(
|
||||
jobService,
|
||||
accountService,
|
||||
authorizationService,
|
||||
chainExecutor);
|
||||
|
||||
Object result = service.execute(scheduledJob(jobId, tenantId));
|
||||
|
||||
Assert.assertSame(executionResult, result);
|
||||
ArgumentCaptor<LoginAccount> accountCaptor =
|
||||
ArgumentCaptor.forClass(LoginAccount.class);
|
||||
verify(authorizationService).requireUsableWorkflow(
|
||||
eq(workflowId),
|
||||
accountCaptor.capture(),
|
||||
anyString());
|
||||
Assert.assertEquals(accountCaptor.getValue().getId(), accountId);
|
||||
Assert.assertEquals(accountCaptor.getValue().getTenantId(), tenantId);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<Map<String, Object>> paramsCaptor =
|
||||
ArgumentCaptor.forClass(Map.class);
|
||||
verify(chainExecutor).execute(eq(workflowId.toString()), paramsCaptor.capture());
|
||||
Object loginUser = paramsCaptor.getValue().get(Constants.LOGIN_USER_KEY);
|
||||
Assert.assertTrue(loginUser instanceof LoginAccount);
|
||||
Assert.assertEquals(((LoginAccount) loginUser).getId(), accountId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证权限已撤销时执行器不会启动工作流。
|
||||
*/
|
||||
@Test
|
||||
public void shouldRejectExecutionAfterPermissionRevoked() {
|
||||
BigInteger jobId = BigInteger.valueOf(102);
|
||||
BigInteger tenantId = BigInteger.valueOf(202);
|
||||
BigInteger accountId = BigInteger.valueOf(302);
|
||||
BigInteger workflowId = BigInteger.valueOf(402);
|
||||
SysJobService jobService = mock(SysJobService.class);
|
||||
SysAccountService accountService = mock(SysAccountService.class);
|
||||
WorkflowUsageAuthorizationService authorizationService =
|
||||
mock(WorkflowUsageAuthorizationService.class);
|
||||
ChainExecutor chainExecutor = mock(ChainExecutor.class);
|
||||
when(jobService.getById(jobId))
|
||||
.thenReturn(workflowJob(jobId, tenantId, accountId, workflowId));
|
||||
when(accountService.getById(accountId))
|
||||
.thenReturn(account(accountId, tenantId, EnumDataStatus.AVAILABLE.getCode()));
|
||||
when(authorizationService.requireUsableWorkflow(
|
||||
eq(workflowId),
|
||||
any(LoginAccount.class),
|
||||
anyString()))
|
||||
.thenThrow(new BusinessException("工作流权限已撤销"));
|
||||
WorkflowJobExecutionService service = new WorkflowJobExecutionService(
|
||||
jobService,
|
||||
accountService,
|
||||
authorizationService,
|
||||
chainExecutor);
|
||||
|
||||
BusinessException exception = Assert.assertThrows(
|
||||
BusinessException.class,
|
||||
() -> service.execute(scheduledJob(jobId, tenantId))
|
||||
);
|
||||
|
||||
Assert.assertTrue(exception.getMessage().contains("权限已撤销"));
|
||||
verify(chainExecutor, never()).execute(anyString(), anyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证任务快照与数据库租户不一致时执行失败。
|
||||
*/
|
||||
@Test
|
||||
public void shouldRejectCrossTenantJobSnapshot() {
|
||||
BigInteger jobId = BigInteger.valueOf(103);
|
||||
BigInteger tenantId = BigInteger.valueOf(203);
|
||||
SysJobService jobService = mock(SysJobService.class);
|
||||
SysAccountService accountService = mock(SysAccountService.class);
|
||||
WorkflowUsageAuthorizationService authorizationService =
|
||||
mock(WorkflowUsageAuthorizationService.class);
|
||||
ChainExecutor chainExecutor = mock(ChainExecutor.class);
|
||||
when(jobService.getById(jobId)).thenReturn(workflowJob(
|
||||
jobId,
|
||||
tenantId,
|
||||
BigInteger.valueOf(303),
|
||||
BigInteger.valueOf(403)));
|
||||
WorkflowJobExecutionService service = new WorkflowJobExecutionService(
|
||||
jobService,
|
||||
accountService,
|
||||
authorizationService,
|
||||
chainExecutor);
|
||||
|
||||
IllegalStateException exception = Assert.assertThrows(
|
||||
IllegalStateException.class,
|
||||
() -> service.execute(scheduledJob(jobId, BigInteger.valueOf(999)))
|
||||
);
|
||||
|
||||
Assert.assertTrue(exception.getMessage().contains("租户"));
|
||||
verify(authorizationService, never()).requireUsableWorkflow(
|
||||
any(BigInteger.class),
|
||||
any(LoginAccount.class),
|
||||
anyString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Quartz 任务快照。
|
||||
*
|
||||
* @param jobId 定时任务 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @return 任务快照
|
||||
*/
|
||||
private SysJob scheduledJob(BigInteger jobId, BigInteger tenantId) {
|
||||
SysJob job = new SysJob();
|
||||
job.setId(jobId);
|
||||
job.setTenantId(tenantId);
|
||||
job.setJobType(EnumJobType.TINY_FLOW.getCode());
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建数据库中的工作流定时任务。
|
||||
*
|
||||
* @param jobId 定时任务 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @param accountId 任务创建账号 ID
|
||||
* @param workflowId 工作流 ID
|
||||
* @return 工作流定时任务
|
||||
*/
|
||||
private SysJob workflowJob(
|
||||
BigInteger jobId,
|
||||
BigInteger tenantId,
|
||||
BigInteger accountId,
|
||||
BigInteger workflowId) {
|
||||
SysJob job = new SysJob();
|
||||
job.setId(jobId);
|
||||
job.setTenantId(tenantId);
|
||||
job.setCreatedBy(accountId);
|
||||
job.setStatus(EnumJobStatus.RUNNING.getCode());
|
||||
job.setJobType(EnumJobType.TINY_FLOW.getCode());
|
||||
job.setJobParams(Map.of(
|
||||
JobConstant.WORKFLOW_KEY, workflowId.toString(),
|
||||
JobConstant.WORKFLOW_PARAMS_KEY, Map.of("question", "hello")
|
||||
));
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建系统账号。
|
||||
*
|
||||
* @param accountId 账号 ID
|
||||
* @param tenantId 租户 ID
|
||||
* @param status 账号状态
|
||||
* @return 系统账号
|
||||
*/
|
||||
private SysAccount account(
|
||||
BigInteger accountId,
|
||||
BigInteger tenantId,
|
||||
Integer status) {
|
||||
SysAccount account = new SysAccount();
|
||||
account.setId(accountId);
|
||||
account.setTenantId(tenantId);
|
||||
account.setStatus(status);
|
||||
return account;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user