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