@@ -35,153 +35,146 @@ type (
35
35
Query = relationtuple.RelationQuery
36
36
)
37
37
38
+ const WildcardRelation = "..."
39
+
38
40
func NewEngine (d EngineDependencies ) * Engine {
39
41
return & Engine {
40
42
d : d ,
41
43
}
42
44
}
43
45
44
- func (e * Engine ) isIncluded (
45
- ctx context.Context ,
46
- requested * RelationTuple ,
47
- rels []* RelationTuple ,
48
- restDepth int ,
49
- ) checkgroup.CheckFunc {
50
- if restDepth < 0 {
51
- e .d .Logger ().Debug ("reached max-depth, therefore this query will not be further expanded" )
52
- return checkgroup .UnknownMemberFunc
46
+ // CheckIsMember checks if the relation tuple's subject has the relation on the
47
+ // object in the namespace either directly or indirectly and returns a boolean
48
+ // result.
49
+ func (e * Engine ) CheckIsMember (ctx context.Context , r * RelationTuple , restDepth int ) (bool , error ) {
50
+ result := e .CheckRelationTuple (ctx , r , restDepth )
51
+ if result .Err != nil {
52
+ return false , result .Err
53
53
}
54
+ return result .Membership == checkgroup .IsMember , nil
55
+ }
54
56
55
- // This is the same as the graph problem "can requested.Subject be reached
56
- // from requested.Object through the first outgoing edge requested.Relation"
57
- //
58
- // We implement recursive depth-first search here.
59
- // TODO replace by more performant algorithm:
60
- // https://github.com/ory/keto/issues/483
61
-
62
- ctx = graph .InitVisited (ctx )
63
- g := checkgroup .New (ctx )
64
-
65
- for _ , sr := range rels {
66
- var wasAlreadyVisited bool
67
- ctx , wasAlreadyVisited = graph .CheckAndAddVisited (ctx , sr .Subject )
68
- if wasAlreadyVisited {
69
- continue
70
- }
71
-
72
- // we only have to check Subject here as we know that sr was reached
73
- // from requested.ObjectID, requested.Relation through 0...n
74
- // indirections
75
- if requested .Subject .Equals (sr .Subject ) {
76
- // found the requested relation
77
- g .SetIsMember ()
78
- break
79
- }
80
-
81
- sub , isSubjectSet := sr .Subject .(* relationtuple.SubjectSet )
82
- if ! isSubjectSet {
83
- continue
84
- }
57
+ // CheckRelationTuple checks if the relation tuple's subject has the relation on
58
+ // the object in the namespace either directly or indirectly and returns a check
59
+ // result.
60
+ func (e * Engine ) CheckRelationTuple (ctx context.Context , r * RelationTuple , restDepth int ) checkgroup.Result {
61
+ // global max-depth takes precedence when it is the lesser or if the request
62
+ // max-depth is less than or equal to 0
63
+ if globalMaxDepth := e .d .Config (ctx ).MaxReadDepth (); restDepth <= 0 || globalMaxDepth < restDepth {
64
+ restDepth = globalMaxDepth
65
+ }
85
66
86
- g .Add (e .subQuery (
87
- requested ,
88
- & Query {Object : sub .Object , Relation : sub .Relation , Namespace : sub .Namespace },
89
- restDepth ,
90
- ))
67
+ resultCh := make (chan checkgroup.Result )
68
+ go e .checkIsAllowed (ctx , r , restDepth )(ctx , resultCh )
69
+ select {
70
+ case result := <- resultCh :
71
+ return result
72
+ case <- ctx .Done ():
73
+ return checkgroup.Result {Err : errors .WithStack (ctx .Err ())}
91
74
}
92
- return checkgroup .WithEdge (checkgroup.Edge {
93
- Tuple : * requested ,
94
- Type : expand .Union ,
95
- }, g .CheckFunc ())
96
75
}
97
76
98
- func (e * Engine ) subQuery (
99
- requested * RelationTuple ,
100
- query * Query ,
101
- restDepth int ,
102
- ) checkgroup.CheckFunc {
77
+ // checkExpandSubject checks the expansions of the subject set of the tuple.
78
+ //
79
+ // For a relation tuple n:obj#rel@user, checkExpandSubject first queries for all
80
+ // subjects that match n:obj#rel@* (arbirary subjects), and then for each
81
+ // subject checks subject@user.
82
+ func (e * Engine ) checkExpandSubject (ctx context.Context , r * RelationTuple , restDepth int ) checkgroup.CheckFunc {
103
83
if restDepth < 0 {
104
84
e .d .Logger ().
105
- WithFields (requested .ToLoggerFields ()).
85
+ WithFields (r .ToLoggerFields ()).
106
86
Debug ("reached max-depth, therefore this query will not be further expanded" )
107
87
return checkgroup .UnknownMemberFunc
108
88
}
109
-
110
89
return func (ctx context.Context , resultCh chan <- checkgroup.Result ) {
111
90
e .d .Logger ().
112
- WithField ("request" , requested .String ()).
113
- WithField ("query" , query .String ()).
114
- Trace ("check one indirection further" )
115
-
116
- // an empty page token denotes the first page (as tokens are opaque)
117
- var prevPage string
118
-
119
- // Special case: check if we can find the subject id directly
120
- if rels , _ , err := e .d .RelationTupleManager ().GetRelationTuples (ctx , requested .ToQuery ()); err == nil && len (rels ) > 0 {
121
- resultCh <- checkgroup.Result {
122
- Membership : checkgroup .IsMember ,
123
- Tree : & expand.Tree {
124
- Type : expand .Leaf ,
125
- Tuple : requested ,
126
- },
127
- }
128
- return
129
- }
91
+ WithField ("request" , r .String ()).
92
+ Trace ("check expand subject" )
130
93
131
94
g := checkgroup .New (ctx )
132
95
96
+ var (
97
+ subjects []* RelationTuple
98
+ pageToken string
99
+ err error
100
+ visited bool
101
+ innerCtx = graph .InitVisited (ctx )
102
+ query = & Query {Namespace : r .Namespace , Object : r .Object , Relation : r .Relation }
103
+ )
133
104
for {
134
- nextRels , nextPage , err := e .d .RelationTupleManager ().GetRelationTuples (ctx , query , x .WithToken (prevPage ))
135
- // herodot.ErrNotFound occurs when the namespace is unknown
105
+ subjects , pageToken , err = e .d .RelationTupleManager ().GetRelationTuples (innerCtx , query , x .WithToken (pageToken ))
136
106
if errors .Is (err , herodot .ErrNotFound ) {
137
107
g .Add (checkgroup .NotMemberFunc )
138
108
break
139
109
} else if err != nil {
140
110
g .Add (checkgroup .ErrorFunc (err ))
141
111
break
142
112
}
143
-
144
- g .Add (e .isIncluded (ctx , requested , nextRels , restDepth - 1 ))
145
-
146
- // loop through pages until either allowed, end of pages, or an error occurred
147
- if nextPage == "" || g .Done () {
113
+ for _ , s := range subjects {
114
+ innerCtx , visited = graph .CheckAndAddVisited (innerCtx , s .Subject )
115
+ if visited {
116
+ continue
117
+ }
118
+ if s .Subject .SubjectSet () == nil || s .Subject .SubjectSet ().Relation == WildcardRelation {
119
+ continue
120
+ }
121
+ g .Add (e .checkIsAllowed (
122
+ innerCtx ,
123
+ & RelationTuple {
124
+ Namespace : s .Subject .SubjectSet ().Namespace ,
125
+ Object : s .Subject .SubjectSet ().Object ,
126
+ Relation : s .Subject .SubjectSet ().Relation ,
127
+ Subject : r .Subject ,
128
+ },
129
+ restDepth - 1 ,
130
+ ))
131
+ }
132
+ if pageToken == "" || g .Done () {
148
133
break
149
134
}
150
- prevPage = nextPage
151
135
}
152
136
153
137
resultCh <- g .Result ()
154
138
}
155
139
}
156
140
157
- func (e * Engine ) CheckIsMember (ctx context.Context , r * RelationTuple , restDepth int ) (bool , error ) {
158
- result := e .CheckRelationTuple (ctx , r , restDepth )
159
- if result .Err != nil {
160
- return false , result .Err
161
- }
162
- return result .Membership == checkgroup .IsMember , nil
163
- }
164
-
165
- func (e * Engine ) CheckRelationTuple (ctx context.Context , r * RelationTuple , restDepth int ) checkgroup.Result {
166
- // global max-depth takes precedence when it is the lesser or if the request
167
- // max-depth is less than or equal to 0
168
- if globalMaxDepth := e .d .Config (ctx ).MaxReadDepth (); restDepth <= 0 || globalMaxDepth < restDepth {
169
- restDepth = globalMaxDepth
141
+ // checkDirect checks if the relation tuple is in the database directly.
142
+ func (e * Engine ) checkDirect (ctx context.Context , r * RelationTuple , restDepth int ) checkgroup.CheckFunc {
143
+ if restDepth < 0 {
144
+ e .d .Logger ().
145
+ WithField ("method" , "checkDirect" ).
146
+ Debug ("reached max-depth, therefore this query will not be further expanded" )
147
+ return checkgroup .UnknownMemberFunc
170
148
}
171
-
172
- resultCh := make (chan checkgroup.Result )
173
- go e .checkIsAllowed (ctx , r , restDepth )(ctx , resultCh )
174
- select {
175
- case result := <- resultCh :
176
- return result
177
- case <- ctx .Done ():
178
- return checkgroup.Result {Err : errors .WithStack (ctx .Err ())}
149
+ return func (ctx context.Context , resultCh chan <- checkgroup.Result ) {
150
+ e .d .Logger ().
151
+ WithField ("request" , r .String ()).
152
+ Trace ("check direct" )
153
+ if rels , _ , err := e .d .RelationTupleManager ().GetRelationTuples (ctx , r .ToQuery ()); err == nil && len (rels ) > 0 {
154
+ resultCh <- checkgroup.Result {
155
+ Membership : checkgroup .IsMember ,
156
+ Tree : & expand.Tree {
157
+ Type : expand .Leaf ,
158
+ Tuple : r ,
159
+ },
160
+ }
161
+ } else {
162
+ resultCh <- checkgroup.Result {
163
+ Membership : checkgroup .NotMember ,
164
+ }
165
+ }
179
166
}
180
167
}
181
168
169
+ // checkIsAllowed checks if the relation tuple is allowed (there is a path from
170
+ // the relation tuple subject to the namespace, object and relation) either
171
+ // directly (in the database), or through subject-set expansions, or through
172
+ // user-set rewrites.
182
173
func (e * Engine ) checkIsAllowed (ctx context.Context , r * RelationTuple , restDepth int ) checkgroup.CheckFunc {
183
174
if restDepth < 0 {
184
- e .d .Logger ().Debug ("reached max-depth, therefore this query will not be further expanded" )
175
+ e .d .Logger ().
176
+ WithField ("method" , "checkIsAllowed" ).
177
+ Debug ("reached max-depth, therefore this query will not be further expanded" )
185
178
return checkgroup .UnknownMemberFunc
186
179
}
187
180
@@ -190,13 +183,21 @@ func (e *Engine) checkIsAllowed(ctx context.Context, r *RelationTuple, restDepth
190
183
Trace ("check is allowed" )
191
184
192
185
g := checkgroup .New (ctx )
193
- g .Add (e .subQuery (r ,
194
- & Query {
195
- Object : r .Object ,
196
- Relation : r .Relation ,
197
- Namespace : r .Namespace ,
198
- }, restDepth ),
199
- )
186
+
187
+ // OLD
188
+ // g.Add(e.subQuery(r,
189
+ // &Query{
190
+ // Object: r.Object,
191
+ // Relation: r.Relation,
192
+ // Namespace: r.Namespace,
193
+ // }, restDepth),
194
+ // )
195
+ // OLD
196
+
197
+ // NEW
198
+ g .Add (e .checkDirect (ctx , r , restDepth - 1 ))
199
+ g .Add (e .checkExpandSubject (ctx , r , restDepth ))
200
+ // NEW
200
201
201
202
relation , err := e .astRelationFor (ctx , r )
202
203
if err != nil {
0 commit comments