Files
PROJECT-CONTORL/backend/app/models/audit_log.py
beabigegg 10db2c9d1f feat: implement audit trail alignment (soft delete & permission audit)
- Task Soft Delete:
  - Add is_deleted, deleted_at, deleted_by fields to Task model
  - Convert DELETE to soft delete with cascade to subtasks
  - Add include_deleted query param (admin only)
  - Add POST /api/tasks/{id}/restore endpoint
  - Exclude deleted tasks from subtask_count

- Permission Change Audit:
  - Add user.role_change event (high sensitivity)
  - Add user.admin_change event (critical, triggers alert)
  - Add PATCH /api/users/{id}/admin endpoint
  - Add role.permission_change event type

- Append-Only Enforcement:
  - Add DB triggers for audit_logs immutability (manual for production)
  - Migration 008 with graceful trigger failure handling

- Tests: 11 new soft delete tests (153 total passing)
- OpenSpec: fix-audit-trail archived, fix-realtime-notifications & fix-weekly-report proposals added

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 06:58:30 +08:00

82 lines
2.8 KiB
Python

import uuid
from sqlalchemy import Column, String, Text, DateTime, ForeignKey, Enum, Index, JSON
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from app.core.database import Base
import enum
class AuditAction(str, enum.Enum):
CREATE = "create"
UPDATE = "update"
DELETE = "delete"
RESTORE = "restore"
LOGIN = "login"
LOGOUT = "logout"
class SensitivityLevel(str, enum.Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
# Event type to sensitivity level mapping
EVENT_SENSITIVITY = {
"task.create": SensitivityLevel.LOW,
"task.update": SensitivityLevel.LOW,
"task.delete": SensitivityLevel.MEDIUM,
"task.restore": SensitivityLevel.MEDIUM,
"task.assign": SensitivityLevel.LOW,
"task.blocker": SensitivityLevel.MEDIUM,
"project.create": SensitivityLevel.MEDIUM,
"project.update": SensitivityLevel.MEDIUM,
"project.delete": SensitivityLevel.HIGH,
"user.login": SensitivityLevel.LOW,
"user.logout": SensitivityLevel.LOW,
"user.role_change": SensitivityLevel.HIGH,
"user.admin_change": SensitivityLevel.CRITICAL,
"user.permission_change": SensitivityLevel.CRITICAL,
"role.permission_change": SensitivityLevel.CRITICAL,
"attachment.upload": SensitivityLevel.LOW,
"attachment.download": SensitivityLevel.LOW,
"attachment.delete": SensitivityLevel.MEDIUM,
}
# Events that should trigger alerts
ALERT_EVENTS = {"project.delete", "user.permission_change", "user.admin_change", "role.permission_change"}
class AuditLog(Base):
__tablename__ = "pjctrl_audit_logs"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
event_type = Column(String(50), nullable=False)
resource_type = Column(String(50), nullable=False)
resource_id = Column(String(36), nullable=True)
user_id = Column(String(36), ForeignKey("pjctrl_users.id", ondelete="SET NULL"), nullable=True)
action = Column(
Enum("create", "update", "delete", "restore", "login", "logout", name="audit_action_enum"),
nullable=False
)
changes = Column(JSON, nullable=True)
request_metadata = Column(JSON, nullable=True)
sensitivity_level = Column(
Enum("low", "medium", "high", "critical", name="sensitivity_level_enum"),
default="low",
nullable=False
)
checksum = Column(String(64), nullable=False)
created_at = Column(DateTime, server_default=func.now(), nullable=False)
# Relationships
user = relationship("User", foreign_keys=[user_id])
alerts = relationship("AuditAlert", back_populates="audit_log", cascade="all, delete-orphan")
__table_args__ = (
Index("idx_audit_user", "user_id", "created_at"),
Index("idx_audit_resource", "resource_type", "resource_id", "created_at"),
Index("idx_audit_time", "created_at"),
)