这是一个批量整理提交,包含以下主要改动: 1. 删除多处冗余的空行和未使用的导入 2. 修复文件末尾缺少换行符的问题 3. 调整部分模块的导入顺序与代码排版 4. 修复部分配置默认值与策略逻辑 5. 新增多个功能模块与辅助工具 6. 完善异常处理与日志记录 7. 修复速率限制、消息缓存、权限校验等逻辑bug 8. 废弃部分旧有API与配置项并添加警告提示
106 lines
3.3 KiB
Python
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
|