229 lines
12 KiB
Python
229 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import date, datetime, timezone
|
|
|
|
from sqlalchemy import Boolean, Date, DateTime, Float, ForeignKey, Integer, String, Text, UniqueConstraint
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.db import Base
|
|
|
|
|
|
def utcnow() -> datetime:
|
|
return datetime.now(timezone.utc)
|
|
|
|
|
|
class InboxStatus(Base):
|
|
__tablename__ = "inbox_statuses"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
inbox_id: Mapped[str] = mapped_column(String(120), unique=True, index=True)
|
|
label: Mapped[str] = mapped_column(String(200))
|
|
domain: Mapped[str] = mapped_column(String(255), index=True)
|
|
folder: Mapped[str] = mapped_column(String(255))
|
|
recipient: Mapped[str] = mapped_column(String(320))
|
|
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
last_check_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
last_success_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
last_error_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
last_error: Mapped[str | None] = mapped_column(Text)
|
|
last_new_messages: Mapped[int] = mapped_column(Integer, default=0)
|
|
last_reports_imported: Mapped[int] = mapped_column(Integer, default=0)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow)
|
|
|
|
|
|
class MailMessage(Base):
|
|
__tablename__ = "mail_messages"
|
|
__table_args__ = (UniqueConstraint("inbox_id", "folder", "imap_uid", name="uq_message_uid"),)
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
inbox_id: Mapped[str] = mapped_column(String(120), index=True)
|
|
imap_uid: Mapped[str] = mapped_column(String(120))
|
|
message_id: Mapped[str | None] = mapped_column(String(500))
|
|
folder: Mapped[str] = mapped_column(String(255))
|
|
subject: Mapped[str | None] = mapped_column(Text)
|
|
sender: Mapped[str | None] = mapped_column(Text)
|
|
recipient: Mapped[str | None] = mapped_column(Text)
|
|
message_date: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
seen: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
status: Mapped[str] = mapped_column(String(40), default="skipped")
|
|
error: Mapped[str | None] = mapped_column(Text)
|
|
processed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
|
|
reports: Mapped[list["Report"]] = relationship(back_populates="mail_message")
|
|
|
|
|
|
class Report(Base):
|
|
__tablename__ = "reports"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
inbox_id: Mapped[str] = mapped_column(String(120), index=True)
|
|
mail_message_id: Mapped[int | None] = mapped_column(ForeignKey("mail_messages.id"))
|
|
raw_xml_sha256: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
|
report_id: Mapped[str | None] = mapped_column(String(500), index=True)
|
|
org_name: Mapped[str | None] = mapped_column(String(255), index=True)
|
|
org_email: Mapped[str | None] = mapped_column(String(320))
|
|
extra_contact_info: Mapped[str | None] = mapped_column(Text)
|
|
domain: Mapped[str] = mapped_column(String(255), index=True)
|
|
date_begin: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), index=True)
|
|
date_end: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), index=True)
|
|
policy_p: Mapped[str | None] = mapped_column(String(40))
|
|
policy_sp: Mapped[str | None] = mapped_column(String(40))
|
|
policy_pct: Mapped[int | None] = mapped_column(Integer)
|
|
adkim: Mapped[str | None] = mapped_column(String(20))
|
|
aspf: Mapped[str | None] = mapped_column(String(20))
|
|
fo: Mapped[str | None] = mapped_column(String(80))
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
|
|
mail_message: Mapped[MailMessage | None] = relationship(back_populates="reports")
|
|
records: Mapped[list["Record"]] = relationship(back_populates="report", cascade="all, delete-orphan")
|
|
|
|
|
|
class Record(Base):
|
|
__tablename__ = "records"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
report_id: Mapped[int] = mapped_column(ForeignKey("reports.id"), index=True)
|
|
source_ip: Mapped[str] = mapped_column(String(80), index=True)
|
|
source_reverse_dns: Mapped[str | None] = mapped_column(String(255))
|
|
source_asn: Mapped[str | None] = mapped_column(String(80))
|
|
source_country: Mapped[str | None] = mapped_column(String(80))
|
|
count: Mapped[int] = mapped_column(Integer, default=0)
|
|
disposition: Mapped[str | None] = mapped_column(String(40), index=True)
|
|
policy_dkim: Mapped[str | None] = mapped_column(String(40))
|
|
policy_spf: Mapped[str | None] = mapped_column(String(40))
|
|
dkim_aligned: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
spf_aligned: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
dmarc_pass: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
header_from: Mapped[str | None] = mapped_column(String(255), index=True)
|
|
reason_type: Mapped[str | None] = mapped_column(String(120))
|
|
reason_comment: Mapped[str | None] = mapped_column(Text)
|
|
known_sender_id: Mapped[str | None] = mapped_column(String(120), index=True)
|
|
known_sender_name: Mapped[str | None] = mapped_column(String(255))
|
|
is_known_sender: Mapped[bool] = mapped_column(Boolean, default=False, index=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
|
|
report: Mapped[Report] = relationship(back_populates="records")
|
|
auth_results: Mapped[list["AuthResult"]] = relationship(back_populates="record", cascade="all, delete-orphan")
|
|
|
|
|
|
class AuthResult(Base):
|
|
__tablename__ = "auth_results"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
record_id: Mapped[int] = mapped_column(ForeignKey("records.id"), index=True)
|
|
auth_type: Mapped[str] = mapped_column(String(20), index=True)
|
|
domain: Mapped[str | None] = mapped_column(String(255), index=True)
|
|
selector: Mapped[str | None] = mapped_column(String(120))
|
|
scope: Mapped[str | None] = mapped_column(String(120))
|
|
result: Mapped[str | None] = mapped_column(String(120))
|
|
human_result: Mapped[str | None] = mapped_column(Text)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
|
|
record: Mapped[Record] = relationship(back_populates="auth_results")
|
|
|
|
|
|
class SkippedReportPayload(Base):
|
|
__tablename__ = "skipped_report_payloads"
|
|
__table_args__ = (
|
|
UniqueConstraint("inbox_id", "folder", "imap_uid", "raw_xml_sha256", "reason", name="uq_skipped_report_payload"),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
inbox_id: Mapped[str] = mapped_column(String(120), index=True)
|
|
folder: Mapped[str] = mapped_column(String(255))
|
|
imap_uid: Mapped[str] = mapped_column(String(120))
|
|
message_id: Mapped[str | None] = mapped_column(String(500))
|
|
mail_message_id: Mapped[int | None] = mapped_column(ForeignKey("mail_messages.id"))
|
|
reason: Mapped[str] = mapped_column(String(80), index=True)
|
|
raw_xml_sha256: Mapped[str | None] = mapped_column(String(64), index=True)
|
|
existing_report_id: Mapped[int | None] = mapped_column(ForeignKey("reports.id"))
|
|
report_identifier: Mapped[str | None] = mapped_column(String(500), index=True)
|
|
reporting_org: Mapped[str | None] = mapped_column(String(255), index=True)
|
|
report_date: Mapped[date | None] = mapped_column(Date, index=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
|
|
|
|
class Alert(Base):
|
|
__tablename__ = "alerts"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
fingerprint: Mapped[str] = mapped_column(String(500), unique=True, index=True)
|
|
inbox_id: Mapped[str] = mapped_column(String(120), index=True)
|
|
domain: Mapped[str] = mapped_column(String(255), index=True)
|
|
severity: Mapped[str] = mapped_column(String(40), index=True)
|
|
type: Mapped[str] = mapped_column(String(120), index=True)
|
|
title: Mapped[str] = mapped_column(String(500))
|
|
summary: Mapped[str] = mapped_column(Text)
|
|
details_json: Mapped[str] = mapped_column(Text, default="{}")
|
|
llm_summary: Mapped[str | None] = mapped_column(Text)
|
|
llm_risk: Mapped[str | None] = mapped_column(Text)
|
|
llm_recommended_action: Mapped[str | None] = mapped_column(Text)
|
|
status: Mapped[str] = mapped_column(String(40), default="open", index=True)
|
|
first_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow)
|
|
|
|
|
|
class DomainDnsSnapshot(Base):
|
|
__tablename__ = "domain_dns_snapshots"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
domain: Mapped[str] = mapped_column(String(255), index=True)
|
|
checked_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, index=True)
|
|
dmarc_record: Mapped[str | None] = mapped_column(Text)
|
|
dmarc_policy_p: Mapped[str | None] = mapped_column(String(40))
|
|
dmarc_policy_sp: Mapped[str | None] = mapped_column(String(40))
|
|
dmarc_policy_pct: Mapped[int | None] = mapped_column(Integer)
|
|
dmarc_adkim: Mapped[str | None] = mapped_column(String(20))
|
|
dmarc_aspf: Mapped[str | None] = mapped_column(String(20))
|
|
dmarc_fo: Mapped[str | None] = mapped_column(String(80))
|
|
dmarc_rua: Mapped[str | None] = mapped_column(Text)
|
|
dmarc_ruf: Mapped[str | None] = mapped_column(Text)
|
|
spf_record: Mapped[str | None] = mapped_column(Text)
|
|
spf_all: Mapped[str | None] = mapped_column(String(20))
|
|
spf_includes_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
dkim_records_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
mx_records_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
errors_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
|
|
|
|
class DailyStat(Base):
|
|
__tablename__ = "daily_stats"
|
|
__table_args__ = (UniqueConstraint("domain", "date", name="uq_daily_stat_domain_date"),)
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
domain: Mapped[str] = mapped_column(String(255), index=True)
|
|
date: Mapped[date] = mapped_column(Date, index=True)
|
|
total_messages: Mapped[int] = mapped_column(Integer, default=0)
|
|
dmarc_pass_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
dmarc_fail_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
spf_aligned_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
spf_failed_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
dkim_aligned_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
dkim_failed_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
unknown_source_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
known_source_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
quarantine_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
reject_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
top_reporters_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
top_sources_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow)
|
|
|
|
|
|
class LLMReport(Base):
|
|
__tablename__ = "llm_reports"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
domain: Mapped[str] = mapped_column(String(255), index=True)
|
|
period_start: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
|
|
period_end: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
|
|
report_type: Mapped[str] = mapped_column(String(40), index=True)
|
|
input_json: Mapped[str] = mapped_column(Text)
|
|
output_json: Mapped[str] = mapped_column(Text)
|
|
plain_text: Mapped[str] = mapped_column(Text)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|