add file
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -285,7 +285,7 @@ def get_network_device(device_id: int, db: Session = Depends(get_db)):
|
|||||||
def create_network_device(
|
def create_network_device(
|
||||||
name: str,
|
name: str,
|
||||||
ip_address: str,
|
ip_address: str,
|
||||||
credential_id: Optional[int] = None,
|
snmp_credential_id: Optional[int] = None, # 前端字段名(与 ORM 列名一致)
|
||||||
port: int = 161,
|
port: int = 161,
|
||||||
device_type: str = "other",
|
device_type: str = "other",
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
@@ -305,7 +305,7 @@ def create_network_device(
|
|||||||
ip_address=ip_address,
|
ip_address=ip_address,
|
||||||
port=port,
|
port=port,
|
||||||
device_type=device_type,
|
device_type=device_type,
|
||||||
snmp_credential_id=credential_id,
|
snmp_credential_id=snmp_credential_id,
|
||||||
description=description,
|
description=description,
|
||||||
location=location,
|
location=location,
|
||||||
is_active=True
|
is_active=True
|
||||||
@@ -337,7 +337,7 @@ def update_network_device(
|
|||||||
device_id: int,
|
device_id: int,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
ip_address: Optional[str] = None,
|
ip_address: Optional[str] = None,
|
||||||
credential_id: Optional[int] = None,
|
snmp_credential_id: Optional[int] = None,
|
||||||
port: Optional[int] = None,
|
port: Optional[int] = None,
|
||||||
device_type: Optional[str] = None,
|
device_type: Optional[str] = None,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
@@ -363,8 +363,8 @@ def update_network_device(
|
|||||||
device.name = name
|
device.name = name
|
||||||
if ip_address:
|
if ip_address:
|
||||||
device.ip_address = ip_address
|
device.ip_address = ip_address
|
||||||
if credential_id is not None:
|
if snmp_credential_id is not None:
|
||||||
device.snmp_credential_id = credential_id
|
device.snmp_credential_id = snmp_credential_id
|
||||||
if port:
|
if port:
|
||||||
device.port = port
|
device.port = port
|
||||||
if device_type:
|
if device_type:
|
||||||
|
|||||||
@@ -17,6 +17,33 @@ class AuditAction(str, enum.Enum):
|
|||||||
EXPORT = "export"
|
EXPORT = "export"
|
||||||
OTHER = "other"
|
OTHER = "other"
|
||||||
|
|
||||||
|
# ===== 兼容层:老 AuditAction 字符串字面量别名,值与新枚举对应 =====
|
||||||
|
# 旧代码(以及当前 AuditService._ACTION_MAP)里硬编码引用这些枚举属性名
|
||||||
|
NETWORK_CREATE = "create"
|
||||||
|
NETWORK_UPDATE = "update"
|
||||||
|
NETWORK_DELETE = "delete"
|
||||||
|
IP_UPDATE = "update"
|
||||||
|
IP_RESERVE = "update"
|
||||||
|
IP_UNRESERVE = "update"
|
||||||
|
SCAN_TRIGGER = "scan"
|
||||||
|
SNMP_CRED_CREATE = "create"
|
||||||
|
SNMP_CRED_UPDATE = "update"
|
||||||
|
SNMP_CRED_DELETE = "delete"
|
||||||
|
SNMP_DEVICE_CREATE = "create"
|
||||||
|
SNMP_DEVICE_UPDATE = "update"
|
||||||
|
SNMP_DEVICE_DELETE = "delete"
|
||||||
|
ALERT_ACK = "update"
|
||||||
|
ALERT_RESOLVE = "update"
|
||||||
|
USER_LOGIN = "login"
|
||||||
|
USER_LOGIN_FAILED = "login"
|
||||||
|
USER_LOGOUT = "logout"
|
||||||
|
USER_CREATE = "create"
|
||||||
|
USER_UPDATE_STATUS = "update"
|
||||||
|
USER_UNLOCK = "update"
|
||||||
|
USER_RESET_PASSWORD = "update"
|
||||||
|
USER_DELETE = "delete"
|
||||||
|
USER_CHANGE_PASSWORD = "update"
|
||||||
|
|
||||||
|
|
||||||
class AuditResource(str, enum.Enum):
|
class AuditResource(str, enum.Enum):
|
||||||
"""资源类型枚举"""
|
"""资源类型枚举"""
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Optional, List
|
from typing import Optional, List, Any
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import json
|
import json
|
||||||
@@ -7,9 +7,134 @@ from app.models.audit import AuditLog, AuditAction, AuditResource
|
|||||||
from app.models.auth import User
|
from app.models.auth import User
|
||||||
|
|
||||||
|
|
||||||
|
# ===== 兼容层:老 API(字符串 action/resource)映射到新枚举 =====
|
||||||
|
|
||||||
|
# 字符串 action -> 枚举 + 资源类型;用于兼容各 endpoint 里硬编码的 "network.create" 等
|
||||||
|
_ACTION_MAP = {
|
||||||
|
# network
|
||||||
|
"network.create": (AuditAction.CREATE, AuditResource.NETWORK),
|
||||||
|
"network.update": (AuditAction.UPDATE, AuditResource.NETWORK),
|
||||||
|
"network.delete": (AuditAction.DELETE, AuditResource.NETWORK),
|
||||||
|
# ip
|
||||||
|
"ip.update": (AuditAction.UPDATE, AuditResource.IP_ADDRESS),
|
||||||
|
"ip.reserve": (AuditAction.UPDATE, AuditResource.IP_ADDRESS),
|
||||||
|
"ip.unreserve": (AuditAction.UPDATE, AuditResource.IP_ADDRESS),
|
||||||
|
# scan
|
||||||
|
"scan.trigger": (AuditAction.SCAN, AuditResource.SCAN_TASK),
|
||||||
|
# snmp
|
||||||
|
"snmp.credential.create": (AuditAction.CREATE, AuditResource.SNMP_CREDENTIAL),
|
||||||
|
"snmp.credential.update": (AuditAction.UPDATE, AuditResource.SNMP_CREDENTIAL),
|
||||||
|
"snmp.credential.delete": (AuditAction.DELETE, AuditResource.SNMP_CREDENTIAL),
|
||||||
|
"snmp.device.create": (AuditAction.CREATE, AuditResource.SNMP_DEVICE),
|
||||||
|
"snmp.device.update": (AuditAction.UPDATE, AuditResource.SNMP_DEVICE),
|
||||||
|
"snmp.device.delete": (AuditAction.DELETE, AuditResource.SNMP_DEVICE),
|
||||||
|
# alert
|
||||||
|
"alert.acknowledge": (AuditAction.UPDATE, AuditResource.ALERT),
|
||||||
|
"alert.resolve": (AuditAction.UPDATE, AuditResource.ALERT),
|
||||||
|
# user
|
||||||
|
"user.login": (AuditAction.LOGIN, AuditResource.USER),
|
||||||
|
"user.login.failed": (AuditAction.LOGIN, AuditResource.USER),
|
||||||
|
"user.logout": (AuditAction.LOGOUT, AuditResource.USER),
|
||||||
|
"user.create": (AuditAction.CREATE, AuditResource.USER),
|
||||||
|
"user.update_status": (AuditAction.UPDATE, AuditResource.USER),
|
||||||
|
"user.unlock": (AuditAction.UPDATE, AuditResource.USER),
|
||||||
|
"user.reset_password": (AuditAction.UPDATE, AuditResource.USER),
|
||||||
|
"user.delete": (AuditAction.DELETE, AuditResource.USER),
|
||||||
|
"user.change_password": (AuditAction.UPDATE, AuditResource.USER),
|
||||||
|
}
|
||||||
|
|
||||||
|
_RESOURCE_MAP = {
|
||||||
|
"network": AuditResource.NETWORK,
|
||||||
|
"ip": AuditResource.IP_ADDRESS,
|
||||||
|
"snmp_credential": AuditResource.SNMP_CREDENTIAL,
|
||||||
|
"snmp_device": AuditResource.SNMP_DEVICE,
|
||||||
|
"user": AuditResource.USER,
|
||||||
|
"alert": AuditResource.ALERT,
|
||||||
|
"scan": AuditResource.SCAN_TASK,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AuditService:
|
class AuditService:
|
||||||
"""审计日志服务"""
|
"""审计日志服务"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def record(
|
||||||
|
db: Session,
|
||||||
|
*,
|
||||||
|
action: str,
|
||||||
|
user=None,
|
||||||
|
resource_type: Optional[str] = None,
|
||||||
|
resource_id: Optional[Any] = None,
|
||||||
|
resource_name: Optional[str] = None,
|
||||||
|
method: Optional[str] = None,
|
||||||
|
path: Optional[str] = None,
|
||||||
|
ip_address: Optional[str] = None,
|
||||||
|
user_agent: Optional[str] = None,
|
||||||
|
status: str = "success",
|
||||||
|
detail: Optional[Any] = None,
|
||||||
|
) -> Optional["AuditLog"]:
|
||||||
|
"""
|
||||||
|
兼容层:将老版 API(字符串 action/resource)映射到新版 log()。
|
||||||
|
所有 endpoint 都通过本方法写审计,避免直接调用新签名造成 500。
|
||||||
|
任意环节失败都安全降级(不抛异常),审计写入失败不影响主业务。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# action 字符串 -> (枚举, 资源枚举) ;未知 action 用 OTHER + SYSTEM
|
||||||
|
enum_action, default_resource = _ACTION_MAP.get(
|
||||||
|
action, (AuditAction.OTHER, AuditResource.SYSTEM)
|
||||||
|
)
|
||||||
|
resource_enum = _RESOURCE_MAP.get(resource_type, default_resource)
|
||||||
|
|
||||||
|
# 详情拆分:dict 拆出 old/new;其余当 description
|
||||||
|
old_value = None
|
||||||
|
new_value = None
|
||||||
|
description = None
|
||||||
|
error_message = None
|
||||||
|
success = status != "failed"
|
||||||
|
|
||||||
|
if isinstance(detail, dict):
|
||||||
|
old_value = detail.get("before")
|
||||||
|
new_value = detail.get("after")
|
||||||
|
# 把 action 原始字符串 + 资源名放到 description 里,保留细节
|
||||||
|
desc_parts = [action]
|
||||||
|
if resource_name:
|
||||||
|
desc_parts.append(resource_name)
|
||||||
|
if status == "failed":
|
||||||
|
error_message = detail.get("error") or detail.get("message") or json.dumps(detail, ensure_ascii=False, default=str)
|
||||||
|
description = " | ".join(desc_parts)
|
||||||
|
else:
|
||||||
|
description = f"{action} | {resource_name or ''}"
|
||||||
|
if status == "failed" and detail:
|
||||||
|
error_message = str(detail)
|
||||||
|
|
||||||
|
# user 不能 None 传给 log();fallback 到 User(id=None, username='anonymous')
|
||||||
|
# 但 log() 会用 user.id / user.username / user.real_name,如果都是 None 就 OK
|
||||||
|
if user is None:
|
||||||
|
anon = User(id=None, username="anonymous", real_name=None)
|
||||||
|
else:
|
||||||
|
anon = user
|
||||||
|
|
||||||
|
return AuditService.log(
|
||||||
|
db=db,
|
||||||
|
user=anon,
|
||||||
|
action=enum_action,
|
||||||
|
resource=resource_enum,
|
||||||
|
resource_id=str(resource_id) if resource_id is not None else None,
|
||||||
|
description=description[:500] if description else None,
|
||||||
|
old_value=old_value,
|
||||||
|
new_value=new_value,
|
||||||
|
user_ip=ip_address,
|
||||||
|
user_agent=(user_agent or "")[:500],
|
||||||
|
request_method=method,
|
||||||
|
request_path=path,
|
||||||
|
success=success,
|
||||||
|
error_message=error_message,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
print(f"[audit] record() failed action={action}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def log(db: Session,
|
def log(db: Session,
|
||||||
user: User,
|
user: User,
|
||||||
|
|||||||
Reference in New Issue
Block a user