ForcePilot/backend/package/yuxi/channels/adapters/whatsapp/logout_security.py
Kris 1f78c44b03 refactor: 整理并清理项目中的冗余代码与格式问题
这是一个批量整理提交,包含以下主要改动:
1.  删除多处冗余的空行和未使用的导入
2.  修复文件末尾缺少换行符的问题
3.  调整部分模块的导入顺序与代码排版
4.  修复部分配置默认值与策略逻辑
5.  新增多个功能模块与辅助工具
6.  完善异常处理与日志记录
7.  修复速率限制、消息缓存、权限校验等逻辑bug
8.  废弃部分旧有API与配置项并添加警告提示
2026-05-12 14:51:53 +08:00

106 lines
3.3 KiB
Python

from __future__ import annotations
import time
from enum import StrEnum
from pathlib import Path
from yuxi.utils.logging_config import logger
class PathOwnership(StrEnum):
OWNED = "owned"
UNSAFE_OWNED = "unsafe_owned"
EXTERNAL = "external"
def _classify_path(auth_dir: Path, target: Path) -> PathOwnership:
try:
resolved_auth = auth_dir.resolve()
resolved_target = target.resolve()
except OSError:
return PathOwnership.EXTERNAL
try:
resolved_target.relative_to(resolved_auth)
except ValueError:
return PathOwnership.EXTERNAL
if target.is_symlink():
return PathOwnership.UNSAFE_OWNED
try:
if not target.exists():
return PathOwnership.OWNED
abs_resolved = target.resolve(strict=False)
abs_resolved.relative_to(resolved_auth)
except ValueError:
return PathOwnership.UNSAFE_OWNED
return PathOwnership.OWNED
def _is_safe_path(auth_dir: Path, target: Path) -> bool:
return _classify_path(auth_dir, target) == PathOwnership.OWNED
def perform_logout_cleanup(auth_dir: Path) -> dict[str, int]:
auth_dir = Path(auth_dir)
classification = _classify_path(auth_dir, auth_dir)
if classification == PathOwnership.EXTERNAL:
logger.error(f"LogoutSecurity: auth_dir '{auth_dir}' is outside expected path tree")
return {"owned": 0, "unsafe_owned": 0, "external": 1}
if classification == PathOwnership.UNSAFE_OWNED:
logger.error(f"LogoutSecurity: auth_dir '{auth_dir}' is a symlink — refusing cleanup")
return {"owned": 0, "unsafe_owned": 1, "external": 0}
counts = {"owned": 0, "unsafe_owned": 0, "external": 0}
files_to_check = {
"creds": "creds.json",
"creds_bak": "creds.json.bak",
"app_state": "app-state-sync-key.json",
"prekeys": "pre-keys.json",
"sender_key": "sender-key-store.json",
}
for label, filename in files_to_check.items():
file_path = auth_dir / filename
if not file_path.exists():
continue
ownership = _classify_path(auth_dir, file_path)
if ownership == PathOwnership.EXTERNAL:
logger.warning(f"LogoutSecurity: skipping external path '{file_path}'")
counts["external"] += 1
continue
if ownership == PathOwnership.UNSAFE_OWNED:
logger.warning(f"LogoutSecurity: skipping symlink/unresolvable '{file_path}'")
counts["unsafe_owned"] += 1
continue
try:
file_path.unlink()
logger.info(f"LogoutSecurity: removed '{file_path}' ({label})")
counts["owned"] += 1
except OSError as e:
logger.error(f"LogoutSecurity: failed to remove '{file_path}': {e}")
logger.info(
f"LogoutSecurity: cleanup complete — owned={counts['owned']}, "
f"unsafe_owned={counts['unsafe_owned']}, external={counts['external']}"
)
return counts
def validate_credential_freshness(auth_dir: Path, max_age_hours: int = 168) -> bool:
creds = auth_dir / "creds.json"
if not creds.exists():
return False
try:
stat = creds.stat()
age_hours = (time.time() - stat.st_mtime) / 3600
return age_hours < max_age_hours
except OSError:
return False