Refer permissions by UUID rather than scope.

This commit is contained in:
2026-01-24 00:06:08 +00:00
parent 57748876cb
commit a9ef20969e
7 changed files with 149 additions and 98 deletions
+2 -2
View File
@@ -299,13 +299,13 @@ async function performPermissionDeletion(permissionScope) {
} }
function deletePermission(p) { function deletePermission(p) {
const userCount = permissionSummary.value[p.scope]?.userCount || 0 const userCount = permissionSummary.value[p.uuid]?.userCount || 0
// Count roles that have this permission // Count roles that have this permission
let roleCount = 0 let roleCount = 0
for (const org of orgs.value) { for (const org of orgs.value) {
for (const role of org.roles) { for (const role of org.roles) {
if (role.permissions.includes(p.scope)) { if (role.permissions.includes(p.uuid)) {
roleCount++ roleCount++
} }
} }
+11 -5
View File
@@ -26,6 +26,12 @@ const sortedRoles = computed(() => {
}) })
}) })
// Get org's grantable permissions as full permission objects (with UUIDs)
const orgPermissions = computed(() => {
const uuidSet = new Set(props.selectedOrg.permissions || [])
return props.permissions.filter(p => uuidSet.has(p.uuid))
})
function permissionDisplayName(scope) { function permissionDisplayName(scope) {
return props.permissions.find(p => p.scope === scope)?.display_name || scope return props.permissions.find(p => p.scope === scope)?.display_name || scope
} }
@@ -302,17 +308,17 @@ defineExpose({ focusFirstElement })
</div> </div>
<div class="grid-head role-head add-role-head" title="Add role" @click="$emit('createRole', selectedOrg)" role="button" tabindex="0" @keydown.enter="$emit('createRole', selectedOrg)"></div> <div class="grid-head role-head add-role-head" title="Add role" @click="$emit('createRole', selectedOrg)" role="button" tabindex="0" @keydown.enter="$emit('createRole', selectedOrg)"></div>
<template v-for="pid in selectedOrg.permissions" :key="pid"> <template v-for="p in orgPermissions" :key="p.uuid">
<div class="perm-name" :title="pid">{{ permissionDisplayName(pid) }}</div> <div class="perm-name" :title="p.scope">{{ p.display_name }}</div>
<div <div
v-for="r in sortedRoles" v-for="r in sortedRoles"
:key="r.uuid + '-' + pid" :key="r.uuid + '-' + p.uuid"
class="matrix-cell" class="matrix-cell"
> >
<input <input
type="checkbox" type="checkbox"
:checked="r.permissions.includes(pid)" :checked="r.permissions.includes(p.uuid)"
@change="e => toggleRolePermission(r, pid, e.target.checked)" @change="e => toggleRolePermission(r, p.uuid, e.target.checked)"
/> />
</div> </div>
<div class="matrix-cell add-role-cell" /> <div class="matrix-cell add-role-cell" />
+6 -6
View File
@@ -296,19 +296,19 @@ defineExpose({ focusFirstElement })
<span>{{ o.display_name }}</span> <span>{{ o.display_name }}</span>
</div> </div>
<template v-for="p in sortedPermissions" :key="p.scope"> <template v-for="p in sortedPermissions" :key="p.uuid">
<div class="perm-name" :title="p.scope"> <div class="perm-name" :title="p.scope">
<span class="display-text">{{ p.display_name }}</span> <span class="display-text">{{ p.display_name }}</span>
</div> </div>
<div <div
v-for="o in sortedOrgs" v-for="o in sortedOrgs"
:key="o.uuid + '-' + p.scope" :key="o.uuid + '-' + p.uuid"
class="matrix-cell" class="matrix-cell"
> >
<input <input
type="checkbox" type="checkbox"
:checked="o.permissions.includes(p.scope)" :checked="o.permissions.includes(p.uuid)"
@change="e => $emit('toggleOrgPermission', o, p.scope, e.target.checked)" @change="e => $emit('toggleOrgPermission', o, p.uuid, e.target.checked)"
/> />
</div> </div>
</template> </template>
@@ -329,7 +329,7 @@ defineExpose({ focusFirstElement })
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="p in sortedPermissions" :key="p.scope"> <tr v-for="p in sortedPermissions" :key="p.uuid">
<td class="perm-name-cell"> <td class="perm-name-cell">
<div class="perm-title"> <div class="perm-title">
<span class="display-text">{{ p.display_name }}</span> <span class="display-text">{{ p.display_name }}</span>
@@ -340,7 +340,7 @@ defineExpose({ focusFirstElement })
</div> </div>
</td> </td>
<td class="perm-domain">{{ p.domain || '—' }}</td> <td class="perm-domain">{{ p.domain || '—' }}</td>
<td class="perm-members center">{{ permissionSummary[p.scope]?.userCount || 0 }}</td> <td class="perm-members center">{{ permissionSummary[p.uuid]?.userCount || 0 }}</td>
<td class="perm-actions center"> <td class="perm-actions center">
<button @click="$emit('deletePermission', p)" class="icon-btn delete-icon" aria-label="Delete permission" title="Delete permission"></button> <button @click="$emit('deletePermission', p)" class="icon-btn delete-icon" aria-label="Delete permission" title="Delete permission"></button>
</td> </td>
+90 -51
View File
@@ -170,14 +170,14 @@ def build_role(uuid: UUID) -> Role:
uuid=uuid, uuid=uuid,
org_uuid=r.org, org_uuid=r.org,
display_name=r.display_name, display_name=r.display_name,
permissions=list(r.permissions.keys()), permissions=[str(pid) for pid in r.permissions.keys()],
) )
def build_org(uuid: UUID, include_roles: bool = False) -> Org: def build_org(uuid: UUID, include_roles: bool = False) -> Org:
o = _db._data.orgs[uuid] o = _db._data.orgs[uuid]
perm_scopes = [p.scope for p in _db._data.permissions.values() if uuid in p.orgs] perm_uuids = [str(pid) for pid, p in _db._data.permissions.items() if uuid in p.orgs]
org = Org(uuid=uuid, display_name=o.display_name, permissions=perm_scopes) org = Org(uuid=uuid, display_name=o.display_name, permissions=perm_uuids)
if include_roles: if include_roles:
org.roles = [ org.roles = [
build_role(rid) for rid, r in _db._data.roles.items() if r.org == uuid build_role(rid) for rid, r in _db._data.roles.items() if r.org == uuid
@@ -461,24 +461,24 @@ def get_session_context(
else None else None
) )
# Effective permissions: role's permission scopes that the org can grant # Effective permissions: role's permissions that the org can grant
# Also filter by domain if host is provided # Also filter by domain if host is provided
org_scopes = set(org.permissions) org_perm_uuids = set(org.permissions) # Set of permission UUID strings
normalized_host = normalize_host(host) normalized_host = normalize_host(host)
host_without_port = normalized_host.rsplit(":", 1)[0] if normalized_host else None host_without_port = normalized_host.rsplit(":", 1)[0] if normalized_host else None
effective_perms = [] effective_perms = []
for scope in role.permissions: for perm_uuid_str in role.permissions:
if scope not in org_scopes: if perm_uuid_str not in org_perm_uuids:
continue continue
# Find permission by scope perm_uuid = UUID(perm_uuid_str)
for pid, p in _db._data.permissions.items(): if perm_uuid not in _db._data.permissions:
if p.scope == scope: continue
# Check domain restriction p = _db._data.permissions[perm_uuid]
if p.domain is not None and p.domain != host_without_port: # Check domain restriction
continue if p.domain is not None and p.domain != host_without_port:
effective_perms.append(build_permission(pid)) continue
break effective_perms.append(build_permission(perm_uuid))
return SessionContext( return SessionContext(
session=session, session=session,
@@ -527,7 +527,7 @@ def rename_permission(
) -> None: ) -> None:
"""Rename a permission's scope. The UUID remains the same. """Rename a permission's scope. The UUID remains the same.
Also updates all role references to use the new scope. Since roles reference permissions by UUID, no role updates are needed.
Note: Scopes do not need to be unique (same scope with different domains is valid). Note: Scopes do not need to be unique (same scope with different domains is valid).
""" """
# Find permission by old scope # Find permission by old scope
@@ -545,21 +545,17 @@ def rename_permission(
_db._data.permissions[key].display_name = display_name _db._data.permissions[key].display_name = display_name
_db._data.permissions[key].domain = domain _db._data.permissions[key].domain = domain
# Update role references if scope changed
if old_scope != new_scope:
for r in _db._data.roles.values():
if old_scope in r.permissions:
del r.permissions[old_scope]
r.permissions[new_scope] = True
def delete_permission(uuid: str | UUID, actor: str = "system") -> None: def delete_permission(uuid: str | UUID, actor: str = "system") -> None:
"""Delete a permission.""" """Delete a permission and remove it from all roles."""
if isinstance(uuid, str): if isinstance(uuid, str):
uuid = UUID(uuid) uuid = UUID(uuid)
if uuid not in _db._data.permissions: if uuid not in _db._data.permissions:
raise ValueError(f"Permission {uuid} not found") raise ValueError(f"Permission {uuid} not found")
with _db.transaction(actor): with _db.transaction(actor):
# Remove this permission from all roles
for role in _db._data.roles.values():
role.permissions.pop(uuid, None)
del _db._data.permissions[uuid] del _db._data.permissions[uuid]
@@ -574,19 +570,26 @@ def create_organization(org: Org, actor: str = "system") -> None:
_db._data.orgs[org.uuid] = _OrgData( _db._data.orgs[org.uuid] = _OrgData(
display_name=org.display_name, created_at=datetime.now(timezone.utc) display_name=org.display_name, created_at=datetime.now(timezone.utc)
) )
# Grant listed permissions to this org # Grant listed permissions to this org (org.permissions contains UUIDs now)
for scope in org.permissions: for perm_uuid_str in org.permissions:
for pid, p in _db._data.permissions.items(): perm_uuid = UUID(perm_uuid_str) if isinstance(perm_uuid_str, str) else perm_uuid_str
if p.scope == scope: if perm_uuid in _db._data.permissions:
p.orgs[org.uuid] = True _db._data.permissions[perm_uuid].orgs[org.uuid] = True
# Create Administration role with org admin permission # Create Administration role with org admin permission
import uuid7 import uuid7
admin_role_uuid = uuid7.create() admin_role_uuid = uuid7.create()
# Find the auth:org:admin permission UUID
org_admin_perm_uuid = None
for pid, p in _db._data.permissions.items():
if p.scope == "auth:org:admin":
org_admin_perm_uuid = pid
break
role_permissions = {org_admin_perm_uuid: True} if org_admin_perm_uuid else {}
_db._data.roles[admin_role_uuid] = _RoleData( _db._data.roles[admin_role_uuid] = _RoleData(
org=org.uuid, org=org.uuid,
display_name="Administration", display_name="Administration",
permissions={"auth:org:admin": True}, permissions=role_permissions,
) )
@@ -624,35 +627,65 @@ def delete_organization(uuid: str | UUID, actor: str = "system") -> None:
def add_permission_to_organization( def add_permission_to_organization(
org_uuid: str | UUID, permission_scope: str, actor: str = "system" org_uuid: str | UUID, permission_id: str | UUID, actor: str = "system"
) -> None: ) -> None:
"""Grant a permission scope to an organization.""" """Grant a permission to an organization by UUID."""
if isinstance(org_uuid, str): if isinstance(org_uuid, str):
org_uuid = UUID(org_uuid) org_uuid = UUID(org_uuid)
if org_uuid not in _db._data.orgs: if org_uuid not in _db._data.orgs:
raise ValueError(f"Organization {org_uuid} not found") raise ValueError(f"Organization {org_uuid} not found")
found = False
# Convert permission_id to UUID
if isinstance(permission_id, str):
try:
permission_uuid = UUID(permission_id)
except ValueError:
# It's a scope - look up the UUID (backwards compat)
for pid, p in _db._data.permissions.items():
if p.scope == permission_id:
permission_uuid = pid
break
else:
raise ValueError(f"Permission {permission_id} not found")
else:
permission_uuid = permission_id
if permission_uuid not in _db._data.permissions:
raise ValueError(f"Permission {permission_uuid} not found")
with _db.transaction(actor): with _db.transaction(actor):
for p in _db._data.permissions.values(): _db._data.permissions[permission_uuid].orgs[org_uuid] = True
if p.scope == permission_scope:
p.orgs[org_uuid] = True
found = True
if not found:
raise ValueError(f"Permission scope {permission_scope} not found")
def remove_permission_from_organization( def remove_permission_from_organization(
org_uuid: str | UUID, permission_scope: str, actor: str = "system" org_uuid: str | UUID, permission_id: str | UUID, actor: str = "system"
) -> None: ) -> None:
"""Remove a permission scope from an organization.""" """Remove a permission from an organization by UUID."""
if isinstance(org_uuid, str): if isinstance(org_uuid, str):
org_uuid = UUID(org_uuid) org_uuid = UUID(org_uuid)
if org_uuid not in _db._data.orgs: if org_uuid not in _db._data.orgs:
raise ValueError(f"Organization {org_uuid} not found") raise ValueError(f"Organization {org_uuid} not found")
# Convert permission_id to UUID
if isinstance(permission_id, str):
try:
permission_uuid = UUID(permission_id)
except ValueError:
# It's a scope - look up the UUID (backwards compat)
for pid, p in _db._data.permissions.items():
if p.scope == permission_id:
permission_uuid = pid
break
else:
return # Permission not found, silently return
else:
permission_uuid = permission_id
if permission_uuid not in _db._data.permissions:
return # Permission not found, silently return
with _db.transaction(actor): with _db.transaction(actor):
for p in _db._data.permissions.values(): _db._data.permissions[permission_uuid].orgs.pop(org_uuid, None)
if p.scope == permission_scope:
p.orgs.pop(org_uuid, None)
def create_role(role: Role, actor: str = "system") -> None: def create_role(role: Role, actor: str = "system") -> None:
@@ -665,7 +698,7 @@ def create_role(role: Role, actor: str = "system") -> None:
_db._data.roles[role.uuid] = _RoleData( _db._data.roles[role.uuid] = _RoleData(
org=role.org_uuid, org=role.org_uuid,
display_name=role.display_name, display_name=role.display_name,
permissions={scope: True for scope in role.permissions}, permissions={UUID(pid): True for pid in role.permissions},
) )
@@ -682,27 +715,33 @@ def update_role_name(
def add_permission_to_role( def add_permission_to_role(
role_uuid: str | UUID, permission_scope: str, actor: str = "system" role_uuid: str | UUID, permission_uuid: str | UUID, actor: str = "system"
) -> None: ) -> None:
"""Add permission scope to role.""" """Add permission to role by UUID."""
if isinstance(role_uuid, str): if isinstance(role_uuid, str):
role_uuid = UUID(role_uuid) role_uuid = UUID(role_uuid)
if isinstance(permission_uuid, str):
permission_uuid = UUID(permission_uuid)
if role_uuid not in _db._data.roles: if role_uuid not in _db._data.roles:
raise ValueError(f"Role {role_uuid} not found") raise ValueError(f"Role {role_uuid} not found")
if permission_uuid not in _db._data.permissions:
raise ValueError(f"Permission {permission_uuid} not found")
with _db.transaction(actor): with _db.transaction(actor):
_db._data.roles[role_uuid].permissions[permission_scope] = True _db._data.roles[role_uuid].permissions[permission_uuid] = True
def remove_permission_from_role( def remove_permission_from_role(
role_uuid: str | UUID, permission_scope: str, actor: str = "system" role_uuid: str | UUID, permission_uuid: str | UUID, actor: str = "system"
) -> None: ) -> None:
"""Remove permission scope from role.""" """Remove permission from role by UUID."""
if isinstance(role_uuid, str): if isinstance(role_uuid, str):
role_uuid = UUID(role_uuid) role_uuid = UUID(role_uuid)
if isinstance(permission_uuid, str):
permission_uuid = UUID(permission_uuid)
if role_uuid not in _db._data.roles: if role_uuid not in _db._data.roles:
raise ValueError(f"Role {role_uuid} not found") raise ValueError(f"Role {role_uuid} not found")
with _db.transaction(actor): with _db.transaction(actor):
_db._data.roles[role_uuid].permissions.pop(permission_scope, None) _db._data.roles[role_uuid].permissions.pop(permission_uuid, None)
def delete_role(uuid: str | UUID, actor: str = "system") -> None: def delete_role(uuid: str | UUID, actor: str = "system") -> None:
+3 -3
View File
@@ -15,13 +15,13 @@ class Role(msgspec.Struct):
uuid: UUID uuid: UUID
org_uuid: UUID org_uuid: UUID
display_name: str display_name: str
permissions: list[str] = [] # permission IDs this role grants permissions: list[str] = [] # permission UUIDs this role grants
class Org(msgspec.Struct): class Org(msgspec.Struct):
uuid: UUID uuid: UUID
display_name: str display_name: str
permissions: list[str] = [] # permission IDs this org can grant permissions: list[str] = [] # permission UUIDs this org can grant
roles: list[Role] = [] # roles belonging to this org roles: list[Role] = [] # roles belonging to this org
@@ -100,7 +100,7 @@ class _OrgData(msgspec.Struct):
class _RoleData(msgspec.Struct): class _RoleData(msgspec.Struct):
org: UUID org: UUID
display_name: str display_name: str
permissions: dict[str, bool] # permission_id -> True permissions: dict[UUID, bool] # permission_uuid -> True
class _UserData(msgspec.Struct): class _UserData(msgspec.Struct):
+22 -14
View File
@@ -319,11 +319,11 @@ async def admin_update_role_name(
return {"status": "ok"} return {"status": "ok"}
@app.post("/orgs/{org_uuid}/roles/{role_uuid}/permissions/{permission_id}") @app.post("/orgs/{org_uuid}/roles/{role_uuid}/permissions/{permission_uuid}")
async def admin_add_role_permission( async def admin_add_role_permission(
org_uuid: UUID, org_uuid: UUID,
role_uuid: UUID, role_uuid: UUID,
permission_id: str, permission_uuid: UUID,
request: Request, request: Request,
auth=AUTH_COOKIE, auth=AUTH_COOKIE,
): ):
@@ -344,20 +344,22 @@ async def admin_add_role_permission(
raise HTTPException(status_code=404, detail="Role not found in organization") raise HTTPException(status_code=404, detail="Role not found in organization")
# Verify permission exists and org can grant it # Verify permission exists and org can grant it
db.get_permission(permission_id) perm = db.get_permission(permission_uuid)
if not perm:
raise HTTPException(status_code=404, detail="Permission not found")
org = db.get_organization(str(org_uuid)) org = db.get_organization(str(org_uuid))
if permission_id not in org.permissions: if str(permission_uuid) not in org.permissions:
raise ValueError("Permission not grantable by organization") raise ValueError("Permission not grantable by organization")
db.add_permission_to_role(role_uuid, permission_id, actor=str(ctx.user.uuid)) db.add_permission_to_role(role_uuid, permission_uuid, actor=str(ctx.user.uuid))
return {"status": "ok"} return {"status": "ok"}
@app.delete("/orgs/{org_uuid}/roles/{role_uuid}/permissions/{permission_id}") @app.delete("/orgs/{org_uuid}/roles/{role_uuid}/permissions/{permission_uuid}")
async def admin_remove_role_permission( async def admin_remove_role_permission(
org_uuid: UUID, org_uuid: UUID,
role_uuid: UUID, role_uuid: UUID,
permission_id: str, permission_uuid: UUID,
request: Request, request: Request,
auth=AUTH_COOKIE, auth=AUTH_COOKIE,
): ):
@@ -378,17 +380,23 @@ async def admin_remove_role_permission(
raise HTTPException(status_code=404, detail="Role not found in organization") raise HTTPException(status_code=404, detail="Role not found in organization")
# Sanity check: prevent admin from removing their own access # Sanity check: prevent admin from removing their own access
# Find auth:admin and auth:org:admin permission UUIDs
perm_uuid_str = str(permission_uuid)
perm = db.get_permission(permission_uuid)
if ctx.org.uuid == org_uuid and ctx.role.uuid == role_uuid: if ctx.org.uuid == org_uuid and ctx.role.uuid == role_uuid:
if permission_id in ["auth:admin", "auth:org:admin"]: if perm and perm.scope in ["auth:admin", "auth:org:admin"]:
# Check if removing this permission would leave no admin access # Check if removing this permission would leave no admin access
remaining_perms = set(role.permissions) - {permission_id} remaining_perms = set(role.permissions) - {perm_uuid_str}
if ( has_admin = False
"auth:admin" not in remaining_perms for rp_uuid in remaining_perms:
and "auth:org:admin" not in remaining_perms rp = db.get_permission(rp_uuid)
): if rp and rp.scope in ["auth:admin", "auth:org:admin"]:
has_admin = True
break
if not has_admin:
raise ValueError("Cannot remove your own admin permissions") raise ValueError("Cannot remove your own admin permissions")
db.remove_permission_from_role(role_uuid, permission_id, actor=str(ctx.user.uuid)) db.remove_permission_from_role(role_uuid, permission_uuid, actor=str(ctx.user.uuid))
return {"status": "ok"} return {"status": "ok"}
+15 -17
View File
@@ -96,19 +96,19 @@ async def migrate_from_sql(
orgs={}, orgs={},
) )
# Mapping from old permission ID to new scope # Mapping from old permission ID to new permission UUID
perm_id_to_scope: dict[str, str] = {} perm_id_to_uuid: dict[str, UUID] = {}
for perm in permissions: for perm in permissions:
# Skip old org-specific admin permissions (auth:org:{uuid}) - they map to auth:org:admin # Skip old org-specific admin permissions (auth:org:{uuid}) - they map to auth:org:admin
match = old_org_admin_pattern.match(perm.id) match = old_org_admin_pattern.match(perm.id)
if match: if match:
perm_id_to_scope[perm.id] = "auth:org:admin" perm_id_to_uuid[perm.id] = org_admin_perm_uuid
continue continue
# Skip if this is already auth:org:admin - we created one above # Skip if this is already auth:org:admin - we created one above
if perm.id == "auth:org:admin": if perm.id == "auth:org:admin":
perm_id_to_scope[perm.id] = "auth:org:admin" perm_id_to_uuid[perm.id] = org_admin_perm_uuid
continue continue
# Regular permission - create with UUID key # Regular permission - create with UUID key
@@ -118,7 +118,7 @@ async def migrate_from_sql(
display_name=perm.display_name, display_name=perm.display_name,
orgs={}, orgs={},
) )
perm_id_to_scope[perm.id] = perm.id # Scope same as old ID perm_id_to_uuid[perm.id] = perm_uuid
print( print(
f" Migrated {len(permissions)} permissions (with {len(org_admin_uuids)} org-specific admins consolidated to auth:org:admin)" f" Migrated {len(permissions)} permissions (with {len(org_admin_uuids)} org-specific admins consolidated to auth:org:admin)"
) )
@@ -130,28 +130,26 @@ async def migrate_from_sql(
json_db._data.orgs[org_key] = _OrgData( json_db._data.orgs[org_key] = _OrgData(
display_name=org.display_name, display_name=org.display_name,
) )
# Update permissions to allow this org to grant them (by scope) # Update permissions to allow this org to grant them (by UUID)
for old_perm_id in org.permissions: for old_perm_id in org.permissions:
new_scope = perm_id_to_scope.get(old_perm_id, old_perm_id) perm_uuid = perm_id_to_uuid.get(old_perm_id)
# Find permission with this scope and add org if perm_uuid and perm_uuid in json_db._data.permissions:
for pid, p in json_db._data.permissions.items(): json_db._data.permissions[perm_uuid].orgs[org_key] = True
if p.scope == new_scope:
p.orgs[org_key] = True
break
# Ensure every org can grant auth:org:admin # Ensure every org can grant auth:org:admin
json_db._data.permissions[org_admin_perm_uuid].orgs[org_key] = True json_db._data.permissions[org_admin_perm_uuid].orgs[org_key] = True
print(f" Migrated {len(orgs)} organizations") print(f" Migrated {len(orgs)} organizations")
# Migrate roles - convert old permission IDs to scopes # Migrate roles - convert old permission IDs to UUIDs
role_count = 0 role_count = 0
for org in orgs: for org in orgs:
for role in org.roles: for role in org.roles:
role_key: UUID = role.uuid role_key: UUID = role.uuid
# Convert old permission IDs to scopes # Convert old permission IDs to UUIDs
new_permissions = {} new_permissions: dict[UUID, bool] = {}
for old_perm_id in role.permissions or []: for old_perm_id in role.permissions or []:
new_scope = perm_id_to_scope.get(old_perm_id, old_perm_id) perm_uuid = perm_id_to_uuid.get(old_perm_id)
new_permissions[new_scope] = True if perm_uuid:
new_permissions[perm_uuid] = True
json_db._data.roles[role_key] = _RoleData( json_db._data.roles[role_key] = _RoleData(
org=role.org_uuid, org=role.org_uuid,
display_name=role.display_name, display_name=role.display_name,