authz_chunk.gno
13.79 Kb · 449 lines
1package chunk
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/grc721"
10 "gno.land/p/g1nqnrt3aldzhu6zzeg75yw97wvavqy7wr77g56q/deploy-test/v2/rbac"
11 "gno.land/r/g1nqnrt3aldzhu6zzeg75yw97wvavqy7wr77g56q/deploy-test/v2/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}