authz.gno
14.55 Kb · 465 lines
1package personal_world
2
3import (
4 "chain"
5 "strconv"
6 "strings"
7
8 "gno.land/p/akkadia/v0/accesscontrol"
9 "gno.land/p/akkadia/v0/rbac"
10 "gno.land/r/akkadia/v0/admin"
11)
12
13const (
14 CreatePermissionEvent = "CreatePermission"
15 DeletePermissionEvent = "DeletePermission"
16 CreateRoleEvent = "CreateRole"
17 UpdateRoleEvent = "UpdateRole"
18 UpdateRolePropertyEvent = "UpdateRoleProperty"
19 DeleteRoleEvent = "DeleteRole"
20 AddPermissionEvent = "AddPermission"
21 RemovePermissionEvent = "RemovePermission"
22 AddGrantableEvent = "AddGrantable"
23 RemoveGrantableEvent = "RemoveGrantable"
24 GrantRoleEvent = "GrantRole"
25 RevokeRoleEvent = "RevokeRole"
26)
27
28var (
29 personalWorldAuthzRBAC = rbac.NewRBAC(32)
30)
31
32func init() {
33 initAuthzDefinitions()
34}
35
36func initAuthzDefinitions() {
37 personalWorldAuthzRBAC = rbac.NewRBAC(32)
38
39 personalWorldAuthzRBAC.AddPermission(rbac.Permission{Name: "role:grant", Description: "Grant Role"})
40 personalWorldAuthzRBAC.AddPermission(rbac.Permission{Name: "role:revoke", Description: "Revoke Role"})
41 personalWorldAuthzRBAC.AddPermission(rbac.Permission{Name: "world:update", Description: "Update World"})
42 personalWorldAuthzRBAC.AddPermission(rbac.Permission{Name: "world:expand", Description: "Expand World"})
43 personalWorldAuthzRBAC.AddPermission(rbac.Permission{Name: "block:install", Description: "Install Block"})
44 personalWorldAuthzRBAC.AddPermission(rbac.Permission{Name: "block:uninstall", Description: "Uninstall Block"})
45
46 personalWorldAuthzRBAC.AddRole(rbac.RoleSpec{
47 Name: "editor",
48 PermissionNames: []string{"block:install", "block:uninstall"},
49 Metadata: map[string]string{"maxAssign": "5"},
50 AssignmentCheck: newMaxAssignCheck(5),
51 })
52}
53
54// ==================== Permission Management (Admin Only) ====================
55
56// CreatePermission creates a new permission definition
57func CreatePermission(cur realm, name string, description string) {
58 assertNotFrozen()
59 accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
60
61 personalWorldAuthzRBAC.AddPermission(rbac.Permission{Name: name, Description: description})
62 chain.Emit(CreatePermissionEvent, "name", name, "description", description)
63}
64
65// DeletePermission deletes a permission and removes it from all roles
66func DeletePermission(cur realm, name string) {
67 assertNotFrozen()
68 accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
69
70 personalWorldAuthzRBAC.DeletePermission(name)
71 chain.Emit(DeletePermissionEvent, "name", name)
72}
73
74// AddPermissionToRole adds a permission to an existing role
75func AddPermissionToRole(cur realm, roleName string, permissionName string) {
76 assertNotFrozen()
77 accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
78
79 personalWorldAuthzRBAC.AddRolePermission(roleName, permissionName)
80 chain.Emit(AddPermissionEvent, "role", roleName, "permission", permissionName)
81}
82
83// RemovePermissionFromRole removes a permission from a role
84func RemovePermissionFromRole(cur realm, roleName string, permissionName string) {
85 assertNotFrozen()
86 accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
87
88 personalWorldAuthzRBAC.DeleteRolePermission(roleName, permissionName)
89 chain.Emit(RemovePermissionEvent, "role", roleName, "permission", permissionName)
90}
91
92// ==================== Role Management (Admin Only) ====================
93
94// CreateRole creates a new role with permissions
95// permissions is a comma-separated string of permission names
96// maxAssign: 0 = unlimited assignments
97func CreateRole(cur realm, roleName string, maxAssign int, permissions string) {
98 assertNotFrozen()
99 accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
100
101 if maxAssign < 0 {
102 panic("maxAssign must be non-negative")
103 }
104
105 personalWorldAuthzRBAC.AddRole(rbac.RoleSpec{
106 Name: roleName,
107 PermissionNames: parsePermissionNamesCSV(permissions),
108 Metadata: map[string]string{"maxAssign": strconv.Itoa(maxAssign)},
109 AssignmentCheck: newMaxAssignCheck(maxAssign),
110 })
111
112 chain.Emit(CreateRoleEvent, "name", roleName, "permissions", permissions)
113}
114
115// UpdateRole updates a role's maxAssign and permissions
116// permissions is a comma-separated string of permission names (replaces all existing permissions)
117// maxAssign: 0 = unlimited assignments
118func UpdateRole(cur realm, roleName string, maxAssign int, permissions string) {
119 assertNotFrozen()
120 accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
121
122 if maxAssign < 0 {
123 panic("maxAssign must be non-negative")
124 }
125
126 personalWorldAuthzRBAC.UpdateRole(roleName, rbac.RoleSpec{
127 Name: roleName,
128 PermissionNames: parsePermissionNamesCSV(permissions),
129 Metadata: map[string]string{"maxAssign": strconv.Itoa(maxAssign)},
130 AssignmentCheck: newMaxAssignCheck(maxAssign),
131 })
132
133 chain.Emit(UpdateRoleEvent, "name", roleName, "maxAssign", strconv.Itoa(maxAssign), "permissions", permissions)
134}
135
136// UpdateRoleProperty updates a single property of a role
137func UpdateRoleProperty(cur realm, roleName string, key string, value string) {
138 assertNotFrozen()
139 accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
140
141 personalWorldAuthzRBAC.SetRoleMetadata(roleName, key, value)
142
143 chain.Emit(UpdateRolePropertyEvent, "name", roleName, "key", key, "value", value)
144}
145
146// DeleteRole deletes a role and removes all its assignments
147func DeleteRole(cur realm, roleName string) {
148 assertNotFrozen()
149 accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
150
151 personalWorldAuthzRBAC.DeleteRole(roleName)
152 chain.Emit(DeleteRoleEvent, "name", roleName)
153}
154
155// ==================== Grantable Management (Admin Only) ====================
156
157// AddGrantable allows granterRole to grant grantableRole to users
158func AddGrantable(cur realm, granterRole, grantableRole string) {
159 assertNotFrozen()
160 accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
161
162 personalWorldAuthzRBAC.AddGrant(granterRole, grantableRole)
163 personalWorldAuthzRBAC.AddRevoke(granterRole, grantableRole)
164 chain.Emit(AddGrantableEvent, "granterRole", granterRole, "grantableRole", grantableRole)
165}
166
167// RemoveGrantable removes the ability for granterRole to grant grantableRole
168func RemoveGrantable(cur realm, granterRole, grantableRole string) {
169 assertNotFrozen()
170 accesscontrol.AssertIsAdmin(0, cur, admin.IsAdmin)
171
172 personalWorldAuthzRBAC.DeleteGrant(granterRole, grantableRole)
173 personalWorldAuthzRBAC.DeleteRevoke(granterRole, grantableRole)
174
175 chain.Emit(RemoveGrantableEvent, "granterRole", granterRole, "grantableRole", grantableRole)
176}
177
178// ==================== Query Functions ====================
179
180func ListPermissions(page int, count int) map[string]string {
181 assertMigrationStateAvailable()
182 assertListPageCount(page, count)
183 result := make(map[string]string)
184 permissions := personalWorldAuthzRBAC.ListPermissions(page, count)
185 for _, permission := range permissions {
186 result[permission.Name] = permission.Description
187 }
188 return result
189}
190
191func GetPermissionSize() int {
192 assertMigrationStateAvailable()
193 return personalWorldAuthzRBAC.PermissionSize()
194}
195
196func GetPermission(name string) map[string]string {
197 assertMigrationStateAvailable()
198 if !personalWorldAuthzRBAC.HasPermission(name) {
199 return nil
200 }
201 permission := personalWorldAuthzRBAC.GetPermission(name)
202 return map[string]string{
203 "name": permission.Name,
204 "description": permission.Description,
205 }
206}
207
208// GetRoleInfo returns detailed information about a role
209func GetRoleInfo(roleName string) map[string]string {
210 assertMigrationStateAvailable()
211 if !personalWorldAuthzRBAC.HasRole(roleName) {
212 return nil
213 }
214 role := personalWorldAuthzRBAC.GetRole(roleName)
215 roleInfo := copyStringMap(role.Metadata)
216 roleInfo["name"] = role.Name
217 return roleInfo
218}
219
220func ListRoles(page int, count int) []string {
221 assertMigrationStateAvailable()
222 assertListPageCount(page, count)
223 result := []string{}
224 roles := personalWorldAuthzRBAC.ListRoles(page, count)
225 for _, role := range roles {
226 result = append(result, role.Name)
227 }
228 return result
229}
230
231func GetRoleSize() int {
232 assertMigrationStateAvailable()
233 return personalWorldAuthzRBAC.RoleSize()
234}
235
236func ListGrantables(granterRole string, page int, count int) []string {
237 assertMigrationStateAvailable()
238 assertListPageCount(page, count)
239 if !personalWorldAuthzRBAC.HasRole(granterRole) {
240 return []string{}
241 }
242 return personalWorldAuthzRBAC.ListGrantableRoles(granterRole, page, count)
243}
244
245func GetGrantableSize(granterRole string) int {
246 assertMigrationStateAvailable()
247 if !personalWorldAuthzRBAC.HasRole(granterRole) {
248 return 0
249 }
250 return personalWorldAuthzRBAC.GrantableRoleSize(granterRole)
251}
252
253func getRoleInfo(roleName string) map[string]string {
254 roleInfo := GetRoleInfo(roleName)
255 if roleInfo == nil {
256 panic("role not found: " + roleName)
257 }
258 return roleInfo
259}
260
261func parsePermissionNamesCSV(permissions string) []string {
262 result := []string{}
263 seen := map[string]bool{}
264 if len(permissions) == 0 {
265 return result
266 }
267 permList := strings.Split(permissions, ",")
268 for _, perm := range permList {
269 perm = strings.TrimSpace(perm)
270 if len(perm) == 0 || seen[perm] {
271 continue
272 }
273 personalWorldAuthzRBAC.GetPermission(perm)
274 seen[perm] = true
275 result = append(result, perm)
276 }
277 return result
278}
279
280func newMaxAssignCheck(maxAssign int) rbac.AssignmentCheck {
281 if maxAssign == 0 {
282 return nil
283 }
284 return func(ctx rbac.AssignmentContext) bool {
285 if ctx.CurrentRoleAssignments >= maxAssign {
286 panic("role assignment limit reached")
287 }
288 return true
289 }
290}
291
292// GrantRole assigns a role to a user for a specific world
293// Owner, admin, or users with grantable permission can grant roles
294func GrantRole(cur realm, worldID uint32, roleName string, user address) {
295 assertNotFrozen()
296 caller := accesscontrol.MustGetUserCaller(0, cur)
297 assertCanGrantRole(worldID, caller, roleName)
298
299 worldStore.AssertWorldExists(worldID)
300
301 getRoleInfo(roleName)
302 personalWorldAuthzRBAC.AssignRole(formatWorldID(worldID), user, roleName)
303
304 chain.Emit(
305 GrantRoleEvent,
306 "worldID", formatWorldID(worldID),
307 "role", roleName,
308 "user", user.String(),
309 )
310}
311
312// RevokeRole removes a role from a user for a specific world
313// Owner, admin, or users with grantable permission can revoke roles
314func RevokeRole(cur realm, worldID uint32, roleName string, user address) {
315 assertNotFrozen()
316 caller := accesscontrol.MustGetUserCaller(0, cur)
317 assertCanRevokeRole(worldID, caller, roleName)
318
319 worldStore.AssertWorldExists(worldID)
320
321 getRoleInfo(roleName)
322 personalWorldAuthzRBAC.UnassignRole(formatWorldID(worldID), user, roleName)
323
324 chain.Emit(
325 RevokeRoleEvent,
326 "worldID", formatWorldID(worldID),
327 "role", roleName,
328 "user", user.String(),
329 )
330}
331
332// ==================== Permission Check ====================
333
334// HasPermission checks if a user has a specific permission for a world.
335// Admins and owners always have all defined permissions.
336func HasPermission(worldID uint32, user address, permission string) bool {
337 assertMigrationStateAvailable()
338 if !personalWorldAuthzRBAC.HasPermission(permission) {
339 return false
340 }
341
342 if admin.IsAdmin(user) {
343 return true
344 }
345
346 // Owner has all defined permissions.
347 if isOwner(worldID, user) {
348 return true
349 }
350
351 return personalWorldAuthzRBAC.HasUserPermission(formatWorldID(worldID), user, permission)
352}
353
354// HasRole checks if a user has a specific role for a world
355func HasRole(worldID uint32, user address, role string) bool {
356 assertMigrationStateAvailable()
357 if !personalWorldAuthzRBAC.HasRole(role) {
358 return false
359 }
360 return personalWorldAuthzRBAC.HasUserRole(formatWorldID(worldID), user, role)
361}
362
363// HasInstallPermission checks if caller has permission to install blocks in the world
364func HasInstallPermission(worldID uint32, caller address) bool {
365 return HasPermission(worldID, caller, "block:install")
366}
367
368// HasUninstallPermission checks if caller has permission to uninstall blocks from the world
369func HasUninstallPermission(worldID uint32, caller address) bool {
370 return HasPermission(worldID, caller, "block:uninstall")
371}
372
373func HasUpdatePermission(worldID uint32, caller address) bool {
374 return HasPermission(worldID, caller, "world:update")
375}
376
377func HasExpandPermission(worldID uint32, caller address) bool {
378 return HasPermission(worldID, caller, "world:expand")
379}
380
381func ListUserRoles(worldID uint32, user address, page int, count int) []map[string]string {
382 assertMigrationStateAvailable()
383 assertListPageCount(page, count)
384 result := []map[string]string{}
385
386 roles := personalWorldAuthzRBAC.ListUserRoles(formatWorldID(worldID), user, page, count)
387 for _, roleName := range roles {
388 role := personalWorldAuthzRBAC.GetRole(roleName)
389 roleInfo := getRoleInfo(roleName)
390 result = append(result, map[string]string{
391 "name": roleInfo["name"],
392 "maxAssign": roleInfo["maxAssign"],
393 "permissions": strings.Join(role.PermissionNames, ","),
394 })
395 }
396
397 return result
398}
399
400func GetUserRoleSize(worldID uint32, user address) int {
401 assertMigrationStateAvailable()
402 return personalWorldAuthzRBAC.UserRoleSize(formatWorldID(worldID), user)
403}
404
405func ListRoleGrants(worldID uint32, roleName string, page int, count int) []address {
406 assertMigrationStateAvailable()
407 assertListPageCount(page, count)
408 if !personalWorldAuthzRBAC.HasRole(roleName) {
409 return []address{}
410 }
411 return personalWorldAuthzRBAC.ListRoleUsers(formatWorldID(worldID), roleName, page, count)
412}
413
414func GetRoleGrantSize(worldID uint32, roleName string) int {
415 assertMigrationStateAvailable()
416 if !personalWorldAuthzRBAC.HasRole(roleName) {
417 return 0
418 }
419 return personalWorldAuthzRBAC.RoleUserSize(formatWorldID(worldID), roleName)
420}
421
422func ListWorldIDsByUserRole(user address, roleName string, page int, count int) []uint32 {
423 assertMigrationStateAvailable()
424 assertListPageCount(page, count)
425 getRoleInfo(roleName)
426
427 result := []uint32{}
428 worldIDs := personalWorldAuthzRBAC.ListEntitiesByUserRole(user, roleName, page, count)
429 for _, worldKey := range worldIDs {
430 worldID, err := strconv.ParseUint(worldKey, 10, 32)
431 if err != nil {
432 panic("invalid worldID: " + worldKey)
433 }
434 result = append(result, uint32(worldID))
435 }
436 return result
437}
438
439func GetUserRoleWorldSize(user address, roleName string) int {
440 assertMigrationStateAvailable()
441 if !personalWorldAuthzRBAC.HasRole(roleName) {
442 return 0
443 }
444 return personalWorldAuthzRBAC.UserRoleEntitySize(user, roleName)
445}
446
447// ==================== Helper Functions ====================
448
449func isOwner(worldID uint32, addr address) bool {
450 return worldStore.IsOwner(worldID, addr)
451}
452
453// hasGrantableRole checks whether any caller role can manage the target role.
454func hasGrantableRole(worldID uint32, addr address, roleName string) bool {
455 return personalWorldAuthzRBAC.CanGrant(formatWorldID(worldID), addr, roleName)
456}
457
458func hasRevokableRole(worldID uint32, addr address, roleName string) bool {
459 return personalWorldAuthzRBAC.CanRevoke(formatWorldID(worldID), addr, roleName)
460}
461
462// cleanupWorldAuthorization removes all role assignments for a deleted world
463func cleanupWorldAuthorization(worldID uint32) {
464 personalWorldAuthzRBAC.DeleteEntity(formatWorldID(worldID))
465}