347 lines
18 KiB
Python
347 lines
18 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, String, Text, UniqueConstraint, JSON, text
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from govoplan_access.backend.db.base import AccessBase, TimestampMixin
|
|
from govoplan_core.tenancy.scope import Tenant
|
|
|
|
|
|
def new_uuid() -> str:
|
|
return str(uuid.uuid4())
|
|
|
|
|
|
class Account(AccessBase, TimestampMixin):
|
|
"""Global login identity shared by one or more tenant memberships."""
|
|
|
|
__tablename__ = "access_accounts"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
email: Mapped[str] = mapped_column(String(320), nullable=False)
|
|
normalized_email: Mapped[str] = mapped_column(String(320), unique=True, nullable=False, index=True)
|
|
display_name: Mapped[str | None] = mapped_column(String(255))
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
auth_provider: Mapped[str] = mapped_column(String(50), default="local", nullable=False)
|
|
password_hash: Mapped[str | None] = mapped_column(String(500))
|
|
password_reset_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
last_login_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
|
|
memberships: Mapped[list[User]] = relationship(back_populates="account")
|
|
identity_links: Mapped[list[IdentityAccountLink]] = relationship(
|
|
back_populates="account", cascade="all, delete-orphan"
|
|
)
|
|
auth_sessions: Mapped[list[AuthSession]] = relationship(back_populates="account", cascade="all, delete-orphan")
|
|
system_role_assignments: Mapped[list[SystemRoleAssignment]] = relationship(
|
|
back_populates="account", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class Identity(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_identities"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
display_name: Mapped[str | None] = mapped_column(String(255))
|
|
external_subject: Mapped[str | None] = mapped_column(String(255), index=True)
|
|
source: Mapped[str] = mapped_column(String(50), default="local", nullable=False)
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
|
|
account_links: Mapped[list[IdentityAccountLink]] = relationship(
|
|
back_populates="identity", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class IdentityAccountLink(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_identity_account_links"
|
|
__table_args__ = (
|
|
UniqueConstraint("identity_id", "account_id", name="uq_identity_account_links_identity_account"),
|
|
Index(
|
|
"uq_identity_account_links_primary_account",
|
|
"account_id",
|
|
unique=True,
|
|
sqlite_where=text("is_primary = 1"),
|
|
postgresql_where=text("is_primary IS TRUE"),
|
|
),
|
|
Index(
|
|
"uq_identity_account_links_primary_identity",
|
|
"identity_id",
|
|
unique=True,
|
|
sqlite_where=text("is_primary = 1"),
|
|
postgresql_where=text("is_primary IS TRUE"),
|
|
),
|
|
)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
identity_id: Mapped[str] = mapped_column(ForeignKey("access_identities.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
is_primary: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
source: Mapped[str] = mapped_column(String(50), default="local", nullable=False)
|
|
|
|
identity: Mapped[Identity] = relationship(back_populates="account_links")
|
|
account: Mapped[Account] = relationship(back_populates="identity_links")
|
|
|
|
|
|
class User(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_users"
|
|
__table_args__ = (
|
|
UniqueConstraint("tenant_id", "email", name="uq_users_tenant_email"),
|
|
UniqueConstraint("tenant_id", "account_id", name="uq_users_tenant_account"),
|
|
)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
|
account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
email: Mapped[str] = mapped_column(String(320), nullable=False, index=True)
|
|
display_name: Mapped[str | None] = mapped_column(String(255))
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
is_tenant_admin: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
auth_provider: Mapped[str] = mapped_column(String(50), default="local", nullable=False)
|
|
password_hash: Mapped[str | None] = mapped_column(String(500))
|
|
last_login_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
mail_profile_policy: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
|
|
account: Mapped[Account] = relationship(back_populates="memberships")
|
|
api_keys: Mapped[list[ApiKey]] = relationship(back_populates="user", cascade="all, delete-orphan")
|
|
auth_sessions: Mapped[list[AuthSession]] = relationship(back_populates="user", cascade="all, delete-orphan")
|
|
|
|
|
|
class Group(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_groups"
|
|
__table_args__ = (UniqueConstraint("tenant_id", "slug", name="uq_groups_tenant_slug"),)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
|
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text)
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
system_template_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
|
|
system_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
mail_profile_policy: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
|
|
|
|
class Role(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_roles"
|
|
__table_args__ = (
|
|
UniqueConstraint("tenant_id", "slug", name="uq_roles_tenant_slug"),
|
|
Index(
|
|
"uq_roles_system_slug",
|
|
"slug",
|
|
unique=True,
|
|
sqlite_where=text("tenant_id IS NULL"),
|
|
postgresql_where=text("tenant_id IS NULL"),
|
|
),
|
|
)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
tenant_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
|
|
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text)
|
|
permissions: Mapped[list[str]] = mapped_column(JSON, default=list)
|
|
is_builtin: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
is_assignable: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
system_template_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
|
|
system_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
|
|
|
|
class OrganizationUnit(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_organization_units"
|
|
__table_args__ = (UniqueConstraint("tenant_id", "slug", name="uq_organization_units_tenant_slug"),)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
|
parent_id: Mapped[str | None] = mapped_column(ForeignKey("access_organization_units.id", ondelete="SET NULL"), nullable=True, index=True)
|
|
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text)
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
|
|
|
|
class Function(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_functions"
|
|
__table_args__ = (UniqueConstraint("tenant_id", "organization_unit_id", "slug", name="uq_functions_tenant_ou_slug"),)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
|
organization_unit_id: Mapped[str] = mapped_column(ForeignKey("access_organization_units.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text)
|
|
delegable: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
act_in_place_allowed: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
|
|
|
|
class FunctionRoleAssignment(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_function_role_assignments"
|
|
__table_args__ = (UniqueConstraint("tenant_id", "function_id", "role_id", name="uq_function_role_assignments"),)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
|
function_id: Mapped[str] = mapped_column(ForeignKey("access_functions.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
role_id: Mapped[str] = mapped_column(ForeignKey("access_roles.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
|
class FunctionAssignment(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_function_assignments"
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id",
|
|
"account_id",
|
|
"function_id",
|
|
"organization_unit_id",
|
|
name="uq_function_assignments_account_scope",
|
|
),
|
|
)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
|
account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
identity_id: Mapped[str | None] = mapped_column(ForeignKey("access_identities.id", ondelete="SET NULL"), nullable=True, index=True)
|
|
function_id: Mapped[str] = mapped_column(ForeignKey("access_functions.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
organization_unit_id: Mapped[str] = mapped_column(ForeignKey("access_organization_units.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
applies_to_subunits: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
source: Mapped[str] = mapped_column(String(50), default="direct", nullable=False)
|
|
delegated_from_assignment_id: Mapped[str | None] = mapped_column(ForeignKey("access_function_assignments.id", ondelete="SET NULL"), nullable=True, index=True)
|
|
acting_for_account_id: Mapped[str | None] = mapped_column(ForeignKey("access_accounts.id", ondelete="SET NULL"), nullable=True, index=True)
|
|
valid_from: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
valid_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
|
|
|
|
class FunctionDelegation(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_function_delegations"
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id",
|
|
"function_assignment_id",
|
|
"delegate_account_id",
|
|
"mode",
|
|
name="uq_function_delegations_assignment_delegate_mode",
|
|
),
|
|
)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
|
function_assignment_id: Mapped[str] = mapped_column(ForeignKey("access_function_assignments.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
delegator_account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
delegate_account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
mode: Mapped[str] = mapped_column(String(30), default="delegate", nullable=False)
|
|
reason: Mapped[str | None] = mapped_column(Text)
|
|
valid_from: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
valid_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True)
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
|
|
|
|
class SystemRoleAssignment(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_system_role_assignments"
|
|
__table_args__ = (UniqueConstraint("account_id", "role_id", name="uq_system_role_assignments"),)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
role_id: Mapped[str] = mapped_column(ForeignKey("access_roles.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
account: Mapped[Account] = relationship(back_populates="system_role_assignments")
|
|
role: Mapped[Role] = relationship()
|
|
|
|
|
|
class UserGroupMembership(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_user_group_memberships"
|
|
__table_args__ = (UniqueConstraint("tenant_id", "user_id", "group_id", name="uq_user_group_memberships"),)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
|
user_id: Mapped[str] = mapped_column(ForeignKey("access_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
group_id: Mapped[str] = mapped_column(ForeignKey("access_groups.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
|
class UserRoleAssignment(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_user_role_assignments"
|
|
__table_args__ = (UniqueConstraint("tenant_id", "user_id", "role_id", name="uq_user_role_assignments"),)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
|
user_id: Mapped[str] = mapped_column(ForeignKey("access_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
role_id: Mapped[str] = mapped_column(ForeignKey("access_roles.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
|
class GroupRoleAssignment(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_group_role_assignments"
|
|
__table_args__ = (UniqueConstraint("tenant_id", "group_id", "role_id", name="uq_group_role_assignments"),)
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
|
group_id: Mapped[str] = mapped_column(ForeignKey("access_groups.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
role_id: Mapped[str] = mapped_column(ForeignKey("access_roles.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
|
class ApiKey(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_api_keys"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
|
user_id: Mapped[str] = mapped_column(ForeignKey("access_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
prefix: Mapped[str] = mapped_column(String(16), nullable=False, index=True)
|
|
key_hash: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
scopes: Mapped[list[str]] = mapped_column(JSON, default=list)
|
|
expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
|
|
user: Mapped[User] = relationship(back_populates="api_keys")
|
|
|
|
|
|
class AuthSession(AccessBase, TimestampMixin):
|
|
__tablename__ = "access_auth_sessions"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
|
|
user_id: Mapped[str] = mapped_column(ForeignKey("access_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
token_hash: Mapped[str] = mapped_column(String(128), nullable=False, unique=True, index=True)
|
|
csrf_token_hash: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True)
|
|
last_seen_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), index=True)
|
|
user_agent: Mapped[str | None] = mapped_column(String(500))
|
|
ip_address: Mapped[str | None] = mapped_column(String(100))
|
|
|
|
user: Mapped[User] = relationship(back_populates="auth_sessions")
|
|
account: Mapped[Account] = relationship(back_populates="auth_sessions")
|
|
|
|
|
|
__all__ = [
|
|
"Account",
|
|
"ApiKey",
|
|
"AuthSession",
|
|
"Function",
|
|
"FunctionAssignment",
|
|
"FunctionDelegation",
|
|
"FunctionRoleAssignment",
|
|
"Group",
|
|
"GroupRoleAssignment",
|
|
"Identity",
|
|
"IdentityAccountLink",
|
|
"OrganizationUnit",
|
|
"Role",
|
|
"SystemRoleAssignment",
|
|
"Tenant",
|
|
"User",
|
|
"UserGroupMembership",
|
|
"UserRoleAssignment",
|
|
"new_uuid",
|
|
]
|