Search Apps Documentation Source Content File Folder Download Copy Actions Download

rbac.gno

14.84 Kb · 445 lines
  1package rbac
  2
  3type RBAC struct {
  4	permissions   *permissions
  5	roles         *roles
  6	grants        *grants
  7	revokes       *grants
  8	assignments   *assignments
  9	callbackDepth int
 10}
 11
 12// NewRBAC creates an unauthenticated library helper.
 13// Owning realms must keep the returned pointer private and expose their own
 14// cur realm authenticated wrappers for every mutating operation.
 15func NewRBAC(degree int) *RBAC {
 16	return &RBAC{
 17		permissions: newPermissions(degree),
 18		roles:       newRoles(degree),
 19		grants:      newGrants(degree),
 20		revokes:     newGrants(degree),
 21		assignments: newAssignments(degree),
 22	}
 23}
 24
 25// AddPermission is expected to be called only by an owning realm's admin
 26// wrapper. RBAC is a /p/ helper and cannot authenticate callers itself.
 27func (r *RBAC) AddPermission(p Permission) {
 28	r.assertNotInCallback()
 29	r.validatePermission(p)
 30	r.permissions.Add(p.Name, p.Description)
 31}
 32
 33// UpdatePermission is expected to be called only by an owning realm's admin
 34// wrapper. RBAC is a /p/ helper and cannot authenticate callers itself.
 35func (r *RBAC) UpdatePermission(name string, p Permission) {
 36	r.assertNotInCallback()
 37	r.validatePermission(p)
 38	r.permissions.Update(name, p.Name, p.Description)
 39}
 40
 41// DeletePermission is expected to be called only by an owning realm's admin
 42// wrapper. RBAC is a /p/ helper and cannot authenticate callers itself.
 43func (r *RBAC) DeletePermission(name string) {
 44	r.assertNotInCallback()
 45	stored := r.permissions.permission(name)
 46	r.roles.DeletePermissionFromAll(stored.ID)
 47	r.permissions.Delete(name)
 48}
 49
 50func (r *RBAC) GetPermission(name string) Permission {
 51	return r.permissionToPermission(r.permissions.Get(name))
 52}
 53
 54func (r *RBAC) HasPermission(name string) bool {
 55	return r.permissions.Has(name)
 56}
 57
 58func (r *RBAC) ListPermissions(page int, count int) []Permission {
 59	storedPermissions := r.permissions.List(page, count)
 60	result := make([]Permission, 0, len(storedPermissions))
 61	for _, stored := range storedPermissions {
 62		result = append(result, r.permissionToPermission(stored))
 63	}
 64	return result
 65}
 66
 67func (r *RBAC) PermissionSize() int {
 68	return r.permissions.Size()
 69}
 70
 71// AddRole is expected to be called only by an owning realm's admin wrapper.
 72// RoleSpec may contain trusted policy code through AssignmentCheck.
 73func (r *RBAC) AddRole(role RoleSpec) {
 74	r.assertNotInCallback()
 75	r.validateRole(role)
 76	r.roles.Add(role.Name, role.Description, r.permissionIDs(role.PermissionNames), role.Metadata, role.AssignmentCheck)
 77}
 78
 79// UpdateRole is expected to be called only by an owning realm's admin wrapper.
 80// RoleSpec may replace trusted policy code through AssignmentCheck.
 81func (r *RBAC) UpdateRole(name string, role RoleSpec) {
 82	r.assertNotInCallback()
 83	r.validateRole(role)
 84	r.roles.Update(name, role.Name, role.Description, r.permissionIDs(role.PermissionNames), role.Metadata, role.AssignmentCheck)
 85}
 86
 87// DeleteRole is expected to be called only by an owning realm's admin wrapper.
 88func (r *RBAC) DeleteRole(name string) {
 89	r.assertNotInCallback()
 90	stored := r.roles.role(name)
 91	r.grants.DeleteRole(stored.ID)
 92	r.revokes.DeleteRole(stored.ID)
 93	r.assignments.DeleteRole(stored.ID)
 94	r.roles.Delete(name)
 95}
 96
 97func (r *RBAC) GetRole(name string) Role {
 98	return r.roleToRole(r.roles.Get(name))
 99}
