| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- package agentserver
- import (
- "context"
- "errors"
- )
- // OwnershipStore 会话归属与子资源索引的存储(Mongo 实现见 services/agentServices.go)
- type OwnershipStore interface {
- // SessionOwner 返回未删除会话的所属用户;不存在或已删除时 found=false
- SessionOwner(ctx context.Context, sessionId string) (owner string, found bool, err error)
- // ResourceSession 返回子资源所属的会话
- ResourceSession(ctx context.Context, resourceId string) (sessionId string, found bool, err error)
- // RecentSessionIds 用户最近活跃的未删除会话(反查交互、文件时遍历)
- RecentSessionIds(ctx context.Context, userId string, limit int) ([]string, error)
- }
- // ResourceSink 记录子资源索引(异步)
- type ResourceSink interface {
- Record(rs []Resource)
- }
- // Upstream 归属反查用到的 AgentServer 接口(*Client 实现)
- type Upstream interface {
- GetRun(ctx context.Context, runId string) (*RunDto, error)
- ListFiles(ctx context.Context, sessionId string) ([]FileDto, error)
- PendingInteractions(ctx context.Context, sessionId string) ([]InteractionRefDto, error)
- }
- // fallbackSessions 反查交互、文件时最多遍历的最近会话数
- const fallbackSessions = 10
- // Guard 代理请求的归属校验(technical-design §3.5 第 2 点)
- type Guard struct {
- Store OwnershipStore
- Upstream Upstream
- Sink ResourceSink
- }
- // Check 校验 route 指向的资源属于 userId。
- // 返回资源所属的会话 ID(OwnerNone 时为空);ok=false 表示不属于本人或不存在(调用方统一返回 404)。
- // err 只在存储或上游出错时返回。
- func (g *Guard) Check(ctx context.Context, userId string, r Route) (sessionId string, ok bool, err error) {
- switch r.Owner {
- case OwnerNone:
- return "", true, nil
- case OwnerSession, OwnerQuerySession:
- owned, err := g.ownsSession(ctx, userId, r.Id)
- if err != nil || !owned {
- return "", false, err
- }
- return r.Id, true, nil
- case OwnerRun, OwnerInteraction, OwnerFile:
- return g.checkResource(ctx, userId, r.Owner, r.Id)
- }
- return "", false, nil
- }
- func (g *Guard) ownsSession(ctx context.Context, userId, sessionId string) (bool, error) {
- owner, found, err := g.Store.SessionOwner(ctx, sessionId)
- if err != nil {
- return false, err
- }
- return found && owner == userId, nil
- }
- func (g *Guard) checkResource(ctx context.Context, userId string, kind OwnerKind, id string) (string, bool, error) {
- sid, found, err := g.Store.ResourceSession(ctx, id)
- if err != nil {
- return "", false, err
- }
- if found {
- owned, err := g.ownsSession(ctx, userId, sid)
- if err != nil || !owned {
- return "", false, err
- }
- return sid, true, nil
- }
- // 索引中没有:向 AgentServer 反查,确认后补写索引
- sid, err = g.lookup(ctx, userId, kind, id)
- if err != nil || sid == "" {
- return "", false, err
- }
- if g.Sink != nil {
- g.Sink.Record([]Resource{{Id: id, SessionId: sid}})
- }
- return sid, true, nil
- }
- // lookup 反查子资源所属会话,返回空表示不属于本人
- func (g *Guard) lookup(ctx context.Context, userId string, kind OwnerKind, id string) (string, error) {
- if g.Upstream == nil {
- return "", nil
- }
- if kind == OwnerRun {
- run, err := g.Upstream.GetRun(ctx, id)
- if errors.Is(err, ErrNotFound) {
- return "", nil
- }
- if err != nil {
- return "", err
- }
- owned, err := g.ownsSession(ctx, userId, run.SessionId)
- if err != nil || !owned {
- return "", err
- }
- return run.SessionId, nil
- }
- sessions, err := g.Store.RecentSessionIds(ctx, userId, fallbackSessions)
- if err != nil {
- return "", err
- }
- for _, sid := range sessions {
- hit, err := g.sessionHas(ctx, kind, sid, id)
- if errors.Is(err, ErrNotFound) {
- continue
- }
- if err != nil {
- return "", err
- }
- if hit {
- return sid, nil
- }
- }
- return "", nil
- }
- func (g *Guard) sessionHas(ctx context.Context, kind OwnerKind, sessionId, id string) (bool, error) {
- switch kind {
- case OwnerFile:
- files, err := g.Upstream.ListFiles(ctx, sessionId)
- if err != nil {
- return false, err
- }
- for _, f := range files {
- if f.Id == id {
- return true, nil
- }
- }
- case OwnerInteraction:
- items, err := g.Upstream.PendingInteractions(ctx, sessionId)
- if err != nil {
- return false, err
- }
- for _, it := range items {
- if it.InteractionId == id {
- return true, nil
- }
- }
- }
- return false, nil
- }
|