Passed
Push — master ( 64abf6...0f1b55 )
by Tolga
01:31 queued 15s
created

schema.LinkedEntrance.LinkedEntranceKind   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
package schema
2
3
import (
4
	"errors"
5
6
	"github.com/Permify/permify/pkg/dsl/utils"
7
	base "github.com/Permify/permify/pkg/pb/base/v1"
8
)
9
10
// LinkedSchemaGraph represents a graph of linked schema objects. The schema object contains definitions for entities,
11
// relationships, and permissions, and the graph is constructed by linking objects together based on their dependencies. The
12
// graph is used by the PermissionEngine to resolve permissions and expand user sets for a given request.
13
//
14
// Fields:
15
//   - schema: pointer to the base.SchemaDefinition that defines the schema objects in the graph
16
type LinkedSchemaGraph struct {
17
	schema *base.SchemaDefinition
18
}
19
20
// NewLinkedGraph returns a new instance of LinkedSchemaGraph with the specified base.SchemaDefinition as its schema.
21
// The schema object contains definitions for entities, relationships, and permissions, and is used to construct a graph of
22
// linked schema objects. The graph is used by the PermissionEngine to resolve permissions and expand user sets for a
23
// given request.
24
//
25
// Parameters:
26
//   - schema: pointer to the base.SchemaDefinition that defines the schema objects in the graph
27
//
28
// Returns:
29
//   - pointer to a new instance of LinkedSchemaGraph with the specified schema object
30
func NewLinkedGraph(schema *base.SchemaDefinition) *LinkedSchemaGraph {
31
	return &LinkedSchemaGraph{
32
		schema: schema,
33
	}
34
}
35
36
// LinkedEntranceKind is a string type that represents the kind of LinkedEntrance object. An LinkedEntrance object defines an entry point
37
// into the LinkedSchemaGraph, which is used to resolve permissions and expand user sets for a given request.
38
//
39
// Values:
40
//   - RelationLinkedEntrance: represents an entry point into a relationship object in the schema graph
41
//   - TupleToUserSetLinkedEntrance: represents an entry point into a tuple-to-user-set object in the schema graph
42
//   - ComputedUserSetLinkedEntrance: represents an entry point into a computed user set object in the schema graph
43
type LinkedEntranceKind string
44
45
const (
46
	RelationLinkedEntrance        LinkedEntranceKind = "relation"
47
	TupleToUserSetLinkedEntrance  LinkedEntranceKind = "tuple_to_user_set"
48
	ComputedUserSetLinkedEntrance LinkedEntranceKind = "computed_user_set"
49
	AttributeLinkedEntrance       LinkedEntranceKind = "attribute"
50
)
51
52
// LinkedEntrance represents an entry point into the LinkedSchemaGraph, which is used to resolve permissions and expand user
53
// sets for a given request. The object contains a kind that specifies the type of entry point (e.g. relation, tuple-to-user-set),
54
// an entry point reference that identifies the specific entry point in the graph, and a tuple set relation reference that
55
// specifies the relation to use when expanding user sets for the entry point.
56
//
57
// Fields:
58
//   - Kind: LinkedEntranceKind representing the type of entry point
59
//   - LinkedEntrance: pointer to a base.RelationReference that identifies the entry point in the schema graph
60
//   - TupleSetRelation: pointer to a base.RelationReference that specifies the relation to use when expanding user sets
61
//     for the entry point
62
type LinkedEntrance struct {
63
	Kind             LinkedEntranceKind
64
	TargetEntrance   *base.Entrance
65
	TupleSetRelation string
66
}
67
68
// LinkedEntranceKind returns the kind of the LinkedEntrance object. The kind specifies the type of entry point (e.g. relation,
69
// tuple-to-user-set, computed user set).
70
//
71
// Returns:
72
//   - LinkedEntranceKind representing the type of entry point
73
func (re LinkedEntrance) LinkedEntranceKind() LinkedEntranceKind {
74
	return re.Kind
75
}
76
77
// RelationshipLinkedEntrances returns a slice of LinkedEntrance objects that represent entry points into the LinkedSchemaGraph
78
// for the specified target and source relations. The function recursively searches the graph for all entry points that can
79
// be reached from the target relation through the specified source relation. The resulting entry points contain a reference
80
// to the relation object in the schema graph and the relation used to expand user sets for the entry point. If the target or
81
// source relation does not exist in the schema graph, the function returns an error.
82
//
83
// Parameters:
84
//   - target: pointer to a base.RelationReference that identifies the target relation
85
//   - source: pointer to a base.RelationReference that identifies the source relation used to reach the target relation
86
//
87
// Returns:
88
//   - slice of LinkedEntrance objects that represent entry points into the LinkedSchemaGraph, or an error if the target or
89
//     source relation does not exist in the schema graph
90
func (g *LinkedSchemaGraph) LinkedEntrances(target, source *base.Entrance) ([]*LinkedEntrance, error) {
91
	entries, err := g.findEntrance(target, source, map[string]struct{}{})
92
	if err != nil {
93
		return nil, err
94
	}
95
96
	return entries, nil
97
}
98
99
// findEntrance is a recursive helper function that searches the LinkedSchemaGraph for all entry points that can be reached
100
// from the specified target relation through the specified source relation. The function uses a depth-first search to traverse
101
// the schema graph and identify entry points, marking visited nodes in a map to avoid infinite recursion. If the target or
102
// source relation does not exist in the schema graph, the function returns an error. If the source relation is an action
103
// reference, the function recursively searches the graph for entry points reachable from the action child. If the source
104
// relation is a regular relational reference, the function delegates to findRelationEntrance to search for entry points.
105
//
106
// Parameters:
107
//   - target: pointer to a base.RelationReference that identifies the target relation
108
//   - source: pointer to a base.RelationReference that identifies the source relation used to reach the target relation
109
//   - visited: map used to track visited nodes and avoid infinite recursion
110
//
111
// Returns:
112
//   - slice of LinkedEntrance objects that represent entry points into the LinkedSchemaGraph, or an error if the target or
113
//     source relation does not exist in the schema graph
114
func (g *LinkedSchemaGraph) findEntrance(target, source *base.Entrance, visited map[string]struct{}) ([]*LinkedEntrance, error) {
115
	key := utils.Key(target.GetType(), target.GetValue())
116
	if _, ok := visited[key]; ok {
117
		return nil, nil
118
	}
119
	visited[key] = struct{}{}
120
121
	def, ok := g.schema.EntityDefinitions[target.GetType()]
122
	if !ok {
123
		return nil, errors.New("entity definition not found")
124
	}
125
126
	switch def.References[target.GetValue()] {
127
	case base.EntityDefinition_REFERENCE_PERMISSION:
128
		permission, ok := def.Permissions[target.GetValue()]
129
		if !ok {
130
			return nil, errors.New("permission not found")
131
		}
132
		child := permission.GetChild()
133
		if child.GetRewrite() != nil {
134
			return g.findEntranceRewrite(target, source, child.GetRewrite(), visited)
135
		}
136
		return g.findEntranceLeaf(target, source, child.GetLeaf(), visited)
137
	case base.EntityDefinition_REFERENCE_ATTRIBUTE:
138
		attribute, ok := def.Attributes[target.GetValue()]
139
		if !ok {
140
			return nil, errors.New("attribute not found")
141
		}
142
		return []*LinkedEntrance{
143
			{
144
				Kind: AttributeLinkedEntrance,
145
				TargetEntrance: &base.Entrance{
146
					Type:  target.GetType(),
147
					Value: attribute.GetName(),
148
				},
149
			},
150
		}, nil
151
	case base.EntityDefinition_REFERENCE_RELATION:
152
		return g.findRelationEntrance(target, source, visited)
153
	default:
154
		return nil, ErrUnimplemented
155
	}
156
}
157
158
// findRelationEntrance is a helper function that searches the LinkedSchemaGraph for entry points that can be reached from
159
// the specified target relation through the specified source relation. The function only returns entry points that are directly
160
// related to the target relation (i.e. the relation specified by the source reference is one of the relation's immediate children).
161
// The function recursively searches the children of the target relation and returns all reachable entry points. If the target
162
// or source relation does not exist in the schema graph, the function returns an error.
163
//
164
// Parameters:
165
//   - target: pointer to a base.RelationReference that identifies the target relation
166
//   - source: pointer to a base.RelationReference that identifies the source relation used to reach the target relation
167
//   - visited: map used to track visited nodes and avoid infinite recursion
168
//
169
// Returns:
170
//   - slice of LinkedEntrance objects that represent entry points into the LinkedSchemaGraph, or an error if the target or
171
//     source relation does not exist in the schema graph
172
func (g *LinkedSchemaGraph) findRelationEntrance(target, source *base.Entrance, visited map[string]struct{}) ([]*LinkedEntrance, error) {
173
	var res []*LinkedEntrance
174
175
	entity, ok := g.schema.EntityDefinitions[target.GetType()]
176
	if !ok {
177
		return nil, errors.New("entity definition not found")
178
	}
179
180
	relation, ok := entity.Relations[target.GetValue()]
181
	if !ok {
182
		return nil, errors.New("relation definition not found")
183
	}
184
185
	if IsDirectlyRelated(relation, source) {
186
		res = append(res, &LinkedEntrance{
187
			Kind: RelationLinkedEntrance,
188
			TargetEntrance: &base.Entrance{
189
				Type:  target.GetType(),
190
				Value: target.GetValue(),
191
			},
192
		})
193
	}
194
195
	for _, rel := range relation.GetRelationReferences() {
196
		if rel.GetRelation() != "" {
197
			entrances, err := g.findEntrance(&base.Entrance{
198
				Type:  rel.GetType(),
199
				Value: rel.GetRelation(),
200
			}, source, visited)
201
			if err != nil {
202
				return nil, err
203
			}
204
			res = append(res, entrances...)
205
		}
206
	}
207
208
	return res, nil
209
}
210
211
// findEntranceWithLeaf is a helper function that searches the LinkedSchemaGraph for entry points that can be reached from
212
// the specified target relation through an action reference with a leaf child. The function searches for entry points that are
213
// reachable through a tuple-to-user-set or computed-user-set action. If the action child is a tuple-to-user-set action, the
214
// function recursively searches for entry points reachable through the child's tuple set relation and the child's computed user
215
// set relation. If the action child is a computed-user-set action, the function recursively searches for entry points reachable
216
// through the computed user set relation. The function only returns entry points that can be reached from the target relation
217
// using the specified source relation. If the target or source relation does not exist in the schema graph, the function returns
218
// an error.
219
//
220
// Parameters:
221
//   - target: pointer to a base.RelationReference that identifies the target relation
222
//   - source: pointer to a base.RelationReference that identifies the source relation used to reach the target relation
223
//   - leaf: pointer to a base.Leaf object that represents the child of an action reference
224
//   - visited: map used to track visited nodes and avoid infinite recursion
225
//
226
// Returns:
227
//   - slice of LinkedEntrance objects that represent entry points into the LinkedSchemaGraph, or an error if the target or
228
//     source relation does not exist in the schema graph
229
func (g *LinkedSchemaGraph) findEntranceLeaf(target, source *base.Entrance, leaf *base.Leaf, visited map[string]struct{}) ([]*LinkedEntrance, error) {
230
	switch t := leaf.GetType().(type) {
231
	case *base.Leaf_TupleToUserSet:
232
		tupleSet := t.TupleToUserSet.GetTupleSet().GetRelation()
233
		computedUserSet := t.TupleToUserSet.GetComputed().GetRelation()
234
235
		var res []*LinkedEntrance
236
		entityDefinitions, exists := g.schema.EntityDefinitions[target.GetType()]
237
		if !exists {
238
			return nil, errors.New("entity definition not found")
239
		}
240
241
		relations, exists := entityDefinitions.Relations[tupleSet]
242
		if !exists {
243
			return nil, errors.New("relation definition not found")
244
		}
245
246
		for _, rel := range relations.GetRelationReferences() {
247
			if rel.GetType() == source.GetType() && source.GetValue() == computedUserSet {
248
				res = append(res, &LinkedEntrance{
249
					Kind:             TupleToUserSetLinkedEntrance,
250
					TargetEntrance:   target,
251
					TupleSetRelation: tupleSet,
252
				})
253
			}
254
255
			results, err := g.findEntrance(
256
				&base.Entrance{
257
					Type:  rel.GetType(),
258
					Value: computedUserSet,
259
				},
260
				source,
261
				visited,
262
			)
263
			if err != nil {
264
				return nil, err
265
			}
266
			res = append(res, results...)
267
		}
268
		return res, nil
269
	case *base.Leaf_ComputedUserSet:
270
		var entrances []*LinkedEntrance
271
272
		if target.GetType() == source.GetType() && t.ComputedUserSet.GetRelation() == source.GetValue() {
273
			entrances = append(entrances, &LinkedEntrance{
274
				Kind:           ComputedUserSetLinkedEntrance,
275
				TargetEntrance: target,
276
			})
277
		}
278
279
		results, err := g.findEntrance(
280
			&base.Entrance{
281
				Type:  target.GetType(),
282
				Value: t.ComputedUserSet.GetRelation(),
283
			},
284
			source,
285
			visited,
286
		)
287
		if err != nil {
288
			return nil, err
289
		}
290
291
		entrances = append(
292
			entrances,
293
			results...,
294
		)
295
		return entrances, nil
296
	case *base.Leaf_ComputedAttribute:
297
		var entrances []*LinkedEntrance
298
		entrances = append(entrances, &LinkedEntrance{
299
			Kind: AttributeLinkedEntrance,
300
			TargetEntrance: &base.Entrance{
301
				Type:  target.GetType(),
302
				Value: t.ComputedAttribute.GetName(),
303
			},
304
		})
305
		return entrances, nil
306
	case *base.Leaf_Call:
307
		var entrances []*LinkedEntrance
308
		for _, arg := range t.Call.GetArguments() {
309
			computedAttr := arg.GetComputedAttribute()
310
			if computedAttr != nil {
311
				entrances = append(entrances, &LinkedEntrance{
312
					Kind: AttributeLinkedEntrance,
313
					TargetEntrance: &base.Entrance{
314
						Type:  target.GetType(),
315
						Value: computedAttr.GetName(),
316
					},
317
				})
318
			}
319
		}
320
		return entrances, nil
321
	default:
322
		return nil, ErrUndefinedLeafType
323
	}
324
}
325
326
// findEntranceWithRewrite is a helper function that searches the LinkedSchemaGraph for entry points that can be reached from
327
// the specified target relation through an action reference with a rewrite child. The function recursively searches each child of
328
// the rewrite and calls either findEntranceWithRewrite or findEntranceWithLeaf, depending on the child's type. The function
329
// only returns entry points that can be reached from the target relation using the specified source relation. If the target or
330
// source relation does not exist in the schema graph, the function returns an error.
331
//
332
// Parameters:
333
//   - target: pointer to a base.RelationReference that identifies the target relation
334
//   - source: pointer to a base.RelationReference that identifies the source relation used to reach the target relation
335
//   - rewrite: pointer to a base.Rewrite object that represents the child of an action reference
336
//   - visited: map used to track visited nodes and avoid infinite recursion
337
//
338
// Returns:
339
//   - slice of LinkedEntrance objects that represent entry points into the LinkedSchemaGraph, or an error if the target or
340
//     source relation does not exist in the schema graph
341
func (g *LinkedSchemaGraph) findEntranceRewrite(target, source *base.Entrance, rewrite *base.Rewrite, visited map[string]struct{}) (results []*LinkedEntrance, err error) {
342
	var res []*LinkedEntrance
343
	for _, child := range rewrite.GetChildren() {
344
		switch child.GetType().(type) {
345
		case *base.Child_Rewrite:
346
			results, err = g.findEntranceRewrite(target, source, child.GetRewrite(), visited)
347
			if err != nil {
348
				return nil, err
349
			}
350
		case *base.Child_Leaf:
351
			results, err = g.findEntranceLeaf(target, source, child.GetLeaf(), visited)
352
			if err != nil {
353
				return nil, err
354
			}
355
		default:
356
			return nil, errors.New("undefined child type")
357
		}
358
		res = append(res, results...)
359
	}
360
	return res, nil
361
}
362