-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathentity_uid.go
More file actions
180 lines (149 loc) · 4.84 KB
/
entity_uid.go
File metadata and controls
180 lines (149 loc) · 4.84 KB
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
package types
import (
"encoding/json"
"errors"
"hash/fnv"
"strconv"
"strings"
"github.com/cedar-policy/cedar-go/internal/mapset"
"github.com/cedar-policy/cedar-go/internal/rust"
)
// Path is a series of idents separated by ::
type Path string
// IsQualified returns whether a Path has any qualifiers (i.e. at least one ::)
func (p Path) IsQualified() bool {
return strings.Contains(string(p), "::")
}
// Qualifier returns a Path with everything but the last element in the original Path or "" if there is only one element.
func (p Path) Qualifier() Path {
idx := strings.LastIndex(string(p), "::")
if idx == -1 {
return ""
}
return p[:idx]
}
// Basename returns the last element in the Path
func (p Path) Basename() string {
idx := strings.LastIndex(string(p), "::")
if idx == -1 {
return string(p)
}
return string(p[idx+2:])
}
// Namespace is a type of Path whose basename does not refer to a type
type Namespace Path
// EntityType is the type portion of an EntityUID
type EntityType Path
// Namespace returns the namespace for the EntityType or "" if the type has no namespace.
func (e EntityType) Namespace() Namespace {
return Namespace(Path(e).Qualifier())
}
// Basename returns the unqualified entity type name.
func (e EntityType) Basename() string {
return Path(e).Basename()
}
// An EntityUID is the identifier for a principal, action, or resource.
type EntityUID struct {
Type EntityType
ID String
}
// NewEntityUID returns an EntityUID given an EntityType and identifier
func NewEntityUID(typ EntityType, id String) EntityUID {
return EntityUID{
Type: typ,
ID: id,
}
}
// IsZero returns true if the EntityUID has an empty Type and ID.
func (e EntityUID) IsZero() bool {
return e.Type == "" && e.ID == ""
}
func (e EntityUID) Equal(bi Value) bool {
b, ok := bi.(EntityUID)
return ok && e == b
}
// String produces a string representation of the EntityUID, e.g. `Type::"id"`.
func (e EntityUID) String() string { return string(e.Type) + "::" + strconv.Quote(string(e.ID)) }
// MarshalCedar produces a valid MarshalCedar language representation of the EntityUID, e.g. `Type::"id"`.
func (e EntityUID) MarshalCedar() []byte {
return []byte(e.String())
}
var errInvalidUID = errors.New("invalid EntityUID")
// UnmarshalCedar parses a Cedar language representation of an EntityUID.
func (e *EntityUID) UnmarshalCedar(data []byte) error {
// NB: In a perfect world we'd use the full parsing from internal/parser, but
// today that imports cedar-go/types (this pkg) which means we'd need to carve
// it out to reuse it. Given that NewEntityUID(.,.) does zero validation
// itself, the juice is not worth the squeeze today.
s := string(data)
idx := strings.Index(s, "::\"")
if idx <= 0 {
// If idx == 0, the entity has no type, which is invalid.
return errInvalidUID
}
typ := EntityType(s[:idx])
quoted := s[idx+2:] // include the leading `"`
if len(quoted) < 2 || quoted[0] != '"' || quoted[len(quoted)-1] != '"' {
return errInvalidUID
}
id, _, err := rust.Unquote([]byte(quoted[1:len(quoted)-1]), false)
if err != nil {
return errInvalidUID
}
*e = NewEntityUID(typ, String(id))
return nil
}
func (e *EntityUID) UnmarshalJSON(b []byte) error {
// TODO: review after adding support for schemas
var res entityValueJSON
if err := json.Unmarshal(b, &res); err != nil {
return err
}
if res.Entity != nil {
e.Type = EntityType(res.Entity.Type)
e.ID = String(res.Entity.ID)
return nil
} else if res.Type != nil && res.ID != nil { // require both Type and ID to parse "implicit" JSON
e.Type = EntityType(*res.Type)
e.ID = String(*res.ID)
return nil
}
return errJSONEntityNotFound
}
// MarshalJSON marshals the EntityUID into JSON using the explicit form.
func (e EntityUID) MarshalJSON() ([]byte, error) {
return json.Marshal(entityValueJSON{
Entity: &extEntity{
Type: string(e.Type),
ID: string(e.ID),
},
})
}
func (e EntityUID) MarshalBinary() ([]byte, error) {
return e.MarshalCedar(), nil
}
func (e *EntityUID) UnmarshalBinary(data []byte) error {
return e.UnmarshalCedar(data)
}
func (e EntityUID) hash() uint64 {
h := fnv.New64()
_, _ = h.Write([]byte(e.Type))
_, _ = h.Write([]byte(e.ID))
return h.Sum64()
}
// ImplicitlyMarshaledEntityUID exists to allow the marshaling of the EntityUID into JSON using the implicit form. Users
// can opt in to this form if they know that this EntityUID will be serialized to a place where its type will be
// unambiguous.
type ImplicitlyMarshaledEntityUID EntityUID
func (i ImplicitlyMarshaledEntityUID) MarshalJSON() ([]byte, error) {
s := struct {
Type EntityType `json:"type"`
ID String `json:"id"`
}{i.Type, i.ID}
return json.Marshal(s)
}
type EntityUIDSet = mapset.ImmutableMapSet[EntityUID]
// NewEntityUIDSet returns an immutable EntityUIDSet ready for use.
func NewEntityUIDSet(args ...EntityUID) EntityUIDSet {
return mapset.Immutable[EntityUID](args...)
}