fix(go-agent): align logs auth with Python and harden event data (#17617)

## Summary

- Align Go agent logs access with the Python behavior by checking tenant
access to the target agent instead of requiring the token to be bound
to that agent.
- Marshal canvas lifecycle event payloads safely so non-serializable
inputs do not break Thinking expansion in the UI.

  ## Testing

- `CGO_ENABLED=0 go test -count=1 -v -run
"Handler|Attachment|Logs|Chatbot|Agentbot|BuildWorkflow|NodeLifecycle|VarRef|Resolve|Extract"./internal/handler/
./internal/agent/canvas/...` PASS
This commit is contained in:
Hz_
2026-07-31 16:24:25 +08:00
committed by GitHub
parent 93513eb1fd
commit 4a43a1d620
5 changed files with 252 additions and 70 deletions

View File

@@ -18,14 +18,11 @@ package handler
import (
"context"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"ragflow/internal/agent/canvas"
"ragflow/internal/common"
"ragflow/internal/engine/redis"
"ragflow/internal/service"
)
@@ -48,6 +45,7 @@ type botService interface {
ec common.ErrorCode, err error)
AgentbotCompletion(ctx context.Context, tenantID, agentID string, req service.AgentbotCompletionRequest) (
<-chan canvas.RunEvent, common.ErrorCode, error)
AgentbotLogs(ctx context.Context, tenantID, agentID, messageID string) (map[string]any, common.ErrorCode, error)
ChatbotCompletion(ctx context.Context, tenantID, dialogID string, req service.ChatbotCompletionRequest) (
<-chan service.ChatbotSSEFrame, common.ErrorCode, error)
}
@@ -230,13 +228,12 @@ func (h *BotHandler) ChatbotCompletion(c *gin.Context) {
// GetAgentbotLogs GET /api/v1/agentbots/<agent_id>/logs/<message_id>
//
// Beta-token sibling of GetAgentLogs. The shared/embedded chat
// page's "Thinking" button hits this endpoint because the share
// flow authenticates with a beta APIToken (no session JWT) and
// the regular /api/v1/agents/<id>/logs/<msg> requires @login_required.
// Mirrors python bot_api.py:agent_bot_logs.
// Beta-token sibling of GetAgentLogs. The shared/embedded chat page's
// "Thinking" button hits this endpoint because the share flow authenticates
// with a beta APIToken (no session JWT). Mirrors python bot_api.py:agent_bot_logs.
func (h *BotHandler) GetAgentbotLogs(c *gin.Context) {
if _, code, msg := GetUser(c); code != common.CodeSuccess {
user, code, msg := GetUser(c)
if code != common.CodeSuccess {
common.ResponseWithCodeData(c, code, nil, msg)
return
}
@@ -245,38 +242,25 @@ func (h *BotHandler) GetAgentbotLogs(c *gin.Context) {
common.ResponseWithCodeData(c, common.CodeArgumentError, nil, "agent_id is required")
return
}
boundAgentID, _ := c.Get("agent_id")
boundAgentIDStr, _ := boundAgentID.(string)
if boundAgentIDStr == "" {
common.ResponseWithCodeData(c, common.CodeDataError, nil, "API token is not bound to an agent.")
return
}
if boundAgentIDStr != agentIDStr {
common.ResponseWithCodeData(c, common.CodeUnauthorized, nil, "API token is not authorized for this agent.")
return
}
messageID := c.Param("message_id")
if messageID == "" {
common.ResponseWithCodeData(c, common.CodeArgumentError, nil, "message_id is required")
return
}
key := fmt.Sprintf("%s-%s-logs", agentIDStr, messageID)
payload, rerr := redis.Get().Get(key)
// Surface Redis / decode failures instead of silently returning
// `{code: 0, data: {}}` — the previous form made the endpoint
// indistinguishable from "logs not yet written", which masked
// real outages and corrupted payloads from operators (PR review
// round 5, Major #6).
if rerr != nil {
common.ResponseWithCodeData(c, common.CodeServerError, nil, "failed to read agent logs")
return
}
data := map[string]interface{}{}
if payload != "" {
if uerr := json.Unmarshal([]byte(payload), &data); uerr != nil {
common.ResponseWithCodeData(c, common.CodeServerError, nil, "failed to decode agent logs")
// A beta token with DialogID is restricted to that agent. Tokens without
// DialogID remain tenant-scoped and are checked by AgentbotLogs through
// the existing Canvas access guard.
if boundAgentID, ok := c.Get("agent_id"); ok {
boundAgentIDStr, ok := boundAgentID.(string)
if ok && boundAgentIDStr != "" && boundAgentIDStr != agentIDStr {
common.ResponseWithCodeData(c, common.CodeUnauthorized, nil, "API token is not authorized for this agent.")
return
}
}
data, ec, err := h.botService.AgentbotLogs(c.Request.Context(), user.ID, agentIDStr, messageID)
if err != nil {
common.ResponseWithCodeData(c, ec, nil, err.Error())
return
}
common.SuccessWithData(c, data, "success")
}