Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
275 lines
8.8 KiB
Go
275 lines
8.8 KiB
Go
// Copyright © 2023 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package relationtuple
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
"github.com/gofrs/uuid"
|
|
keysetpagination "github.com/ory/x/pagination/keysetpagination_v2"
|
|
|
|
rts "github.com/ory/keto/gen/go/ory/keto/relation_tuples/v1alpha2"
|
|
"github.com/ory/keto/ketoapi"
|
|
)
|
|
|
|
type (
|
|
ManagerProvider interface {
|
|
RelationTupleManager() Manager
|
|
}
|
|
|
|
// SubjectSetType identifies a subject-set pointer by its target namespace and
|
|
// relation, as declared in OPL. AllowsDirect indicates that OPL permits a
|
|
// direct subject match for this type.
|
|
SubjectSetType struct {
|
|
Namespace string
|
|
Relation string
|
|
AllowsDirect bool
|
|
}
|
|
|
|
Manager interface {
|
|
GetRelationTuples(ctx context.Context, query *RelationQuery, options ...keysetpagination.Option) ([]*RelationTuple, *keysetpagination.Paginator, error)
|
|
ExistsRelationTuples(ctx context.Context, query *RelationQuery) (bool, error)
|
|
WriteRelationTuples(ctx context.Context, rs ...*RelationTuple) error
|
|
DeleteRelationTuples(ctx context.Context, rs ...*RelationTuple) error
|
|
DeleteAllRelationTuples(ctx context.Context, query *RelationQuery) error
|
|
TransactRelationTuples(ctx context.Context, insert []*RelationTuple, delete []*RelationTuple) error
|
|
// TraverseSubjectSetExpansion gets all subject sets for the tuple's
|
|
// object#relation and checks whether the tuple's subject is a member of
|
|
// each. When allowedSubjectSets is non-empty, only subject-set pointers
|
|
// matching the declared (namespace, relation) pairs are considered.
|
|
TraverseSubjectSetExpansion(ctx context.Context, tuple *RelationTuple, allowedSubjectSets []SubjectSetType) ([]*TraversalResult, error)
|
|
// FindTupleWithRelations returns the first tuple that matches the given
|
|
// tuple on namespace, object, and subject and has any of the given
|
|
// relations, or nil if there is none.
|
|
FindTupleWithRelations(ctx context.Context, tuple *RelationTuple, relations []string) (*RelationTuple, error)
|
|
|
|
// Transaction runs f inside a database transaction. Nested calls reuse
|
|
// the outer transaction.
|
|
Transaction(ctx context.Context, f func(ctx context.Context) error) error
|
|
// WithLatestSnapshot runs f inside a snapshot-isolated read transaction,
|
|
// ensuring all SQL queries within f see a consistent database state.
|
|
WithLatestSnapshot(ctx context.Context, f func(ctx context.Context) error) error
|
|
}
|
|
SubjectID struct {
|
|
ID uuid.UUID `json:"id"`
|
|
}
|
|
RelationQuery struct {
|
|
Namespace *string `json:"namespace"`
|
|
Object *uuid.UUID `json:"object"`
|
|
Relation *string `json:"relation"`
|
|
Subject Subject `json:"subject_id,omitempty"`
|
|
}
|
|
TupleData interface {
|
|
GetSubject() *rts.Subject
|
|
GetObject() string
|
|
GetNamespace() string
|
|
GetRelation() string
|
|
}
|
|
Subject interface {
|
|
Equals(Subject) bool
|
|
UniqueID() uuid.UUID
|
|
String() string
|
|
}
|
|
RelationTuple struct {
|
|
Namespace string `json:"namespace"`
|
|
Object uuid.UUID `json:"object"`
|
|
Relation string `json:"relation"`
|
|
Subject Subject `json:"subject"`
|
|
}
|
|
InternalRelationTuples []*RelationTuple
|
|
SubjectSet struct {
|
|
Namespace string `json:"namespace"`
|
|
Object uuid.UUID `json:"object"`
|
|
Relation string `json:"relation"`
|
|
}
|
|
|
|
// TODO(hperl): Also use a ketoapi.Tree here.
|
|
Tree struct {
|
|
Type ketoapi.TreeNodeType `json:"type"`
|
|
Subject Subject `json:"subject"`
|
|
Children []*Tree `json:"children,omitempty"`
|
|
Truncation *Truncation `json:"truncation,omitempty"`
|
|
}
|
|
|
|
Truncation struct {
|
|
Reason TruncationReason `json:"reason,omitempty"`
|
|
Cursor *ExpandCursor `json:"cursor,omitempty"`
|
|
}
|
|
|
|
TruncationReason string
|
|
ExpandCursorKind string
|
|
ExpandCursor struct {
|
|
Kind ExpandCursorKind `json:"kind"`
|
|
SubjectSet *SubjectSet `json:"subject_set,omitempty"`
|
|
NextPageToken keysetpagination.PageToken `json:"next_page_token,omitempty"`
|
|
|
|
// TraverseRelation is the ComputedRelation in TupleToSubjectSet relation
|
|
TraverseRelation *string `json:"traverse_relation,omitempty"`
|
|
}
|
|
)
|
|
|
|
const (
|
|
ExpandCursorKindDirect ExpandCursorKind = "direct"
|
|
ExpandCursorKindTTU ExpandCursorKind = "ttu"
|
|
)
|
|
|
|
type (
|
|
TraversalResult struct {
|
|
From *RelationTuple
|
|
To *RelationTuple
|
|
Via Traversal
|
|
Found bool
|
|
}
|
|
|
|
Traversal string
|
|
)
|
|
|
|
const (
|
|
TraversalUnknown Traversal = "unknown"
|
|
TraversalSubjectSetExpand Traversal = "subject set expand"
|
|
TraversalComputedUserset Traversal = "computed userset"
|
|
TraversalTupleToUserset Traversal = "tuple to userset"
|
|
)
|
|
|
|
const (
|
|
TruncationReasonDepthLimit TruncationReason = "depth_limit"
|
|
TruncationReasonTupleLimit TruncationReason = "tuple_limit"
|
|
TruncationReasonCycle TruncationReason = "cycle"
|
|
)
|
|
|
|
var _, _ Subject = (*SubjectID)(nil), (*SubjectSet)(nil)
|
|
|
|
func (s *SubjectID) Equals(other Subject) bool {
|
|
uv, ok := other.(*SubjectID)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return uv.ID == s.ID
|
|
}
|
|
|
|
func (s *SubjectID) UniqueID() uuid.UUID { return s.ID }
|
|
func (s *SubjectID) String() string { return s.ID.String() }
|
|
|
|
func (s *SubjectSet) Equals(other Subject) bool {
|
|
uv, ok := other.(*SubjectSet)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return uv.Relation == s.Relation && uv.Object == s.Object && uv.Namespace == s.Namespace
|
|
}
|
|
|
|
func (s *SubjectSet) UniqueID() uuid.UUID {
|
|
return uuid.NewV5(s.Object, s.Namespace+"-"+s.Relation)
|
|
}
|
|
|
|
func (s *SubjectSet) String() string {
|
|
if s == nil {
|
|
return ""
|
|
}
|
|
return s.Namespace + ":" + s.Object.String() + "#" + s.Relation
|
|
}
|
|
|
|
func (t *RelationTuple) ToQuery() *RelationQuery {
|
|
return &RelationQuery{
|
|
Namespace: &t.Namespace,
|
|
Object: &t.Object,
|
|
Relation: &t.Relation,
|
|
Subject: t.Subject,
|
|
}
|
|
}
|
|
|
|
func (t *RelationTuple) String() string {
|
|
if t == nil {
|
|
return ""
|
|
}
|
|
|
|
subject := ""
|
|
if t.Subject != nil {
|
|
subject = t.Subject.String()
|
|
}
|
|
return t.Namespace + ":" + t.Object.String() + "#" + t.Relation + "@" + subject
|
|
}
|
|
|
|
func (t *RelationTuple) FromProto(proto *rts.RelationTuple) *RelationTuple {
|
|
// TODO(hperl)
|
|
return t
|
|
}
|
|
|
|
func (t *RelationTuple) ToProto() *rts.RelationTuple {
|
|
// TODO(hperl)
|
|
return &rts.RelationTuple{}
|
|
}
|
|
|
|
type ManagerWrapper struct {
|
|
Reg ManagerProvider
|
|
PageOpts []keysetpagination.Option
|
|
RequestedPages []keysetpagination.PageToken
|
|
// lock is necessary so that GetRelationTuples() is safe for concurrency.
|
|
requestedPagesLock sync.Mutex
|
|
}
|
|
|
|
var (
|
|
_ Manager = (*ManagerWrapper)(nil)
|
|
_ ManagerProvider = (*ManagerWrapper)(nil)
|
|
)
|
|
|
|
func NewManagerWrapper(_ any, reg ManagerProvider, options ...keysetpagination.Option) *ManagerWrapper {
|
|
return &ManagerWrapper{
|
|
Reg: reg,
|
|
PageOpts: options,
|
|
}
|
|
}
|
|
|
|
func (t *ManagerWrapper) GetRelationTuples(ctx context.Context, query *RelationQuery, options ...keysetpagination.Option) ([]*RelationTuple, *keysetpagination.Paginator, error) {
|
|
p, err := keysetpagination.NewPaginator(options...)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
t.requestedPagesLock.Lock()
|
|
defer t.requestedPagesLock.Unlock()
|
|
t.RequestedPages = append(t.RequestedPages, p.PageToken())
|
|
return t.Reg.RelationTupleManager().GetRelationTuples(ctx, query, append(t.PageOpts, options...)...)
|
|
}
|
|
|
|
func (t *ManagerWrapper) ExistsRelationTuples(ctx context.Context, query *RelationQuery) (bool, error) {
|
|
return t.Reg.RelationTupleManager().ExistsRelationTuples(ctx, query)
|
|
}
|
|
|
|
func (t *ManagerWrapper) WriteRelationTuples(ctx context.Context, rs ...*RelationTuple) error {
|
|
return t.Reg.RelationTupleManager().WriteRelationTuples(ctx, rs...)
|
|
}
|
|
|
|
func (t *ManagerWrapper) DeleteRelationTuples(ctx context.Context, rs ...*RelationTuple) error {
|
|
return t.Reg.RelationTupleManager().DeleteRelationTuples(ctx, rs...)
|
|
}
|
|
|
|
func (t *ManagerWrapper) DeleteAllRelationTuples(ctx context.Context, query *RelationQuery) error {
|
|
return t.Reg.RelationTupleManager().DeleteAllRelationTuples(ctx, query)
|
|
}
|
|
|
|
func (t *ManagerWrapper) TransactRelationTuples(ctx context.Context, insert []*RelationTuple, delete []*RelationTuple) error {
|
|
return t.Reg.RelationTupleManager().TransactRelationTuples(ctx, insert, delete)
|
|
}
|
|
|
|
func (t *ManagerWrapper) TraverseSubjectSetExpansion(ctx context.Context, tuple *RelationTuple, allowedSubjectSets []SubjectSetType) ([]*TraversalResult, error) {
|
|
return t.Reg.RelationTupleManager().TraverseSubjectSetExpansion(ctx, tuple, allowedSubjectSets)
|
|
}
|
|
|
|
func (t *ManagerWrapper) FindTupleWithRelations(ctx context.Context, tuple *RelationTuple, relations []string) (*RelationTuple, error) {
|
|
return t.Reg.RelationTupleManager().FindTupleWithRelations(ctx, tuple, relations)
|
|
}
|
|
|
|
func (t *ManagerWrapper) Transaction(ctx context.Context, f func(ctx context.Context) error) error {
|
|
return t.Reg.RelationTupleManager().Transaction(ctx, f)
|
|
}
|
|
|
|
func (t *ManagerWrapper) WithLatestSnapshot(ctx context.Context, f func(ctx context.Context) error) error {
|
|
return t.Reg.RelationTupleManager().WithLatestSnapshot(ctx, f)
|
|
}
|
|
|
|
func (t *ManagerWrapper) RelationTupleManager() Manager {
|
|
return t
|
|
}
|