feat(api): duty-check canonical docs before they are persisted
Template doc_proj ids were skipping CheckDocTaskAccess and returning 400 not-found. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"chat-agent-system/internal/auth"
|
||||
"chat-agent-system/internal/chat"
|
||||
@@ -63,14 +65,38 @@ func (s *Server) enforceWorkflowDocTaskAction(w http.ResponseWriter, ctx context
|
||||
}
|
||||
|
||||
func (s *Server) enforceDocGovWorkflowAction(w http.ResponseWriter, ctx context.Context, userID int64, docID, action string) bool {
|
||||
if s.svc == nil || s.svc.DocGov() == nil || docID == "" {
|
||||
if docID == "" {
|
||||
return true
|
||||
}
|
||||
doc, err := s.svc.DocGov().GetDocument(ctx, docID)
|
||||
if err != nil || doc == nil || doc.ProjectID <= 0 || doc.TaskKey == "" {
|
||||
return true
|
||||
if s.svc != nil && s.svc.DocGov() != nil {
|
||||
doc, err := s.svc.DocGov().GetDocument(ctx, docID)
|
||||
if err == nil && doc != nil && doc.ProjectID > 0 && doc.TaskKey != "" {
|
||||
return s.enforceWorkflowDocTaskAction(w, ctx, doc.ProjectID, userID, doc.TaskKey, action)
|
||||
}
|
||||
}
|
||||
return s.enforceWorkflowDocTaskAction(w, ctx, doc.ProjectID, userID, doc.TaskKey, action)
|
||||
if projectID, taskKey, ok := parseCanonicalWorkflowDocID(docID); ok {
|
||||
return s.enforceWorkflowDocTaskAction(w, ctx, projectID, userID, taskKey, action)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// parseCanonicalWorkflowDocID 解析 doc_proj{projectID}_{taskKey}。
|
||||
// 模板尚未落 document_records 时,送审/审核/批准仍按任务职责鉴权。
|
||||
func parseCanonicalWorkflowDocID(docID string) (int64, string, bool) {
|
||||
const prefix = "doc_proj"
|
||||
if !strings.HasPrefix(docID, prefix) {
|
||||
return 0, "", false
|
||||
}
|
||||
rest := strings.TrimPrefix(docID, prefix)
|
||||
idx := strings.IndexByte(rest, '_')
|
||||
if idx <= 0 || idx >= len(rest)-1 {
|
||||
return 0, "", false
|
||||
}
|
||||
projectID, err := strconv.ParseInt(rest[:idx], 10, 64)
|
||||
if err != nil || projectID <= 0 {
|
||||
return 0, "", false
|
||||
}
|
||||
return projectID, rest[idx+1:], true
|
||||
}
|
||||
|
||||
func (s *Server) writeDocAccessDecision(w http.ResponseWriter, decision projectinstance.DocAccessDecision) {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package server
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseCanonicalWorkflowDocID(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
in string
|
||||
wantID int64
|
||||
wantKey string
|
||||
ok bool
|
||||
}{
|
||||
{"doc_proj6201_intake", 6201, "intake", true},
|
||||
{"doc_proj12_feasibility_reg", 12, "feasibility_reg", true},
|
||||
{"doc-p6201-intake", 0, "", false},
|
||||
{"doc_proj_intake", 0, "", false},
|
||||
{"doc_proj0_intake", 0, "", false},
|
||||
{"", 0, "", false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
gotID, gotKey, ok := parseCanonicalWorkflowDocID(tc.in)
|
||||
if ok != tc.ok || gotID != tc.wantID || gotKey != tc.wantKey {
|
||||
t.Fatalf("%q -> (%d,%q,%v) want (%d,%q,%v)", tc.in, gotID, gotKey, ok, tc.wantID, tc.wantKey, tc.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ type RunRecord struct {
|
||||
WorkflowInstanceID int64
|
||||
StageInstanceID int64
|
||||
WorkflowStageKey string
|
||||
Status string // pending | running | completed | failed | cancelled | pending_author_confirmation | …
|
||||
Status string // pending | running | completed | failed | cancelled | pending_author_confirmation | blocked_pending_governance | …
|
||||
WorkspaceID string
|
||||
AssistantMsgID int64
|
||||
WorkerID string
|
||||
|
||||
Reference in New Issue
Block a user