|
1
|
|
|
package graph |
|
2
|
|
|
|
|
3
|
|
|
import ( |
|
4
|
|
|
"errors" |
|
5
|
|
|
"fmt" |
|
6
|
|
|
"strings" |
|
7
|
|
|
|
|
8
|
|
|
"github.com/rs/xid" |
|
9
|
|
|
|
|
10
|
|
|
"github.com/Permify/permify/internal/schema" |
|
11
|
|
|
base "github.com/Permify/permify/pkg/pb/base/v1" |
|
12
|
|
|
"github.com/Permify/permify/pkg/tuple" |
|
13
|
|
|
) |
|
14
|
|
|
|
|
15
|
|
|
// SchemaToGraph takes a schema definition and converts it into a graph |
|
16
|
|
|
// representation, returning the created graph and an error if any occurs. |
|
17
|
|
|
func SchemaToGraph(schema *base.SchemaDefinition) (g Graph, err error) { |
|
18
|
|
|
// Iterate through entity definitions in the schema |
|
19
|
|
|
for _, en := range schema.GetEntityDefinitions() { |
|
20
|
|
|
// Convert each entity into a graph |
|
21
|
|
|
eg, err := EntityToGraph(en) |
|
22
|
|
|
if err != nil { |
|
23
|
|
|
return Graph{}, err |
|
24
|
|
|
} |
|
25
|
|
|
// Add the nodes and edges from the entity graph to the schema graph |
|
26
|
|
|
g.AddNodes(eg.Nodes()) |
|
27
|
|
|
g.AddEdges(eg.Edges()) |
|
28
|
|
|
} |
|
29
|
|
|
return |
|
30
|
|
|
} |
|
31
|
|
|
|
|
32
|
|
|
// EntityToGraph takes an entity definition and converts it into a graph |
|
33
|
|
|
// representation, returning the created graph and an error if any occurs. |
|
34
|
|
|
func EntityToGraph(entity *base.EntityDefinition) (g Graph, err error) { |
|
35
|
|
|
// Create a node for the entity |
|
36
|
|
|
enNode := &Node{ |
|
37
|
|
|
Type: "entity", |
|
38
|
|
|
ID: entity.GetName(), |
|
39
|
|
|
Label: entity.GetName(), |
|
40
|
|
|
} |
|
41
|
|
|
g.AddNode(enNode) |
|
42
|
|
|
|
|
43
|
|
|
// Iterate through the relations in the entity |
|
44
|
|
|
for _, re := range entity.GetRelations() { |
|
45
|
|
|
// Create a node for each relation |
|
46
|
|
|
reNode := &Node{ |
|
47
|
|
|
Type: "relation", |
|
48
|
|
|
ID: fmt.Sprintf("%s#%s", entity.GetName(), re.GetName()), |
|
49
|
|
|
Label: re.Name, |
|
50
|
|
|
} |
|
51
|
|
|
|
|
52
|
|
|
// Iterate through the relation references |
|
53
|
|
|
for _, ref := range re.GetRelationReferences() { |
|
54
|
|
|
if ref.GetRelation() != "" { |
|
55
|
|
|
g.AddEdge(reNode, &Node{ |
|
56
|
|
|
Type: "relation", |
|
57
|
|
|
ID: fmt.Sprintf("%s#%s", ref.GetType(), ref.GetRelation()), |
|
58
|
|
|
Label: re.Name, |
|
59
|
|
|
}, nil) |
|
60
|
|
|
} else { |
|
61
|
|
|
g.AddEdge(reNode, &Node{ |
|
62
|
|
|
Type: "entity", |
|
63
|
|
|
ID: fmt.Sprintf("%s", ref.GetType()), |
|
64
|
|
|
Label: re.Name, |
|
65
|
|
|
}, nil) |
|
66
|
|
|
} |
|
67
|
|
|
} |
|
68
|
|
|
|
|
69
|
|
|
// Add relation node and edge to the graph |
|
70
|
|
|
g.AddNode(reNode) |
|
71
|
|
|
g.AddEdge(enNode, reNode, nil) |
|
72
|
|
|
} |
|
73
|
|
|
|
|
74
|
|
|
// Iterate through the permissions in the entity |
|
75
|
|
|
for _, permission := range entity.GetPermissions() { |
|
76
|
|
|
// Create a node for each permission |
|
77
|
|
|
acNode := &Node{ |
|
78
|
|
|
Type: "permission", |
|
79
|
|
|
ID: fmt.Sprintf("%s#%s", entity.GetName(), permission.GetName()), |
|
80
|
|
|
Label: permission.GetName(), |
|
81
|
|
|
} |
|
82
|
|
|
g.AddNode(acNode) |
|
83
|
|
|
g.AddEdge(enNode, acNode, nil) |
|
84
|
|
|
// Build permission graph for each permission |
|
85
|
|
|
ag, err := buildPermissionGraph(entity, acNode, []*base.Child{permission.GetChild()}) |
|
86
|
|
|
if err != nil { |
|
87
|
|
|
return Graph{}, err |
|
88
|
|
|
} |
|
89
|
|
|
// Add nodes and edges from permission graph to entity graph |
|
90
|
|
|
g.AddNodes(ag.Nodes()) |
|
91
|
|
|
g.AddEdges(ag.Edges()) |
|
92
|
|
|
} |
|
93
|
|
|
return |
|
94
|
|
|
} |
|
95
|
|
|
|
|
96
|
|
|
// buildActionGraph creates a permission graph for the given entity and node, |
|
97
|
|
|
// and recursively processes the children of the node. Returns the created |
|
98
|
|
|
// graph and an error if any occurs. |
|
99
|
|
|
func buildPermissionGraph(entity *base.EntityDefinition, from *Node, children []*base.Child) (g Graph, err error) { |
|
100
|
|
|
// Iterate through the children |
|
101
|
|
|
for _, child := range children { |
|
102
|
|
|
switch child.GetType().(type) { |
|
103
|
|
|
case *base.Child_Rewrite: |
|
104
|
|
|
// Create a node for the rewrite operation |
|
105
|
|
|
rw := &Node{ |
|
106
|
|
|
Type: "operation", |
|
107
|
|
|
ID: xid.New().String(), |
|
108
|
|
|
Label: child.GetRewrite().GetRewriteOperation().String(), |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
// Add the rewrite node to the graph and connect it to the parent node |
|
112
|
|
|
g.AddNode(rw) |
|
113
|
|
|
g.AddEdge(from, rw, nil) |
|
114
|
|
|
// Recursively process the children of the rewrite node |
|
115
|
|
|
ag, err := buildPermissionGraph(entity, rw, child.GetRewrite().GetChildren()) |
|
116
|
|
|
if err != nil { |
|
117
|
|
|
return Graph{}, err |
|
118
|
|
|
} |
|
119
|
|
|
// Add the nodes and edges from the child graph to the current graph |
|
120
|
|
|
g.AddNodes(ag.Nodes()) |
|
121
|
|
|
g.AddEdges(ag.Edges()) |
|
122
|
|
|
case *base.Child_Leaf: |
|
123
|
|
|
// Process the leaf node |
|
124
|
|
|
leaf := child.GetLeaf() |
|
125
|
|
|
|
|
126
|
|
|
switch leaf.GetType().(type) { |
|
127
|
|
|
case *base.Leaf_TupleToUserSet: |
|
128
|
|
|
// Find the relation in the entity definition |
|
129
|
|
|
re, err := schema.GetRelationByNameInEntityDefinition(entity, leaf.GetTupleToUserSet().GetTupleSet().GetRelation()) |
|
130
|
|
|
if err != nil { |
|
131
|
|
|
return Graph{}, errors.New(base.ErrorCode_ERROR_CODE_RELATION_DEFINITION_NOT_FOUND.String()) |
|
132
|
|
|
} |
|
133
|
|
|
|
|
134
|
|
|
// Add an edge between the parent node and the tuple set relation node |
|
135
|
|
|
g.AddEdge(from, &Node{ |
|
136
|
|
|
Type: "relation", |
|
137
|
|
|
ID: fmt.Sprintf("%s#%s", GetTupleSetReferenceReference(re), leaf.GetTupleToUserSet().GetComputed().GetRelation()), |
|
|
|
|
|
|
138
|
|
|
Label: leaf.GetTupleToUserSet().GetComputed().GetRelation(), |
|
139
|
|
|
}, leaf.GetExclusion()) |
|
140
|
|
|
|
|
141
|
|
|
case *base.Leaf_ComputedUserSet: |
|
142
|
|
|
// Add an edge between the parent node and the computed user set relation node |
|
143
|
|
|
g.AddEdge(from, &Node{ |
|
144
|
|
|
Type: "relation", |
|
145
|
|
|
ID: fmt.Sprintf("%s#%s", entity.GetName(), leaf.GetComputedUserSet().GetRelation()), |
|
|
|
|
|
|
146
|
|
|
Label: leaf.GetComputedUserSet().GetRelation(), |
|
147
|
|
|
}, leaf.GetExclusion()) |
|
148
|
|
|
default: |
|
149
|
|
|
break |
|
150
|
|
|
} |
|
151
|
|
|
} |
|
152
|
|
|
} |
|
153
|
|
|
return |
|
154
|
|
|
} |
|
155
|
|
|
|
|
156
|
|
|
// GetTupleSetReferenceReference iterates through the relation references |
|
157
|
|
|
// and returns the first reference that doesn't contain a "#" symbol. |
|
158
|
|
|
// If no such reference is found, it returns the tuple.USER constant. |
|
159
|
|
|
func GetTupleSetReferenceReference(definition *base.RelationDefinition) string { |
|
160
|
|
|
for _, ref := range definition.GetRelationReferences() { |
|
161
|
|
|
if !strings.Contains(ref.String(), "#") { |
|
162
|
|
|
return ref.GetType() |
|
163
|
|
|
} |
|
164
|
|
|
} |
|
165
|
|
|
return tuple.USER |
|
166
|
|
|
} |
|
167
|
|
|
|