memos/server/access/memo_resolve.go

89 lines
3.2 KiB
Go

package access
import (
"context"
"github.com/usememos/memos/store"
)
// MemoReadStore is the store subset needed to resolve a memo read context.
// *store.Store satisfies it.
type MemoReadStore interface {
GetUser(ctx context.Context, find *store.FindUser) (*store.User, error)
GetSpace(ctx context.Context, find *store.FindSpace) (*store.Space, error)
GetSpaceMember(ctx context.Context, find *store.FindSpaceMember) (*store.SpaceMember, error)
}
// MemoReadFacts holds the viewer-independent authorization inputs for one memo.
// They are resolved once and reused across every viewer evaluated against the
// same memo.
type MemoReadFacts struct {
Memo *store.Memo
CreatorValid bool
SpaceValid bool
}
// ResolveMemoReadFacts resolves the memo-local authorization inputs that do not
// depend on who is reading: whether the memo has a valid creator and, when it is
// assigned, whether its placement still exists. SpaceValid is authorization
// input only for SPACE reads and placement-dependent projection and
// writes; other audiences remain readable through their own memo-local rules.
func ResolveMemoReadFacts(ctx context.Context, s MemoReadStore, memo *store.Memo) (MemoReadFacts, error) {
facts := MemoReadFacts{Memo: memo}
if memo == nil {
return facts, nil
}
creatorID := memo.CreatorID
creator, err := s.GetUser(ctx, &store.FindUser{ID: &creatorID})
if err != nil {
return MemoReadFacts{}, err
}
facts.CreatorValid = creator != nil && creator.ID == creatorID &&
(creator.RowStatus == store.Normal || creator.RowStatus == store.Archived)
facts.SpaceValid = memo.SpaceID == nil
if memo.SpaceID != nil {
space, err := s.GetSpace(ctx, &store.FindSpace{ID: memo.SpaceID})
if err != nil {
return MemoReadFacts{}, err
}
facts.SpaceValid = space != nil
}
return facts, nil
}
// WithViewer completes the read context for one viewer. Only the membership
// lookup is viewer-dependent, so evaluating additional viewers against the same
// memo costs at most one query each.
func (f MemoReadFacts) WithViewer(ctx context.Context, s MemoReadStore, viewer *store.User, allowAnonymous bool, sharedMemoID *int32) (MemoReadContext, error) {
readContext := MemoReadContext{
Memo: f.Memo,
Viewer: viewer,
AllowAnonymous: allowAnonymous,
SharedMemoID: sharedMemoID,
CreatorValid: f.CreatorValid,
SpaceValid: f.SpaceValid,
}
if f.Memo == nil || f.Memo.SpaceID == nil || !f.SpaceValid {
return readContext, nil
}
if viewer == nil || viewer.RowStatus != store.Normal {
return readContext, nil
}
membership, err := s.GetSpaceMember(ctx, &store.FindSpaceMember{SpaceID: f.Memo.SpaceID, UserID: &viewer.ID})
if err != nil {
return MemoReadContext{}, err
}
readContext.ViewerSpaceMember = membership != nil && membership.Role.IsActiveMember()
return readContext, nil
}
// ResolveMemoReadContext resolves a complete read context for one memo and one
// viewer.
func ResolveMemoReadContext(ctx context.Context, s MemoReadStore, memo *store.Memo, viewer *store.User, allowAnonymous bool, sharedMemoID *int32) (MemoReadContext, error) {
facts, err := ResolveMemoReadFacts(ctx, s, memo)
if err != nil {
return MemoReadContext{}, err
}
return facts.WithViewer(ctx, s, viewer, allowAnonymous, sharedMemoID)
}