Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}