CRUD store and delete on the DB classes directly.
This commit is contained in:
@@ -70,7 +70,7 @@ def bootstrap(
|
|||||||
orgs={org_uuid: True}, # Grant to org
|
orgs={org_uuid: True}, # Grant to org
|
||||||
)
|
)
|
||||||
perm_admin.uuid = perm_admin_uuid
|
perm_admin.uuid = perm_admin_uuid
|
||||||
_ops._db.permissions[perm_admin_uuid] = perm_admin
|
perm_admin.store()
|
||||||
|
|
||||||
# Create auth:org:admin permission
|
# Create auth:org:admin permission
|
||||||
perm_org_admin = Permission(
|
perm_org_admin = Permission(
|
||||||
@@ -79,12 +79,12 @@ def bootstrap(
|
|||||||
orgs={org_uuid: True}, # Grant to org
|
orgs={org_uuid: True}, # Grant to org
|
||||||
)
|
)
|
||||||
perm_org_admin.uuid = perm_org_admin_uuid
|
perm_org_admin.uuid = perm_org_admin_uuid
|
||||||
_ops._db.permissions[perm_org_admin_uuid] = perm_org_admin
|
perm_org_admin.store()
|
||||||
|
|
||||||
# Create organization
|
# Create organization
|
||||||
new_org = Org.create(display_name=org_name)
|
new_org = Org.create(display_name=org_name)
|
||||||
new_org.uuid = org_uuid
|
new_org.uuid = org_uuid
|
||||||
_ops._db.orgs[org_uuid] = new_org
|
new_org.store()
|
||||||
|
|
||||||
# Create Administration role with both permissions
|
# Create Administration role with both permissions
|
||||||
admin_role = Role(
|
admin_role = Role(
|
||||||
@@ -93,7 +93,7 @@ def bootstrap(
|
|||||||
permissions={perm_admin_uuid: True, perm_org_admin_uuid: True},
|
permissions={perm_admin_uuid: True, perm_org_admin_uuid: True},
|
||||||
)
|
)
|
||||||
admin_role.uuid = role_uuid
|
admin_role.uuid = role_uuid
|
||||||
_ops._db.roles[role_uuid] = admin_role
|
admin_role.store()
|
||||||
|
|
||||||
# Create admin user
|
# Create admin user
|
||||||
admin_user = User(
|
admin_user = User(
|
||||||
@@ -104,7 +104,7 @@ def bootstrap(
|
|||||||
visits=0,
|
visits=0,
|
||||||
)
|
)
|
||||||
admin_user.uuid = user_uuid
|
admin_user.uuid = user_uuid
|
||||||
_ops._db.users[user_uuid] = admin_user
|
admin_user.store()
|
||||||
|
|
||||||
# Create reset token
|
# Create reset token
|
||||||
reset_token, reset_passphrase = ResetToken.create(
|
reset_token, reset_passphrase = ResetToken.create(
|
||||||
@@ -113,7 +113,7 @@ def bootstrap(
|
|||||||
token_type="admin bootstrap",
|
token_type="admin bootstrap",
|
||||||
passphrase=reset_passphrase,
|
passphrase=reset_passphrase,
|
||||||
)
|
)
|
||||||
_ops._db.reset_tokens[reset_token.key] = reset_token
|
reset_token.store()
|
||||||
|
|
||||||
# Set config if provided
|
# Set config if provided
|
||||||
if config is not None:
|
if config is not None:
|
||||||
|
|||||||
+19
-45
@@ -128,7 +128,7 @@ def create_permission(perm: Permission, *, ctx: SessionContext | None = None) ->
|
|||||||
if perm.uuid in _db.permissions:
|
if perm.uuid in _db.permissions:
|
||||||
raise ValueError(f"Permission {perm.uuid} already exists")
|
raise ValueError(f"Permission {perm.uuid} already exists")
|
||||||
with _db.transaction("admin:create_permission", ctx):
|
with _db.transaction("admin:create_permission", ctx):
|
||||||
_db.permissions[perm.uuid] = perm
|
perm.store()
|
||||||
|
|
||||||
|
|
||||||
def update_permission(
|
def update_permission(
|
||||||
@@ -156,10 +156,7 @@ def delete_permission(uuid: UUID, *, ctx: SessionContext | None = None) -> None:
|
|||||||
if uuid not in _db.permissions:
|
if uuid not in _db.permissions:
|
||||||
raise ValueError(f"Permission {uuid} not found")
|
raise ValueError(f"Permission {uuid} not found")
|
||||||
with _db.transaction("admin:delete_permission", ctx):
|
with _db.transaction("admin:delete_permission", ctx):
|
||||||
# Remove this permission from all roles
|
_db.permissions[uuid].delete()
|
||||||
for role in _db.roles.values():
|
|
||||||
role.permissions.pop(uuid, None)
|
|
||||||
del _db.permissions[uuid]
|
|
||||||
|
|
||||||
|
|
||||||
def create_org(org: Org, *, ctx: SessionContext | None = None) -> None:
|
def create_org(org: Org, *, ctx: SessionContext | None = None) -> None:
|
||||||
@@ -173,7 +170,7 @@ def create_org(org: Org, *, ctx: SessionContext | None = None) -> None:
|
|||||||
with _db.transaction("admin:create_org", ctx):
|
with _db.transaction("admin:create_org", ctx):
|
||||||
new_org = Org.create(display_name=org.display_name, created_at=now)
|
new_org = Org.create(display_name=org.display_name, created_at=now)
|
||||||
new_org.uuid = org.uuid
|
new_org.uuid = org.uuid
|
||||||
_db.orgs[org.uuid] = new_org
|
new_org.store()
|
||||||
# Create Administration role with org admin permission
|
# Create Administration role with org admin permission
|
||||||
|
|
||||||
admin_role_uuid = uuid7.create(now)
|
admin_role_uuid = uuid7.create(now)
|
||||||
@@ -190,7 +187,7 @@ def create_org(org: Org, *, ctx: SessionContext | None = None) -> None:
|
|||||||
permissions=role_permissions,
|
permissions=role_permissions,
|
||||||
)
|
)
|
||||||
admin_role.uuid = admin_role_uuid
|
admin_role.uuid = admin_role_uuid
|
||||||
_db.roles[admin_role_uuid] = admin_role
|
admin_role.store()
|
||||||
|
|
||||||
|
|
||||||
def update_org_name(
|
def update_org_name(
|
||||||
@@ -211,16 +208,7 @@ def delete_org(uuid: UUID, *, ctx: SessionContext | None = None) -> None:
|
|||||||
if uuid not in _db.orgs:
|
if uuid not in _db.orgs:
|
||||||
raise ValueError(f"Organization {uuid} not found")
|
raise ValueError(f"Organization {uuid} not found")
|
||||||
with _db.transaction("admin:delete_org", ctx):
|
with _db.transaction("admin:delete_org", ctx):
|
||||||
org = _db.orgs[uuid]
|
_db.orgs[uuid].delete()
|
||||||
# Remove org from all permissions
|
|
||||||
for p in _db.permissions.values():
|
|
||||||
p.orgs.pop(uuid, None)
|
|
||||||
# Delete roles in this org and their users
|
|
||||||
for role in org.roles:
|
|
||||||
for user in role.users:
|
|
||||||
del _db.users[user.uuid]
|
|
||||||
del _db.roles[role.uuid]
|
|
||||||
del _db.orgs[uuid]
|
|
||||||
|
|
||||||
|
|
||||||
def add_permission_to_org(
|
def add_permission_to_org(
|
||||||
@@ -264,7 +252,7 @@ def create_role(role: Role, *, ctx: SessionContext | None = None) -> None:
|
|||||||
if role.org_uuid not in _db.orgs:
|
if role.org_uuid not in _db.orgs:
|
||||||
raise ValueError(f"Organization {role.org_uuid} not found")
|
raise ValueError(f"Organization {role.org_uuid} not found")
|
||||||
with _db.transaction("admin:create_role", ctx):
|
with _db.transaction("admin:create_role", ctx):
|
||||||
_db.roles[role.uuid] = role
|
role.store()
|
||||||
|
|
||||||
|
|
||||||
def update_role_name(
|
def update_role_name(
|
||||||
@@ -317,7 +305,7 @@ def delete_role(uuid: UUID, *, ctx: SessionContext | None = None) -> None:
|
|||||||
if role.users:
|
if role.users:
|
||||||
raise ValueError(f"Cannot delete role {uuid}: users still assigned")
|
raise ValueError(f"Cannot delete role {uuid}: users still assigned")
|
||||||
with _db.transaction("admin:delete_role", ctx):
|
with _db.transaction("admin:delete_role", ctx):
|
||||||
del _db.roles[uuid]
|
_db.roles[uuid].delete()
|
||||||
|
|
||||||
|
|
||||||
def create_user(new_user: User, *, ctx: SessionContext | None = None) -> None:
|
def create_user(new_user: User, *, ctx: SessionContext | None = None) -> None:
|
||||||
@@ -327,7 +315,7 @@ def create_user(new_user: User, *, ctx: SessionContext | None = None) -> None:
|
|||||||
if new_user.role_uuid not in _db.roles:
|
if new_user.role_uuid not in _db.roles:
|
||||||
raise ValueError(f"Role {new_user.role_uuid} not found")
|
raise ValueError(f"Role {new_user.role_uuid} not found")
|
||||||
with _db.transaction("admin:create_user", ctx):
|
with _db.transaction("admin:create_user", ctx):
|
||||||
_db.users[new_user.uuid] = new_user
|
new_user.store()
|
||||||
|
|
||||||
|
|
||||||
def update_user_display_name(
|
def update_user_display_name(
|
||||||
@@ -386,19 +374,8 @@ def delete_user(uuid: UUID, *, ctx: SessionContext | None = None) -> None:
|
|||||||
"""Delete user and their credentials/sessions."""
|
"""Delete user and their credentials/sessions."""
|
||||||
if uuid not in _db.users:
|
if uuid not in _db.users:
|
||||||
raise ValueError(f"User {uuid} not found")
|
raise ValueError(f"User {uuid} not found")
|
||||||
user = _db.users[uuid]
|
|
||||||
with _db.transaction("admin:delete_user", ctx):
|
with _db.transaction("admin:delete_user", ctx):
|
||||||
# Delete credentials
|
_db.users[uuid].delete()
|
||||||
for cred in user.credentials:
|
|
||||||
del _db.credentials[cred.uuid]
|
|
||||||
# Delete sessions
|
|
||||||
for sess in user.sessions:
|
|
||||||
del _db.sessions[sess.key]
|
|
||||||
# Delete reset tokens (iterate over dict items to get correct keys)
|
|
||||||
for key, token in list(_db.reset_tokens.items()):
|
|
||||||
if token.user_uuid == uuid:
|
|
||||||
del _db.reset_tokens[key]
|
|
||||||
del _db.users[uuid]
|
|
||||||
|
|
||||||
|
|
||||||
def create_credential(cred: Credential, *, ctx: SessionContext | None = None) -> None:
|
def create_credential(cred: Credential, *, ctx: SessionContext | None = None) -> None:
|
||||||
@@ -408,7 +385,7 @@ def create_credential(cred: Credential, *, ctx: SessionContext | None = None) ->
|
|||||||
if cred.user_uuid not in _db.users:
|
if cred.user_uuid not in _db.users:
|
||||||
raise ValueError(f"User {cred.user_uuid} not found")
|
raise ValueError(f"User {cred.user_uuid} not found")
|
||||||
with _db.transaction("create_credential", ctx):
|
with _db.transaction("create_credential", ctx):
|
||||||
_db.credentials[cred.uuid] = cred
|
cred.store()
|
||||||
|
|
||||||
|
|
||||||
def update_credential_sign_count(
|
def update_credential_sign_count(
|
||||||
@@ -444,11 +421,7 @@ def delete_credential(
|
|||||||
if cred.user_uuid != user_uuid:
|
if cred.user_uuid != user_uuid:
|
||||||
raise ValueError(f"Credential {uuid} does not belong to user {user_uuid}")
|
raise ValueError(f"Credential {uuid} does not belong to user {user_uuid}")
|
||||||
with _db.transaction("delete_credential", ctx):
|
with _db.transaction("delete_credential", ctx):
|
||||||
# Delete all sessions using this credential
|
cred.delete()
|
||||||
for sess in cred.sessions:
|
|
||||||
print(sess, repr(sess.key))
|
|
||||||
del _db.sessions[sess.key]
|
|
||||||
del _db.credentials[uuid]
|
|
||||||
|
|
||||||
|
|
||||||
def create_session(
|
def create_session(
|
||||||
@@ -523,7 +496,7 @@ def delete_session(
|
|||||||
if key not in _db.sessions:
|
if key not in _db.sessions:
|
||||||
raise ValueError("Session not found")
|
raise ValueError("Session not found")
|
||||||
with _db.transaction(action, ctx):
|
with _db.transaction(action, ctx):
|
||||||
del _db.sessions[key]
|
_db.sessions[key].delete()
|
||||||
|
|
||||||
|
|
||||||
def delete_sessions_for_user(
|
def delete_sessions_for_user(
|
||||||
@@ -540,7 +513,7 @@ def delete_sessions_for_user(
|
|||||||
return
|
return
|
||||||
with _db.transaction("admin:delete_sessions_for_user", ctx):
|
with _db.transaction("admin:delete_sessions_for_user", ctx):
|
||||||
for sess in user.sessions:
|
for sess in user.sessions:
|
||||||
del _db.sessions[sess.key]
|
sess.delete()
|
||||||
|
|
||||||
|
|
||||||
def create_reset_token(
|
def create_reset_token(
|
||||||
@@ -570,7 +543,7 @@ def create_reset_token(
|
|||||||
if token.key in _db.reset_tokens:
|
if token.key in _db.reset_tokens:
|
||||||
raise ValueError("Reset token already exists")
|
raise ValueError("Reset token already exists")
|
||||||
with _db.transaction("create_reset_token", ctx, user=user):
|
with _db.transaction("create_reset_token", ctx, user=user):
|
||||||
_db.reset_tokens[token.key] = token
|
token.store()
|
||||||
return passphrase
|
return passphrase
|
||||||
|
|
||||||
|
|
||||||
@@ -579,7 +552,7 @@ def delete_reset_token(key: bytes, *, ctx: SessionContext | None = None) -> None
|
|||||||
if key not in _db.reset_tokens:
|
if key not in _db.reset_tokens:
|
||||||
raise ValueError("Reset token not found")
|
raise ValueError("Reset token not found")
|
||||||
with _db.transaction("delete_reset_token", ctx):
|
with _db.transaction("delete_reset_token", ctx):
|
||||||
del _db.reset_tokens[key]
|
_db.reset_tokens[key].delete()
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@@ -697,15 +670,16 @@ def create_credential_session(
|
|||||||
credential.last_verified = now
|
credential.last_verified = now
|
||||||
|
|
||||||
# Create credential
|
# Create credential
|
||||||
_db.credentials[credential.uuid] = credential
|
credential.store()
|
||||||
|
|
||||||
# Store session and record visit
|
# Store session and record visit
|
||||||
session.store(now)
|
session.store(now)
|
||||||
|
|
||||||
# Delete reset token if provided
|
# Delete reset token if provided
|
||||||
if reset_key:
|
if reset_key:
|
||||||
if reset_key in _db.reset_tokens:
|
token = _db.reset_tokens.get(reset_key)
|
||||||
del _db.reset_tokens[reset_key]
|
if token:
|
||||||
|
token.delete()
|
||||||
return session.key
|
return session.key
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,20 @@ class Permission(msgspec.Struct, dict=True, omit_defaults=True):
|
|||||||
if org_uuid in db.data().orgs
|
if org_uuid in db.data().orgs
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def store(self) -> None:
|
||||||
|
"""Store this permission in the database. Must be called inside a transaction."""
|
||||||
|
db.data().permissions[self.uuid] = self
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
|
"""Delete this permission and remove it from all roles.
|
||||||
|
|
||||||
|
Must be called inside a transaction.
|
||||||
|
"""
|
||||||
|
_data = db.data()
|
||||||
|
for role in _data.roles.values():
|
||||||
|
role.permissions.pop(self.uuid, None)
|
||||||
|
del _data.permissions[self.uuid]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(
|
def create(
|
||||||
cls,
|
cls,
|
||||||
@@ -86,6 +100,24 @@ class Org(msgspec.Struct, dict=True):
|
|||||||
"""Get all permissions that this organization can grant."""
|
"""Get all permissions that this organization can grant."""
|
||||||
return [p for p in db.data().permissions.values() if self.uuid in p.orgs]
|
return [p for p in db.data().permissions.values() if self.uuid in p.orgs]
|
||||||
|
|
||||||
|
def store(self) -> None:
|
||||||
|
"""Store this organization in the database. Must be called inside a transaction."""
|
||||||
|
db.data().orgs[self.uuid] = self
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
|
"""Delete this org and cascade to roles, users. Remove from permissions.
|
||||||
|
|
||||||
|
Must be called inside a transaction.
|
||||||
|
"""
|
||||||
|
_data = db.data()
|
||||||
|
for p in _data.permissions.values():
|
||||||
|
p.orgs.pop(self.uuid, None)
|
||||||
|
for role in self.roles:
|
||||||
|
for user in role.users:
|
||||||
|
del _data.users[user.uuid]
|
||||||
|
del _data.roles[role.uuid]
|
||||||
|
del _data.orgs[self.uuid]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, display_name: str, created_at: datetime | None = None) -> Org:
|
def create(cls, display_name: str, created_at: datetime | None = None) -> Org:
|
||||||
"""Create a new Org with auto-generated uuid7."""
|
"""Create a new Org with auto-generated uuid7."""
|
||||||
@@ -135,6 +167,14 @@ class Role(msgspec.Struct, dict=True, omit_defaults=True):
|
|||||||
"""Get all users that have this role."""
|
"""Get all users that have this role."""
|
||||||
return [u for u in db.data().users.values() if u.role_uuid == self.uuid]
|
return [u for u in db.data().users.values() if u.role_uuid == self.uuid]
|
||||||
|
|
||||||
|
def store(self) -> None:
|
||||||
|
"""Store this role in the database. Must be called inside a transaction."""
|
||||||
|
db.data().roles[self.uuid] = self
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
|
"""Delete this role from the database. Must be called inside a transaction."""
|
||||||
|
del db.data().roles[self.uuid]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(
|
def create(
|
||||||
cls,
|
cls,
|
||||||
@@ -199,6 +239,24 @@ class User(msgspec.Struct, dict=True, omit_defaults=True):
|
|||||||
"""Get all reset tokens for this user."""
|
"""Get all reset tokens for this user."""
|
||||||
return [t for t in db.data().reset_tokens.values() if t.user_uuid == self.uuid]
|
return [t for t in db.data().reset_tokens.values() if t.user_uuid == self.uuid]
|
||||||
|
|
||||||
|
def store(self) -> None:
|
||||||
|
"""Store this user in the database. Must be called inside a transaction."""
|
||||||
|
db.data().users[self.uuid] = self
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
|
"""Delete this user and cascade to credentials, sessions, reset tokens.
|
||||||
|
|
||||||
|
Must be called inside a transaction.
|
||||||
|
"""
|
||||||
|
_data = db.data()
|
||||||
|
for cred in self.credentials:
|
||||||
|
del _data.credentials[cred.uuid]
|
||||||
|
for sess in self.sessions:
|
||||||
|
del _data.sessions[sess.key]
|
||||||
|
for token in self.reset_tokens:
|
||||||
|
del _data.reset_tokens[token.key]
|
||||||
|
del _data.users[self.uuid]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(
|
def create(
|
||||||
cls,
|
cls,
|
||||||
@@ -250,6 +308,20 @@ class Credential(msgspec.Struct, dict=True):
|
|||||||
s for s in db.data().sessions.values() if s.credential_uuid == self.uuid
|
s for s in db.data().sessions.values() if s.credential_uuid == self.uuid
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def store(self) -> None:
|
||||||
|
"""Store this credential in the database. Must be called inside a transaction."""
|
||||||
|
db.data().credentials[self.uuid] = self
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
|
"""Delete this credential and all its sessions.
|
||||||
|
|
||||||
|
Must be called inside a transaction.
|
||||||
|
"""
|
||||||
|
_data = db.data()
|
||||||
|
for sess in self.sessions:
|
||||||
|
del _data.sessions[sess.key]
|
||||||
|
del _data.credentials[self.uuid]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(
|
def create(
|
||||||
cls,
|
cls,
|
||||||
@@ -325,6 +397,10 @@ class Session(msgspec.Struct, dict=True):
|
|||||||
_data.users[self.user_uuid].last_seen = last_seen
|
_data.users[self.user_uuid].last_seen = last_seen
|
||||||
_data.users[self.user_uuid].visits += 1
|
_data.users[self.user_uuid].visits += 1
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
|
"""Delete this session from the database. Must be called inside a transaction."""
|
||||||
|
del db.data().sessions[self.key]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(
|
def create(
|
||||||
cls,
|
cls,
|
||||||
@@ -372,6 +448,14 @@ class ResetToken(msgspec.Struct, dict=True):
|
|||||||
"""Get the User object for this reset token."""
|
"""Get the User object for this reset token."""
|
||||||
return db.data().users[self.user_uuid]
|
return db.data().users[self.user_uuid]
|
||||||
|
|
||||||
|
def store(self) -> None:
|
||||||
|
"""Store this reset token in the database. Must be called inside a transaction."""
|
||||||
|
db.data().reset_tokens[self.key] = self
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
|
"""Delete this reset token from the database. Must be called inside a transaction."""
|
||||||
|
del db.data().reset_tokens[self.key]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(
|
def create(
|
||||||
cls,
|
cls,
|
||||||
|
|||||||
Reference in New Issue
Block a user