guard.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package agentserver
  2. import (
  3. "context"
  4. "errors"
  5. )
  6. // OwnershipStore 会话归属与子资源索引的存储(Mongo 实现见 services/agentServices.go)
  7. type OwnershipStore interface {
  8. // SessionOwner 返回未删除会话的所属用户;不存在或已删除时 found=false
  9. SessionOwner(ctx context.Context, sessionId string) (owner string, found bool, err error)
  10. // ResourceSession 返回子资源所属的会话
  11. ResourceSession(ctx context.Context, resourceId string) (sessionId string, found bool, err error)
  12. // RecentSessionIds 用户最近活跃的未删除会话(反查交互、文件时遍历)
  13. RecentSessionIds(ctx context.Context, userId string, limit int) ([]string, error)
  14. }
  15. // ResourceSink 记录子资源索引(异步)
  16. type ResourceSink interface {
  17. Record(rs []Resource)
  18. }
  19. // Upstream 归属反查用到的 AgentServer 接口(*Client 实现)
  20. type Upstream interface {
  21. GetRun(ctx context.Context, runId string) (*RunDto, error)
  22. ListFiles(ctx context.Context, sessionId string) ([]FileDto, error)
  23. PendingInteractions(ctx context.Context, sessionId string) ([]InteractionRefDto, error)
  24. }
  25. // fallbackSessions 反查交互、文件时最多遍历的最近会话数
  26. const fallbackSessions = 10
  27. // Guard 代理请求的归属校验(technical-design §3.5 第 2 点)
  28. type Guard struct {
  29. Store OwnershipStore
  30. Upstream Upstream
  31. Sink ResourceSink
  32. }
  33. // Check 校验 route 指向的资源属于 userId。
  34. // 返回资源所属的会话 ID(OwnerNone 时为空);ok=false 表示不属于本人或不存在(调用方统一返回 404)。
  35. // err 只在存储或上游出错时返回。
  36. func (g *Guard) Check(ctx context.Context, userId string, r Route) (sessionId string, ok bool, err error) {
  37. switch r.Owner {
  38. case OwnerNone:
  39. return "", true, nil
  40. case OwnerSession, OwnerQuerySession:
  41. owned, err := g.ownsSession(ctx, userId, r.Id)
  42. if err != nil || !owned {
  43. return "", false, err
  44. }
  45. return r.Id, true, nil
  46. case OwnerRun, OwnerInteraction, OwnerFile:
  47. return g.checkResource(ctx, userId, r.Owner, r.Id)
  48. }
  49. return "", false, nil
  50. }
  51. func (g *Guard) ownsSession(ctx context.Context, userId, sessionId string) (bool, error) {
  52. owner, found, err := g.Store.SessionOwner(ctx, sessionId)
  53. if err != nil {
  54. return false, err
  55. }
  56. return found && owner == userId, nil
  57. }
  58. func (g *Guard) checkResource(ctx context.Context, userId string, kind OwnerKind, id string) (string, bool, error) {
  59. sid, found, err := g.Store.ResourceSession(ctx, id)
  60. if err != nil {
  61. return "", false, err
  62. }
  63. if found {
  64. owned, err := g.ownsSession(ctx, userId, sid)
  65. if err != nil || !owned {
  66. return "", false, err
  67. }
  68. return sid, true, nil
  69. }
  70. // 索引中没有:向 AgentServer 反查,确认后补写索引
  71. sid, err = g.lookup(ctx, userId, kind, id)
  72. if err != nil || sid == "" {
  73. return "", false, err
  74. }
  75. if g.Sink != nil {
  76. g.Sink.Record([]Resource{{Id: id, SessionId: sid}})
  77. }
  78. return sid, true, nil
  79. }
  80. // lookup 反查子资源所属会话,返回空表示不属于本人
  81. func (g *Guard) lookup(ctx context.Context, userId string, kind OwnerKind, id string) (string, error) {
  82. if g.Upstream == nil {
  83. return "", nil
  84. }
  85. if kind == OwnerRun {
  86. run, err := g.Upstream.GetRun(ctx, id)
  87. if errors.Is(err, ErrNotFound) {
  88. return "", nil
  89. }
  90. if err != nil {
  91. return "", err
  92. }
  93. owned, err := g.ownsSession(ctx, userId, run.SessionId)
  94. if err != nil || !owned {
  95. return "", err
  96. }
  97. return run.SessionId, nil
  98. }
  99. sessions, err := g.Store.RecentSessionIds(ctx, userId, fallbackSessions)
  100. if err != nil {
  101. return "", err
  102. }
  103. for _, sid := range sessions {
  104. hit, err := g.sessionHas(ctx, kind, sid, id)
  105. if errors.Is(err, ErrNotFound) {
  106. continue
  107. }
  108. if err != nil {
  109. return "", err
  110. }
  111. if hit {
  112. return sid, nil
  113. }
  114. }
  115. return "", nil
  116. }
  117. func (g *Guard) sessionHas(ctx context.Context, kind OwnerKind, sessionId, id string) (bool, error) {
  118. switch kind {
  119. case OwnerFile:
  120. files, err := g.Upstream.ListFiles(ctx, sessionId)
  121. if err != nil {
  122. return false, err
  123. }
  124. for _, f := range files {
  125. if f.Id == id {
  126. return true, nil
  127. }
  128. }
  129. case OwnerInteraction:
  130. items, err := g.Upstream.PendingInteractions(ctx, sessionId)
  131. if err != nil {
  132. return false, err
  133. }
  134. for _, it := range items {
  135. if it.InteractionId == id {
  136. return true, nil
  137. }
  138. }
  139. }
  140. return false, nil
  141. }