feat(refactor): PEP8, type annotations, docstrings and PyJWT security fix
This commit is contained in:
@@ -1,10 +1,5 @@
|
||||
"""SQLAlchemy ORM model definitions for all database tables."""
|
||||
# Import all models here so Alembic can detect them
|
||||
from app.models.user import User
|
||||
from app.models.technique import Technique
|
||||
from app.models.test import Test
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.models.evidence import Evidence
|
||||
from app.models.intel import IntelItem
|
||||
from app.models.audit import AuditLog
|
||||
from app.models.notification import Notification
|
||||
from app.models.data_source import DataSource
|
||||
@@ -45,17 +40,96 @@ from app.models.api_key import ApiKey
|
||||
from app.models.sso_config import SsoConfig
|
||||
from app.models.operational_alert import AlertRule, AlertInstance
|
||||
|
||||
# Import Campaign, CampaignTest from app.models.campaign
|
||||
from app.models.campaign import Campaign, CampaignTest
|
||||
|
||||
# Import from app.models.compliance
|
||||
from app.models.compliance import (
|
||||
ComplianceControl,
|
||||
ComplianceControlMapping,
|
||||
ComplianceFramework,
|
||||
)
|
||||
|
||||
# Import CoverageSnapshot, SnapshotTechniqueState from app.models.coverage_snapshot
|
||||
from app.models.coverage_snapshot import CoverageSnapshot, SnapshotTechniqueState
|
||||
|
||||
# Import DataSource from app.models.data_source
|
||||
from app.models.data_source import DataSource
|
||||
|
||||
# Import DefensiveTechnique, DefensiveTechniqueMapping from app.models.defensive_technique
|
||||
from app.models.defensive_technique import DefensiveTechnique, DefensiveTechniqueMapping
|
||||
|
||||
# Import DetectionRule from app.models.detection_rule
|
||||
from app.models.detection_rule import DetectionRule
|
||||
|
||||
# Import TeamSide, TechniqueStatus, TestResult, TestState from app.models.enums
|
||||
from app.models.enums import TeamSide, TechniqueStatus, TestResult, TestState
|
||||
|
||||
# Import Evidence from app.models.evidence
|
||||
from app.models.evidence import Evidence
|
||||
|
||||
# Import IntelItem from app.models.intel
|
||||
from app.models.intel import IntelItem
|
||||
|
||||
# Import JiraLink, JiraLinkEntityType, JiraSyncDirection from app.models.jira_link
|
||||
from app.models.jira_link import JiraLink, JiraLinkEntityType, JiraSyncDirection
|
||||
|
||||
# Import Notification from app.models.notification
|
||||
from app.models.notification import Notification
|
||||
|
||||
# Import OsintItem from app.models.osint_item
|
||||
from app.models.osint_item import OsintItem
|
||||
|
||||
# Import ScoringConfig from app.models.scoring_config
|
||||
from app.models.scoring_config import ScoringConfig
|
||||
|
||||
# Import Technique from app.models.technique
|
||||
from app.models.technique import Technique
|
||||
|
||||
# Import Test from app.models.test
|
||||
from app.models.test import Test
|
||||
|
||||
# Import TestDetectionResult from app.models.test_detection_result
|
||||
from app.models.test_detection_result import TestDetectionResult
|
||||
|
||||
# Import TestTemplate from app.models.test_template
|
||||
from app.models.test_template import TestTemplate
|
||||
|
||||
# Import TestTemplateDetectionRule from app.models.test_template_detection_rule
|
||||
from app.models.test_template_detection_rule import TestTemplateDetectionRule
|
||||
|
||||
# Import ThreatActor, ThreatActorTechnique from app.models.threat_actor
|
||||
from app.models.threat_actor import ThreatActor, ThreatActorTechnique
|
||||
|
||||
# Import User from app.models.user
|
||||
from app.models.user import User
|
||||
|
||||
# Import Worklog from app.models.worklog
|
||||
from app.models.worklog import Worklog
|
||||
|
||||
# Assign __all__ = [
|
||||
__all__ = [
|
||||
# Literal argument value
|
||||
"User", "Technique", "Test", "TestTemplate", "Evidence",
|
||||
# Literal argument value
|
||||
"IntelItem", "AuditLog", "Notification", "DataSource",
|
||||
# Literal argument value
|
||||
"DetectionRule", "ThreatActor", "ThreatActorTechnique",
|
||||
# Literal argument value
|
||||
"DefensiveTechnique", "DefensiveTechniqueMapping",
|
||||
# Literal argument value
|
||||
"TestTemplateDetectionRule", "TestDetectionResult",
|
||||
# Literal argument value
|
||||
"Campaign", "CampaignTest",
|
||||
# Literal argument value
|
||||
"ComplianceFramework", "ComplianceControl", "ComplianceControlMapping",
|
||||
# Literal argument value
|
||||
"CoverageSnapshot", "SnapshotTechniqueState",
|
||||
# Literal argument value
|
||||
"JiraLink", "JiraLinkEntityType", "JiraSyncDirection",
|
||||
# Literal argument value
|
||||
"Worklog", "OsintItem", "ScoringConfig",
|
||||
# Literal argument value
|
||||
"TechniqueStatus", "TestState", "TestResult", "TeamSide",
|
||||
"WebhookConfig", "SystemConfig",
|
||||
"DetectionAsset", "DetectionTechniqueMapping", "DetectionValidation",
|
||||
|
||||
@@ -1,35 +1,58 @@
|
||||
"""SQLAlchemy model for the audit log table."""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, DateTime, ForeignKey, Index, func
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
# Import Column, DateTime, ForeignKey, Index, String, func from sqlalchemy
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Index, String, func
|
||||
|
||||
# Import JSONB, UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class AuditLog
|
||||
class AuditLog(Base):
|
||||
"""
|
||||
Audit log model for tracking all system actions.
|
||||
|
||||
"""Audit log model for tracking all system actions.
|
||||
|
||||
Records user actions, entity changes, and system events
|
||||
for security auditing and compliance purposes.
|
||||
"""
|
||||
# Assign __tablename__ = "audit_logs"
|
||||
__tablename__ = "audit_logs"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
# Assign action = Column(String, nullable=False)
|
||||
action = Column(String, nullable=False)
|
||||
# Assign entity_type = Column(String, nullable=True)
|
||||
entity_type = Column(String, nullable=True)
|
||||
# Assign entity_id = Column(String, nullable=True)
|
||||
entity_id = Column(String, nullable=True)
|
||||
# Assign timestamp = Column(DateTime(timezone=True), server_default=func.now())
|
||||
timestamp = Column(DateTime(timezone=True), server_default=func.now())
|
||||
# Assign details = Column(JSONB, nullable=True)
|
||||
details = Column(JSONB, nullable=True)
|
||||
# Assign ip_address = Column(String(45), nullable=True)
|
||||
ip_address = Column(String(45), nullable=True)
|
||||
# Assign user_agent = Column(String(500), nullable=True)
|
||||
user_agent = Column(String(500), nullable=True)
|
||||
# Assign integrity_hash = Column(String(64), nullable=True)
|
||||
integrity_hash = Column(String(64), nullable=True)
|
||||
# Assign session_id = Column(String(100), nullable=True)
|
||||
session_id = Column(String(100), nullable=True)
|
||||
|
||||
# Relationships
|
||||
user = relationship("User")
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index("ix_audit_logs_entity", "entity_type", "entity_id"),
|
||||
Index("ix_audit_logs_timestamp", "timestamp"),
|
||||
|
||||
@@ -4,20 +4,35 @@ Campaigns group multiple tests into a kill chain sequence,
|
||||
enabling simulation of complete attack chains and APT emulations.
|
||||
"""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
|
||||
# Import from sqlalchemy
|
||||
from sqlalchemy import (
|
||||
Column, String, Text, Integer, Boolean, DateTime,
|
||||
ForeignKey, Index, func,
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
# Import JSONB, UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class Campaign
|
||||
class Campaign(Base):
|
||||
"""
|
||||
A campaign groups multiple tests into a sequenced attack chain.
|
||||
"""A campaign groups multiple tests into a sequenced attack chain.
|
||||
|
||||
Types:
|
||||
- custom: manually created campaign
|
||||
@@ -31,62 +46,97 @@ class Campaign(Base):
|
||||
- completed: all tests done
|
||||
- archived: historical record
|
||||
"""
|
||||
# Assign __tablename__ = "campaigns"
|
||||
__tablename__ = "campaigns"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign name = Column(String, nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
# Assign description = Column(Text, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
# Assign type = Column(String, nullable=False, default="custom") # custom, ap...
|
||||
type = Column(String, nullable=False, default="custom") # custom, apt_emulation, kill_chain, compliance
|
||||
# Assign threat_actor_id = Column(
|
||||
threat_actor_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("threat_actors.id", ondelete="SET NULL"),
|
||||
# Keyword argument: nullable
|
||||
nullable=True,
|
||||
)
|
||||
# Assign status = Column(String, nullable=False, default="draft") # draft, activ...
|
||||
status = Column(String, nullable=False, default="draft") # draft, active, completed, archived
|
||||
# Assign created_by = Column(
|
||||
created_by = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("users.id", ondelete="SET NULL"),
|
||||
# Keyword argument: nullable
|
||||
nullable=True,
|
||||
)
|
||||
start_date = Column(DateTime, nullable=True) # campaign won't activate before this date
|
||||
scheduled_at = Column(DateTime, nullable=True)
|
||||
# Assign completed_at = Column(DateTime, nullable=True)
|
||||
completed_at = Column(DateTime, nullable=True)
|
||||
# Assign target_platform = Column(String, nullable=True)
|
||||
target_platform = Column(String, nullable=True)
|
||||
# Assign tags = Column(JSONB, nullable=True, default=[])
|
||||
tags = Column(JSONB, nullable=True, default=[])
|
||||
# Assign created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
# Assign data_classification = Column(String(20), nullable=False, server_default="internal")
|
||||
data_classification = Column(String(20), nullable=False, server_default="internal")
|
||||
|
||||
# Recurring scheduling fields
|
||||
is_recurring = Column(Boolean, default=False)
|
||||
# Assign recurrence_pattern = Column(String, nullable=True) # weekly, monthly, quarterly
|
||||
recurrence_pattern = Column(String, nullable=True) # weekly, monthly, quarterly
|
||||
# Assign next_run_at = Column(DateTime, nullable=True)
|
||||
next_run_at = Column(DateTime, nullable=True)
|
||||
# Assign last_run_at = Column(DateTime, nullable=True)
|
||||
last_run_at = Column(DateTime, nullable=True)
|
||||
# Assign parent_campaign_id = Column(
|
||||
parent_campaign_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("campaigns.id", ondelete="SET NULL"),
|
||||
# Keyword argument: nullable
|
||||
nullable=True,
|
||||
)
|
||||
|
||||
# Relationships
|
||||
threat_actor = relationship("ThreatActor")
|
||||
# Assign creator = relationship("User", foreign_keys=[created_by])
|
||||
creator = relationship("User", foreign_keys=[created_by])
|
||||
# Assign campaign_tests = relationship(
|
||||
campaign_tests = relationship(
|
||||
# Literal argument value
|
||||
"CampaignTest",
|
||||
# Keyword argument: back_populates
|
||||
back_populates="campaign",
|
||||
# Keyword argument: cascade
|
||||
cascade="all, delete-orphan",
|
||||
# Keyword argument: order_by
|
||||
order_by="CampaignTest.order_index",
|
||||
)
|
||||
# Assign parent_campaign = relationship(
|
||||
parent_campaign = relationship(
|
||||
# Literal argument value
|
||||
"Campaign",
|
||||
# Keyword argument: remote_side
|
||||
remote_side="Campaign.id",
|
||||
# Keyword argument: foreign_keys
|
||||
foreign_keys=[parent_campaign_id],
|
||||
)
|
||||
# Assign child_campaigns = relationship(
|
||||
child_campaigns = relationship(
|
||||
# Literal argument value
|
||||
"Campaign",
|
||||
# Keyword argument: foreign_keys
|
||||
foreign_keys=[parent_campaign_id],
|
||||
# Keyword argument: back_populates
|
||||
back_populates="parent_campaign",
|
||||
)
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index('ix_campaigns_status', 'status'),
|
||||
Index('ix_campaigns_type', 'type'),
|
||||
@@ -98,56 +148,83 @@ class Campaign(Base):
|
||||
|
||||
# Kill chain phases in order (for sorting and validation)
|
||||
KILL_CHAIN_PHASES = [
|
||||
# Literal argument value
|
||||
"reconnaissance",
|
||||
# Literal argument value
|
||||
"resource_development",
|
||||
# Literal argument value
|
||||
"initial_access",
|
||||
# Literal argument value
|
||||
"execution",
|
||||
# Literal argument value
|
||||
"persistence",
|
||||
# Literal argument value
|
||||
"privilege_escalation",
|
||||
# Literal argument value
|
||||
"defense_evasion",
|
||||
# Literal argument value
|
||||
"credential_access",
|
||||
# Literal argument value
|
||||
"discovery",
|
||||
# Literal argument value
|
||||
"lateral_movement",
|
||||
# Literal argument value
|
||||
"collection",
|
||||
# Literal argument value
|
||||
"command_and_control",
|
||||
# Literal argument value
|
||||
"exfiltration",
|
||||
# Literal argument value
|
||||
"impact",
|
||||
]
|
||||
|
||||
|
||||
# Define class CampaignTest
|
||||
class CampaignTest(Base):
|
||||
"""
|
||||
A test within a campaign, with ordering and dependency information.
|
||||
"""A test within a campaign, with ordering and dependency information.
|
||||
|
||||
``depends_on`` creates a self-referential chain (A -> B -> C).
|
||||
Circular dependencies are validated at the service layer.
|
||||
"""
|
||||
# Assign __tablename__ = "campaign_tests"
|
||||
__tablename__ = "campaign_tests"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign campaign_id = Column(
|
||||
campaign_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("campaigns.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
# Assign test_id = Column(
|
||||
test_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("tests.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
# Assign order_index = Column(Integer, nullable=False, default=0)
|
||||
order_index = Column(Integer, nullable=False, default=0)
|
||||
# Assign depends_on = Column(
|
||||
depends_on = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("campaign_tests.id", ondelete="SET NULL"),
|
||||
# Keyword argument: nullable
|
||||
nullable=True,
|
||||
)
|
||||
# Assign phase = Column(String, nullable=True) # kill chain phase
|
||||
phase = Column(String, nullable=True) # kill chain phase
|
||||
|
||||
# Relationships
|
||||
campaign = relationship("Campaign", back_populates="campaign_tests")
|
||||
# Assign test = relationship("Test")
|
||||
test = relationship("Test")
|
||||
# Assign dependency = relationship("CampaignTest", remote_side="CampaignTest.id")
|
||||
dependency = relationship("CampaignTest", remote_side="CampaignTest.id")
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index('ix_campaign_tests_campaign', 'campaign_id'),
|
||||
Index('ix_campaign_tests_test', 'test_id'),
|
||||
|
||||
@@ -4,92 +4,145 @@ Maps compliance frameworks (NIST 800-53, DORA, NIS2, ISO 27001) to
|
||||
MITRE ATT&CK techniques, enabling compliance gap analysis.
|
||||
"""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
|
||||
# Import from sqlalchemy
|
||||
from sqlalchemy import (
|
||||
Column, String, Text, Boolean, DateTime,
|
||||
ForeignKey, Index, UniqueConstraint, func,
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
)
|
||||
|
||||
# Import UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class ComplianceFramework
|
||||
class ComplianceFramework(Base):
|
||||
"""A compliance framework (e.g. NIST 800-53, ISO 27001)."""
|
||||
# Assign __tablename__ = "compliance_frameworks"
|
||||
__tablename__ = "compliance_frameworks"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign name = Column(String, unique=True, nullable=False)
|
||||
name = Column(String, unique=True, nullable=False)
|
||||
# Assign version = Column(String, nullable=True)
|
||||
version = Column(String, nullable=True)
|
||||
# Assign description = Column(Text, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
# Assign url = Column(String, nullable=True)
|
||||
url = Column(String, nullable=True)
|
||||
# Assign is_active = Column(Boolean, default=True)
|
||||
is_active = Column(Boolean, default=True)
|
||||
# Assign created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Relationships
|
||||
controls = relationship(
|
||||
# Literal argument value
|
||||
"ComplianceControl",
|
||||
# Keyword argument: back_populates
|
||||
back_populates="framework",
|
||||
# Keyword argument: cascade
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
|
||||
# Define class ComplianceControl
|
||||
class ComplianceControl(Base):
|
||||
"""A control within a compliance framework (e.g. AC-2, PR.AC-1)."""
|
||||
# Assign __tablename__ = "compliance_controls"
|
||||
__tablename__ = "compliance_controls"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign framework_id = Column(
|
||||
framework_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("compliance_frameworks.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
# Assign control_id = Column(String, nullable=False) # e.g. "AC-2"
|
||||
control_id = Column(String, nullable=False) # e.g. "AC-2"
|
||||
# Assign title = Column(String, nullable=False)
|
||||
title = Column(String, nullable=False)
|
||||
# Assign description = Column(Text, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
# Assign category = Column(String, nullable=True)
|
||||
category = Column(String, nullable=True)
|
||||
|
||||
# Relationships
|
||||
framework = relationship("ComplianceFramework", back_populates="controls")
|
||||
# Assign technique_mappings = relationship(
|
||||
technique_mappings = relationship(
|
||||
# Literal argument value
|
||||
"ComplianceControlMapping",
|
||||
# Keyword argument: back_populates
|
||||
back_populates="compliance_control",
|
||||
# Keyword argument: cascade
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index('ix_compliance_controls_framework', 'framework_id'),
|
||||
)
|
||||
|
||||
|
||||
# Define class ComplianceControlMapping
|
||||
class ComplianceControlMapping(Base):
|
||||
"""Maps a compliance control to a MITRE ATT&CK technique."""
|
||||
# Assign __tablename__ = "compliance_control_mappings"
|
||||
__tablename__ = "compliance_control_mappings"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign compliance_control_id = Column(
|
||||
compliance_control_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("compliance_controls.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
# Assign technique_id = Column(
|
||||
technique_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("techniques.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# Relationships
|
||||
compliance_control = relationship(
|
||||
# Literal argument value
|
||||
"ComplianceControl", back_populates="technique_mappings"
|
||||
)
|
||||
# Assign technique = relationship("Technique")
|
||||
technique = relationship("Technique")
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index('ix_compliance_mappings_control', 'compliance_control_id'),
|
||||
Index('ix_compliance_mappings_technique', 'technique_id'),
|
||||
UniqueConstraint(
|
||||
# Literal argument value
|
||||
'compliance_control_id', 'technique_id',
|
||||
# Keyword argument: name
|
||||
name='uq_control_technique',
|
||||
),
|
||||
)
|
||||
|
||||
@@ -5,76 +5,125 @@ SnapshotTechniqueState stores per-technique state (normalized, one row
|
||||
per technique per snapshot) to avoid bloated JSONB fields.
|
||||
"""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
|
||||
# Import from sqlalchemy
|
||||
from sqlalchemy import (
|
||||
Column, String, Float, Integer, DateTime,
|
||||
ForeignKey, Index, func,
|
||||
Column,
|
||||
DateTime,
|
||||
Float,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
func,
|
||||
)
|
||||
|
||||
# Import JSONB, UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class CoverageSnapshot
|
||||
class CoverageSnapshot(Base):
|
||||
"""A point-in-time snapshot of the organisation's overall coverage."""
|
||||
|
||||
# Assign __tablename__ = "coverage_snapshots"
|
||||
__tablename__ = "coverage_snapshots"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign name = Column(String, nullable=True) # e.g. "Pre-remediación Q1"
|
||||
name = Column(String, nullable=True) # e.g. "Pre-remediación Q1"
|
||||
# Assign organization_score = Column(Float, nullable=False)
|
||||
organization_score = Column(Float, nullable=False)
|
||||
# Assign total_techniques = Column(Integer, nullable=False)
|
||||
total_techniques = Column(Integer, nullable=False)
|
||||
# Assign validated_count = Column(Integer, nullable=False)
|
||||
validated_count = Column(Integer, nullable=False)
|
||||
# Assign partial_count = Column(Integer, nullable=False)
|
||||
partial_count = Column(Integer, nullable=False)
|
||||
# Assign not_covered_count = Column(Integer, nullable=False)
|
||||
not_covered_count = Column(Integer, nullable=False)
|
||||
# Assign in_progress_count = Column(Integer, nullable=False)
|
||||
in_progress_count = Column(Integer, nullable=False)
|
||||
# Assign not_evaluated_count = Column(Integer, nullable=False)
|
||||
not_evaluated_count = Column(Integer, nullable=False)
|
||||
# Assign coverage_percentage = Column(Float, nullable=False, default=0.0)
|
||||
coverage_percentage = Column(Float, nullable=False, default=0.0)
|
||||
# Assign by_tactic = Column(JSONB, nullable=False, default=dict)
|
||||
by_tactic = Column(JSONB, nullable=False, default=dict)
|
||||
# Assign by_status = Column(JSONB, nullable=False, default=dict)
|
||||
by_status = Column(JSONB, nullable=False, default=dict)
|
||||
# Assign stale_count = Column(Integer, nullable=False, default=0)
|
||||
stale_count = Column(Integer, nullable=False, default=0)
|
||||
# Assign never_tested_count = Column(Integer, nullable=False, default=0)
|
||||
never_tested_count = Column(Integer, nullable=False, default=0)
|
||||
# Assign created_by = Column(
|
||||
created_by = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("users.id", ondelete="SET NULL"),
|
||||
# Keyword argument: nullable
|
||||
nullable=True,
|
||||
)
|
||||
# Assign created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Relationships
|
||||
creator = relationship("User", foreign_keys=[created_by])
|
||||
# Assign technique_states = relationship(
|
||||
technique_states = relationship(
|
||||
# Literal argument value
|
||||
"SnapshotTechniqueState",
|
||||
# Keyword argument: back_populates
|
||||
back_populates="snapshot",
|
||||
# Keyword argument: cascade
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
|
||||
# Define class SnapshotTechniqueState
|
||||
class SnapshotTechniqueState(Base):
|
||||
"""Per-technique state within a snapshot (normalised storage)."""
|
||||
|
||||
# Assign __tablename__ = "snapshot_technique_states"
|
||||
__tablename__ = "snapshot_technique_states"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign snapshot_id = Column(
|
||||
snapshot_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("coverage_snapshots.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
# Assign technique_id = Column(
|
||||
technique_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("techniques.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
# Assign mitre_id = Column(String, nullable=False) # denormalised for fast queries
|
||||
mitre_id = Column(String, nullable=False) # denormalised for fast queries
|
||||
# Assign status = Column(String, nullable=False)
|
||||
status = Column(String, nullable=False)
|
||||
# Assign score = Column(Float, nullable=True)
|
||||
score = Column(Float, nullable=True)
|
||||
|
||||
# Relationships
|
||||
snapshot = relationship("CoverageSnapshot", back_populates="technique_states")
|
||||
# Assign technique = relationship("Technique")
|
||||
technique = relationship("Technique")
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index("ix_snapshot_technique_states_snapshot", "snapshot_id"),
|
||||
Index("ix_snapshot_technique_states_technique", "technique_id"),
|
||||
|
||||
@@ -1,36 +1,56 @@
|
||||
"""DataSource model — registry of external data sources for import."""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, Index, func
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
# Import Boolean, Column, DateTime, Index, String, Text,... from sqlalchemy
|
||||
from sqlalchemy import Boolean, Column, DateTime, Index, String, Text, func
|
||||
|
||||
# Import JSONB, UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class DataSource
|
||||
class DataSource(Base):
|
||||
"""
|
||||
Unified registry of all external data sources (attack procedures,
|
||||
detection rules, threat intel, defensive techniques).
|
||||
"""Unified registry of all external data sources.
|
||||
|
||||
Each source can be independently enabled/disabled and tracks its own
|
||||
synchronisation state.
|
||||
Covers attack procedures, detection rules, threat intel, and defensive techniques.
|
||||
Each source can be independently enabled/disabled and tracks its own synchronisation state.
|
||||
"""
|
||||
# Assign __tablename__ = "data_sources"
|
||||
__tablename__ = "data_sources"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign name = Column(String, unique=True, nullable=False) # e.g. "atom...
|
||||
name = Column(String, unique=True, nullable=False) # e.g. "atomic_red_team"
|
||||
# Assign display_name = Column(String, nullable=False) # e.g. "Atomic Red ...
|
||||
display_name = Column(String, nullable=False) # e.g. "Atomic Red Team"
|
||||
type = Column(String, nullable=False) # attack_procedure / detection_rule / threat_intel / defensive_technique
|
||||
# Values: attack_procedure / detection_rule / threat_intel / defensive_technique
|
||||
type = Column(String, nullable=False)
|
||||
# Assign url = Column(String, nullable=True) # URL base...
|
||||
url = Column(String, nullable=True) # URL base of repo/API
|
||||
# Assign description = Column(Text, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
# Assign is_enabled = Column(Boolean, default=True)
|
||||
is_enabled = Column(Boolean, default=True)
|
||||
# Assign last_sync_at = Column(DateTime, nullable=True)
|
||||
last_sync_at = Column(DateTime, nullable=True)
|
||||
# Assign last_sync_status = Column(String, nullable=True) # success / error / in_...
|
||||
last_sync_status = Column(String, nullable=True) # success / error / in_progress
|
||||
# Assign last_sync_stats = Column(JSONB, nullable=True) # {"imported": X, "upd...
|
||||
last_sync_stats = Column(JSONB, nullable=True) # {"imported": X, "updated": Y, ...}
|
||||
# Assign sync_frequency = Column(String, nullable=True) # daily / weekly / mo...
|
||||
sync_frequency = Column(String, nullable=True) # daily / weekly / monthly / manual
|
||||
# Assign config = Column(JSONB, nullable=True) # source-spec...
|
||||
config = Column(JSONB, nullable=True) # source-specific configuration
|
||||
# Assign created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index('ix_data_sources_type', 'type'),
|
||||
Index('ix_data_sources_is_enabled', 'is_enabled'),
|
||||
|
||||
@@ -4,74 +4,108 @@ Stores MITRE D3FEND defensive techniques and their mappings to
|
||||
ATT&CK techniques, enabling recommended countermeasure lookups.
|
||||
"""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
|
||||
# Import from sqlalchemy
|
||||
from sqlalchemy import (
|
||||
Column, String, Text, DateTime,
|
||||
ForeignKey, Index, UniqueConstraint, func,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
)
|
||||
|
||||
# Import UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class DefensiveTechnique
|
||||
class DefensiveTechnique(Base):
|
||||
"""
|
||||
MITRE D3FEND defensive technique.
|
||||
"""MITRE D3FEND defensive technique.
|
||||
|
||||
Represents a countermeasure from the D3FEND framework that can be
|
||||
mapped to one or more ATT&CK techniques via DefensiveTechniqueMapping.
|
||||
"""
|
||||
# Assign __tablename__ = "defensive_techniques"
|
||||
__tablename__ = "defensive_techniques"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign d3fend_id = Column(String, unique=True, nullable=False) # e.g. "D3-AL"
|
||||
d3fend_id = Column(String, unique=True, nullable=False) # e.g. "D3-AL"
|
||||
# Assign name = Column(String, nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
# Assign description = Column(Text, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
# Assign tactic = Column(String, nullable=True) # Detect, ...
|
||||
tactic = Column(String, nullable=True) # Detect, Isolate, Deceive, Evict, etc.
|
||||
# Assign d3fend_url = Column(String, nullable=True)
|
||||
d3fend_url = Column(String, nullable=True)
|
||||
# Assign created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Relationships
|
||||
attack_mappings = relationship(
|
||||
# Literal argument value
|
||||
"DefensiveTechniqueMapping",
|
||||
# Keyword argument: back_populates
|
||||
back_populates="defensive_technique",
|
||||
# Keyword argument: cascade
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index('ix_defensive_techniques_tactic', 'tactic'),
|
||||
)
|
||||
|
||||
|
||||
# Define class DefensiveTechniqueMapping
|
||||
class DefensiveTechniqueMapping(Base):
|
||||
"""
|
||||
Association between a MITRE ATT&CK technique and a D3FEND
|
||||
defensive technique.
|
||||
"""
|
||||
"""Association between a MITRE ATT&CK technique and a D3FEND defensive technique."""
|
||||
# Assign __tablename__ = "defensive_technique_mappings"
|
||||
__tablename__ = "defensive_technique_mappings"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign attack_technique_id = Column(
|
||||
attack_technique_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("techniques.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
# Assign defensive_technique_id = Column(
|
||||
defensive_technique_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("defensive_techniques.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# Relationships
|
||||
attack_technique = relationship("Technique")
|
||||
# Assign defensive_technique = relationship("DefensiveTechnique", back_populates="attack_mappings")
|
||||
defensive_technique = relationship("DefensiveTechnique", back_populates="attack_mappings")
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index('ix_dtm_attack_technique', 'attack_technique_id'),
|
||||
Index('ix_dtm_defensive_technique', 'defensive_technique_id'),
|
||||
UniqueConstraint(
|
||||
# Literal argument value
|
||||
'attack_technique_id', 'defensive_technique_id',
|
||||
# Keyword argument: name
|
||||
name='uq_attack_defensive_technique',
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,38 +1,61 @@
|
||||
"""DetectionRule model — detection rules from multiple sources."""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, Index, func
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
# Import Boolean, Column, DateTime, Index, String, Text,... from sqlalchemy
|
||||
from sqlalchemy import Boolean, Column, DateTime, Index, String, Text, func
|
||||
|
||||
# Import JSONB, UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class DetectionRule
|
||||
class DetectionRule(Base):
|
||||
"""
|
||||
Detection rule from an external source (Sigma, Elastic, Splunk, custom).
|
||||
"""Detection rule from an external source (Sigma, Elastic, Splunk, custom).
|
||||
|
||||
Each rule is mapped to one MITRE ATT&CK technique via
|
||||
``mitre_technique_id`` and stores the complete rule content in
|
||||
``rule_content``.
|
||||
"""
|
||||
# Assign __tablename__ = "detection_rules"
|
||||
__tablename__ = "detection_rules"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign mitre_technique_id = Column(String, nullable=False) # e.g. "T1059.001"
|
||||
mitre_technique_id = Column(String, nullable=False) # e.g. "T1059.001"
|
||||
# Assign title = Column(String, nullable=False)
|
||||
title = Column(String, nullable=False)
|
||||
# Assign description = Column(Text, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
# Assign source = Column(String, nullable=False) # sigma / ela...
|
||||
source = Column(String, nullable=False) # sigma / elastic / splunk / custom
|
||||
# Assign source_id = Column(String, nullable=True) # ID in the sour...
|
||||
source_id = Column(String, nullable=True) # ID in the source repo (for dedup)
|
||||
# Assign source_url = Column(String, nullable=True)
|
||||
source_url = Column(String, nullable=True)
|
||||
# Assign rule_content = Column(Text, nullable=False) # YAML / KQL / SPL ...
|
||||
rule_content = Column(Text, nullable=False) # YAML / KQL / SPL content
|
||||
# Assign rule_format = Column(String, nullable=False) # sigma_yaml / kql...
|
||||
rule_format = Column(String, nullable=False) # sigma_yaml / kql / spl / custom
|
||||
# Assign severity = Column(String, nullable=True) # informational...
|
||||
severity = Column(String, nullable=True) # informational / low / medium / high / critical
|
||||
# Assign platforms = Column(JSONB, nullable=True, default=[])
|
||||
platforms = Column(JSONB, nullable=True, default=[])
|
||||
# Assign log_sources = Column(JSONB, nullable=True) # e.g. {"product":...
|
||||
log_sources = Column(JSONB, nullable=True) # e.g. {"product": "windows", "service": "sysmon"}
|
||||
# Assign false_positive_rate = Column(String, nullable=True) # low / medium / high
|
||||
false_positive_rate = Column(String, nullable=True) # low / medium / high
|
||||
# Assign is_active = Column(Boolean, default=True)
|
||||
is_active = Column(Boolean, default=True)
|
||||
# Assign created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index('ix_detection_rules_mitre_technique_id', 'mitre_technique_id'),
|
||||
Index('ix_detection_rules_source', 'source'),
|
||||
|
||||
@@ -5,6 +5,7 @@ re-exports every enum so that existing model and router code keeps
|
||||
working with ``from app.models.enums import ...``.
|
||||
"""
|
||||
|
||||
# Import # noqa: F401 from app.domain.enums
|
||||
from app.domain.enums import ( # noqa: F401
|
||||
DataClassification,
|
||||
TeamSide,
|
||||
|
||||
@@ -1,35 +1,59 @@
|
||||
"""SQLAlchemy model for the evidence table."""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, DateTime, ForeignKey, Enum, func
|
||||
|
||||
# Import Column, DateTime, Enum, ForeignKey, String, Tex... from sqlalchemy
|
||||
from sqlalchemy import Column, DateTime, Enum, ForeignKey, String, Text, func
|
||||
|
||||
# Import UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
# Import TeamSide from app.models.enums
|
||||
from app.models.enums import TeamSide
|
||||
|
||||
|
||||
# Define class Evidence
|
||||
class Evidence(Base):
|
||||
"""
|
||||
Evidence model for storing file metadata associated with tests.
|
||||
|
||||
"""Evidence model for storing file metadata associated with tests.
|
||||
|
||||
Files are stored in MinIO, and this model tracks the file location,
|
||||
integrity hash, and upload metadata.
|
||||
|
||||
|
||||
The ``team`` field distinguishes whether this evidence was uploaded by
|
||||
Red Team (attack evidence) or Blue Team (detection evidence).
|
||||
"""
|
||||
# Assign __tablename__ = "evidences"
|
||||
__tablename__ = "evidences"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign test_id = Column(UUID(as_uuid=True), ForeignKey("tests.id"), nullable=False)
|
||||
test_id = Column(UUID(as_uuid=True), ForeignKey("tests.id"), nullable=False)
|
||||
# Assign file_name = Column(String, nullable=False)
|
||||
file_name = Column(String, nullable=False)
|
||||
# Assign file_path = Column(String, nullable=False) # Path in MinIO
|
||||
file_path = Column(String, nullable=False) # Path in MinIO
|
||||
# Assign sha256_hash = Column(String, nullable=False)
|
||||
sha256_hash = Column(String, nullable=False)
|
||||
# Assign uploaded_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
uploaded_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
# Assign uploaded_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
uploaded_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
# Assign team = Column(Enum(TeamSide, name="teamside"), nullable=False, default=Tea...
|
||||
team = Column(Enum(TeamSide, name="teamside"), nullable=False, default=TeamSide.red)
|
||||
# Assign notes = Column(Text, nullable=True)
|
||||
notes = Column(Text, nullable=True)
|
||||
# Assign data_classification = Column(String(20), nullable=False, server_default="internal")
|
||||
data_classification = Column(String(20), nullable=False, server_default="internal")
|
||||
|
||||
# Relationships
|
||||
test = relationship("Test", back_populates="evidences")
|
||||
# Assign uploader = relationship("User", foreign_keys=[uploaded_by])
|
||||
uploader = relationship("User", foreign_keys=[uploaded_by])
|
||||
|
||||
@@ -1,26 +1,44 @@
|
||||
"""SQLAlchemy model for the intel_items table."""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Boolean, DateTime, ForeignKey, func
|
||||
|
||||
# Import Boolean, Column, DateTime, ForeignKey, String, ... from sqlalchemy
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String, func
|
||||
|
||||
# Import UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class IntelItem
|
||||
class IntelItem(Base):
|
||||
"""
|
||||
Intelligence item model for tracking threat intelligence related to techniques.
|
||||
|
||||
"""Intelligence item model for tracking threat intelligence related to techniques.
|
||||
|
||||
Stores URLs and metadata from automated intel scans that may indicate
|
||||
new attack variations or detection bypasses for specific techniques.
|
||||
"""
|
||||
# Assign __tablename__ = "intel_items"
|
||||
__tablename__ = "intel_items"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign technique_id = Column(UUID(as_uuid=True), ForeignKey("techniques.id"), nullable=True)
|
||||
technique_id = Column(UUID(as_uuid=True), ForeignKey("techniques.id"), nullable=True)
|
||||
# Assign url = Column(String, nullable=False)
|
||||
url = Column(String, nullable=False)
|
||||
# Assign title = Column(String, nullable=True)
|
||||
title = Column(String, nullable=True)
|
||||
# Assign source = Column(String, nullable=True)
|
||||
source = Column(String, nullable=True)
|
||||
# Assign detected_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
detected_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
# Assign reviewed = Column(Boolean, default=False)
|
||||
reviewed = Column(Boolean, default=False)
|
||||
|
||||
# Relationships
|
||||
|
||||
@@ -1,53 +1,99 @@
|
||||
"""Jira integration models — link Aegis entities to Jira issues."""
|
||||
|
||||
# Import enum
|
||||
import enum
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, DateTime, ForeignKey, Enum as SQLEnum, Index, func
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
# Import Column, DateTime, ForeignKey, Index, String, func from sqlalchemy
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Index, String, func
|
||||
|
||||
# Import Enum as SQLEnum from sqlalchemy
|
||||
from sqlalchemy import Enum as SQLEnum
|
||||
|
||||
# Import JSONB, UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class JiraLinkEntityType
|
||||
class JiraLinkEntityType(str, enum.Enum):
|
||||
"""Aegis entity types that can be linked to a Jira issue."""
|
||||
|
||||
# Assign test = "test"
|
||||
test = "test"
|
||||
# Assign technique = "technique"
|
||||
technique = "technique"
|
||||
# Assign campaign = "campaign"
|
||||
campaign = "campaign"
|
||||
# Assign evidence = "evidence"
|
||||
evidence = "evidence"
|
||||
|
||||
|
||||
# Define class JiraSyncDirection
|
||||
class JiraSyncDirection(str, enum.Enum):
|
||||
"""Direction of synchronisation between Aegis and Jira."""
|
||||
|
||||
# Assign aegis_to_jira = "aegis_to_jira"
|
||||
aegis_to_jira = "aegis_to_jira"
|
||||
# Assign jira_to_aegis = "jira_to_aegis"
|
||||
jira_to_aegis = "jira_to_aegis"
|
||||
# Assign bidirectional = "bidirectional"
|
||||
bidirectional = "bidirectional"
|
||||
|
||||
|
||||
# Define class JiraLink
|
||||
class JiraLink(Base):
|
||||
"""Associates an Aegis entity with a Jira issue for bidirectional sync."""
|
||||
|
||||
# Assign __tablename__ = "jira_links"
|
||||
__tablename__ = "jira_links"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign entity_type = Column(SQLEnum(JiraLinkEntityType), nullable=False)
|
||||
entity_type = Column(SQLEnum(JiraLinkEntityType), nullable=False)
|
||||
# Assign entity_id = Column(UUID(as_uuid=True), nullable=False)
|
||||
entity_id = Column(UUID(as_uuid=True), nullable=False)
|
||||
# Assign jira_issue_key = Column(String(50), nullable=False)
|
||||
jira_issue_key = Column(String(50), nullable=False)
|
||||
# Assign jira_issue_id = Column(String(50))
|
||||
jira_issue_id = Column(String(50))
|
||||
# Assign jira_project_key = Column(String(20))
|
||||
jira_project_key = Column(String(20))
|
||||
# Assign jira_status = Column(String(100))
|
||||
jira_status = Column(String(100))
|
||||
# Assign jira_priority = Column(String(50))
|
||||
jira_priority = Column(String(50))
|
||||
# Assign jira_assignee = Column(String(255))
|
||||
jira_assignee = Column(String(255))
|
||||
# Assign jira_story_points = Column(String(10))
|
||||
jira_story_points = Column(String(10))
|
||||
# Assign sync_direction = Column(
|
||||
sync_direction = Column(
|
||||
SQLEnum(JiraSyncDirection), default=JiraSyncDirection.bidirectional
|
||||
)
|
||||
# Assign last_synced_at = Column(DateTime)
|
||||
last_synced_at = Column(DateTime)
|
||||
# Assign sync_metadata = Column(JSONB, default={})
|
||||
sync_metadata = Column(JSONB, default={})
|
||||
# Assign created_by = Column(UUID(as_uuid=True), ForeignKey("users.id"))
|
||||
created_by = Column(UUID(as_uuid=True), ForeignKey("users.id"))
|
||||
# Assign created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
# Assign updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate...
|
||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
# Assign creator = relationship("User", foreign_keys=[created_by])
|
||||
creator = relationship("User", foreign_keys=[created_by])
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index("ix_jira_links_entity_id", "entity_id"),
|
||||
Index("ix_jira_links_issue_key", "jira_issue_key"),
|
||||
|
||||
@@ -1,35 +1,54 @@
|
||||
"""Notification model — in-app notifications for user actions."""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, ForeignKey, Index, func
|
||||
|
||||
# Import Boolean, Column, DateTime, ForeignKey, Index, S... from sqlalchemy
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Index, String, Text, func
|
||||
|
||||
# Import UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class Notification
|
||||
class Notification(Base):
|
||||
"""
|
||||
In-app notification for alerting users when they need to act.
|
||||
"""In-app notification for alerting users when they need to act.
|
||||
|
||||
Types include: test_assigned, validation_needed, test_rejected,
|
||||
test_validated, test_state_changed, etc.
|
||||
"""
|
||||
# Assign __tablename__ = "notifications"
|
||||
__tablename__ = "notifications"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||
# Assign type = Column(String, nullable=False)
|
||||
type = Column(String, nullable=False)
|
||||
# Assign title = Column(String, nullable=False)
|
||||
title = Column(String, nullable=False)
|
||||
# Assign message = Column(Text, nullable=True)
|
||||
message = Column(Text, nullable=True)
|
||||
# Assign entity_type = Column(String, nullable=True)
|
||||
entity_type = Column(String, nullable=True)
|
||||
# Assign entity_id = Column(UUID(as_uuid=True), nullable=True)
|
||||
entity_id = Column(UUID(as_uuid=True), nullable=True)
|
||||
# Assign read = Column(Boolean, default=False)
|
||||
read = Column(Boolean, default=False)
|
||||
# Assign created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Relationships
|
||||
user = relationship("User")
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index("ix_notifications_user_id", "user_id"),
|
||||
Index("ix_notifications_read", "read"),
|
||||
|
||||
@@ -1,37 +1,58 @@
|
||||
"""OSINT enrichment items — CVEs, blogs, PoCs, and advisories linked to techniques."""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, ForeignKey, func
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
# Import Boolean, Column, DateTime, ForeignKey, String, ... from sqlalchemy
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String, Text, func
|
||||
|
||||
# Import JSONB, UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class OsintItem
|
||||
class OsintItem(Base):
|
||||
"""Represents an OSINT data point (CVE, blog, PoC, advisory) associated
|
||||
with a MITRE ATT&CK technique.
|
||||
"""Represents an OSINT data point (CVE, blog, PoC, advisory) associated with a MITRE ATT&CK technique.
|
||||
|
||||
Used by the enrichment pipeline to surface relevant threat intelligence
|
||||
for each technique, flagging those that need review.
|
||||
"""
|
||||
|
||||
# Assign __tablename__ = "osint_items"
|
||||
__tablename__ = "osint_items"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign technique_id = Column(
|
||||
technique_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("techniques.id"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
# Keyword argument: index
|
||||
index=True,
|
||||
)
|
||||
# Assign source_type = Column(String(50), nullable=False) # "cve", "blog", "poc", "advisory"
|
||||
source_type = Column(String(50), nullable=False) # "cve", "blog", "poc", "advisory"
|
||||
# Assign source_url = Column(Text, nullable=False)
|
||||
source_url = Column(Text, nullable=False)
|
||||
# Assign title = Column(String(500), nullable=False)
|
||||
title = Column(String(500), nullable=False)
|
||||
# Assign description = Column(Text, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
# Assign severity = Column(String(20), nullable=True) # CRITICAL, HIGH, MEDIUM, LOW, U...
|
||||
severity = Column(String(20), nullable=True) # CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN
|
||||
# Assign discovered_at = Column(DateTime(timezone=True), server_default=func.now(), nullable...
|
||||
discovered_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
||||
# Assign reviewed = Column(Boolean, default=False)
|
||||
reviewed = Column(Boolean, default=False)
|
||||
# Assign metadata_ = Column("metadata", JSONB, default={})
|
||||
metadata_ = Column("metadata", JSONB, default={})
|
||||
|
||||
# ── Relationships ─────────────────────────────────────────────────
|
||||
|
||||
@@ -1,25 +1,43 @@
|
||||
"""ScoringConfig — single-row table for persisted scoring weights."""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import Column, Float, DateTime, ForeignKey, func
|
||||
# Import Column, DateTime, Float, ForeignKey, func from sqlalchemy
|
||||
from sqlalchemy import Column, DateTime, Float, ForeignKey, func
|
||||
|
||||
# Import UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class ScoringConfig
|
||||
class ScoringConfig(Base):
|
||||
"""Single-row table persisting the active scoring weight configuration."""
|
||||
|
||||
# Assign __tablename__ = "scoring_config"
|
||||
__tablename__ = "scoring_config"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign weight_tests = Column(Float, nullable=False, default=40.0)
|
||||
weight_tests = Column(Float, nullable=False, default=40.0)
|
||||
# Assign weight_detection_rules = Column(Float, nullable=False, default=25.0)
|
||||
weight_detection_rules = Column(Float, nullable=False, default=25.0)
|
||||
# Assign weight_d3fend = Column(Float, nullable=False, default=15.0)
|
||||
weight_d3fend = Column(Float, nullable=False, default=15.0)
|
||||
# Assign weight_recency = Column(Float, nullable=False, default=10.0)
|
||||
weight_recency = Column(Float, nullable=False, default=10.0)
|
||||
# Assign weight_severity = Column(Float, nullable=False, default=10.0)
|
||||
weight_severity = Column(Float, nullable=False, default=10.0)
|
||||
# Assign updated_by = Column(
|
||||
updated_by = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("users.id", ondelete="SET NULL"),
|
||||
# Keyword argument: nullable
|
||||
nullable=True,
|
||||
)
|
||||
# Assign updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate...
|
||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
@@ -1,38 +1,63 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
"""SQLAlchemy model for the techniques table."""
|
||||
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, Enum
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
# Import uuid
|
||||
import uuid
|
||||
|
||||
# Import Boolean, Column, DateTime, Enum, String, Text from sqlalchemy
|
||||
from sqlalchemy import Boolean, Column, DateTime, Enum, String, Text
|
||||
|
||||
# Import JSONB, UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
# Import TechniqueStatus from app.models.enums
|
||||
from app.models.enums import TechniqueStatus
|
||||
|
||||
|
||||
# Define class Technique
|
||||
class Technique(Base):
|
||||
"""
|
||||
MITRE ATT&CK Technique model.
|
||||
|
||||
"""MITRE ATT&CK Technique model.
|
||||
|
||||
Represents an attack technique from the MITRE ATT&CK framework,
|
||||
including its coverage status and associated tests.
|
||||
"""
|
||||
# Assign __tablename__ = "techniques"
|
||||
__tablename__ = "techniques"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign mitre_id = Column(String, unique=True, nullable=False) # e.g., "T1059.001"
|
||||
mitre_id = Column(String, unique=True, nullable=False) # e.g., "T1059.001"
|
||||
# Assign name = Column(String, nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
# Assign description = Column(Text, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
# Assign tactic = Column(String, nullable=True)
|
||||
tactic = Column(String, nullable=True)
|
||||
# Assign platforms = Column(JSONB, nullable=True, default=[])
|
||||
platforms = Column(JSONB, nullable=True, default=[])
|
||||
# Assign mitre_version = Column(String, nullable=True)
|
||||
mitre_version = Column(String, nullable=True)
|
||||
# Assign mitre_last_modified = Column(DateTime, nullable=True)
|
||||
mitre_last_modified = Column(DateTime, nullable=True)
|
||||
# Assign is_subtechnique = Column(Boolean, default=False)
|
||||
is_subtechnique = Column(Boolean, default=False)
|
||||
# Assign parent_mitre_id = Column(String, nullable=True)
|
||||
parent_mitre_id = Column(String, nullable=True)
|
||||
# Assign status_global = Column(
|
||||
status_global = Column(
|
||||
Enum(TechniqueStatus, name="techniquestatus"),
|
||||
# Keyword argument: default
|
||||
default=TechniqueStatus.not_evaluated
|
||||
)
|
||||
# Assign review_required = Column(Boolean, default=False)
|
||||
review_required = Column(Boolean, default=False)
|
||||
# Assign last_review_date = Column(DateTime, nullable=True)
|
||||
last_review_date = Column(DateTime, nullable=True)
|
||||
|
||||
# Relationships
|
||||
|
||||
@@ -1,80 +1,140 @@
|
||||
"""SQLAlchemy model for the tests table."""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, Boolean, Integer, DateTime, ForeignKey, Enum, Index, func
|
||||
|
||||
# Import from sqlalchemy
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
Enum,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
func,
|
||||
)
|
||||
|
||||
# Import UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
from app.models.enums import TestState, TestResult
|
||||
|
||||
# Import TestResult, TestState from app.models.enums
|
||||
from app.models.enums import TestResult, TestState
|
||||
|
||||
|
||||
# Define class Test
|
||||
class Test(Base):
|
||||
"""
|
||||
Test model representing a security test for a MITRE ATT&CK technique.
|
||||
"""Test model representing a security test for a MITRE ATT&CK technique.
|
||||
|
||||
Each test documents an attempt to validate coverage of a specific technique,
|
||||
including the procedure, tools used, and outcome. V2 introduces dual
|
||||
validation: Red Lead and Blue Lead must each approve independently.
|
||||
"""
|
||||
# Assign __tablename__ = "tests"
|
||||
__tablename__ = "tests"
|
||||
|
||||
# ── Core fields ─────────────────────────────────────────────────
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign technique_id = Column(UUID(as_uuid=True), ForeignKey("techniques.id"), nullable=Fa...
|
||||
technique_id = Column(UUID(as_uuid=True), ForeignKey("techniques.id"), nullable=False)
|
||||
# Assign name = Column(String, nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
# Assign description = Column(Text, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
# Assign platform = Column(String, nullable=True)
|
||||
platform = Column(String, nullable=True)
|
||||
# Assign procedure_text = Column(Text, nullable=True)
|
||||
procedure_text = Column(Text, nullable=True)
|
||||
# Assign tool_used = Column(String, nullable=True)
|
||||
tool_used = Column(String, nullable=True)
|
||||
# Assign execution_date = Column(DateTime, nullable=True)
|
||||
execution_date = Column(DateTime, nullable=True)
|
||||
# Assign created_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
# Assign result = Column(Enum(TestResult, name="testresult"), nullable=True)
|
||||
result = Column(Enum(TestResult, name="testresult"), nullable=True)
|
||||
# Assign state = Column(Enum(TestState, name="teststate"), default=TestState.draft)
|
||||
state = Column(Enum(TestState, name="teststate"), default=TestState.draft)
|
||||
# Assign created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# ── Red Team fields ─────────────────────────────────────────────
|
||||
red_summary = Column(Text, nullable=True)
|
||||
# Assign attack_success = Column(Boolean, nullable=True)
|
||||
attack_success = Column(Boolean, nullable=True)
|
||||
# Assign red_validated_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
red_validated_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
# Assign red_validated_at = Column(DateTime, nullable=True)
|
||||
red_validated_at = Column(DateTime, nullable=True)
|
||||
# Assign red_validation_status = Column(String, nullable=True) # pending / approved / rejected
|
||||
red_validation_status = Column(String, nullable=True) # pending / approved / rejected
|
||||
# Assign red_validation_notes = Column(Text, nullable=True)
|
||||
red_validation_notes = Column(Text, nullable=True)
|
||||
|
||||
# ── Blue Team fields ────────────────────────────────────────────
|
||||
blue_summary = Column(Text, nullable=True)
|
||||
# Assign detection_result = Column(Enum(TestResult, name="testresult"), nullable=True)
|
||||
detection_result = Column(Enum(TestResult, name="testresult"), nullable=True)
|
||||
# Assign blue_validated_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
blue_validated_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
# Assign blue_validated_at = Column(DateTime, nullable=True)
|
||||
blue_validated_at = Column(DateTime, nullable=True)
|
||||
# Assign blue_validation_status = Column(String, nullable=True) # pending / approved / rejected
|
||||
blue_validation_status = Column(String, nullable=True) # pending / approved / rejected
|
||||
# Assign blue_validation_notes = Column(Text, nullable=True)
|
||||
blue_validation_notes = Column(Text, nullable=True)
|
||||
|
||||
# ── Phase timing fields (for automatic Tempo worklogs) ──────────
|
||||
red_started_at = Column(DateTime, nullable=True)
|
||||
# Assign blue_started_at = Column(DateTime, nullable=True)
|
||||
blue_started_at = Column(DateTime, nullable=True)
|
||||
blue_work_started_at = Column(DateTime, nullable=True) # when blue tech picks up (Tempo start)
|
||||
paused_at = Column(DateTime, nullable=True)
|
||||
# Assign red_paused_seconds = Column(Integer, default=0)
|
||||
red_paused_seconds = Column(Integer, default=0)
|
||||
# Assign blue_paused_seconds = Column(Integer, default=0)
|
||||
blue_paused_seconds = Column(Integer, default=0)
|
||||
|
||||
# ── Remediation fields ───────────────────────────────────────────
|
||||
remediation_steps = Column(Text, nullable=True)
|
||||
# Assign remediation_status = Column(String, nullable=True) # pending / in_progress / completed ...
|
||||
remediation_status = Column(String, nullable=True) # pending / in_progress / completed / not_applicable
|
||||
# Assign remediation_assignee = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
remediation_assignee = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
|
||||
# ── Re-test fields ────────────────────────────────────────────
|
||||
retest_of = Column(UUID(as_uuid=True), ForeignKey("tests.id"), nullable=True)
|
||||
# Assign retest_count = Column(Integer, default=0)
|
||||
retest_count = Column(Integer, default=0)
|
||||
# Assign data_classification = Column(String(20), nullable=False, server_default="internal")
|
||||
data_classification = Column(String(20), nullable=False, server_default="internal")
|
||||
|
||||
# ── Relationships ───────────────────────────────────────────────
|
||||
technique = relationship("Technique", back_populates="tests")
|
||||
# Assign evidences = relationship("Evidence", back_populates="test")
|
||||
evidences = relationship("Evidence", back_populates="test")
|
||||
# Assign creator = relationship("User", foreign_keys=[created_by])
|
||||
creator = relationship("User", foreign_keys=[created_by])
|
||||
# Assign red_validator = relationship("User", foreign_keys=[red_validated_by])
|
||||
red_validator = relationship("User", foreign_keys=[red_validated_by])
|
||||
# Assign blue_validator = relationship("User", foreign_keys=[blue_validated_by])
|
||||
blue_validator = relationship("User", foreign_keys=[blue_validated_by])
|
||||
# Assign remediation_user = relationship("User", foreign_keys=[remediation_assignee])
|
||||
remediation_user = relationship("User", foreign_keys=[remediation_assignee])
|
||||
# Assign original_test = relationship("Test", remote_side="Test.id", foreign_keys=[retest_of])
|
||||
original_test = relationship("Test", remote_side="Test.id", foreign_keys=[retest_of])
|
||||
# Assign retests = relationship("Test", foreign_keys=[retest_of], back_populates="orig...
|
||||
retests = relationship("Test", foreign_keys=[retest_of], back_populates="original_test")
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index("ix_tests_technique_id", "technique_id"),
|
||||
Index("ix_tests_state", "state"),
|
||||
|
||||
@@ -4,51 +4,79 @@ When the Blue Team evaluates a test, they mark each associated detection
|
||||
rule as triggered / not triggered / not applicable, along with notes.
|
||||
"""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, ForeignKey, Index, UniqueConstraint
|
||||
# Import from sqlalchemy
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
)
|
||||
|
||||
# Import UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class TestDetectionResult
|
||||
class TestDetectionResult(Base):
|
||||
"""
|
||||
Per-test, per-rule evaluation result.
|
||||
"""Per-test, per-rule evaluation result.
|
||||
|
||||
- ``triggered`` = True: rule detected the attack
|
||||
- ``triggered`` = False: rule did NOT detect the attack
|
||||
- ``triggered`` = None: not yet evaluated
|
||||
"""
|
||||
# Assign __tablename__ = "test_detection_results"
|
||||
__tablename__ = "test_detection_results"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign test_id = Column(
|
||||
test_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("tests.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
# Assign detection_rule_id = Column(
|
||||
detection_rule_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("detection_rules.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
# Assign triggered = Column(Boolean, nullable=True) # None = not evaluated
|
||||
triggered = Column(Boolean, nullable=True) # None = not evaluated
|
||||
# Assign notes = Column(Text, nullable=True)
|
||||
notes = Column(Text, nullable=True)
|
||||
# Assign evaluated_by = Column(
|
||||
evaluated_by = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("users.id", ondelete="SET NULL"),
|
||||
# Keyword argument: nullable
|
||||
nullable=True,
|
||||
)
|
||||
# Assign evaluated_at = Column(DateTime, nullable=True)
|
||||
evaluated_at = Column(DateTime, nullable=True)
|
||||
|
||||
# Relationships
|
||||
test = relationship("Test")
|
||||
# Assign detection_rule = relationship("DetectionRule")
|
||||
detection_rule = relationship("DetectionRule")
|
||||
# Assign evaluator = relationship("User", foreign_keys=[evaluated_by])
|
||||
evaluator = relationship("User", foreign_keys=[evaluated_by])
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index('ix_tdr_test', 'test_id'),
|
||||
Index('ix_tdr_rule', 'detection_rule_id'),
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
"""TestTemplate model — predefined test catalog entries."""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, Index, func
|
||||
|
||||
# Import Boolean, Column, DateTime, Index, String, Text,... from sqlalchemy
|
||||
from sqlalchemy import Boolean, Column, DateTime, Index, String, Text, func
|
||||
|
||||
# Import UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class TestTemplate
|
||||
class TestTemplate(Base):
|
||||
"""
|
||||
Predefined test template mapped to a MITRE ATT&CK technique.
|
||||
"""Predefined test template mapped to a MITRE ATT&CK technique.
|
||||
|
||||
Templates come from several sources:
|
||||
- **atomic_red_team**: Atomic Red Team by Red Canary
|
||||
@@ -18,24 +24,41 @@ class TestTemplate(Base):
|
||||
|
||||
Users can instantiate a real Test from a template.
|
||||
"""
|
||||
# Assign __tablename__ = "test_templates"
|
||||
__tablename__ = "test_templates"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign mitre_technique_id = Column(String, nullable=False) # e.g. "T1059.001"
|
||||
mitre_technique_id = Column(String, nullable=False) # e.g. "T1059.001"
|
||||
# Assign name = Column(String, nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
# Assign description = Column(Text, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
# Assign source = Column(String, nullable=False) # atomic_red_te...
|
||||
source = Column(String, nullable=False) # atomic_red_team / mitre / custom
|
||||
# Assign source_url = Column(String, nullable=True)
|
||||
source_url = Column(String, nullable=True)
|
||||
# Assign attack_procedure = Column(Text, nullable=True) # Suggested attack procedure
|
||||
attack_procedure = Column(Text, nullable=True) # Suggested attack procedure
|
||||
# Assign expected_detection = Column(Text, nullable=True) # What blue team should detect
|
||||
expected_detection = Column(Text, nullable=True) # What blue team should detect
|
||||
# Assign platform = Column(String, nullable=True) # windows / linux...
|
||||
platform = Column(String, nullable=True) # windows / linux / macos
|
||||
# Assign tool_suggested = Column(String, nullable=True)
|
||||
tool_suggested = Column(String, nullable=True)
|
||||
# Assign severity = Column(String, nullable=True) # low / medium / ...
|
||||
severity = Column(String, nullable=True) # low / medium / high / critical
|
||||
# Assign atomic_test_id = Column(String, nullable=True) # ID in Atomic Red Team...
|
||||
atomic_test_id = Column(String, nullable=True) # ID in Atomic Red Team repo
|
||||
# Assign suggested_remediation = Column(Text, nullable=True)
|
||||
suggested_remediation = Column(Text, nullable=True)
|
||||
# Assign is_active = Column(Boolean, default=True)
|
||||
is_active = Column(Boolean, default=True)
|
||||
# Assign created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index('ix_test_templates_mitre_technique_id', 'mitre_technique_id'),
|
||||
Index('ix_test_templates_source', 'source'),
|
||||
|
||||
@@ -4,47 +4,64 @@ Enables the Blue Team to see which detection rules should fire
|
||||
for a given test template / attack procedure.
|
||||
"""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Column, Boolean, ForeignKey, Index, UniqueConstraint
|
||||
# Import Boolean, Column, ForeignKey, Index, UniqueConst... from sqlalchemy
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Index, UniqueConstraint
|
||||
|
||||
# Import UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class TestTemplateDetectionRule
|
||||
class TestTemplateDetectionRule(Base):
|
||||
"""
|
||||
Association between a test template and a detection rule.
|
||||
"""Association between a test template and a detection rule.
|
||||
|
||||
Auto-generated by matching mitre_technique_id, or manually curated.
|
||||
``is_primary`` marks rules with severity >= high as primary detections.
|
||||
"""
|
||||
# Assign __tablename__ = "test_template_detection_rules"
|
||||
__tablename__ = "test_template_detection_rules"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign test_template_id = Column(
|
||||
test_template_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("test_templates.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=True,
|
||||
)
|
||||
# Assign detection_rule_id = Column(
|
||||
detection_rule_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("detection_rules.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
# Assign is_primary = Column(Boolean, default=False)
|
||||
is_primary = Column(Boolean, default=False)
|
||||
|
||||
# Relationships
|
||||
test_template = relationship("TestTemplate")
|
||||
# Assign detection_rule = relationship("DetectionRule")
|
||||
detection_rule = relationship("DetectionRule")
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index('ix_ttdr_template', 'test_template_id'),
|
||||
Index('ix_ttdr_rule', 'detection_rule_id'),
|
||||
UniqueConstraint(
|
||||
# Literal argument value
|
||||
'test_template_id', 'detection_rule_id',
|
||||
# Keyword argument: name
|
||||
name='uq_template_detection_rule',
|
||||
),
|
||||
)
|
||||
|
||||
@@ -4,87 +4,135 @@ Stores profiles of APT groups and their associated MITRE ATT&CK
|
||||
techniques, imported from MITRE CTI (STIX 2.0).
|
||||
"""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
|
||||
# Import from sqlalchemy
|
||||
from sqlalchemy import (
|
||||
Column, String, Text, Boolean, DateTime,
|
||||
ForeignKey, Index, UniqueConstraint, func,
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
# Import JSONB, UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class ThreatActor
|
||||
class ThreatActor(Base):
|
||||
"""
|
||||
Threat actor / APT group profile.
|
||||
"""Threat actor / APT group profile.
|
||||
|
||||
Imported from MITRE CTI ``intrusion-set`` STIX objects.
|
||||
"""
|
||||
# Assign __tablename__ = "threat_actors"
|
||||
__tablename__ = "threat_actors"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign mitre_id = Column(String, unique=True, nullable=True) # e.g. "G00...
|
||||
mitre_id = Column(String, unique=True, nullable=True) # e.g. "G0016" (APT29)
|
||||
# Assign name = Column(String, nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
# Assign aliases = Column(JSONB, nullable=True, default=[]) # ["Cozy ...
|
||||
aliases = Column(JSONB, nullable=True, default=[]) # ["Cozy Bear", "The Dukes", ...]
|
||||
# Assign description = Column(Text, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
# Assign country = Column(String, nullable=True)
|
||||
country = Column(String, nullable=True)
|
||||
# Assign target_sectors = Column(JSONB, nullable=True, default=[]) # ["government",...
|
||||
target_sectors = Column(JSONB, nullable=True, default=[]) # ["government", "defense", ...]
|
||||
# Assign target_regions = Column(JSONB, nullable=True, default=[]) # ["north-americ...
|
||||
target_regions = Column(JSONB, nullable=True, default=[]) # ["north-america", "europe", ...]
|
||||
# Assign motivation = Column(String, nullable=True) # espionage ...
|
||||
motivation = Column(String, nullable=True) # espionage / financial / destruction / ...
|
||||
# Assign sophistication = Column(String, nullable=True) # low / medium /...
|
||||
sophistication = Column(String, nullable=True) # low / medium / high / advanced
|
||||
# Assign first_seen = Column(String, nullable=True)
|
||||
first_seen = Column(String, nullable=True)
|
||||
# Assign last_seen = Column(String, nullable=True)
|
||||
last_seen = Column(String, nullable=True)
|
||||
# Assign references = Column(JSONB, nullable=True, default=[]) # [{"url": "...
|
||||
references = Column(JSONB, nullable=True, default=[]) # [{"url": "...", "description": "..."}]
|
||||
# Assign mitre_url = Column(String, nullable=True)
|
||||
mitre_url = Column(String, nullable=True)
|
||||
# Assign is_active = Column(Boolean, default=True)
|
||||
is_active = Column(Boolean, default=True)
|
||||
# Assign created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Relationships
|
||||
techniques = relationship(
|
||||
# Literal argument value
|
||||
"ThreatActorTechnique",
|
||||
# Keyword argument: back_populates
|
||||
back_populates="threat_actor",
|
||||
# Keyword argument: cascade
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index('ix_threat_actors_country', 'country'),
|
||||
Index('ix_threat_actors_motivation', 'motivation'),
|
||||
)
|
||||
|
||||
|
||||
# Define class ThreatActorTechnique
|
||||
class ThreatActorTechnique(Base):
|
||||
"""
|
||||
Association between a threat actor and a MITRE ATT&CK technique.
|
||||
"""Association between a threat actor and a MITRE ATT&CK technique.
|
||||
|
||||
Stores additional context about how the actor uses the technique
|
||||
(from the STIX ``relationship`` ``uses`` objects).
|
||||
"""
|
||||
# Assign __tablename__ = "threat_actor_techniques"
|
||||
__tablename__ = "threat_actor_techniques"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign threat_actor_id = Column(
|
||||
threat_actor_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("threat_actors.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
# Assign technique_id = Column(
|
||||
technique_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("techniques.id", ondelete="CASCADE"),
|
||||
# Keyword argument: nullable
|
||||
nullable=False,
|
||||
)
|
||||
# Assign usage_description = Column(Text, nullable=True)
|
||||
usage_description = Column(Text, nullable=True)
|
||||
# Assign first_seen_using = Column(String, nullable=True)
|
||||
first_seen_using = Column(String, nullable=True)
|
||||
|
||||
# Relationships
|
||||
threat_actor = relationship("ThreatActor", back_populates="techniques")
|
||||
# Assign technique = relationship("Technique")
|
||||
technique = relationship("Technique")
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index('ix_threat_actor_techniques_actor', 'threat_actor_id'),
|
||||
Index('ix_threat_actor_techniques_technique', 'technique_id'),
|
||||
UniqueConstraint(
|
||||
# Literal argument value
|
||||
'threat_actor_id', 'technique_id',
|
||||
# Keyword argument: name
|
||||
name='uq_actor_technique',
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
"""SQLAlchemy model for the users table."""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Boolean, DateTime, func
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class User
|
||||
class User(Base):
|
||||
"""
|
||||
User model for authentication and authorization.
|
||||
|
||||
"""User model for authentication and authorization.
|
||||
|
||||
Possible roles:
|
||||
- admin: Full system access
|
||||
- red_tech: Red team technician - can create and edit tests
|
||||
@@ -17,16 +21,26 @@ class User(Base):
|
||||
- blue_lead: Blue team lead - can validate tests
|
||||
- viewer: Read-only access (default)
|
||||
"""
|
||||
# Assign __tablename__ = "users"
|
||||
__tablename__ = "users"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign username = Column(String, unique=True, nullable=False)
|
||||
username = Column(String, unique=True, nullable=False)
|
||||
# Assign email = Column(String, nullable=True)
|
||||
email = Column(String, nullable=True)
|
||||
# Assign hashed_password = Column(String, nullable=False)
|
||||
hashed_password = Column(String, nullable=False)
|
||||
# Assign role = Column(String, nullable=False, default="viewer")
|
||||
role = Column(String, nullable=False, default="viewer")
|
||||
# Assign is_active = Column(Boolean, default=True)
|
||||
is_active = Column(Boolean, default=True)
|
||||
# Assign must_change_password = Column(Boolean, default=True)
|
||||
must_change_password = Column(Boolean, default=True)
|
||||
# Assign created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
# Assign last_login = Column(DateTime, nullable=True)
|
||||
last_login = Column(DateTime, nullable=True)
|
||||
notification_preferences = Column(JSONB, nullable=True, server_default='{"email_on_test_validated": true, "email_on_campaign_completed": true, "email_on_new_mitre_techniques": false, "in_app_all": true}')
|
||||
jira_account_id = Column(String(100), nullable=True)
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
"""Worklog model — immutable internal time-tracking records."""
|
||||
|
||||
# Import uuid
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Integer, DateTime, ForeignKey, Text, Index, func
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
# Import Column, DateTime, ForeignKey, Index, Integer, S... from sqlalchemy
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Index, Integer, String, Text, func
|
||||
|
||||
# Import JSONB, UUID from sqlalchemy.dialects.postgresql
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
|
||||
# Import relationship from sqlalchemy.orm
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Import Base from app.database
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# Define class Worklog
|
||||
class Worklog(Base):
|
||||
"""Internal worklog entry with integrity hash for audit compliance.
|
||||
|
||||
@@ -16,25 +25,42 @@ class Worklog(Base):
|
||||
the immutable fields so tampering can be detected.
|
||||
"""
|
||||
|
||||
# Assign __tablename__ = "worklogs"
|
||||
__tablename__ = "worklogs"
|
||||
|
||||
# Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
# Assign entity_type = Column(String(50), nullable=False)
|
||||
entity_type = Column(String(50), nullable=False)
|
||||
# Assign entity_id = Column(UUID(as_uuid=True), nullable=False)
|
||||
entity_id = Column(UUID(as_uuid=True), nullable=False)
|
||||
# Assign user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||
# Assign activity_type = Column(String(100), nullable=False)
|
||||
activity_type = Column(String(100), nullable=False)
|
||||
# Assign started_at = Column(DateTime, nullable=False)
|
||||
started_at = Column(DateTime, nullable=False)
|
||||
# Assign ended_at = Column(DateTime)
|
||||
ended_at = Column(DateTime)
|
||||
# Assign duration_seconds = Column(Integer, nullable=False)
|
||||
duration_seconds = Column(Integer, nullable=False)
|
||||
# Assign description = Column(Text)
|
||||
description = Column(Text)
|
||||
# Assign tempo_synced = Column(DateTime)
|
||||
tempo_synced = Column(DateTime)
|
||||
# Assign tempo_worklog_id = Column(String(100))
|
||||
tempo_worklog_id = Column(String(100))
|
||||
# Assign integrity_hash = Column(String(64))
|
||||
integrity_hash = Column(String(64))
|
||||
# Assign created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
# Assign extra_metadata = Column("metadata", JSONB, default={})
|
||||
extra_metadata = Column("metadata", JSONB, default={})
|
||||
|
||||
# Assign user = relationship("User", foreign_keys=[user_id])
|
||||
user = relationship("User", foreign_keys=[user_id])
|
||||
|
||||
# Assign __table_args__ = (
|
||||
__table_args__ = (
|
||||
Index("ix_worklogs_entity_id", "entity_id"),
|
||||
Index("ix_worklogs_user_id", "user_id"),
|
||||
|
||||
Reference in New Issue
Block a user