roles.gno
7.43 Kb · 319 lines
1package rbac
2
3import (
4 "strconv"
5 "strings"
6
7 "gno.land/p/akkadia/v0/ds/btree"
8 "gno.land/p/akkadia/v0/ds/btreeset"
9)
10
11const maxRoleID uint32 = 4294967295
12
13type role struct {
14 ID uint32
15 Name string
16 Description string
17 metadata map[string]string
18 permissionIDs *btreeset.Uint32BTreeSet
19 assignmentCheck AssignmentCheck
20}
21
22func (r *role) AddPermission(permissionID uint32) {
23 if r.permissionIDs.Has(permissionID) {
24 panic("role already has permission: role=" + r.Name + ", permissionID=" + formatRoleID(permissionID))
25 }
26 r.permissionIDs.Set(permissionID)
27}
28
29func (r *role) HasPermission(permissionID uint32) bool {
30 return r.permissionIDs.Has(permissionID)
31}
32
33func (r *role) DeletePermission(permissionID uint32) {
34 if !r.permissionIDs.Has(permissionID) {
35 panic("role permission not found: role=" + r.Name + ", permissionID=" + formatRoleID(permissionID))
36 }
37 r.permissionIDs.Remove(permissionID)
38}
39
40func (r *role) PermissionIDs() []uint32 {
41 result := make([]uint32, 0, r.permissionIDs.Size())
42 r.permissionIDs.Iterate(nil, nil, func(permissionID uint32) bool {
43 result = append(result, permissionID)
44 return false
45 })
46 return result
47}
48
49type roles struct {
50 idCounter uint32
51 degree int
52 roleMap *btree.Uint32BTree
53 nameIndex *btree.StringBTree
54}
55
56func newRoles(degree int) *roles {
57 return &roles{
58 degree: degree,
59 roleMap: btree.NewUint32BTree(degree),
60 nameIndex: btree.NewStringBTree(degree),
61 }
62}
63
64func (rs *roles) Size() int {
65 return rs.roleMap.Size()
66}
67
68func (rs *roles) Add(name string, description string, permissionIDs []uint32, metadata map[string]string, assignmentCheck AssignmentCheck) {
69 rs.validateName(name)
70 if rs.nameIndex.Has(name) {
71 panic("role already exists: " + name)
72 }
73
74 id := rs.nextID()
75 stored := &role{
76 ID: id,
77 Name: name,
78 Description: description,
79 metadata: copyMetadata(metadata),
80 permissionIDs: permissionIDSet(permissionIDs, rs.degree),
81 assignmentCheck: assignmentCheck,
82 }
83 rs.roleMap.Set(id, stored)
84 rs.nameIndex.Set(stored.Name, id)
85}
86
87func (rs *roles) Update(name string, newName string, description string, permissionIDs []uint32, metadata map[string]string, assignmentCheck AssignmentCheck) {
88 rs.validateName(name)
89 rs.validateName(newName)
90
91 stored := rs.role(name)
92 if name != newName && rs.nameIndex.Has(newName) {
93 panic("role already exists: " + newName)
94 }
95
96 storedPermissionIDs := permissionIDSet(permissionIDs, rs.degree)
97 if stored.Name != newName {
98 rs.nameIndex.Remove(stored.Name)
99 rs.nameIndex.Set(newName, stored.ID)
100 stored.Name = newName
101 }
102 stored.Description = description
103 ensureMetadata(stored)
104 mergeMetadata(stored.metadata, metadata)
105 stored.permissionIDs = storedPermissionIDs
106 stored.assignmentCheck = assignmentCheck
107}
108
109func (rs *roles) Delete(name string) {
110 stored := rs.role(name)
111 rs.roleMap.Remove(stored.ID)
112 rs.nameIndex.Remove(stored.Name)
113}
114
115func (rs *roles) Has(name string) bool {
116 rs.validateName(name)
117 return rs.nameIndex.Has(name)
118}
119
120func (rs *roles) Get(name string) *role {
121 return rs.role(name)
122}
123
124func (rs *roles) List(page int, count int) []*role {
125 offset := pageOffset(page, count)
126 result := make([]*role, 0, count)
127 rs.nameIndex.IterateByOffset(offset, count, func(_ string, idValue any) bool {
128 id, ok := idValue.(uint32)
129 if !ok {
130 panic("invalid role index")
131 }
132 result = append(result, rs.roleByID(id))
133 return false
134 })
135 return result
136}
137
138func (rs *roles) AddPermission(roleName string, permissionID uint32) {
139 stored := rs.role(roleName)
140 stored.AddPermission(permissionID)
141}
142
143func (rs *roles) DeletePermission(roleName string, permissionID uint32) {
144 stored := rs.role(roleName)
145 stored.DeletePermission(permissionID)
146}
147
148func (rs *roles) DeletePermissionFromAll(permissionID uint32) {
149 rs.roleMap.Iterate(nil, nil, func(_ uint32, roleValue any) bool {
150 stored, ok := roleValue.(*role)
151 if !ok {
152 panic("invalid role")
153 }
154 stored.permissionIDs.Remove(permissionID)
155 return false
156 })
157}
158
159func (rs *roles) HasPermission(roleName string, permissionID uint32) bool {
160 stored := rs.role(roleName)
161 return stored.HasPermission(permissionID)
162}
163
164func (rs *roles) Metadata(name string) map[string]string {
165 return copyMetadata(rs.role(name).metadata)
166}
167
168func (rs *roles) SetMetadata(name string, key string, value string) {
169 rs.validateMutableMetadataKey(key)
170 stored := rs.role(name)
171 ensureMetadata(stored)
172 stored.metadata[key] = value
173}
174
175func (rs *roles) SetMetadataCSV(name string, keys string, values string) {
176 pending := rs.parseMetadataCSV(keys, values)
177 stored := rs.role(name)
178 ensureMetadata(stored)
179 mergeMetadata(stored.metadata, pending)
180}
181
182func (rs *roles) role(name string) *role {
183 rs.validateName(name)
184
185 idValue, found := rs.nameIndex.Get(name)
186 if !found {
187 panic("role not found: " + name)
188 }
189 id, ok := idValue.(uint32)
190 if !ok {
191 panic("invalid role index")
192 }
193 return rs.roleByID(id)
194}
195
196func (rs *roles) roleByID(id uint32) *role {
197 roleValue, found := rs.roleMap.Get(id)
198 if !found {
199 panic("role not found: " + formatRoleID(id))
200 }
201 stored, ok := roleValue.(*role)
202 if !ok {
203 panic("invalid role: " + formatRoleID(id))
204 }
205 return stored
206}
207
208func (rs *roles) nextID() uint32 {
209 if rs.idCounter == maxRoleID {
210 panic("role id exhausted")
211 }
212 id := rs.idCounter
213 rs.idCounter += 1
214 return id
215}
216
217func (rs *roles) validateName(name string) {
218 if name == "" {
219 panic("role name required")
220 }
221}
222
223func (rs *roles) validateMetadataKey(key string) {
224 if key == "" {
225 panic("role property key must be not empty")
226 }
227 if key == "name" {
228 panic("reserved key: " + key)
229 }
230}
231
232func (rs *roles) validateMutableMetadataKey(key string) {
233 rs.validateMetadataKey(key)
234 if key == "maxAssign" {
235 panic("reserved key: " + key)
236 }
237}
238
239func (rs *roles) parseMetadataCSV(keys string, values string) map[string]string {
240 if keys == "" {
241 panic("role property keys must be not empty")
242 }
243 if values == "" {
244 panic("role property values must be not empty")
245 }
246
247 pending := map[string]string{}
248 keyStart, valStart := 0, 0
249 keyIdx, valIdx := 0, 0
250 for {
251 for keyIdx < len(keys) && keys[keyIdx] != ',' {
252 keyIdx++
253 }
254 for valIdx < len(values) && values[valIdx] != ',' {
255 valIdx++
256 }
257
258 key := strings.TrimSpace(keys[keyStart:keyIdx])
259 value := values[valStart:valIdx]
260 if key == "" {
261 panic("role property key must be not empty")
262 }
263 if strings.TrimSpace(value) == "" {
264 panic("role property value must be not empty")
265 }
266 rs.validateMutableMetadataKey(key)
267 pending[key] = value
268
269 keyEnd := keyIdx >= len(keys)
270 valEnd := valIdx >= len(values)
271 if keyEnd != valEnd {
272 panic("role property keys and values count mismatch")
273 }
274 if keyEnd {
275 break
276 }
277
278 keyIdx++
279 valIdx++
280 keyStart = keyIdx
281 valStart = valIdx
282 }
283 return pending
284}
285
286func permissionIDSet(permissionIDs []uint32, degree int) *btreeset.Uint32BTreeSet {
287 result := btreeset.NewUint32BTreeSet(degree)
288 for _, permissionID := range permissionIDs {
289 if result.Has(permissionID) {
290 panic("duplicate role permission: " + formatRoleID(permissionID))
291 }
292 result.Set(permissionID)
293 }
294 return result
295}
296
297func copyMetadata(source map[string]string) map[string]string {
298 result := map[string]string{}
299 for key, value := range source {
300 result[key] = value
301 }
302 return result
303}
304
305func ensureMetadata(stored *role) {
306 if stored.metadata == nil {
307 stored.metadata = map[string]string{}
308 }
309}
310
311func mergeMetadata(target map[string]string, source map[string]string) {
312 for key, value := range source {
313 target[key] = value
314 }
315}
316
317func formatRoleID(id uint32) string {
318 return strconv.FormatUint(uint64(id), 10)
319}