authz.gno
6.70 Kb ยท 265 lines
1package chunk
2
3import (
4 "chain"
5 "chain/runtime"
6 "strconv"
7 "strings"
8
9 "gno.land/p/demo/tokens/grc721"
10)
11
12var (
13 // Chunk authorization: tid -> address -> role name -> bool
14 chunkAuthorization = make(map[grc721.TokenID]map[address]map[string]bool)
15)
16
17// ==================== Role Grant/Revoke ====================
18
19// canGrant checks if an address can grant a specific role for a chunk
20// Owner can always grant any role
21// Users with roles that have grantable permissions can grant those roles
22func canGrant(tid grc721.TokenID, addr address, role string) bool {
23 // Owner can always grant any role
24 owner := OwnerOf(tid)
25 if owner == addr {
26 return true
27 }
28
29 // Get user's roles for this chunk
30 if _, found := chunkAuthorization[tid]; !found {
31 return false
32 }
33
34 if _, found := chunkAuthorization[tid][addr]; !found {
35 return false
36 }
37
38 // Check if any of user's roles can grant the target role
39 for userRole := range chunkAuthorization[tid][addr] {
40 if grantables, found := grantableMap[userRole]; found {
41 if grantables[role] {
42 return true
43 }
44 }
45 }
46
47 return false
48}
49
50// GrantRole assigns a role to a user for a specific chunk
51// Owner, admin, or users with grantable permission can grant roles
52func GrantRole(cur realm, tid grc721.TokenID, roleName string, user address) {
53 caller := runtime.PreviousRealm().Address()
54
55 // Check grant permission
56 if getAdmin() != caller && !canGrant(tid, caller, roleName) {
57 panic("cannot grant role: caller lacks permission to assign role '" + roleName + "'")
58 }
59
60 // Check if role exists
61 roleInfo, found := roleMap[roleName]
62 if !found {
63 panic("role not found: " + roleName)
64 }
65
66 // Initialize maps if needed
67 if _, found := chunkAuthorization[tid]; !found {
68 chunkAuthorization[tid] = make(map[address]map[string]bool)
69 }
70
71 if _, found := chunkAuthorization[tid][user]; !found {
72 chunkAuthorization[tid][user] = make(map[string]bool)
73 }
74
75 // Check if user already has this role
76 if chunkAuthorization[tid][user][roleName] {
77 panic("user " + user.String() + " already has role '" + roleName + "'")
78 }
79
80 // Check MaxAssign limit (0 = unlimited)
81 maxAssign, _ := strconv.Atoi(roleInfo["maxAssign"])
82 if maxAssign > 0 {
83 currentCount := 0
84 for addr := range chunkAuthorization[tid] {
85 if chunkAuthorization[tid][addr][roleName] {
86 currentCount++
87 }
88 }
89
90 if currentCount >= maxAssign {
91 panic("role assignment limit reached")
92 }
93 }
94
95 // Grant the role
96 chunkAuthorization[tid][user][roleName] = true
97
98 chain.Emit(
99 GrantRoleEvent,
100 "tid", string(tid),
101 "role", roleName,
102 "user", user.String(),
103 )
104}
105
106// RevokeRole removes a role from a user for a specific chunk
107// Owner, admin, or users with grantable permission can revoke roles
108func RevokeRole(cur realm, tid grc721.TokenID, roleName string, user address) {
109 caller := runtime.PreviousRealm().Address()
110
111 // Check revoke permission (same as grant permission)
112 if getAdmin() != caller && !canGrant(tid, caller, roleName) {
113 panic("cannot revoke role: caller lacks permission to revoke role '" + roleName + "'")
114 }
115
116 // Check if role exists
117 if _, found := roleMap[roleName]; !found {
118 panic("role not found: " + roleName)
119 }
120
121 // Check if user has the role
122 if _, found := chunkAuthorization[tid]; !found {
123 panic("user " + user.String() + " does not have role '" + roleName + "'")
124 }
125
126 if _, found := chunkAuthorization[tid][user]; !found {
127 panic("user " + user.String() + " does not have role '" + roleName + "'")
128 }
129
130 if !chunkAuthorization[tid][user][roleName] {
131 panic("user " + user.String() + " does not have role '" + roleName + "'")
132 }
133
134 // Revoke the role
135 delete(chunkAuthorization[tid][user], roleName)
136
137 // Clean up empty maps
138 if len(chunkAuthorization[tid][user]) == 0 {
139 delete(chunkAuthorization[tid], user)
140 }
141
142 if len(chunkAuthorization[tid]) == 0 {
143 delete(chunkAuthorization, tid)
144 }
145
146 chain.Emit(
147 RevokeRoleEvent,
148 "tid", string(tid),
149 "role", roleName,
150 "user", user.String(),
151 )
152}
153
154// ==================== Permission Check ====================
155
156// HasPermission checks if a user has a specific permission for a chunk
157// Owner always has all permissions
158func HasPermission(tid grc721.TokenID, user address, permission string) bool {
159 // Admin has all permissions
160 if getAdmin() == user {
161 return true
162 }
163
164 // Master has all permissions for the world
165 worldID := getWorldIDFromTokenID(tid)
166 if IsMaster(worldID, user) {
167 return true
168 }
169
170 // Owner has all permissions
171 owner := OwnerOf(tid)
172 if owner == user {
173 return true
174 }
175
176 // Check user's roles for the permission
177 if _, found := chunkAuthorization[tid]; !found {
178 return false
179 }
180
181 if _, found := chunkAuthorization[tid][user]; !found {
182 return false
183 }
184
185 for roleName := range chunkAuthorization[tid][user] {
186 if perms, found := rolePermissions[roleName]; found {
187 if perms[permission] {
188 return true
189 }
190 }
191 }
192
193 return false
194}
195
196// HasInstallPermission checks if caller has permission to install blocks in the chunk
197func HasInstallPermission(tid grc721.TokenID, caller address) bool {
198 return HasPermission(tid, caller, "block:install")
199}
200
201// HasUninstallPermission checks if caller has permission to uninstall blocks from the chunk
202func HasUninstallPermission(tid grc721.TokenID, caller address) bool {
203 return HasPermission(tid, caller, "block:uninstall")
204}
205
206// HasRole checks if a user has a specific role for a chunk
207func HasRole(tid grc721.TokenID, user address, roleName string) bool {
208 if _, found := chunkAuthorization[tid]; !found {
209 return false
210 }
211 if _, found := chunkAuthorization[tid][user]; !found {
212 return false
213 }
214 return chunkAuthorization[tid][user][roleName]
215}
216
217// ==================== Query Functions ====================
218
219// ListUserRoles returns all roles assigned to a user for a specific chunk
220func ListUserRoles(tid grc721.TokenID, user address) []map[string]string {
221 result := []map[string]string{}
222
223 if _, found := chunkAuthorization[tid]; !found {
224 return result
225 }
226
227 if _, found := chunkAuthorization[tid][user]; !found {
228 return result
229 }
230
231 for roleName := range chunkAuthorization[tid][user] {
232 if role, found := roleMap[roleName]; found {
233 permList := []string{}
234 if perms, ok := rolePermissions[roleName]; ok {
235 for perm := range perms {
236 permList = append(permList, perm)
237 }
238 }
239 result = append(result, map[string]string{
240 "name": role["name"],
241 "maxAssign": role["maxAssign"],
242 "permissions": strings.Join(permList, ","),
243 })
244 }
245 }
246
247 return result
248}
249
250// ListRoleGrants returns all users assigned to a specific role for a chunk
251func ListRoleGrants(tid grc721.TokenID, roleName string) []address {
252 result := []address{}
253
254 if _, found := chunkAuthorization[tid]; !found {
255 return result
256 }
257
258 for addr := range chunkAuthorization[tid] {
259 if chunkAuthorization[tid][addr][roleName] {
260 result = append(result, addr)
261 }
262 }
263
264 return result
265}