100
101func (r *RBAC) HasRole(name string) bool {
102	return r.roles.Has(name)
103}
104
105func (r *RBAC) ListRoles(page int, count int) []Role {
106	storedRoles := r.roles.List(page, count)
107	result := make([]Role, 0, len(storedRoles))
108	for _, stored := range storedRoles {
109		result = append(result, r.roleToRole(stored))
110	}
111	return result
112}
113
114func (r *RBAC) RoleSize() int {
115	return r.roles.Size()
116}
117
118func (r *RBAC) GetRoleMetadata(roleName string) map[string]string {
119	return r.roles.Metadata(roleName)
120}
121
122// SetRoleMetadata is expected to be called only by an owning realm's admin
123// wrapper.
124func (r *RBAC) SetRoleMetadata(roleName string, key string, value string) {
125	r.assertNotInCallback()
126	r.roles.SetMetadata(roleName, key, value)
127}
128
129// SetRoleMetadataCSV updates role metadata from comma-separated keys and
130// values. It validates the full batch before mutating stored metadata.
131func (r *RBAC) SetRoleMetadataCSV(roleName string, keys string, values string) {
132	r.assertNotInCallback()
133	r.roles.SetMetadataCSV(roleName, keys, values)
134}
135
136// AddRolePermission is expected to be called only by an owning realm's admin
137// wrapper.
138func (r *RBAC) AddRolePermission(roleName string, permissionName string) {
139	r.assertNotInCallback()
140	role := r.roles.role(roleName)
141	permission := r.permissions.permission(permissionName)
142	if role.HasPermission(permission.ID) {
143		panic("role already has permission: role=" + role.Name + ", permission=" + permission.Name)
144	}
145	role.AddPermission(permission.ID)
146}
147
148// DeleteRolePermission is expected to be called only by an owning realm's
149// admin wrapper.
150func (r *RBAC) DeleteRolePermission(roleName string, permissionName string) {
151	r.assertNotInCallback()
152	role := r.roles.role(roleName)
153	permission := r.permissions.permission(permissionName)
154	if !role.HasPermission(permission.ID) {
155		panic("role permission not found: role=" + role.Name + ", permission=" + permission.Name)
156	}
157	role.DeletePermission(permission.ID)
158}
159
160func (r *RBAC) HasRolePermission(roleName string, permissionName string) bool {
161	role := r.roles.role(roleName)
162	permission := r.permissions.permission(permissionName)
163	return role.HasPermission(permission.ID)
164}
165
166// AddGrant is expected to be called only by an owning realm's admin wrapper.
167func (r *RBAC) AddGrant(granterRoleName string, granteeRoleName string) {
168	r.assertNotInCallback()
169	granter := r.roles.role(granterRoleName)
170	grantee := r.roles.role(granteeRoleName)
171	if r.grants.Has(granter.ID, grantee.ID) {
172		panic("grant already exists: granter=" + granter.Name + ", grantee=" + grantee.Name)
173	}
174	r.grants.Add(granter.ID, grantee.ID)
175}
176
177// DeleteGrant is expected to be called only by an owning realm's admin wrapper.
178func (r *RBAC) DeleteGrant(granterRoleName string, granteeRoleName string) {
179	r.assertNotInCallback()
180	granter := r.roles.role(granterRoleName)
181	grantee := r.roles.role(granteeRoleName)
182	if !r.grants.Has(granter.ID, grantee.ID) {
183		panic("grant not found: granter=" + granter.Name + ", grantee=" + grantee.Name)
184	}
185	r.grants.Delete(granter.ID, grantee.ID)
186}
187
188func (r *RBAC) canRoleGrant(granterRoleName string, granteeRoleName string) bool {
189	granter := r.roles.role(granterRoleName)
190	grantee := r.roles.role(granteeRoleName)
191	return r.grants.Has(granter.ID, grantee.ID)
192}
193
194func (r *RBAC) ListGrantableRoles(granterRoleName string, page int, count int) []string {
195	granter := r.roles.role(granterRoleName)
196	return r.roleNames(r.grants.ListGrantees(granter.ID, page, count))
197}
198
199func (r *RBAC) GrantableRoleSize(granterRoleName string) int {
200	granter := r.roles.role(granterRoleName)
201	return r.grants.GranteeSize(granter.ID)
202}
203
204func (r *RBAC) GrantSize() int {
205	return r.grants.Size()
206}
207
208// CanGrant checks whether granter has any role in entityID that can grant
209// granteeRoleName. Owner/admin bypass rules belong in the owning realm wrapper.
210func (r *RBAC) CanGrant(entityID string, granter address, granteeRoleName string) bool {
211	grantee := r.roles.role(granteeRoleName)
212	granterRoleIDs := r.assignments.ListRoles(entityID, granter, 1, r.assignments.UserSize(entityID, granter))
213	for _, granterRoleID := range granterRoleIDs {
214		if r.grants.Has(granterRoleID, grantee.ID) {
215			return true
216		}
217	}
218	return false
219}
220
221// AddRevoke is expected to be called only by an owning realm's admin wrapper.
222func (r *RBAC) AddRevoke(revokerRoleName string, revokeeRoleName string) {
223	r.assertNotInCallback()
224	revoker := r.roles.role(revokerRoleName)
225	revokee := r.roles.role(revokeeRoleName)
226	if r.revokes.Has(revoker.ID, revokee.ID) {
227		panic("revoke already exists: revoker=" + revoker.Name + ", revokee=" + revokee.Name)
228	}
229	r.revokes.Add(revoker.ID, revokee.ID)
230}
231
232// DeleteRevoke is expected to be called only by an owning realm's admin wrapper.
233func (r *RBAC) DeleteRevoke(revokerRoleName string, revokeeRoleName string) {
234	r.assertNotInCallback()
235	revoker := r.roles.role(revokerRoleName)
236	revokee := r.roles.role(revokeeRoleName)
237	if !r.revokes.Has(revoker.ID, revokee.ID) {
238		panic("revoke not found: revoker=" + revoker.Name + ", revokee=" + revokee.Name)
239	}
240	r.revokes.Delete(revoker.ID, revokee.ID)
241}
242
243func (r *RBAC) ListRevokableRoles(revokerRoleName string, page int, count int) []string {
244	revoker := r.roles.role(revokerRoleName)
245	return r.roleNames(r.revokes.ListGrantees(revoker.ID, page, count))
246}
247
248func (r *RBAC) RevokableRoleSize(revokerRoleName string) int {
249	revoker := r.roles.role(revokerRoleName)
250	return r.revokes.GranteeSize(revoker.ID)
251}
252
253func (r *RBAC) RevokeSize() int {
254	return r.revokes.Size()
255}
256
257// CanRevoke checks whether revoker has any role in entityID that can revoke
258// revokeeRoleName. Owner/admin bypass rules belong in the owning realm wrapper.
259func (r *RBAC) CanRevoke(entityID string, revoker address, revokeeRoleName string) bool {
260	revokee := r.roles.role(revokeeRoleName)
261	revokerRoleIDs := r.assignments.ListRoles(entityID, revoker, 1, r.assignments.UserSize(entityID, revoker))
262	for _, revokerRoleID := range revokerRoleIDs {
263		if r.revokes.Has(revokerRoleID, revokee.ID) {
264			return true
265		}
266	}
267	return false
268}
269
270// AssignRole trusts the supplied user address.
271// Self-assignment wrappers must derive user from cur.Previous().Address()
272// instead of accepting it from caller input. Admin wrappers may pass an
273// explicit target user after performing their own authorization.
274// Special/system role wrappers may call this directly after their own policy
275// checks. User-driven grant wrappers should call CanGrant first, then call
276// AssignRole only when the granter is allowed to grant roleName in entityID.
277func (r *RBAC) AssignRole(entityID string, user address, roleName string) {
278	r.assertNotInCallback()
279	role := r.roles.role(roleName)
280	if r.assignments.Has(entityID, user, role.ID) {
281		panic("assignment already exists: entityID=" + entityID + ", user=" + user.String() + ", role=" + role.Name)
282	}
283	r.assertCanAssignRole(entityID, user, role)
284	r.assignments.Add(entityID, user, role.ID)
285}
286
287// UnassignRole trusts the supplied user address.
288// Special/system role wrappers may call this directly after their own policy
289// checks. User-driven revoke wrappers should call CanRevoke first, then call
290// UnassignRole only when the revoker is allowed to revoke roleName in entityID.
291func (r *RBAC) UnassignRole(entityID string, user address, roleName string) {
292	r.assertNotInCallback()
293	role := r.roles.role(roleName)
294	if !r.assignments.Has(entityID, user, role.ID) {
295		panic("assignment not found: entityID=" + entityID + ", user=" + user.String() + ", role=" + role.Name)
296	}
297	r.assignments.Delete(entityID, user, role.ID)
298}
299
300// DeleteEntity is expected to be called only by an owning realm's admin or
301// entity lifecycle wrapper after verifying the entity can be deleted.
302func (r *RBAC) DeleteEntity(entityID string) {
303	r.assertNotInCallback()
304	r.assignments.DeleteEntity(entityID)
305}
306
307func (r *RBAC) HasUserRole(entityID string, user address, roleName string) bool {
308	role := r.roles.role(roleName)
309	return r.assignments.Has(entityID, user, role.ID)
310}
311
312func (r *RBAC) HasUserPermission(entityID string, user address, permissionName string) bool {
313	permission := r.permissions.permission(permissionName)
314	roleIDs := r.assignments.ListRoles(entityID, user, 1, r.assignments.UserSize(entityID, user))
315	for _, roleID := range roleIDs {
316		if r.roles.roleByID(roleID).HasPermission(permission.ID) {
317			return true
318		}
319	}
320	return false
321}
322
323func (r *RBAC) ListUserRoles(entityID string, user address, page int, count int) []string {
324	return r.roleNames(r.assignments.ListRoles(entityID, user, page, count))
325}
326
327func (r *RBAC) UserRoleSize(entityID string, user address) int {
328	return r.assignments.UserSize(entityID, user)
329}
330
331func (r *RBAC) ListRoleUsers(entityID string, roleName string, page int, count int) []address {
332	role := r.roles.role(roleName)
333	return r.assignments.ListUsers(entityID, role.ID, page, count)
334}
335
336func (r *RBAC) RoleUserSize(entityID string, roleName string) int {
337	role := r.roles.role(roleName)
338	return r.assignments.RoleSize(entityID, role.ID)
339}
340
341func (r *RBAC) ListEntitiesByUserRole(user address, roleName string, page int, count int) []string {
342	role := r.roles.role(roleName)
343	return r.assignments.ListEntities(user, role.ID, page, count)
344}
345
346func (r *RBAC) UserRoleEntitySize(user address, roleName string) int {
347	role := r.roles.role(roleName)
348	return r.assignments.EntitySize(user, role.ID)
349}
350
351func (r *RBAC) AssignmentSize() int {
352	return r.assignments.Size()
353}
354
355func (r *RBAC) validatePermission(p Permission) {
356	r.permissions.validateName(p.Name)
357}
358
359func (r *RBAC) validateRole(role RoleSpec) {
360	r.roles.validateName(role.Name)
361	for key := range role.Metadata {
362		r.roles.validateMetadataKey(key)
363	}
364	seenPermissionNames := map[string]bool{}
365	for _, permissionName := range role.PermissionNames {
366		if seenPermissionNames[permissionName] {
367			panic("duplicate role permission: " + permissionName)
368		}
369		seenPermissionNames[permissionName] = true
370		r.permissions.permission(permissionName)
371	}
372}
373
374func (r *RBAC) permissionIDs(permissionNames []string) []uint32 {
375	permissionIDs := make([]uint32, 0, len(permissionNames))
376	for _, permissionName := range permissionNames {
377		permissionIDs = append(permissionIDs, r.permissions.permission(permissionName).ID)
378	}
379	return permissionIDs
380}
381
382func (r *RBAC) permissionNames(permissionIDs []uint32) []string {
383	permissionNames := make([]string, 0, len(permissionIDs))
384	for _, permissionID := range permissionIDs {
385		permissionNames = append(permissionNames, r.permissions.permissionByID(permissionID).Name)
386	}
387	return permissionNames
388}
389
390func (r *RBAC) roleNames(roleIDs []uint32) []string {
391	roleNames := make([]string, 0, len(roleIDs))
392	for _, roleID := range roleIDs {
393		roleNames = append(roleNames, r.roles.roleByID(roleID).Name)
394	}
395	return roleNames
396}
397
398func (r *RBAC) assertCanAssignRole(entityID string, user address, role *role) {
399	if role.assignmentCheck == nil {
400		return
401	}
402
403	ctx := AssignmentContext{
404		EntityID:               entityID,
405		User:                   user,
406		RoleName:               role.Name,
407		CurrentRoleAssignments: r.assignments.RoleSize(entityID, role.ID),
408		UserRoleCount:          r.assignments.UserSize(entityID, user),
409	}
410	r.inCallback()
411	defer r.outCallback()
412	if !role.assignmentCheck(ctx) {
413		panic("role assignment rejected: role=" + role.Name + ", entityID=" + entityID + ", user=" + user.String())
414	}
415}
416
417func (r *RBAC) inCallback() {
418	r.callbackDepth += 1
419}
420
421func (r *RBAC) outCallback() {
422	r.callbackDepth -= 1
423}
424
425func (r *RBAC) assertNotInCallback() {
426	if r.callbackDepth > 0 {
427		panic("rbac mutation during assignment callback")
428	}
429}
430
431func (r *RBAC) permissionToPermission(stored *permission) Permission {
432	return Permission{
433		Name:        stored.Name,
434		Description: stored.Description,
435	}
436}
437
438func (r *RBAC) roleToRole(stored *role) Role {
439	return Role{
440		Name:            stored.Name,
441		Description:     stored.Description,
442		PermissionNames: r.permissionNames(stored.PermissionIDs()),
443		Metadata:        copyMetadata(stored.metadata),
444	}
445}