Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}