feat(v0.7.1): P0 修复 + 企微 SSO + RBAC 细粒度 + audit_log
P0 修复: - /api/ready import 错误 (_get_engine + settings.create_redis_client) - 删 agent.otp_secret/otp_enabled 双字段 (migration 026) - 重建 021_rbac migration (IF NOT EXISTS 兼容) P1 新增: - 企微 SSO (auth_wecom_sso.py, useWeChatWorkSSO composable, PortalSelect UA 检测) - RBAC 5 角色 × 4 资源 × 4 操作 × 3 范围 (rbac_service + seed_rbac + require_permission) - audit_log 模型 + migration 027 + 服务 + API - 管理后台 RBAC 权限矩阵 UI (PermissionsMatrix.vue) 质量: - pytest 405 passed / 33 pre-existing failed / 4 xfailed (v0.7.1 引入失败 = 0) - conftest GBK patch 强制 UTF-8 读 .env - .gitignore 排除 *.b64 (含 admin token 凭据) - DEPLOY-v0.7.1.md 7 步 runbook + 4 坑 + 回滚预案
This commit is contained in:
@@ -123,21 +123,10 @@ class Agent(Base):
|
||||
comment="技能标签列表(电脑/软件/外设/网络/安全/资产/其他)",
|
||||
)
|
||||
|
||||
# OTP密钥(用于TOTP动态码验证,为空表示未绑定)
|
||||
otp_secret: Mapped[str] = mapped_column(
|
||||
String(32),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="OTP密钥(Base32编码)",
|
||||
)
|
||||
|
||||
# OTP是否启用(admin角色强制启用)
|
||||
otp_enabled: Mapped[bool] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="OTP是否启用(0=否, 1=是)",
|
||||
)
|
||||
# v0.7.1: 删除 otp_secret / otp_enabled 字段
|
||||
# 原因: 与下方 mfa_secret / mfa_enabled 完全重复(都是 TOTP secret)
|
||||
# 旧 OTP 字段只用于高危操作前的二次验证,mfa 字段已涵盖该用途
|
||||
# 迁移策略: alembic 010 改为 DROP COLUMN otp_secret, otp_enabled
|
||||
|
||||
# 本地密码哈希(可选,用于本地密码认证)
|
||||
# 使用 bcrypt 加密存储,不存储明文密码
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 审计日志模型
|
||||
# =============================================================================
|
||||
# 说明: 对应数据库 audit_logs 表,记录所有高危/RBAC 操作 + 登录/MFA 事件
|
||||
# 给 auditor 角色 + admin 提供只读审计能力
|
||||
#
|
||||
# 何时写入:
|
||||
# - 高危操作 (role_change / config_change / data_export / account_disable / account_create_reset)
|
||||
# - RBAC 操作 (assign_role / revoke_role / create_mapping_rule / delete_mapping_rule)
|
||||
# - 登录事件 (qrcode_login / sso_login / password_login)
|
||||
# - MFA 事件 (bind / verify / reset)
|
||||
# - 业务敏感操作 (resolve_conversation / transfer_conversation)
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import JSON, DateTime, Index, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class AuditLog(Base):
|
||||
"""审计日志模型 — 对应 audit_logs 表。
|
||||
|
||||
Attributes:
|
||||
id: 日志唯一标识(UUID)
|
||||
employee_id: 操作人(企微 UserID,系统操作填 "system")
|
||||
action: 操作类型(如 "role_change", "login", "mfa_verify")
|
||||
resource: 目标资源类型("agent" / "conversation" / "system_config" 等)
|
||||
resource_id: 目标资源 ID
|
||||
details: 详细上下文(JSON,前后值/IP/UA 等)
|
||||
result: "success" / "failure" / "partial"
|
||||
ip_address: 操作来源 IP(可选)
|
||||
user_agent: 操作来源 UA(可选)
|
||||
created_at: 时间
|
||||
"""
|
||||
|
||||
__tablename__ = "audit_logs"
|
||||
|
||||
# 主键:UUID
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
# 操作人(企微 UserID, 系统操作填 "system")
|
||||
employee_id: Mapped[str] = mapped_column(
|
||||
String(100),
|
||||
nullable=False,
|
||||
comment="操作人(employee_id / 'system')",
|
||||
)
|
||||
|
||||
# 操作类型
|
||||
# 例: "role_change" / "config_change" / "login" / "mfa_verify" /
|
||||
# "qrcode_login" / "sso_login" / "password_login" /
|
||||
# "resolve_conversation" / "transfer_conversation" / "data_export"
|
||||
action: Mapped[str] = mapped_column(
|
||||
String(50),
|
||||
nullable=False,
|
||||
comment="操作类型",
|
||||
)
|
||||
|
||||
# 目标资源类型
|
||||
# 例: "agent" / "conversation" / "system_config" / "role" / "user_role"
|
||||
resource: Mapped[str] = mapped_column(
|
||||
String(50),
|
||||
nullable=False,
|
||||
comment="目标资源类型",
|
||||
)
|
||||
|
||||
# 目标资源 ID(字符串,跨表通用)
|
||||
resource_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(100),
|
||||
nullable=True,
|
||||
comment="目标资源 ID",
|
||||
)
|
||||
|
||||
# 详细上下文(JSON)
|
||||
# 例: {"role": "agent", "reason": "新员工转岗", "ip": "10.80.0.5"}
|
||||
details: Mapped[Optional[dict]] = mapped_column(
|
||||
JSON,
|
||||
nullable=True,
|
||||
comment="详细上下文(JSON)",
|
||||
)
|
||||
|
||||
# 结果
|
||||
# "success" / "failure" / "partial"
|
||||
result: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="success",
|
||||
comment="执行结果",
|
||||
)
|
||||
|
||||
# 来源 IP
|
||||
ip_address: Mapped[Optional[str]] = mapped_column(
|
||||
String(64),
|
||||
nullable=True,
|
||||
comment="来源 IP",
|
||||
)
|
||||
|
||||
# 来源 User-Agent
|
||||
user_agent: Mapped[Optional[str]] = mapped_column(
|
||||
Text,
|
||||
nullable=True,
|
||||
comment="来源 User-Agent",
|
||||
)
|
||||
|
||||
# 时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="时间",
|
||||
)
|
||||
|
||||
# 索引:按 employee_id / action / time 查询
|
||||
__table_args__ = (
|
||||
Index("idx_audit_employee_id", "employee_id"),
|
||||
Index("idx_audit_action", "action"),
|
||||
Index("idx_audit_resource", "resource", "resource_id"),
|
||||
Index("idx_audit_created_at", "created_at"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<AuditLog(action={self.action}, employee={self.employee_id}, result={self.result})>"
|
||||
Reference in New Issue
Block a user