新增群晖Chat渠道适配器的全套实现,包括: 1. 基础适配器与导出接口定义 2. DSM API认证、探测与会话管理 3. 轮询与Webhook两种消息接收方式 4. 消息去重、格式化与规范化处理 5. 多账号支持与权限安全策略 6. 目录用户/群组发现功能 7. 审批配对与流量控制机制 8. 安全审计与配置检查功能
67 lines
2.7 KiB
Python
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})
|