Search Apps Documentation Source Content File Folder Download Copy Actions Download

authz_chunk.gno

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