Database cleanup continues, build functions replaced by post init. Simplified some APIs.
This commit is contained in:
+3
-14
@@ -30,29 +30,18 @@ def _log_reset_link(message: str, passphrase: str) -> str:
|
|||||||
return reset_link
|
return reset_link
|
||||||
|
|
||||||
|
|
||||||
async def bootstrap_system() -> dict:
|
async def bootstrap_system() -> None:
|
||||||
"""
|
"""
|
||||||
Bootstrap the entire system with default data.
|
Bootstrap the entire system with default data.
|
||||||
|
|
||||||
Uses db.bootstrap() which performs all operations in a single transaction.
|
Uses db.bootstrap() which performs all operations in a single transaction.
|
||||||
The transaction log will show a single "bootstrap" action with all changes.
|
The transaction log will show a single "bootstrap" action with all changes.
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: Contains information about created entities and reset link
|
|
||||||
"""
|
"""
|
||||||
# Call the single-transaction bootstrap function
|
# Call the single-transaction bootstrap function
|
||||||
result = db.bootstrap()
|
reset_passphrase = db.bootstrap()
|
||||||
|
|
||||||
# Log the reset link (this is separate from the transaction log)
|
# Log the reset link (this is separate from the transaction log)
|
||||||
reset_link = _log_reset_link("✅ Bootstrap completed!", result["reset_passphrase"])
|
_log_reset_link("✅ Bootstrap completed!", reset_passphrase)
|
||||||
|
|
||||||
return {
|
|
||||||
"user": result["user"],
|
|
||||||
"org": result["org"],
|
|
||||||
"role": result["role"],
|
|
||||||
"permissions": [result["perm_admin"], result["perm_org_admin"]],
|
|
||||||
"reset_link": reset_link,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def check_admin_credentials() -> bool:
|
async def check_admin_credentials() -> bool:
|
||||||
|
|||||||
@@ -31,13 +31,7 @@ from paskia.db.operations import (
|
|||||||
add_permission_to_organization,
|
add_permission_to_organization,
|
||||||
add_permission_to_role,
|
add_permission_to_role,
|
||||||
bootstrap,
|
bootstrap,
|
||||||
build_credential,
|
|
||||||
build_org,
|
build_org,
|
||||||
build_permission,
|
|
||||||
build_reset_token,
|
|
||||||
build_role,
|
|
||||||
build_session,
|
|
||||||
build_user,
|
|
||||||
cleanup_expired,
|
cleanup_expired,
|
||||||
create_credential,
|
create_credential,
|
||||||
create_credential_session,
|
create_credential_session,
|
||||||
|
|||||||
+28
-85
@@ -163,53 +163,16 @@ async def init(*args, **kwargs):
|
|||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def build_permission(uuid: UUID) -> Permission:
|
|
||||||
perm = _db._data.permissions[uuid]
|
|
||||||
perm.uuid = uuid
|
|
||||||
return perm
|
|
||||||
|
|
||||||
|
|
||||||
def build_user(uuid: UUID) -> User:
|
|
||||||
user = _db._data.users[uuid]
|
|
||||||
user.uuid = uuid
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
def build_role(uuid: UUID) -> Role:
|
|
||||||
r = _db._data.roles[uuid]
|
|
||||||
r.uuid = uuid
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
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]
|
||||||
o.uuid = uuid
|
|
||||||
o.permissions = {pid for pid, p in _db._data.permissions.items() if uuid in p.orgs}
|
o.permissions = {pid for pid, p in _db._data.permissions.items() if uuid in p.orgs}
|
||||||
if include_roles:
|
if include_roles:
|
||||||
o.roles = [
|
o.roles = [
|
||||||
build_role(rid) for rid, r in _db._data.roles.items() if r.org == uuid
|
_db._data.roles[rid] for rid, r in _db._data.roles.items() if r.org == uuid
|
||||||
]
|
]
|
||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
def build_credential(uuid: UUID) -> Credential:
|
|
||||||
cred = _db._data.credentials[uuid]
|
|
||||||
cred.uuid = uuid
|
|
||||||
return cred
|
|
||||||
|
|
||||||
|
|
||||||
def build_session(key: str) -> Session:
|
|
||||||
s = _db._data.sessions[key]
|
|
||||||
s.key = key
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def build_reset_token(key: bytes) -> ResetToken:
|
|
||||||
t = _db._data.reset_tokens[key]
|
|
||||||
t.key = key
|
|
||||||
return t
|
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Read/lookup functions
|
# Read/lookup functions
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@@ -228,9 +191,7 @@ def get_permission(uuid: UUID) -> Permission | None:
|
|||||||
- Get permission for renaming its scope (admin.py:1031)
|
- Get permission for renaming its scope (admin.py:1031)
|
||||||
- Get permission to check scope before deleting (admin.py:1071)
|
- Get permission to check scope before deleting (admin.py:1071)
|
||||||
"""
|
"""
|
||||||
if uuid in _db._data.permissions:
|
return _db._data.permissions.get(uuid)
|
||||||
return build_permission(uuid)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_permission_by_scope(scope: str) -> Permission | None:
|
def get_permission_by_scope(scope: str) -> Permission | None:
|
||||||
@@ -239,9 +200,9 @@ def get_permission_by_scope(scope: str) -> Permission | None:
|
|||||||
Call sites:
|
Call sites:
|
||||||
- Check if system is already bootstrapped by looking for auth:admin permission (bootstrap.py:113)
|
- Check if system is already bootstrapped by looking for auth:admin permission (bootstrap.py:113)
|
||||||
"""
|
"""
|
||||||
for uuid, p in _db._data.permissions.items():
|
for p in _db._data.permissions.values():
|
||||||
if p.scope == scope:
|
if p.scope == scope:
|
||||||
return build_permission(uuid)
|
return p
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -251,11 +212,7 @@ def get_permissions_by_scope(scope: str) -> list[Permission]:
|
|||||||
Since scopes are not unique, this returns all matching permissions.
|
Since scopes are not unique, this returns all matching permissions.
|
||||||
Use this for scope-based permission checking.
|
Use this for scope-based permission checking.
|
||||||
"""
|
"""
|
||||||
return [
|
return [p for p in _db._data.permissions.values() if p.scope == scope]
|
||||||
build_permission(uuid)
|
|
||||||
for uuid, p in _db._data.permissions.items()
|
|
||||||
if p.scope == scope
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def list_permissions() -> list[Permission]:
|
def list_permissions() -> list[Permission]:
|
||||||
@@ -268,7 +225,7 @@ def list_permissions() -> list[Permission]:
|
|||||||
- List permissions to check admin permissions when deleting permission (admin.py:882)
|
- List permissions to check admin permissions when deleting permission (admin.py:882)
|
||||||
- Admin API endpoint to list permissions (admin.py:914)
|
- Admin API endpoint to list permissions (admin.py:914)
|
||||||
"""
|
"""
|
||||||
return [build_permission(uuid) for uuid in _db._data.permissions]
|
return list(_db._data.permissions.values())
|
||||||
|
|
||||||
|
|
||||||
def get_permission_organizations(scope: str) -> list[Org]:
|
def get_permission_organizations(scope: str) -> list[Org]:
|
||||||
@@ -316,8 +273,8 @@ def get_organization_users(org_uuid: UUID) -> list[tuple[User, str]]:
|
|||||||
rid: r.display_name for rid, r in _db._data.roles.items() if r.org == org_uuid
|
rid: r.display_name for rid, r in _db._data.roles.items() if r.org == org_uuid
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
(build_user(uid), role_map[u.role])
|
(u, role_map[u.role])
|
||||||
for uid, u in _db._data.users.items()
|
for u in _db._data.users.values()
|
||||||
if u.role in role_map
|
if u.role in role_map
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -331,7 +288,7 @@ def get_role(uuid: UUID) -> Role | None:
|
|||||||
- Get role to remove permission from it (admin.py:380)
|
- Get role to remove permission from it (admin.py:380)
|
||||||
- Get role to delete it (admin.py:421)
|
- Get role to delete it (admin.py:421)
|
||||||
"""
|
"""
|
||||||
return build_role(uuid) if uuid in _db._data.roles else None
|
return _db._data.roles.get(uuid)
|
||||||
|
|
||||||
|
|
||||||
def get_roles_by_organization(org_uuid: UUID) -> list[Role]:
|
def get_roles_by_organization(org_uuid: UUID) -> list[Role]:
|
||||||
@@ -341,7 +298,7 @@ def get_roles_by_organization(org_uuid: UUID) -> list[Role]:
|
|||||||
- Get roles by organization when creating a user to find the role by name (admin.py:459)
|
- 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)
|
- Get roles by organization when updating user role to validate the new role name (admin.py:498)
|
||||||
"""
|
"""
|
||||||
return [build_role(rid) for rid, r in _db._data.roles.items() if r.org == org_uuid]
|
return [r for r in _db._data.roles.values() if r.org == org_uuid]
|
||||||
|
|
||||||
|
|
||||||
def get_user_by_uuid(uuid: UUID) -> User | None:
|
def get_user_by_uuid(uuid: UUID) -> User | None:
|
||||||
@@ -352,7 +309,7 @@ def get_user_by_uuid(uuid: UUID) -> User | None:
|
|||||||
- Get user from reset token for registration info (api.py:127)
|
- Get user from reset token for registration info (api.py:127)
|
||||||
- Get user for listing user credentials in admin API (admin.py:594)
|
- Get user for listing user credentials in admin API (admin.py:594)
|
||||||
"""
|
"""
|
||||||
return build_user(uuid) if uuid in _db._data.users else None
|
return _db._data.users.get(uuid)
|
||||||
|
|
||||||
|
|
||||||
def get_user_organization(user_uuid: UUID) -> tuple[Org, str]:
|
def get_user_organization(user_uuid: UUID) -> tuple[Org, str]:
|
||||||
@@ -385,9 +342,9 @@ def get_credential_by_id(credential_id: bytes) -> Credential | None:
|
|||||||
- Get credential by ID for WebAuthn authentication (ws.py:132)
|
- Get credential by ID for WebAuthn authentication (ws.py:132)
|
||||||
- Get credential by ID for remote authentication (remote.py:325)
|
- Get credential by ID for remote authentication (remote.py:325)
|
||||||
"""
|
"""
|
||||||
for uuid, c in _db._data.credentials.items():
|
for c in _db._data.credentials.values():
|
||||||
if c.credential_id == credential_id:
|
if c.credential_id == credential_id:
|
||||||
return build_credential(uuid)
|
return c
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -402,11 +359,7 @@ def get_credentials_by_user_uuid(user_uuid: UUID) -> list[Credential]:
|
|||||||
- Get credentials to check if admin user has credentials (bootstrap.py:81)
|
- Get credentials to check if admin user has credentials (bootstrap.py:81)
|
||||||
- Get credentials for user info formatting (userinfo.py:51)
|
- Get credentials for user info formatting (userinfo.py:51)
|
||||||
"""
|
"""
|
||||||
return [
|
return [c for c in _db._data.credentials.values() if c.user == user_uuid]
|
||||||
build_credential(cid)
|
|
||||||
for cid, c in _db._data.credentials.items()
|
|
||||||
if c.user == user_uuid
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_session(key: str) -> Session | None:
|
def get_session(key: str) -> Session | None:
|
||||||
@@ -423,7 +376,7 @@ def get_session(key: str) -> Session | None:
|
|||||||
s = _db._data.sessions[key]
|
s = _db._data.sessions[key]
|
||||||
if s.expiry < datetime.now(timezone.utc):
|
if s.expiry < datetime.now(timezone.utc):
|
||||||
return None
|
return None
|
||||||
return build_session(key)
|
return s
|
||||||
|
|
||||||
|
|
||||||
def list_sessions_for_user(user_uuid: UUID) -> list[Session]:
|
def list_sessions_for_user(user_uuid: UUID) -> list[Session]:
|
||||||
@@ -434,11 +387,7 @@ def list_sessions_for_user(user_uuid: UUID) -> list[Session]:
|
|||||||
- List sessions for user details API (admin.py:651)
|
- List sessions for user details API (admin.py:651)
|
||||||
"""
|
"""
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
return [
|
return [s for s in _db._data.sessions.values() if s.user == user_uuid and s.expiry >= now]
|
||||||
build_session(key)
|
|
||||||
for key, s in _db._data.sessions.items()
|
|
||||||
if s.user == user_uuid and s.expiry >= now
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _reset_key(passphrase: str) -> bytes:
|
def _reset_key(passphrase: str) -> bytes:
|
||||||
@@ -464,7 +413,7 @@ def get_reset_token(passphrase: str) -> ResetToken | None:
|
|||||||
t = _db._data.reset_tokens[key]
|
t = _db._data.reset_tokens[key]
|
||||||
if t.expiry < datetime.now(timezone.utc):
|
if t.expiry < datetime.now(timezone.utc):
|
||||||
return None
|
return None
|
||||||
return build_reset_token(key)
|
return t
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@@ -521,15 +470,15 @@ def get_session_context(
|
|||||||
if org_uuid not in _db._data.orgs:
|
if org_uuid not in _db._data.orgs:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
session = build_session(session_key)
|
session = _db._data.sessions[session_key]
|
||||||
user = build_user(s.user)
|
user = _db._data.users[s.user]
|
||||||
role = build_role(role_uuid)
|
role = _db._data.roles[role_uuid]
|
||||||
org = build_org(org_uuid)
|
org = build_org(org_uuid)
|
||||||
|
|
||||||
# Credential must exist (sessions are cascade-deleted when credential is deleted)
|
# Credential must exist (sessions are cascade-deleted when credential is deleted)
|
||||||
if s.credential not in _db._data.credentials:
|
if s.credential not in _db._data.credentials:
|
||||||
return None
|
return None
|
||||||
credential = build_credential(s.credential)
|
credential = _db._data.credentials[s.credential]
|
||||||
|
|
||||||
# Effective permissions: role's permissions 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
|
||||||
@@ -547,7 +496,7 @@ def get_session_context(
|
|||||||
# Check domain restriction
|
# Check domain restriction
|
||||||
if p.domain is not None and p.domain != host_without_port:
|
if p.domain is not None and p.domain != host_without_port:
|
||||||
continue
|
continue
|
||||||
effective_perms.append(build_permission(perm_uuid))
|
effective_perms.append(_db._data.permissions[perm_uuid])
|
||||||
|
|
||||||
return SessionContext(
|
return SessionContext(
|
||||||
session=session,
|
session=session,
|
||||||
@@ -624,9 +573,11 @@ def create_organization(org: Org, *, ctx: SessionContext | None = None) -> None:
|
|||||||
if org.uuid in _db._data.orgs:
|
if org.uuid in _db._data.orgs:
|
||||||
raise ValueError(f"Organization {org.uuid} already exists")
|
raise ValueError(f"Organization {org.uuid} already exists")
|
||||||
with _db.transaction("Created organization", ctx):
|
with _db.transaction("Created organization", ctx):
|
||||||
_db._data.orgs[org.uuid] = Org(
|
new_org = Org(
|
||||||
display_name=org.display_name, created_at=datetime.now(timezone.utc)
|
display_name=org.display_name, created_at=datetime.now(timezone.utc)
|
||||||
)
|
)
|
||||||
|
_db._data.orgs[org.uuid] = new_org
|
||||||
|
new_org.uuid = org.uuid
|
||||||
# Create Administration role with org admin permission
|
# Create Administration role with org admin permission
|
||||||
import uuid7
|
import uuid7
|
||||||
|
|
||||||
@@ -1182,7 +1133,7 @@ def bootstrap(
|
|||||||
admin_name: str = "Admin",
|
admin_name: str = "Admin",
|
||||||
reset_passphrase: str | None = None,
|
reset_passphrase: str | None = None,
|
||||||
reset_expiry: datetime | None = None,
|
reset_expiry: datetime | None = None,
|
||||||
) -> dict:
|
) -> str:
|
||||||
"""Bootstrap the entire system in a single transaction.
|
"""Bootstrap the entire system in a single transaction.
|
||||||
|
|
||||||
Creates:
|
Creates:
|
||||||
@@ -1202,7 +1153,7 @@ def bootstrap(
|
|||||||
reset_expiry: Expiry datetime for the reset token (default: 14 days)
|
reset_expiry: Expiry datetime for the reset token (default: 14 days)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict with keys: perm_admin, perm_org_admin, org, role, user, reset_passphrase
|
The reset passphrase for admin registration.
|
||||||
"""
|
"""
|
||||||
import uuid7
|
import uuid7
|
||||||
|
|
||||||
@@ -1286,12 +1237,4 @@ def bootstrap(
|
|||||||
token_type="admin bootstrap",
|
token_type="admin bootstrap",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return info about what was created (for logging by caller)
|
return reset_passphrase
|
||||||
return {
|
|
||||||
"perm_admin": build_permission(perm_admin_uuid),
|
|
||||||
"perm_org_admin": build_permission(perm_org_admin_uuid),
|
|
||||||
"org": build_org(org_uuid),
|
|
||||||
"role": build_role(role_uuid),
|
|
||||||
"user": build_user(user_uuid),
|
|
||||||
"reset_passphrase": reset_passphrase,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -208,3 +208,20 @@ class DatabaseData(msgspec.Struct, omit_defaults=True):
|
|||||||
sessions: dict[str, Session]
|
sessions: dict[str, Session]
|
||||||
reset_tokens: dict[bytes, ResetToken]
|
reset_tokens: dict[bytes, ResetToken]
|
||||||
v: int = 0
|
v: int = 0
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
# Set the key fields on all stored objects
|
||||||
|
for uuid, perm in self.permissions.items():
|
||||||
|
perm.uuid = uuid
|
||||||
|
for uuid, org in self.orgs.items():
|
||||||
|
org.uuid = uuid
|
||||||
|
for uuid, role in self.roles.items():
|
||||||
|
role.uuid = uuid
|
||||||
|
for uuid, user in self.users.items():
|
||||||
|
user.uuid = uuid
|
||||||
|
for uuid, cred in self.credentials.items():
|
||||||
|
cred.uuid = uuid
|
||||||
|
for key, session in self.sessions.items():
|
||||||
|
session.key = key
|
||||||
|
for key, token in self.reset_tokens.items():
|
||||||
|
token.key = key
|
||||||
|
|||||||
Reference in New Issue
Block a user