forked from canonical/ofga
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtuples.go
195 lines (170 loc) · 5.79 KB
/
tuples.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// Copyright 2023 Canonical Ltd.
// Licensed under the LGPL license, see LICENSE file for details.
package ofga
import (
"fmt"
"regexp"
"time"
openfga "github.com/openfga/go-sdk"
)
// entityRegex is used to validate that a string represents an Entity/EntitySet
// and helps to convert from a string representation into an Entity struct.
var entityRegex = regexp.MustCompile(`([A-Za-z0-9_][A-Za-z0-9_-]*):([A-Za-z0-9_][A-Za-z0-9_@.+-]*|[*])(#([A-Za-z0-9_][A-Za-z0-9_-]*))?$`)
// Kind represents the type of the entity in OpenFGA.
type Kind string
// String implements the Stringer interface.
func (k Kind) String() string {
return string(k)
}
// Relation represents the type of relation between entities in OpenFGA.
type Relation string
// String implements the Stringer interface.
func (r Relation) String() string {
return string(r)
}
// Entity represents an entity/entity-set in OpenFGA.
// Example: `user:<user-id>`, `org:<org-id>#member`
type Entity struct {
Kind Kind
ID string
Relation Relation
}
// IsPublicAccess returns true when the entity ID is the * wildcard, representing any entity.
func (e *Entity) IsPublicAccess() bool {
return e.ID == "*"
}
// String returns a string representation of the entity/entity-set.
func (e *Entity) String() string {
if e.Relation == "" {
return e.Kind.String() + ":" + e.ID
}
return e.Kind.String() + ":" + e.ID + "#" + e.Relation.String()
}
// ParseEntity will parse a string representation into an Entity. It expects to
// find entities of the form:
// - <entityType>:<ID>
// eg. organization:canonical
// - <entityType>:<ID>#<relationship-set>
// eg. organization:canonical#member
func ParseEntity(s string) (Entity, error) {
match := entityRegex.FindStringSubmatch(s)
if match == nil {
return Entity{}, fmt.Errorf("invalid entity representation: %s", s)
}
// Extract and return the relevant information from the sub-matches.
return Entity{
Kind: Kind(match[1]),
ID: match[2],
Relation: Relation(match[4]),
}, nil
}
// Tuple represents a relation between an object and a target. Note that OpenFGA
// represents a Tuple as (User, Relation, Object). However, the `User` field is
// not restricted to just being users, it could also refer to objects when we
// need to create object to object relationships. Hence, we chose to use
// (Object, Relation, Target), as it results in more consistent naming.
type Tuple struct {
Object *Entity
Relation Relation
Target *Entity
}
// ToOpenFGATupleKey converts our Tuple struct into an OpenFGA TupleKey.
func (t Tuple) ToOpenFGATupleKey() *openfga.TupleKey {
k := openfga.NewTupleKeyWithDefaults()
// In some cases, specifying the object is not required.
if t.Object != nil {
k.SetUser(t.Object.String())
}
// In some cases, specifying the relation is not required.
if t.Relation != "" {
k.SetRelation(t.Relation.String())
}
k.SetObject(t.Target.String())
return k
}
// ToOpenFGACheckRequestTupleKey converts our Tuple struct into an
// OpenFGA CheckRequestTupleKey.
func (t Tuple) ToOpenFGACheckRequestTupleKey() *openfga.CheckRequestTupleKey {
tk := t.ToOpenFGATupleKey()
return openfga.NewCheckRequestTupleKey(tk.User, tk.Relation, tk.Object)
}
// ToOpenFGAExpandRequestTupleKey converts our Tuple struct into an
// OpenFGA ExpandRequestTupleKey.
func (t Tuple) ToOpenFGAExpandRequestTupleKey() *openfga.ExpandRequestTupleKey {
tk := t.ToOpenFGATupleKey()
return openfga.NewExpandRequestTupleKey(tk.Relation, tk.Object)
}
// ToOpenFGAReadRequestTupleKey converts our Tuple struct into an
// OpenFGA ReadRequestTupleKey.
func (t Tuple) ToOpenFGAReadRequestTupleKey() *openfga.ReadRequestTupleKey {
k := openfga.NewReadRequestTupleKeyWithDefaults()
// In some cases, specifying the object is not required.
if t.Object != nil {
k.SetUser(t.Object.String())
}
// In some cases, specifying the relation is not required.
if t.Relation != "" {
k.SetRelation(t.Relation.String())
}
k.SetObject(t.Target.String())
return k
}
// ToOpenFGATupleKeyWithoutCondition converts our Tuple struct into an
// OpenFGA TupleKeyWithoutCondition.
func (t Tuple) ToOpenFGATupleKeyWithoutCondition() *openfga.TupleKeyWithoutCondition {
tk := t.ToOpenFGATupleKey()
return openfga.NewTupleKeyWithoutCondition(tk.User, tk.Relation, tk.Object)
}
// FromOpenFGATupleKey converts an openfga.TupleKey struct into a Tuple.
func FromOpenFGATupleKey(key openfga.TupleKey) (Tuple, error) {
var user, object Entity
var err error
if key.User != "" {
user, err = ParseEntity(key.GetUser())
if err != nil {
return Tuple{}, err
}
}
if key.Object != "" {
object, err = ParseEntity(key.GetObject())
if err != nil {
return Tuple{}, err
}
}
return Tuple{
Object: &user,
Relation: Relation(key.GetRelation()),
Target: &object,
}, nil
}
// tuplesToOpenFGATupleKeys converts a slice of tuples into OpenFGA TupleKeys.
func tuplesToOpenFGATupleKeys(tuples []Tuple) []openfga.TupleKey {
keys := make([]openfga.TupleKey, len(tuples))
for i, tuple := range tuples {
keys[i] = *tuple.ToOpenFGATupleKey()
}
return keys
}
// tuplesToOpenFGATupleKeysWithoutCondition converts a slice of tuples into
// a slice of OpenFGA TupleKeyWithoutCondition.
func tuplesToOpenFGATupleKeysWithoutCondition(tuples []Tuple) []openfga.TupleKeyWithoutCondition {
keys := make([]openfga.TupleKeyWithoutCondition, len(tuples))
for i, tuple := range tuples {
keys[i] = *tuple.ToOpenFGATupleKeyWithoutCondition()
}
return keys
}
// isEmpty is a helper method to check whether a tuple is set to a non-empty
// value or not.
func (t Tuple) isEmpty() bool {
if t.Object == nil && t.Relation == "" && t.Target == nil {
return true
}
return false
}
// TimestampedTuple is a tuple accompanied by a timestamp that represents
// the timestamp at which the tuple was created.
type TimestampedTuple struct {
Tuple Tuple
Timestamp time.Time
}