ForcePilot/backend/package/yuxi/channels/adapters/synologychat/pairing.py
Kris 3ad218e537 feat(synologychat): 新增群晖Chat适配器完整实现
新增群晖Chat渠道适配器的全套实现,包括:
1. 基础适配器与导出接口定义
2. DSM API认证、探测与会话管理
3. 轮询与Webhook两种消息接收方式
4. 消息去重、格式化与规范化处理
5. 多账号支持与权限安全策略
6. 目录用户/群组发现功能
7. 审批配对与流量控制机制
8. 安全审计与配置检查功能
2026-05-12 00:49:16 +08:00

67 lines
2.7 KiB
Python

"""DM Pairing flow for Synology Chat security policy.
Handles the complete pairing lifecycle: unknown user triggers pairing request,
approval adds user to allowlist, denial blocks permanently.
Includes standardized approval/denial message templates for user notification.
"""
from __future__ import annotations
from typing import Any
from yuxi.utils.logging_config import logger
_PAIRING_APPROVED_MESSAGE = "ForcePilot: your access has been approved. You may now interact with the bot."
_PAIRING_DENIED_MESSAGE = "ForcePilot: your access request has been denied."
class PairingManager:
def __init__(self, security_policy):
self._policy = security_policy
self._pairing_requests: dict[str, dict[str, Any]] = {}
def request_pairing(self, user_id: str, metadata: dict[str, Any] | None = None) -> dict[str, Any]:
request_id = f"pairing:{user_id}"
self._pairing_requests[request_id] = {
"user_id": user_id,
"status": "pending",
"metadata": metadata or {},
}
logger.info(f"[SynologyChat] Pairing requested for user {user_id}")
return {"request_id": request_id, "status": "pending", "user_id": user_id}
def approve(self, user_id: str) -> dict[str, Any]:
request_id = f"pairing:{user_id}"
if request_id not in self._pairing_requests:
return {"status": "error", "message": f"No pairing request for user {user_id}"}
self._pairing_requests[request_id]["status"] = "approved"
self._policy.approve_pairing(user_id)
logger.info(f"[SynologyChat] Pairing approved for user {user_id}")
return {
"request_id": request_id,
"status": "approved",
"user_id": user_id,
"approved_message": _PAIRING_APPROVED_MESSAGE,
}
def deny(self, user_id: str) -> dict[str, Any]:
request_id = f"pairing:{user_id}"
if request_id not in self._pairing_requests:
return {"status": "error", "message": f"No pairing request for user {user_id}"}
self._pairing_requests[request_id]["status"] = "denied"
self._policy.deny_pairing(user_id)
logger.info(f"[SynologyChat] Pairing denied for user {user_id}")
return {
"request_id": request_id,
"status": "denied",
"user_id": user_id,
"denied_message": _PAIRING_DENIED_MESSAGE,
}
def get_pending(self) -> list[dict[str, Any]]:
return [r for r in self._pairing_requests.values() if r["status"] == "pending"]
def get_status(self, user_id: str) -> dict[str, Any]:
request_id = f"pairing:{user_id}"
return self._pairing_requests.get(request_id, {"status": "not_requested", "user_id": user_id})