package personal_world import ( "chain" "strconv" "strings" "gno.land/p/nt/ufmt" "gno.land/r/akkadia/admin" ) var ( // World authorization: worldID -> address -> role name -> bool worldAuthorization = make(map[uint32]map[address]map[string]bool) ) // ==================== Role Grant/Revoke ==================== // canGrant checks if an address can grant a specific role for a world // Owner can always grant any role // Users with roles that have grantable permissions can grant those roles func canGrant(worldID uint32, addr address, role string) bool { // Owner can always grant any role if isOwner(worldID, addr) { return true } // Get user's roles for this world if _, found := worldAuthorization[worldID]; !found { return false } if _, found := worldAuthorization[worldID][addr]; !found { return false } // Check if any of user's roles can grant the target role for userRole := range worldAuthorization[worldID][addr] { if grantables, found := grantableMap[userRole]; found { if grantables[role] { return true } } } return false } // GrantRole assigns a role to a user for a specific world // Owner, admin, or users with grantable permission can grant roles func GrantRole(cur realm, worldID uint32, roleName string, user address) { caller := validateUser() // Check grant permission if !admin.IsAdmin(caller) && !canGrant(worldID, caller, roleName) { panic("cannot grant role: caller lacks permission to assign role '" + roleName + "'") } // Check if role exists roleInfo, found := roleMap[roleName] if !found { panic("role not found: " + roleName) } // Check if world exists _ = mustGetWorld(worldID) // Initialize maps if needed if _, found := worldAuthorization[worldID]; !found { worldAuthorization[worldID] = make(map[address]map[string]bool) } if _, found := worldAuthorization[worldID][user]; !found { worldAuthorization[worldID][user] = make(map[string]bool) } // Check if user already has this role if worldAuthorization[worldID][user][roleName] { panic("user " + user.String() + " already has role '" + roleName + "'") } // Check MaxAssign limit (0 = unlimited) maxAssign, _ := strconv.Atoi(roleInfo["maxAssign"]) if maxAssign > 0 { currentCount := 0 for addr := range worldAuthorization[worldID] { if worldAuthorization[worldID][addr][roleName] { currentCount++ } } if currentCount >= maxAssign { panic("role assignment limit reached") } } // Grant the role worldAuthorization[worldID][user][roleName] = true chain.Emit( GrantRoleEvent, "worldID", ufmt.Sprintf("%d", worldID), "role", roleName, "user", user.String(), ) } // RevokeRole removes a role from a user for a specific world // Owner, admin, or users with grantable permission can revoke roles func RevokeRole(cur realm, worldID uint32, roleName string, user address) { caller := validateUser() // Check revoke permission (same as grant permission) if !admin.IsAdmin(caller) && !canGrant(worldID, caller, roleName) { panic("cannot revoke role: caller lacks permission to revoke role '" + roleName + "'") } // Check if role exists if _, found := roleMap[roleName]; !found { panic("role not found: " + roleName) } // Check if world exists _ = mustGetWorld(worldID) // Check if user has the role if _, found := worldAuthorization[worldID]; !found { panic("user " + user.String() + " does not have role '" + roleName + "'") } if _, found := worldAuthorization[worldID][user]; !found { panic("user " + user.String() + " does not have role '" + roleName + "'") } if !worldAuthorization[worldID][user][roleName] { panic("user " + user.String() + " does not have role '" + roleName + "'") } // Revoke the role delete(worldAuthorization[worldID][user], roleName) // Clean up empty maps if len(worldAuthorization[worldID][user]) == 0 { delete(worldAuthorization[worldID], user) } if len(worldAuthorization[worldID]) == 0 { delete(worldAuthorization, worldID) } chain.Emit( RevokeRoleEvent, "worldID", ufmt.Sprintf("%d", worldID), "role", roleName, "user", user.String(), ) } // ==================== Permission Check ==================== // HasPermission checks if a user has a specific permission for a world // Owner always has all permissions func HasPermission(worldID uint32, user address, permission string) bool { // Owner has all permissions if isOwner(worldID, user) { return true } // Check user's roles for the permission if _, found := worldAuthorization[worldID]; !found { return false } if _, found := worldAuthorization[worldID][user]; !found { return false } for roleName := range worldAuthorization[worldID][user] { if perms, found := rolePermissions[roleName]; found { if perms[permission] { return true } } } return false } // HasRole checks if a user has a specific role for a world func HasRole(worldID uint32, user address, role string) bool { if _, found := worldAuthorization[worldID]; !found { return false } if _, found := worldAuthorization[worldID][user]; !found { return false } return worldAuthorization[worldID][user][role] } // HasInstallPermission checks if caller has permission to install blocks in the world func HasInstallPermission(worldID uint32, caller address) bool { return HasPermission(worldID, caller, "block:install") } // HasUninstallPermission checks if caller has permission to uninstall blocks from the world func HasUninstallPermission(worldID uint32, caller address) bool { return HasPermission(worldID, caller, "block:uninstall") } // ==================== Query Functions ==================== // ListUserRoles returns all roles assigned to a user for a specific world func ListUserRoles(worldID uint32, user address) []map[string]string { result := []map[string]string{} if _, found := worldAuthorization[worldID]; !found { return result } if _, found := worldAuthorization[worldID][user]; !found { return result } for roleName := range worldAuthorization[worldID][user] { if role, found := roleMap[roleName]; found { permList := []string{} if perms, ok := rolePermissions[roleName]; ok { for perm := range perms { permList = append(permList, perm) } } result = append(result, map[string]string{ "name": role["name"], "maxAssign": role["maxAssign"], "permissions": strings.Join(permList, ","), }) } } return result } // ListRoleGrants returns all users assigned to a specific role for a specific world func ListRoleGrants(worldID uint32, roleName string) []address { result := []address{} if _, found := worldAuthorization[worldID]; !found { return result } for addr := range worldAuthorization[worldID] { if worldAuthorization[worldID][addr][roleName] { result = append(result, addr) } } return result } // ==================== Helper Functions ==================== func isOwner(worldID uint32, addr address) bool { world := mustGetWorld(worldID) return address(world["owner"]) == addr } // cleanupWorldAuthorization removes all role assignments for a deleted world func cleanupWorldAuthorization(worldID uint32) { delete(worldAuthorization, worldID) }