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}