Refactor database layer and auditing
This commit is contained in:
@ -7,128 +7,182 @@ This might require some changes to these schemas.
|
||||
|
||||
"""
|
||||
|
||||
import enum
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlmodel import JSON, Column, DateTime, Field, Relationship, SQLModel
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Client(SQLModel, table=True):
|
||||
"""Client model."""
|
||||
class SubSystem(enum.StrEnum):
|
||||
"""Available subsystems."""
|
||||
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||
name: str = Field(unique=True)
|
||||
description: str | None = None
|
||||
public_key: str
|
||||
ADMIN = enum.auto()
|
||||
SSHD = enum.auto()
|
||||
BACKEND = enum.auto()
|
||||
TEST = enum.auto()
|
||||
|
||||
created_at: datetime | None = Field(
|
||||
default=None,
|
||||
sa_type=sa.DateTime(timezone=True),
|
||||
sa_column_kwargs={"server_default": sa.func.now()},
|
||||
nullable=False,
|
||||
|
||||
class Operation(enum.StrEnum):
|
||||
"""Various operations for the audit logging module."""
|
||||
|
||||
CREATE = enum.auto()
|
||||
READ = enum.auto()
|
||||
UPDATE = enum.auto()
|
||||
DELETE = enum.auto()
|
||||
DENY = enum.auto()
|
||||
PERMIT = enum.auto()
|
||||
LOGIN = enum.auto()
|
||||
REGISTER = enum.auto()
|
||||
NONE = enum.auto()
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
class Client(Base):
|
||||
"""Clients."""
|
||||
|
||||
__tablename__: str = "client"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
sa.Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||||
)
|
||||
name: Mapped[str] = mapped_column(sa.String, unique=True)
|
||||
description: Mapped[str | None] = mapped_column(sa.String, nullable=True)
|
||||
public_key: Mapped[str] = mapped_column(sa.Text)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
)
|
||||
|
||||
updated_at: datetime | None = Field(
|
||||
default=None,
|
||||
sa_type=sa.DateTime(timezone=True),
|
||||
sa_column_kwargs={"onupdate": sa.func.now(), "server_default": sa.func.now()},
|
||||
updated_at: Mapped[datetime | None] = mapped_column(
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now(),
|
||||
onupdate=sa.func.now(),
|
||||
)
|
||||
|
||||
secrets: list["ClientSecret"] = Relationship(
|
||||
back_populates="client", passive_deletes="all"
|
||||
secrets: Mapped[list["ClientSecret"]] = relationship(
|
||||
back_populates="client", passive_deletes=True
|
||||
)
|
||||
|
||||
policies: list["ClientAccessPolicy"] = Relationship(back_populates="client")
|
||||
policies: Mapped[list["ClientAccessPolicy"]] = relationship(back_populates="client")
|
||||
|
||||
|
||||
class ClientAccessPolicy(SQLModel, table=True):
|
||||
class ClientAccessPolicy(Base):
|
||||
"""Client access policies."""
|
||||
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||
source: str
|
||||
client_id: uuid.UUID | None = Field(foreign_key="client.id", ondelete="CASCADE")
|
||||
client: Client | None = Relationship(back_populates="policies")
|
||||
__tablename__: str = "client_access_policy"
|
||||
|
||||
created_at: datetime | None = Field(
|
||||
default=None,
|
||||
sa_type=sa.DateTime(timezone=True),
|
||||
sa_column_kwargs={"server_default": sa.func.now()},
|
||||
nullable=False,
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
sa.Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||||
)
|
||||
source: Mapped[str] = mapped_column(sa.String)
|
||||
client_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
sa.Uuid(as_uuid=True), sa.ForeignKey("client.id", ondelete="CASCADE")
|
||||
)
|
||||
client: Mapped[Client] = relationship(back_populates="policies")
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
)
|
||||
|
||||
updated_at: datetime | None = Field(
|
||||
default=None,
|
||||
sa_type=sa.DateTime(timezone=True),
|
||||
sa_column_kwargs={"onupdate": sa.func.now(), "server_default": sa.func.now()},
|
||||
updated_at: Mapped[datetime | None] = mapped_column(
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now(),
|
||||
onupdate=sa.func.now(),
|
||||
)
|
||||
|
||||
|
||||
class ClientSecret(SQLModel, table=True):
|
||||
class ClientSecret(Base):
|
||||
"""A client secret."""
|
||||
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||
name: str
|
||||
description: str | None = None
|
||||
client_id: uuid.UUID | None = Field(foreign_key="client.id", ondelete="CASCADE")
|
||||
client: Client | None = Relationship(back_populates="secrets")
|
||||
secret: str
|
||||
invalidated: bool = Field(default=False)
|
||||
created_at: datetime | None = Field(
|
||||
default=None,
|
||||
sa_type=sa.DateTime(timezone=True),
|
||||
sa_column_kwargs={"server_default": sa.func.now()},
|
||||
nullable=False,
|
||||
__tablename__: str = "client_secret"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
sa.Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||||
)
|
||||
name: Mapped[str] = mapped_column(sa.String)
|
||||
description: Mapped[str | None] = mapped_column(sa.String, nullable=True)
|
||||
secret: Mapped[str] = mapped_column(sa.String)
|
||||
|
||||
client_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
sa.Uuid(as_uuid=True), sa.ForeignKey("client.id", ondelete="CASCADE")
|
||||
)
|
||||
client: Mapped[Client] = relationship(back_populates="secrets")
|
||||
invalidated: Mapped[bool] = mapped_column(default=False)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
)
|
||||
|
||||
updated_at: datetime | None = Field(
|
||||
default=None,
|
||||
sa_type=sa.DateTime(timezone=True),
|
||||
sa_column_kwargs={"onupdate": sa.func.now(), "server_default": sa.func.now()},
|
||||
updated_at: Mapped[datetime | None] = mapped_column(
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now(),
|
||||
onupdate=sa.func.now(),
|
||||
)
|
||||
|
||||
|
||||
class AuditLog(SQLModel, table=True):
|
||||
class APIClient(Base):
|
||||
"""A client on the API.
|
||||
|
||||
This should eventually get more granular permissions.
|
||||
"""
|
||||
|
||||
__tablename__: str = "api_client"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
sa.Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||||
)
|
||||
subsystem: Mapped[SubSystem | None] = mapped_column(sa.String, nullable=True)
|
||||
token: Mapped[str] = mapped_column(sa.String)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
)
|
||||
updated_at: Mapped[datetime | None] = mapped_column(
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now(),
|
||||
onupdate=sa.func.now(),
|
||||
)
|
||||
|
||||
|
||||
class AuditLog(Base):
|
||||
"""Audit log.
|
||||
|
||||
This is implemented without any foreign keys to avoid losing data on
|
||||
deletions.
|
||||
"""
|
||||
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||
subsystem: str
|
||||
message: str
|
||||
operation: str
|
||||
client_id: uuid.UUID | None = None
|
||||
client_name: str | None = None
|
||||
origin: str | None = None
|
||||
Field(default=None, sa_column=Column(JSON))
|
||||
|
||||
timestamp: datetime | None = Field(
|
||||
default=None,
|
||||
sa_type=sa.DateTime(timezone=True),
|
||||
sa_column_kwargs={"server_default": sa.func.now()},
|
||||
nullable=False,
|
||||
__tablename__: str = "audit_log"
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
sa.Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4
|
||||
)
|
||||
|
||||
subsystem: Mapped[SubSystem] = mapped_column(sa.String)
|
||||
message: Mapped[str] = mapped_column(sa.String)
|
||||
operation: Mapped[Operation] = mapped_column(sa.String)
|
||||
client_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
sa.Uuid(as_uuid=True), nullable=True
|
||||
)
|
||||
data: Mapped[dict[str, str] | None] = mapped_column(sa.JSON, nullable=True)
|
||||
client_name: Mapped[str | None] = mapped_column(sa.String, nullable=True)
|
||||
secret_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
sa.Uuid(as_uuid=True), nullable=True
|
||||
)
|
||||
secret_name: Mapped[str | None] = mapped_column(sa.String, nullable=True)
|
||||
|
||||
class APIClient(SQLModel, table=True):
|
||||
"""Stores API Keys."""
|
||||
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||
token: str
|
||||
read_write: bool
|
||||
created_at: datetime | None = Field(
|
||||
default=None,
|
||||
sa_type=sa.DateTime(timezone=True),
|
||||
sa_column_kwargs={"server_default": sa.func.now()},
|
||||
nullable=False,
|
||||
origin: Mapped[str | None] = mapped_column(sa.String, nullable=True)
|
||||
timestamp: Mapped[datetime] = mapped_column(
|
||||
sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
)
|
||||
|
||||
|
||||
def init_db(engine: sa.Engine) -> None:
|
||||
"""Create database."""
|
||||
LOG.info("Running init_db")
|
||||
SQLModel.metadata.create_all(engine)
|
||||
"""Initialize database."""
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
Reference in New Issue
Block a user