Remove most remaining DB getters. Add ws auth chat helper function to avoid repetition, along with the existing register chat in wschat.py.
This commit is contained in:
+2
-3
@@ -69,9 +69,8 @@ async def check_admin_credentials() -> bool:
|
|||||||
|
|
||||||
# Check first admin user for credentials
|
# Check first admin user for credentials
|
||||||
admin_user = admin_users[0]
|
admin_user = admin_users[0]
|
||||||
credentials = db.get_credentials_by_user_uuid(admin_user.uuid)
|
|
||||||
|
|
||||||
if not credentials:
|
if not db.get_user_credential_ids(admin_user.uuid):
|
||||||
# Admin exists but has no credentials, create reset link
|
# Admin exists but has no credentials, create reset link
|
||||||
from paskia import authsession
|
from paskia import authsession
|
||||||
from paskia.util import passphrase
|
from paskia.util import passphrase
|
||||||
@@ -101,7 +100,7 @@ async def bootstrap_if_needed() -> bool:
|
|||||||
bool: True if bootstrapping was performed, False if system was already set up
|
bool: True if bootstrapping was performed, False if system was already set up
|
||||||
"""
|
"""
|
||||||
# Check if the admin permission exists - if it does, system is already bootstrapped
|
# Check if the admin permission exists - if it does, system is already bootstrapped
|
||||||
if db.get_permission_by_scope("auth:admin"):
|
if any(p.scope == "auth:admin" for p in db.data().permissions.values()):
|
||||||
# Permission exists, system is already bootstrapped
|
# Permission exists, system is already bootstrapped
|
||||||
# Check if admin needs credentials (only for already-bootstrapped systems)
|
# Check if admin needs credentials (only for already-bootstrapped systems)
|
||||||
await check_admin_credentials()
|
await check_admin_credentials()
|
||||||
|
|||||||
+2
-21
@@ -46,21 +46,12 @@ from paskia.db.operations import (
|
|||||||
delete_session,
|
delete_session,
|
||||||
delete_sessions_for_user,
|
delete_sessions_for_user,
|
||||||
delete_user,
|
delete_user,
|
||||||
get_credential_by_id,
|
|
||||||
get_credentials_by_user_uuid,
|
|
||||||
get_organization,
|
|
||||||
get_organization_users,
|
get_organization_users,
|
||||||
get_permission,
|
|
||||||
get_permission_by_scope,
|
|
||||||
get_reset_token,
|
get_reset_token,
|
||||||
get_role,
|
|
||||||
get_roles_by_organization,
|
|
||||||
get_session_context,
|
get_session_context,
|
||||||
get_user_by_uuid,
|
get_user_credential_ids,
|
||||||
get_user_organization,
|
get_user_organization,
|
||||||
init,
|
init,
|
||||||
list_organizations,
|
|
||||||
list_permissions,
|
|
||||||
login,
|
login,
|
||||||
remove_permission_from_organization,
|
remove_permission_from_organization,
|
||||||
remove_permission_from_role,
|
remove_permission_from_role,
|
||||||
@@ -122,21 +113,11 @@ __all__ = [
|
|||||||
"build_session",
|
"build_session",
|
||||||
"build_user",
|
"build_user",
|
||||||
# Read ops
|
# Read ops
|
||||||
"get_credential_by_id",
|
|
||||||
"get_credentials_by_user_uuid",
|
|
||||||
"get_organization",
|
|
||||||
"get_organization_users",
|
"get_organization_users",
|
||||||
"get_permission",
|
|
||||||
"get_permission_by_scope",
|
|
||||||
"get_reset_token",
|
"get_reset_token",
|
||||||
"get_role",
|
|
||||||
"get_roles_by_organization",
|
|
||||||
"get_session_context",
|
"get_session_context",
|
||||||
"get_user_by_uuid",
|
"get_user_credential_ids",
|
||||||
"get_user_organization",
|
"get_user_organization",
|
||||||
"list_organizations",
|
|
||||||
"list_permissions",
|
|
||||||
"list_permissions",
|
|
||||||
# Write ops
|
# Write ops
|
||||||
"add_permission_to_organization",
|
"add_permission_to_organization",
|
||||||
"add_permission_to_role",
|
"add_permission_to_role",
|
||||||
|
|||||||
+11
-136
@@ -53,123 +53,6 @@ async def init(*args, **kwargs):
|
|||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def get_permission(uuid: UUID) -> Permission | None:
|
|
||||||
"""Get permission by UUID.
|
|
||||||
|
|
||||||
Call sites:
|
|
||||||
- Normalize permission IDs to UUIDs when creating a role (admin.py:277)
|
|
||||||
- Verify permission exists when adding to role (admin.py:349)
|
|
||||||
- Check permission scope when removing from role to prevent losing admin access (admin.py:385)
|
|
||||||
- Get permission to check scope for admin access check (admin.py:392)
|
|
||||||
- Check if new role has admin permissions when user changes own role (admin.py:509)
|
|
||||||
- Get permission for updating its details (admin.py:977)
|
|
||||||
- Get permission for renaming its scope (admin.py:1031)
|
|
||||||
- Get permission to check scope before deleting (admin.py:1071)
|
|
||||||
"""
|
|
||||||
return _db.permissions.get(uuid)
|
|
||||||
|
|
||||||
|
|
||||||
def get_permission_by_scope(scope: str) -> Permission | None:
|
|
||||||
"""Get permission by scope identifier.
|
|
||||||
|
|
||||||
Call sites:
|
|
||||||
- Check if system is already bootstrapped by looking for auth:admin permission (bootstrap.py:113)
|
|
||||||
"""
|
|
||||||
for p in _db.permissions.values():
|
|
||||||
if p.scope == scope:
|
|
||||||
return p
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_permissions_by_scope(scope: str) -> list[Permission]:
|
|
||||||
"""Get all permissions with the given scope.
|
|
||||||
|
|
||||||
Since scopes are not unique, this returns all matching permissions.
|
|
||||||
Use this for scope-based permission checking.
|
|
||||||
"""
|
|
||||||
return [p for p in _db.permissions.values() if p.scope == scope]
|
|
||||||
|
|
||||||
|
|
||||||
def list_permissions() -> list[Permission]:
|
|
||||||
"""List all permissions.
|
|
||||||
|
|
||||||
Call sites:
|
|
||||||
- List permissions during migration to identify org-specific admin permissions (migrate/__init__.py:84)
|
|
||||||
- List permissions to delete organization-specific permissions when deleting org (admin.py:193)
|
|
||||||
- List permissions to check admin permissions when updating permission domain (admin.py:847)
|
|
||||||
- List permissions to check admin permissions when deleting permission (admin.py:882)
|
|
||||||
- Admin API endpoint to list permissions (admin.py:914)
|
|
||||||
"""
|
|
||||||
return list(_db.permissions.values())
|
|
||||||
|
|
||||||
|
|
||||||
def get_organization(uuid: UUID) -> Org | None:
|
|
||||||
"""Get organization by UUID.
|
|
||||||
|
|
||||||
Call sites:
|
|
||||||
- Get organization when creating a role to check grantable permissions (admin.py:271)
|
|
||||||
- Get organization when adding permission to role to check if org can grant it (admin.py:352)
|
|
||||||
"""
|
|
||||||
return _db.orgs.get(uuid)
|
|
||||||
|
|
||||||
|
|
||||||
def list_organizations() -> list[Org]:
|
|
||||||
"""List all organizations.
|
|
||||||
|
|
||||||
Call sites:
|
|
||||||
- List organizations during migration (migrate/__init__.py:131)
|
|
||||||
- Admin API endpoint to list organizations (admin.py:94)
|
|
||||||
"""
|
|
||||||
return list(_db.orgs.values())
|
|
||||||
|
|
||||||
|
|
||||||
def get_organization_users(org_uuid: UUID) -> list[tuple[User, str]]:
|
|
||||||
"""Get all users in an organization with their role names.
|
|
||||||
|
|
||||||
Call sites:
|
|
||||||
- Get users for each organization in the admin list orgs API (admin.py:108)
|
|
||||||
- Get users from organizations with auth:admin for reset targets (reset.py:31,42,58)
|
|
||||||
- Get users from organization to check if admin has credentials (bootstrap.py:73)
|
|
||||||
"""
|
|
||||||
role_map = {
|
|
||||||
rid: r.display_name for rid, r in _db.roles.items() if r.org == org_uuid
|
|
||||||
}
|
|
||||||
return [(u, role_map[u.role]) for u in _db.users.values() if u.role in role_map]
|
|
||||||
|
|
||||||
|
|
||||||
def get_role(uuid: UUID) -> Role | None:
|
|
||||||
"""Get role by UUID.
|
|
||||||
|
|
||||||
Call sites:
|
|
||||||
- Get role to update its display name (admin.py:312)
|
|
||||||
- Get role to add permission to it (admin.py:344)
|
|
||||||
- Get role to remove permission from it (admin.py:380)
|
|
||||||
- Get role to delete it (admin.py:421)
|
|
||||||
"""
|
|
||||||
return _db.roles.get(uuid)
|
|
||||||
|
|
||||||
|
|
||||||
def get_roles_by_organization(org_uuid: UUID) -> list[Role]:
|
|
||||||
"""Get all roles in an organization.
|
|
||||||
|
|
||||||
Call sites:
|
|
||||||
- Get roles by organization when creating a user to find the role by name (admin.py:459)
|
|
||||||
- Get roles by organization when updating user role to validate the new role name (admin.py:498)
|
|
||||||
"""
|
|
||||||
return [r for r in _db.roles.values() if r.org == org_uuid]
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_by_uuid(uuid: UUID) -> User | None:
|
|
||||||
"""Get user by UUID.
|
|
||||||
|
|
||||||
Call sites:
|
|
||||||
- Get user for WebAuthn credential registration (ws.py:68)
|
|
||||||
- Get user from reset token for registration info (api.py:127)
|
|
||||||
- Get user for listing user credentials in admin API (admin.py:594)
|
|
||||||
"""
|
|
||||||
return _db.users.get(uuid)
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_organization(user_uuid: UUID) -> tuple[Org, str]:
|
def get_user_organization(user_uuid: UUID) -> tuple[Org, str]:
|
||||||
"""Get the organization a user belongs to and their role name.
|
"""Get the organization a user belongs to and their role name.
|
||||||
|
|
||||||
@@ -193,31 +76,23 @@ def get_user_organization(user_uuid: UUID) -> tuple[Org, str]:
|
|||||||
return _db.orgs[org_uuid], role_data.display_name
|
return _db.orgs[org_uuid], role_data.display_name
|
||||||
|
|
||||||
|
|
||||||
def get_credential_by_id(credential_id: bytes) -> Credential | None:
|
def get_organization_users(org_uuid: UUID) -> list[tuple[User, str]]:
|
||||||
"""Get credential by credential_id (the authenticator's ID).
|
"""Get all users in an organization with their role names.
|
||||||
|
|
||||||
Call sites:
|
Returns list of (User, role_display_name) tuples.
|
||||||
- Get credential by ID for WebAuthn authentication (ws.py:132)
|
|
||||||
- Get credential by ID for remote authentication (remote.py:325)
|
|
||||||
"""
|
"""
|
||||||
for c in _db.credentials.values():
|
role_map = {
|
||||||
if c.credential_id == credential_id:
|
rid: r.display_name for rid, r in _db.roles.items() if r.org == org_uuid
|
||||||
return c
|
}
|
||||||
return None
|
return [(u, role_map[u.role]) for u in _db.users.values() if u.role in role_map]
|
||||||
|
|
||||||
|
|
||||||
def get_credentials_by_user_uuid(user_uuid: UUID) -> list[Credential]:
|
def get_user_credential_ids(user_uuid: UUID) -> list[bytes]:
|
||||||
"""Get all credentials for a user.
|
"""Get credential IDs for a user (for WebAuthn exclude lists).
|
||||||
|
|
||||||
Call sites:
|
Returns empty list if user has no credentials.
|
||||||
- Get credentials for user during registration to exclude existing ones (ws.py:74)
|
|
||||||
- Get credentials for session user during reauth to restrict to user's credentials (ws.py:117)
|
|
||||||
- Get credentials to check if user has existing ones for reset token type (admin.py:548)
|
|
||||||
- Get credentials for user details API (admin.py:595)
|
|
||||||
- Get credentials to check if admin user has credentials (bootstrap.py:81)
|
|
||||||
- Get credentials for user info formatting (userinfo.py:51)
|
|
||||||
"""
|
"""
|
||||||
return [c for c in _db.credentials.values() if c.user == user_uuid]
|
return [c.credential_id for c in _db.credentials.values() if c.user == user_uuid]
|
||||||
|
|
||||||
|
|
||||||
def _reset_key(passphrase: str) -> bytes:
|
def _reset_key(passphrase: str) -> bytes:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
import msgspec
|
import msgspec
|
||||||
@@ -76,13 +76,11 @@ class Org(msgspec.Struct, dict=True, omit_defaults=True):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, display_name: str) -> "Org":
|
def create(cls, display_name: str) -> "Org":
|
||||||
"""Create a new Org with auto-generated uuid7."""
|
"""Create a new Org with auto-generated uuid7."""
|
||||||
from datetime import timezone
|
|
||||||
|
|
||||||
org = cls(
|
org = cls(
|
||||||
display_name=display_name,
|
display_name=display_name,
|
||||||
created_at=datetime.now(timezone.utc),
|
created_at=datetime.now(timezone.utc),
|
||||||
)
|
)
|
||||||
org.uuid = uuid7.create()
|
org.uuid = uuid7.create(org.created_at)
|
||||||
return org
|
return org
|
||||||
|
|
||||||
|
|
||||||
@@ -104,7 +102,6 @@ class User(msgspec.Struct, dict=True):
|
|||||||
created_at: datetime | None = None,
|
created_at: datetime | None = None,
|
||||||
) -> "User":
|
) -> "User":
|
||||||
"""Create a new User with auto-generated uuid7."""
|
"""Create a new User with auto-generated uuid7."""
|
||||||
from datetime import timezone
|
|
||||||
|
|
||||||
user = cls(
|
user = cls(
|
||||||
display_name=display_name,
|
display_name=display_name,
|
||||||
@@ -139,8 +136,6 @@ class Credential(msgspec.Struct, dict=True):
|
|||||||
created_at: datetime | None = None,
|
created_at: datetime | None = None,
|
||||||
) -> "Credential":
|
) -> "Credential":
|
||||||
"""Create a new Credential with auto-generated uuid7."""
|
"""Create a new Credential with auto-generated uuid7."""
|
||||||
from datetime import timezone
|
|
||||||
|
|
||||||
now = created_at or datetime.now(timezone.utc)
|
now = created_at or datetime.now(timezone.utc)
|
||||||
cred = cls(
|
cred = cls(
|
||||||
credential_id=credential_id,
|
credential_id=credential_id,
|
||||||
|
|||||||
+37
-32
@@ -91,7 +91,7 @@ async def admin_list_orgs(request: Request, auth=AUTH_COOKIE):
|
|||||||
match=permutil.has_any,
|
match=permutil.has_any,
|
||||||
host=request.headers.get("host"),
|
host=request.headers.get("host"),
|
||||||
)
|
)
|
||||||
orgs = db.list_organizations()
|
orgs = list(db.data().orgs.values())
|
||||||
if not is_global_admin(ctx):
|
if not is_global_admin(ctx):
|
||||||
# Org admins can only see their own organization
|
# Org admins can only see their own organization
|
||||||
orgs = [o for o in orgs if o.uuid == ctx.org.uuid]
|
orgs = [o for o in orgs if o.uuid == ctx.org.uuid]
|
||||||
@@ -194,7 +194,7 @@ async def admin_delete_org(org_uuid: UUID, request: Request, auth=AUTH_COOKIE):
|
|||||||
|
|
||||||
# Delete organization-specific permissions
|
# Delete organization-specific permissions
|
||||||
org_perm_pattern = f"org:{str(org_uuid).lower()}"
|
org_perm_pattern = f"org:{str(org_uuid).lower()}"
|
||||||
all_permissions = db.list_permissions()
|
all_permissions = list(db.data().permissions.values())
|
||||||
for perm in all_permissions:
|
for perm in all_permissions:
|
||||||
perm_scope_lower = perm.scope.lower()
|
perm_scope_lower = perm.scope.lower()
|
||||||
# Check if permission contains "org:{uuid}" separated by colons or at boundaries
|
# Check if permission contains "org:{uuid}" separated by colons or at boundaries
|
||||||
@@ -226,7 +226,10 @@ async def admin_add_org_permission(
|
|||||||
permission_uuid = UUID(permission_id)
|
permission_uuid = UUID(permission_id)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# It's a scope - look up the UUID
|
# It's a scope - look up the UUID
|
||||||
perm = db.get_permission_by_scope(permission_id)
|
perm = next(
|
||||||
|
(p for p in db.data().permissions.values() if p.scope == permission_id),
|
||||||
|
None,
|
||||||
|
)
|
||||||
if not perm:
|
if not perm:
|
||||||
raise HTTPException(status_code=404, detail="Permission not found")
|
raise HTTPException(status_code=404, detail="Permission not found")
|
||||||
permission_uuid = perm.uuid
|
permission_uuid = perm.uuid
|
||||||
@@ -251,13 +254,16 @@ async def admin_remove_org_permission(
|
|||||||
permission_uuid = UUID(permission_id)
|
permission_uuid = UUID(permission_id)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# It's a scope - look up the UUID
|
# It's a scope - look up the UUID
|
||||||
perm = db.get_permission_by_scope(permission_id)
|
perm = next(
|
||||||
|
(p for p in db.data().permissions.values() if p.scope == permission_id),
|
||||||
|
None,
|
||||||
|
)
|
||||||
if not perm:
|
if not perm:
|
||||||
raise HTTPException(status_code=404, detail="Permission not found")
|
raise HTTPException(status_code=404, detail="Permission not found")
|
||||||
permission_uuid = perm.uuid
|
permission_uuid = perm.uuid
|
||||||
|
|
||||||
# Guard rail: prevent removing auth:admin from your own org if it would lock you out
|
# Guard rail: prevent removing auth:admin from your own org if it would lock you out
|
||||||
perm = db.get_permission(permission_uuid)
|
perm = db.data().permissions.get(permission_uuid)
|
||||||
if perm and perm.scope == "auth:admin" and ctx.org.uuid == org_uuid:
|
if perm and perm.scope == "auth:admin" and ctx.org.uuid == org_uuid:
|
||||||
# Check if any other org grants auth:admin that we're a member of
|
# Check if any other org grants auth:admin that we're a member of
|
||||||
# (we only know our current org, so this effectively means we can't remove it from our own org)
|
# (we only know our current org, so this effectively means we can't remove it from our own org)
|
||||||
@@ -294,15 +300,14 @@ async def admin_create_role(
|
|||||||
|
|
||||||
display_name = payload.get("display_name") or "New Role"
|
display_name = payload.get("display_name") or "New Role"
|
||||||
perms = payload.get("permissions") or []
|
perms = payload.get("permissions") or []
|
||||||
org = db.get_organization(org_uuid)
|
if org_uuid not in db.data().orgs:
|
||||||
if not org:
|
|
||||||
raise HTTPException(status_code=404, detail="Organization not found")
|
raise HTTPException(status_code=404, detail="Organization not found")
|
||||||
grantable = {pid for pid, p in db.data().permissions.items() if org_uuid in p.orgs}
|
grantable = {pid for pid, p in db.data().permissions.items() if org_uuid in p.orgs}
|
||||||
|
|
||||||
# Normalize permission IDs to UUIDs
|
# Normalize permission IDs to UUIDs
|
||||||
permission_uuids: set[UUID] = set()
|
permission_uuids: set[UUID] = set()
|
||||||
for pid in perms:
|
for pid in perms:
|
||||||
perm = db.get_permission(UUID(pid))
|
perm = db.data().permissions.get(UUID(pid))
|
||||||
if not perm:
|
if not perm:
|
||||||
raise ValueError(f"Permission {pid} not found")
|
raise ValueError(f"Permission {pid} not found")
|
||||||
if perm.uuid not in grantable:
|
if perm.uuid not in grantable:
|
||||||
@@ -337,8 +342,8 @@ async def admin_update_role_name(
|
|||||||
raise authz.AuthException(
|
raise authz.AuthException(
|
||||||
status_code=403, detail="Insufficient permissions", mode="forbidden"
|
status_code=403, detail="Insufficient permissions", mode="forbidden"
|
||||||
)
|
)
|
||||||
role = db.get_role(role_uuid)
|
role = db.data().roles.get(role_uuid)
|
||||||
if role.org != org_uuid:
|
if not role or role.org != org_uuid:
|
||||||
raise HTTPException(status_code=404, detail="Role not found in organization")
|
raise HTTPException(status_code=404, detail="Role not found in organization")
|
||||||
|
|
||||||
display_name = payload.get("display_name")
|
display_name = payload.get("display_name")
|
||||||
@@ -369,12 +374,12 @@ async def admin_add_role_permission(
|
|||||||
status_code=403, detail="Insufficient permissions", mode="forbidden"
|
status_code=403, detail="Insufficient permissions", mode="forbidden"
|
||||||
)
|
)
|
||||||
|
|
||||||
role = db.get_role(role_uuid)
|
role = db.data().roles.get(role_uuid)
|
||||||
if role.org != org_uuid:
|
if not role or role.org != org_uuid:
|
||||||
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
|
||||||
perm = db.get_permission(permission_uuid)
|
perm = db.data().permissions.get(permission_uuid)
|
||||||
if not perm:
|
if not perm:
|
||||||
raise HTTPException(status_code=404, detail="Permission not found")
|
raise HTTPException(status_code=404, detail="Permission not found")
|
||||||
if org_uuid not in perm.orgs:
|
if org_uuid not in perm.orgs:
|
||||||
@@ -404,19 +409,19 @@ async def admin_remove_role_permission(
|
|||||||
status_code=403, detail="Insufficient permissions", mode="forbidden"
|
status_code=403, detail="Insufficient permissions", mode="forbidden"
|
||||||
)
|
)
|
||||||
|
|
||||||
role = db.get_role(role_uuid)
|
role = db.data().roles.get(role_uuid)
|
||||||
if role.org != org_uuid:
|
if not role or role.org != org_uuid:
|
||||||
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
|
||||||
perm = db.get_permission(permission_uuid)
|
perm = db.data().permissions.get(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 perm and perm.scope 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 = role.permission_set - {permission_uuid}
|
remaining_perms = role.permission_set - {permission_uuid}
|
||||||
has_admin = False
|
has_admin = False
|
||||||
for rp_uuid in remaining_perms:
|
for rp_uuid in remaining_perms:
|
||||||
rp = db.get_permission(rp_uuid)
|
rp = db.data().permissions.get(rp_uuid)
|
||||||
if rp and rp.scope in ["auth:admin", "auth:org:admin"]:
|
if rp and rp.scope in ["auth:admin", "auth:org:admin"]:
|
||||||
has_admin = True
|
has_admin = True
|
||||||
break
|
break
|
||||||
@@ -445,8 +450,8 @@ async def admin_delete_role(
|
|||||||
raise authz.AuthException(
|
raise authz.AuthException(
|
||||||
status_code=403, detail="Insufficient permissions", mode="forbidden"
|
status_code=403, detail="Insufficient permissions", mode="forbidden"
|
||||||
)
|
)
|
||||||
role = db.get_role(role_uuid)
|
role = db.data().roles.get(role_uuid)
|
||||||
if role.org != org_uuid:
|
if not role or role.org != org_uuid:
|
||||||
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 deleting their own role
|
# Sanity check: prevent admin from deleting their own role
|
||||||
@@ -483,7 +488,7 @@ async def admin_create_user(
|
|||||||
raise ValueError("display_name and role are required")
|
raise ValueError("display_name and role are required")
|
||||||
from ..db import User as UserDC
|
from ..db import User as UserDC
|
||||||
|
|
||||||
roles = db.get_roles_by_organization(org_uuid)
|
roles = [r for r in db.data().roles.values() if r.org == org_uuid]
|
||||||
role_obj = next((r for r in roles if r.display_name == role_name), None)
|
role_obj = next((r for r in roles if r.display_name == role_name), None)
|
||||||
if not role_obj:
|
if not role_obj:
|
||||||
raise ValueError("Role not found in organization")
|
raise ValueError("Role not found in organization")
|
||||||
@@ -522,7 +527,7 @@ async def admin_update_user_role(
|
|||||||
raise ValueError("User not found")
|
raise ValueError("User not found")
|
||||||
if user_org.uuid != org_uuid:
|
if user_org.uuid != org_uuid:
|
||||||
raise ValueError("User does not belong to this organization")
|
raise ValueError("User does not belong to this organization")
|
||||||
roles = db.get_roles_by_organization(org_uuid)
|
roles = [r for r in db.data().roles.values() if r.org == org_uuid]
|
||||||
if not any(r.display_name == new_role for r in roles):
|
if not any(r.display_name == new_role for r in roles):
|
||||||
raise ValueError("Role not found in organization")
|
raise ValueError("Role not found in organization")
|
||||||
|
|
||||||
@@ -533,7 +538,7 @@ async def admin_update_user_role(
|
|||||||
# Check if any permission in the new role is an admin permission
|
# Check if any permission in the new role is an admin permission
|
||||||
has_admin_access = False
|
has_admin_access = False
|
||||||
for perm_uuid in new_role_obj.permissions:
|
for perm_uuid in new_role_obj.permissions:
|
||||||
perm = db.get_permission(perm_uuid)
|
perm = db.data().permissions.get(perm_uuid)
|
||||||
if perm and perm.scope in ["auth:admin", "auth:org:admin"]:
|
if perm and perm.scope in ["auth:admin", "auth:org:admin"]:
|
||||||
has_admin_access = True
|
has_admin_access = True
|
||||||
break
|
break
|
||||||
@@ -572,8 +577,8 @@ async def admin_create_user_registration_link(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Check if user has existing credentials
|
# Check if user has existing credentials
|
||||||
credentials = db.get_credentials_by_user_uuid(user_uuid)
|
has_credentials = db.get_user_credential_ids(user_uuid)
|
||||||
token_type = "user registration" if not credentials else "account recovery"
|
token_type = "user registration" if not has_credentials else "account recovery"
|
||||||
|
|
||||||
token = passphrase.generate()
|
token = passphrase.generate()
|
||||||
expiry = reset_expires()
|
expiry = reset_expires()
|
||||||
@@ -618,8 +623,8 @@ async def admin_get_user_detail(
|
|||||||
raise authz.AuthException(
|
raise authz.AuthException(
|
||||||
status_code=403, detail="Insufficient permissions", mode="forbidden"
|
status_code=403, detail="Insufficient permissions", mode="forbidden"
|
||||||
)
|
)
|
||||||
user = db.get_user_by_uuid(user_uuid)
|
user = db.data().users.get(user_uuid)
|
||||||
user_creds = db.get_credentials_by_user_uuid(user_uuid)
|
user_creds = [c for c in db.data().credentials.values() if c.user == user_uuid]
|
||||||
creds: list[dict] = []
|
creds: list[dict] = []
|
||||||
aaguids: set[str] = set()
|
aaguids: set[str] = set()
|
||||||
for c in user_creds:
|
for c in user_creds:
|
||||||
@@ -871,7 +876,7 @@ def _check_admin_lockout(
|
|||||||
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
|
||||||
|
|
||||||
# Get all auth:admin permissions
|
# Get all auth:admin permissions
|
||||||
all_perms = db.list_permissions()
|
all_perms = list(db.data().permissions.values())
|
||||||
admin_perms = [p for p in all_perms if p.scope == "auth:admin"]
|
admin_perms = [p for p in all_perms if p.scope == "auth:admin"]
|
||||||
|
|
||||||
# Check if at least one auth:admin would remain accessible
|
# Check if at least one auth:admin would remain accessible
|
||||||
@@ -906,7 +911,7 @@ def _check_admin_lockout_on_delete(perm_uuid: str, current_host: str | None) ->
|
|||||||
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
|
||||||
|
|
||||||
# Get all auth:admin permissions
|
# Get all auth:admin permissions
|
||||||
all_perms = db.list_permissions()
|
all_perms = list(db.data().permissions.values())
|
||||||
admin_perms = [p for p in all_perms if p.scope == "auth:admin"]
|
admin_perms = [p for p in all_perms if p.scope == "auth:admin"]
|
||||||
|
|
||||||
# Check if at least one auth:admin would remain accessible after deletion
|
# Check if at least one auth:admin would remain accessible after deletion
|
||||||
@@ -938,7 +943,7 @@ async def admin_list_permissions(request: Request, auth=AUTH_COOKIE):
|
|||||||
match=permutil.has_any,
|
match=permutil.has_any,
|
||||||
host=request.headers.get("host"),
|
host=request.headers.get("host"),
|
||||||
)
|
)
|
||||||
perms = db.list_permissions()
|
perms = list(db.data().permissions.values())
|
||||||
|
|
||||||
# Global admins see all permissions
|
# Global admins see all permissions
|
||||||
if is_global_admin(ctx):
|
if is_global_admin(ctx):
|
||||||
@@ -997,7 +1002,7 @@ async def admin_update_permission(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Get existing permission
|
# Get existing permission
|
||||||
perm = db.get_permission(permission_uuid)
|
perm = db.data().permissions.get(permission_uuid)
|
||||||
|
|
||||||
# Update fields that were provided
|
# Update fields that were provided
|
||||||
new_scope = scope if scope is not None else perm.scope
|
new_scope = scope if scope is not None else perm.scope
|
||||||
@@ -1045,7 +1050,7 @@ async def admin_rename_permission(
|
|||||||
raise ValueError("new_scope required")
|
raise ValueError("new_scope required")
|
||||||
|
|
||||||
# Sanity check: prevent renaming critical permissions
|
# Sanity check: prevent renaming critical permissions
|
||||||
perm = db.get_permission(permission_uuid)
|
perm = db.data().permissions.get(permission_uuid)
|
||||||
if perm.scope == "auth:admin":
|
if perm.scope == "auth:admin":
|
||||||
raise ValueError("Cannot rename the master admin permission")
|
raise ValueError("Cannot rename the master admin permission")
|
||||||
|
|
||||||
@@ -1086,7 +1091,7 @@ async def admin_delete_permission(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Get the permission to check its scope
|
# Get the permission to check its scope
|
||||||
perm = db.get_permission(permission_uuid)
|
perm = db.data().permissions.get(permission_uuid)
|
||||||
|
|
||||||
# Sanity check: prevent deleting critical permissions if it would lock out admin
|
# Sanity check: prevent deleting critical permissions if it would lock out admin
|
||||||
if perm.scope == "auth:admin":
|
if perm.scope == "auth:admin":
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ async def token_info(credentials=Depends(bearer_auth)):
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise HTTPException(401, str(e))
|
raise HTTPException(401, str(e))
|
||||||
|
|
||||||
u = db.get_user_by_uuid(reset_token.user)
|
u = db.data().users.get(reset_token.user)
|
||||||
return {
|
return {
|
||||||
"token_type": reset_token.token_type,
|
"token_type": reset_token.token_type,
|
||||||
"display_name": u.display_name,
|
"display_name": u.display_name,
|
||||||
|
|||||||
+10
-30
@@ -17,8 +17,8 @@ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|||||||
|
|
||||||
from paskia import db, remoteauth
|
from paskia import db, remoteauth
|
||||||
from paskia.fastapi.session import infodict
|
from paskia.fastapi.session import infodict
|
||||||
|
from paskia.fastapi.wschat import authenticate_chat
|
||||||
from paskia.fastapi.wsutil import validate_origin, websocket_error_handler
|
from paskia.fastapi.wsutil import validate_origin, websocket_error_handler
|
||||||
from paskia.globals import passkey
|
|
||||||
from paskia.util import passphrase, pow
|
from paskia.util import passphrase, pow
|
||||||
|
|
||||||
# Create a FastAPI subapp for remote auth WebSocket endpoints
|
# Create a FastAPI subapp for remote auth WebSocket endpoints
|
||||||
@@ -311,30 +311,10 @@ async def websocket_remote_auth_permit(ws: WebSocket):
|
|||||||
|
|
||||||
# Handle authenticate request (no PoW needed - already validated during lookup)
|
# Handle authenticate request (no PoW needed - already validated during lookup)
|
||||||
if msg.get("authenticate") and request is not None:
|
if msg.get("authenticate") and request is not None:
|
||||||
# Generate authentication options
|
cred = await authenticate_chat(ws, origin)
|
||||||
options, webauthn_challenge = passkey.instance.auth_generate_options(
|
|
||||||
credential_ids=None
|
|
||||||
)
|
|
||||||
await ws.send_json({"optionsJSON": options})
|
|
||||||
|
|
||||||
# Wait for WebAuthn response
|
|
||||||
credential = passkey.instance.auth_parse(await ws.receive_json())
|
|
||||||
|
|
||||||
# Fetch and verify credential
|
|
||||||
try:
|
|
||||||
stored_cred = db.get_credential_by_id(credential.raw_id)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError(
|
|
||||||
f"This passkey is no longer registered with {passkey.instance.rp_name}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verify the credential
|
|
||||||
passkey.instance.auth_verify(
|
|
||||||
credential, webauthn_challenge, stored_cred, origin
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create a session for the REQUESTING device
|
# Create a session for the REQUESTING device
|
||||||
assert stored_cred.uuid is not None
|
assert cred.uuid is not None
|
||||||
|
|
||||||
session_token = None
|
session_token = None
|
||||||
reset_token = None
|
reset_token = None
|
||||||
@@ -347,7 +327,7 @@ async def websocket_remote_auth_permit(ws: WebSocket):
|
|||||||
token_str = passphrase.generate()
|
token_str = passphrase.generate()
|
||||||
expiry = expires()
|
expiry = expires()
|
||||||
db.create_reset_token(
|
db.create_reset_token(
|
||||||
user_uuid=stored_cred.user,
|
user_uuid=cred.user,
|
||||||
passphrase=token_str,
|
passphrase=token_str,
|
||||||
expiry=expiry,
|
expiry=expiry,
|
||||||
token_type="device addition",
|
token_type="device addition",
|
||||||
@@ -356,8 +336,8 @@ async def websocket_remote_auth_permit(ws: WebSocket):
|
|||||||
# Also create a session so the device is logged in
|
# Also create a session so the device is logged in
|
||||||
normalized_host = hostutil.normalize_host(request.host)
|
normalized_host = hostutil.normalize_host(request.host)
|
||||||
session_token = db.login(
|
session_token = db.login(
|
||||||
user_uuid=stored_cred.user,
|
user_uuid=cred.user,
|
||||||
credential=stored_cred,
|
credential=cred,
|
||||||
host=normalized_host,
|
host=normalized_host,
|
||||||
ip=request.ip,
|
ip=request.ip,
|
||||||
user_agent=request.user_agent,
|
user_agent=request.user_agent,
|
||||||
@@ -370,8 +350,8 @@ async def websocket_remote_auth_permit(ws: WebSocket):
|
|||||||
|
|
||||||
normalized_host = hostutil.normalize_host(request.host)
|
normalized_host = hostutil.normalize_host(request.host)
|
||||||
session_token = db.login(
|
session_token = db.login(
|
||||||
user_uuid=stored_cred.user,
|
user_uuid=cred.user,
|
||||||
credential=stored_cred,
|
credential=cred,
|
||||||
host=normalized_host,
|
host=normalized_host,
|
||||||
ip=request.ip,
|
ip=request.ip,
|
||||||
user_agent=request.user_agent,
|
user_agent=request.user_agent,
|
||||||
@@ -382,8 +362,8 @@ async def websocket_remote_auth_permit(ws: WebSocket):
|
|||||||
completed = await remoteauth.instance.complete_request(
|
completed = await remoteauth.instance.complete_request(
|
||||||
token=request.key,
|
token=request.key,
|
||||||
session_token=session_token,
|
session_token=session_token,
|
||||||
user_uuid=stored_cred.user,
|
user_uuid=cred.user,
|
||||||
credential_uuid=stored_cred.uuid,
|
credential_uuid=cred.uuid,
|
||||||
reset_token=reset_token,
|
reset_token=reset_token,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+10
-48
@@ -1,11 +1,10 @@
|
|||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from fastapi import FastAPI, WebSocket
|
from fastapi import FastAPI, WebSocket
|
||||||
|
|
||||||
from paskia import db
|
from paskia import db
|
||||||
from paskia.authsession import expires, get_reset, get_session
|
from paskia.authsession import expires, get_reset, get_session
|
||||||
from paskia.fastapi import authz, remote
|
from paskia.fastapi import authz, remote
|
||||||
from paskia.fastapi.session import AUTH_COOKIE, infodict
|
from paskia.fastapi.session import AUTH_COOKIE, infodict
|
||||||
|
from paskia.fastapi.wschat import authenticate_chat, register_chat
|
||||||
from paskia.fastapi.wsutil import validate_origin, websocket_error_handler
|
from paskia.fastapi.wsutil import validate_origin, websocket_error_handler
|
||||||
from paskia.globals import passkey
|
from paskia.globals import passkey
|
||||||
from paskia.util import hostutil, passphrase
|
from paskia.util import hostutil, passphrase
|
||||||
@@ -17,24 +16,6 @@ app = FastAPI()
|
|||||||
app.mount("/remote-auth", remote.app)
|
app.mount("/remote-auth", remote.app)
|
||||||
|
|
||||||
|
|
||||||
async def register_chat(
|
|
||||||
ws: WebSocket,
|
|
||||||
user_uuid: UUID,
|
|
||||||
user_name: str,
|
|
||||||
origin: str,
|
|
||||||
credential_ids: list[bytes] | None = None,
|
|
||||||
):
|
|
||||||
"""Generate registration options and send them to the client."""
|
|
||||||
options, challenge = passkey.instance.reg_generate_options(
|
|
||||||
user_id=user_uuid,
|
|
||||||
user_name=user_name,
|
|
||||||
credential_ids=credential_ids,
|
|
||||||
)
|
|
||||||
await ws.send_json({"optionsJSON": options})
|
|
||||||
response = await ws.receive_json()
|
|
||||||
return passkey.instance.reg_verify(response, challenge, user_uuid, origin=origin)
|
|
||||||
|
|
||||||
|
|
||||||
@app.websocket("/register")
|
@app.websocket("/register")
|
||||||
@websocket_error_handler
|
@websocket_error_handler
|
||||||
async def websocket_register_add(
|
async def websocket_register_add(
|
||||||
@@ -65,14 +46,13 @@ async def websocket_register_add(
|
|||||||
s = ctx.session
|
s = ctx.session
|
||||||
|
|
||||||
# Get user information and determine effective user_name for this registration
|
# Get user information and determine effective user_name for this registration
|
||||||
user = db.get_user_by_uuid(user_uuid)
|
user = db.data().users.get(user_uuid)
|
||||||
user_name = user.display_name
|
user_name = user.display_name
|
||||||
if name is not None:
|
if name is not None:
|
||||||
stripped = name.strip()
|
stripped = name.strip()
|
||||||
if stripped:
|
if stripped:
|
||||||
user_name = stripped
|
user_name = stripped
|
||||||
credentials = db.get_credentials_by_user_uuid(user_uuid)
|
credential_ids = db.get_user_credential_ids(user_uuid) or None
|
||||||
credential_ids = [c.credential_id for c in credentials] if credentials else None
|
|
||||||
|
|
||||||
# WebAuthn registration
|
# WebAuthn registration
|
||||||
credential = await register_chat(ws, user_uuid, user_name, origin, credential_ids)
|
credential = await register_chat(ws, user_uuid, user_name, origin, credential_ids)
|
||||||
@@ -114,36 +94,18 @@ async def websocket_authenticate(ws: WebSocket, auth=AUTH_COOKIE):
|
|||||||
try:
|
try:
|
||||||
session = await get_session(auth, host=host)
|
session = await get_session(auth, host=host)
|
||||||
session_user_uuid = session.user
|
session_user_uuid = session.user
|
||||||
credentials = db.get_credentials_by_user_uuid(session_user_uuid)
|
credential_ids = db.get_user_credential_ids(session_user_uuid) or None
|
||||||
credential_ids = (
|
|
||||||
[c.credential_id for c in credentials] if credentials else None
|
|
||||||
)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass # Invalid/expired session - allow normal authentication
|
pass # Invalid/expired session - allow normal authentication
|
||||||
|
|
||||||
options, challenge = passkey.instance.auth_generate_options(
|
cred = await authenticate_chat(ws, origin, credential_ids)
|
||||||
credential_ids=credential_ids
|
|
||||||
)
|
|
||||||
await ws.send_json({"optionsJSON": options})
|
|
||||||
# Wait for the client to use his authenticator to authenticate
|
|
||||||
credential = passkey.instance.auth_parse(await ws.receive_json())
|
|
||||||
# Fetch from the database by credential ID
|
|
||||||
try:
|
|
||||||
stored_cred = db.get_credential_by_id(credential.raw_id)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError(
|
|
||||||
f"This passkey is no longer registered with {passkey.instance.rp_name}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# If reauth mode, verify the credential belongs to the session's user
|
# If reauth mode, verify the credential belongs to the session's user
|
||||||
if session_user_uuid and stored_cred.user != session_user_uuid:
|
if session_user_uuid and cred.user != session_user_uuid:
|
||||||
raise ValueError("This passkey belongs to a different account")
|
raise ValueError("This passkey belongs to a different account")
|
||||||
|
|
||||||
# Verify the credential matches the stored data
|
|
||||||
passkey.instance.auth_verify(credential, challenge, stored_cred, origin)
|
|
||||||
|
|
||||||
# Create session and update user/credential in a single transaction
|
# Create session and update user/credential in a single transaction
|
||||||
assert stored_cred.uuid is not None
|
assert cred.uuid is not None
|
||||||
metadata = infodict(ws, "auth")
|
metadata = infodict(ws, "auth")
|
||||||
normalized_host = hostutil.normalize_host(host)
|
normalized_host = hostutil.normalize_host(host)
|
||||||
if not normalized_host:
|
if not normalized_host:
|
||||||
@@ -154,8 +116,8 @@ async def websocket_authenticate(ws: WebSocket, auth=AUTH_COOKIE):
|
|||||||
raise ValueError(f"Host must be the same as or a subdomain of {rp_id}")
|
raise ValueError(f"Host must be the same as or a subdomain of {rp_id}")
|
||||||
|
|
||||||
token = db.login(
|
token = db.login(
|
||||||
user_uuid=stored_cred.user,
|
user_uuid=cred.user,
|
||||||
credential=stored_cred,
|
credential=cred,
|
||||||
host=normalized_host,
|
host=normalized_host,
|
||||||
ip=metadata.get("ip") or "",
|
ip=metadata.get("ip") or "",
|
||||||
user_agent=metadata.get("user_agent") or "",
|
user_agent=metadata.get("user_agent") or "",
|
||||||
@@ -164,7 +126,7 @@ async def websocket_authenticate(ws: WebSocket, auth=AUTH_COOKIE):
|
|||||||
|
|
||||||
await ws.send_json(
|
await ws.send_json(
|
||||||
{
|
{
|
||||||
"user": str(stored_cred.user),
|
"user": str(cred.user),
|
||||||
"session_token": token,
|
"session_token": token,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
"""
|
||||||
|
WebSocket chat functions for WebAuthn registration and authentication flows.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from fastapi import WebSocket
|
||||||
|
|
||||||
|
from paskia import db
|
||||||
|
from paskia.db import Credential
|
||||||
|
from paskia.globals import passkey
|
||||||
|
|
||||||
|
|
||||||
|
async def register_chat(
|
||||||
|
ws: WebSocket,
|
||||||
|
user_uuid: UUID,
|
||||||
|
user_name: str,
|
||||||
|
origin: str,
|
||||||
|
credential_ids: list[bytes] | None = None,
|
||||||
|
):
|
||||||
|
"""Run WebAuthn registration flow and return the verified credential."""
|
||||||
|
options, challenge = passkey.instance.reg_generate_options(
|
||||||
|
user_id=user_uuid,
|
||||||
|
user_name=user_name,
|
||||||
|
credential_ids=credential_ids,
|
||||||
|
)
|
||||||
|
await ws.send_json({"optionsJSON": options})
|
||||||
|
response = await ws.receive_json()
|
||||||
|
return passkey.instance.reg_verify(response, challenge, user_uuid, origin=origin)
|
||||||
|
|
||||||
|
|
||||||
|
async def authenticate_chat(
|
||||||
|
ws: WebSocket,
|
||||||
|
origin: str,
|
||||||
|
credential_ids: list[bytes] | None = None,
|
||||||
|
) -> Credential:
|
||||||
|
"""Run WebAuthn authentication flow and return the verified credential."""
|
||||||
|
options, challenge = passkey.instance.auth_generate_options(
|
||||||
|
credential_ids=credential_ids
|
||||||
|
)
|
||||||
|
await ws.send_json({"optionsJSON": options})
|
||||||
|
authcred = passkey.instance.auth_parse(await ws.receive_json())
|
||||||
|
|
||||||
|
cred = next(
|
||||||
|
(
|
||||||
|
c
|
||||||
|
for c in db.data().credentials.values()
|
||||||
|
if c.credential_id == authcred.raw_id
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if not cred:
|
||||||
|
raise ValueError(
|
||||||
|
f"This passkey is no longer registered with {passkey.instance.rp_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
passkey.instance.auth_verify(authcred, challenge, cred, origin)
|
||||||
|
return cred
|
||||||
@@ -48,7 +48,9 @@ async def format_user_info(
|
|||||||
ctx = await permutil.session_context(auth, request_host)
|
ctx = await permutil.session_context(auth, request_host)
|
||||||
|
|
||||||
# Fetch and format credentials
|
# Fetch and format credentials
|
||||||
user_credentials = db.get_credentials_by_user_uuid(user_uuid)
|
user_credentials = [
|
||||||
|
c for c in db.data().credentials.values() if c.user == user_uuid
|
||||||
|
]
|
||||||
credentials: list[dict] = []
|
credentials: list[dict] = []
|
||||||
user_aaguids: set[str] = set()
|
user_aaguids: set[str] = set()
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -1423,9 +1423,9 @@ class TestAdminPermissions:
|
|||||||
):
|
):
|
||||||
"""Cannot rename the auth:admin permission."""
|
"""Cannot rename the auth:admin permission."""
|
||||||
# Get the auth:admin permission
|
# Get the auth:admin permission
|
||||||
from paskia.db import list_permissions
|
from paskia import db
|
||||||
|
|
||||||
perms = list_permissions()
|
perms = list(db.data().permissions.values())
|
||||||
admin_perm = next(p for p in perms if p.scope == "auth:admin")
|
admin_perm = next(p for p in perms if p.scope == "auth:admin")
|
||||||
|
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
@@ -1478,9 +1478,9 @@ class TestAdminPermissions:
|
|||||||
):
|
):
|
||||||
"""Cannot delete the only auth:admin permission (would lock out admin)."""
|
"""Cannot delete the only auth:admin permission (would lock out admin)."""
|
||||||
# Get the auth:admin permission
|
# Get the auth:admin permission
|
||||||
from paskia.db import list_permissions
|
from paskia import db
|
||||||
|
|
||||||
perms = list_permissions()
|
perms = list(db.data().permissions.values())
|
||||||
admin_perm = next(p for p in perms if p.scope == "auth:admin")
|
admin_perm = next(p for p in perms if p.scope == "auth:admin")
|
||||||
|
|
||||||
response = await client.delete(
|
response = await client.delete(
|
||||||
@@ -1503,9 +1503,9 @@ class TestAdminPermissions:
|
|||||||
create_permission(perm2)
|
create_permission(perm2)
|
||||||
|
|
||||||
# Get the original auth:admin permission (the one created in setup)
|
# Get the original auth:admin permission (the one created in setup)
|
||||||
from paskia.db import list_permissions
|
from paskia import db
|
||||||
|
|
||||||
perms = list_permissions()
|
perms = list(db.data().permissions.values())
|
||||||
admin_perms = [p for p in perms if p.scope == "auth:admin"]
|
admin_perms = [p for p in perms if p.scope == "auth:admin"]
|
||||||
# Delete the first one (not the one we just created)
|
# Delete the first one (not the one we just created)
|
||||||
original_admin_perm = next(p for p in admin_perms if p.uuid != perm2.uuid)
|
original_admin_perm = next(p for p in admin_perms if p.uuid != perm2.uuid)
|
||||||
@@ -1536,9 +1536,9 @@ class TestAdminPermissions:
|
|||||||
|
|
||||||
# Cannot delete the original one because the remaining one is not accessible
|
# Cannot delete the original one because the remaining one is not accessible
|
||||||
# Get the original auth:admin permission
|
# Get the original auth:admin permission
|
||||||
from paskia.db import list_permissions
|
from paskia import db
|
||||||
|
|
||||||
perms = list_permissions()
|
perms = list(db.data().permissions.values())
|
||||||
admin_perms = [p for p in perms if p.scope == "auth:admin" and p.domain is None]
|
admin_perms = [p for p in perms if p.scope == "auth:admin" and p.domain is None]
|
||||||
original_admin_perm = admin_perms[0] # The one without domain
|
original_admin_perm = admin_perms[0] # The one without domain
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user