Search Apps Documentation Source Content File Folder Download Copy Actions Download

authz.gno

14.68 Kb · 465 lines
  1package personal_world
  2
  3import (
  4	"chain"
  5	"strconv"
  6	"strings"
  7
  8	"gno.land/p/g1nqnrt3aldzhu6zzeg75yw97wvavqy7wr77g56q/deploy-test/v2/accesscontrol"
  9	"gno.land/p/g1nqnrt3aldzhu6zzeg75yw97wvavqy7wr77g56q/deploy-test/v2/rbac"
 10	"gno.land/r/g1nqnrt3aldzhu6zzeg75yw97wvavqy7wr77g56q/deploy-test/v2/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}