🚧 WIP
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
"""ORM models — Release 1 subset (spec sections 39, 9, 11, 13, 14, 26).
|
||||
|
||||
Dynamic Security objects (clients/groups/roles) are NOT stored here; the
|
||||
broker owns them (spec section 40). PostgreSQL holds management users,
|
||||
RBAC, broker config model + revisions, and MQTT client metadata.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
JSON,
|
||||
Boolean,
|
||||
DateTime,
|
||||
Enum,
|
||||
ForeignKey,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db import Base
|
||||
|
||||
|
||||
def utcnow() -> datetime:
|
||||
"""Timezone-aware UTC now — Postgres timestamptz requires aware values."""
|
||||
return datetime.now(UTC)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Management identities + RBAC (spec sections 8, 9)
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
class ManagementRole(str, enum.Enum):
|
||||
"""Built-in platform roles (spec section 9). Custom roles: later release."""
|
||||
|
||||
SUPER_ADMIN = "super_admin"
|
||||
BROKER_ADMIN = "broker_admin"
|
||||
PKI_ADMIN = "pki_admin"
|
||||
OPERATOR = "operator"
|
||||
AUDITOR = "auditor"
|
||||
|
||||
|
||||
class ManagementUser(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
username: Mapped[str] = mapped_column(String(100), unique=True, index=True)
|
||||
# Argon2id hash (spec section 43); never the plaintext password.
|
||||
password_hash: Mapped[str] = mapped_column(String(255))
|
||||
display_name: Mapped[str | None] = mapped_column(String(200))
|
||||
role: Mapped[ManagementRole] = mapped_column(
|
||||
Enum(ManagementRole, name="management_role"),
|
||||
default=ManagementRole.BROKER_ADMIN,
|
||||
)
|
||||
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
must_change_password: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
||||
modified_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=utcnow, onupdate=utcnow
|
||||
)
|
||||
|
||||
|
||||
class ManagementSession(Base):
|
||||
"""Server-side session store (spec section 8: revocable, expiring)."""
|
||||
|
||||
__tablename__ = "sessions"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
user_id: Mapped[uuid.UUID] = mapped_column(
|
||||
ForeignKey("users.id", ondelete="CASCADE"), index=True
|
||||
)
|
||||
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
|
||||
revoked: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Broker configuration model + revisions (spec sections 25, 26, 30, 31, 61)
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
class ListenerConfig(Base):
|
||||
"""One Mosquitto listener as managed through the UI (spec section 30)."""
|
||||
|
||||
__tablename__ = "broker_listeners"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
name: Mapped[str] = mapped_column(String(100))
|
||||
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
port: Mapped[int] = mapped_column(Integer)
|
||||
bind_address: Mapped[str | None] = mapped_column(String(100))
|
||||
protocol: Mapped[str] = mapped_column(String(20), default="mqtt") # mqtt|websockets
|
||||
allow_anonymous: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
max_connections: Mapped[int] = mapped_column(Integer, default=-1)
|
||||
# TLS settings (used once PKI ships in Release 2; kept now so the
|
||||
# config model is stable from the start).
|
||||
tls_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
tls_min_version: Mapped[str | None] = mapped_column(String(20))
|
||||
require_certificate: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
use_identity_as_username: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
cert_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("certificates.id"))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
||||
modified_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=utcnow, onupdate=utcnow
|
||||
)
|
||||
|
||||
|
||||
class BrokerSettings(Base):
|
||||
"""Key/value store for global broker options (spec section 31)."""
|
||||
|
||||
__tablename__ = "broker_settings"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
key: Mapped[str] = mapped_column(String(100), unique=True, index=True)
|
||||
value: Mapped[str] = mapped_column(Text)
|
||||
value_type: Mapped[str] = mapped_column(String(20), default="string")
|
||||
|
||||
|
||||
class ConfigRevision(Base):
|
||||
"""Generated mosquitto.conf revisions (spec sections 26, 61)."""
|
||||
|
||||
__tablename__ = "broker_config_revisions"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
revision_number: Mapped[int] = mapped_column(Integer, unique=True, index=True)
|
||||
created_by: Mapped[str | None] = mapped_column(String(100))
|
||||
comment: Mapped[str | None] = mapped_column(Text)
|
||||
# Full generated mosquitto.conf text
|
||||
config_text: Mapped[str] = mapped_column(Text)
|
||||
validation_result: Mapped[dict] = mapped_column(JSON, default=dict)
|
||||
deployed: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
deployed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("revision_number", name="uq_config_revision_number"),
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# PKI placeholders (populated in Release 2; referenced by listeners)
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
class Certificate(Base):
|
||||
__tablename__ = "certificates"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
common_name: Mapped[str] = mapped_column(String(200))
|
||||
certificate_pem: Mapped[str] = mapped_column(Text)
|
||||
# Encrypted at rest (AES-256-GCM) once keys are managed (Release 2).
|
||||
private_key_pem: Mapped[str | None] = mapped_column(Text)
|
||||
expiry_date: Mapped[datetime] = mapped_column(DateTime(timezone=True))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# MQTT client metadata (spec sections 11, 40) — dynsec owns the auth data,
|
||||
# PostgreSQL stores the descriptive/ownership metadata.
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
class MqttClientMetadata(Base):
|
||||
__tablename__ = "mqtt_client_metadata"
|
||||
|
||||
username: Mapped[str] = mapped_column(String(100), primary_key=True)
|
||||
display_name: Mapped[str | None] = mapped_column(String(200))
|
||||
description: Mapped[str | None] = mapped_column(Text)
|
||||
owner: Mapped[str | None] = mapped_column(String(200))
|
||||
purpose: Mapped[str | None] = mapped_column(String(200))
|
||||
tags: Mapped[list] = mapped_column(JSON, default=list)
|
||||
last_known_connection: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
||||
modified_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=utcnow, onupdate=utcnow
|
||||
)
|
||||
# Certificate link becomes active in Release 2 (mTLS identity binding).
|
||||
certificate_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
ForeignKey("certificates.id", ondelete="SET NULL")
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Audit log (spec section 38) — append-only from the application's view.
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
class AuditEvent(Base):
|
||||
__tablename__ = "audit_events"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
timestamp: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=utcnow, index=True
|
||||
)
|
||||
user: Mapped[str | None] = mapped_column(String(100))
|
||||
source_ip: Mapped[str | None] = mapped_column(String(64))
|
||||
action: Mapped[str] = mapped_column(String(100), index=True)
|
||||
object_type: Mapped[str | None] = mapped_column(String(100))
|
||||
object_id: Mapped[str | None] = mapped_column(String(200))
|
||||
old_value: Mapped[str | None] = mapped_column(JSON)
|
||||
new_value: Mapped[str | None] = mapped_column(JSON)
|
||||
result: Mapped[str] = mapped_column(String(20), default="success")
|
||||
correlation_id: Mapped[str | None] = mapped_column(String(64))
|
||||
Reference in New Issue
Block a user