mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(workspace): verify a path-bound agent belongs to the request workspace
This commit is contained in:
parent
92d35a3d3d
commit
32d633ae4b
@ -9,12 +9,17 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import vip.mate.agent.model.AgentEntity;
|
||||
import vip.mate.agent.repository.AgentMapper;
|
||||
import vip.mate.auth.model.UserEntity;
|
||||
import vip.mate.auth.service.AuthService;
|
||||
import vip.mate.workspace.core.annotation.RequireGlobalAdmin;
|
||||
import vip.mate.workspace.core.annotation.RequireWorkspaceRole;
|
||||
import vip.mate.workspace.core.service.WorkspaceService;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Workspace 访问拦截器
|
||||
* <p>
|
||||
@ -34,6 +39,7 @@ public class WorkspaceAccessInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final WorkspaceService workspaceService;
|
||||
private final AuthService authService;
|
||||
private final AgentMapper agentMapper;
|
||||
|
||||
/** 默认 workspace ID(未传 header 时使用) */
|
||||
private static final long DEFAULT_WORKSPACE_ID = 1L;
|
||||
@ -91,9 +97,50 @@ public class WorkspaceAccessInterceptor implements HandlerInterceptor {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The role check above only proves the user belongs to the *header*
|
||||
// workspace — not that a path-bound {agentId} actually lives there.
|
||||
// Without this a member of workspace A could read workspace B's agent
|
||||
// memory / context files by supplying B's agent id with their own header.
|
||||
if (!agentBelongsToWorkspace(request, workspaceId)) {
|
||||
log.warn("Cross-workspace agent access denied: user={}, workspaceId={}, path={}",
|
||||
username, workspaceId, request.getRequestURI());
|
||||
sendForbidden(response, "Agent does not belong to the current workspace");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the matched route carries an {@code {agentId}} path variable, verify
|
||||
* that agent belongs to the resolved workspace. Allows the request through
|
||||
* when there is no agent id, the id is unparseable, the agent does not
|
||||
* exist (so the handler can return its own 404), or the agent has not been
|
||||
* assigned a workspace.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean agentBelongsToWorkspace(HttpServletRequest request, long workspaceId) {
|
||||
Object attr = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
if (!(attr instanceof Map)) {
|
||||
return true;
|
||||
}
|
||||
Object rawAgentId = ((Map<String, String>) attr).get("agentId");
|
||||
if (rawAgentId == null) {
|
||||
return true;
|
||||
}
|
||||
long agentId;
|
||||
try {
|
||||
agentId = Long.parseLong(rawAgentId.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
return true;
|
||||
}
|
||||
AgentEntity agent = agentMapper.selectById(agentId);
|
||||
if (agent == null || agent.getWorkspaceId() == null) {
|
||||
return true;
|
||||
}
|
||||
return agent.getWorkspaceId() == workspaceId;
|
||||
}
|
||||
|
||||
private long resolveWorkspaceId(HttpServletRequest request) {
|
||||
String header = request.getHeader("X-Workspace-Id");
|
||||
if (header != null && !header.isBlank()) {
|
||||
|
||||
@ -39,6 +39,7 @@ public class WorkspaceFileController {
|
||||
* 列出 Agent 的所有工作区文件(不含内容)
|
||||
*/
|
||||
@Operation(summary = "列出工作区文件")
|
||||
@RequireWorkspaceRole("viewer")
|
||||
@GetMapping("/files")
|
||||
public R<List<WorkspaceFileEntity>> listFiles(@PathVariable Long agentId) {
|
||||
return R.ok(workspaceFileService.listFiles(agentId));
|
||||
@ -48,6 +49,7 @@ public class WorkspaceFileController {
|
||||
* 读取单个文件内容(支持子目录,如 memory/2026-04-03.md)
|
||||
*/
|
||||
@Operation(summary = "读取工作区文件")
|
||||
@RequireWorkspaceRole("viewer")
|
||||
@GetMapping("/files/**")
|
||||
public R<WorkspaceFileEntity> getFile(@PathVariable Long agentId, HttpServletRequest request) {
|
||||
String filename = extractFilename(request);
|
||||
@ -62,6 +64,7 @@ public class WorkspaceFileController {
|
||||
* 创建或更新文件(支持子目录)
|
||||
*/
|
||||
@Operation(summary = "保存工作区文件")
|
||||
@RequireWorkspaceRole("member")
|
||||
@PutMapping("/files/**")
|
||||
public R<WorkspaceFileEntity> saveFile(@PathVariable Long agentId,
|
||||
HttpServletRequest httpRequest,
|
||||
@ -74,6 +77,7 @@ public class WorkspaceFileController {
|
||||
* 删除文件(支持子目录)
|
||||
*/
|
||||
@Operation(summary = "删除工作区文件")
|
||||
@RequireWorkspaceRole("member")
|
||||
@DeleteMapping("/files/**")
|
||||
public R<Void> deleteFile(@PathVariable Long agentId, HttpServletRequest request) {
|
||||
String filename = extractFilename(request);
|
||||
@ -94,6 +98,7 @@ public class WorkspaceFileController {
|
||||
* 获取启用的系统提示文件列表(有序)
|
||||
*/
|
||||
@Operation(summary = "获取系统提示文件列表")
|
||||
@RequireWorkspaceRole("viewer")
|
||||
@GetMapping("/prompt-files")
|
||||
public R<List<String>> getPromptFiles(@PathVariable Long agentId) {
|
||||
return R.ok(workspaceFileService.getPromptFiles(agentId));
|
||||
@ -103,6 +108,7 @@ public class WorkspaceFileController {
|
||||
* 设置启用的系统提示文件列表(有序)
|
||||
*/
|
||||
@Operation(summary = "设置系统提示文件列表")
|
||||
@RequireWorkspaceRole("member")
|
||||
@PutMapping("/prompt-files")
|
||||
public R<Void> setPromptFiles(@PathVariable Long agentId,
|
||||
@RequestBody PromptFilesRequest request) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user