ForcePilot/backend/package/yuxi/channel/extensions/feishu/security.py
Kris 5e91bb9985 feat(feishu): 新增飞书渠道完整插件实现
新增飞书渠道插件,包含基础配置、事件处理、消息收发、工具调用、权限管理等完整功能模块,支持WebSocket长连接、卡片消息、流式响应、群管理、审批通知等能力
2026-05-21 10:46:42 +08:00

132 lines
5.0 KiB
Python

from __future__ import annotations
import logging
from yuxi.channel.extensions.feishu.utils import parse_feishu_allow_entry
logger = logging.getLogger(__name__)
class FeishuSecurityAdapter:
def __init__(self):
self._dm_allowlist: set[str] = set()
self._group_allowlist: set[str] = set()
def resolve_dm_policy(self) -> dict:
return {"mode": "pairing", "allow_from": []}
def resolve_dm_policy_for_account(self, account: dict) -> dict:
mode = account.get("dm_policy", "pairing")
return {"mode": mode, "allow_from": account.get("allow_from", [])}
def resolve_group_policy_for_account(self, account: dict) -> dict:
mode = account.get("group_policy", "allowlist")
return {"mode": mode, "allow_from": account.get("group_allow_from", [])}
async def check_allowlist(self, peer_id: str, channel_type: str) -> bool:
if channel_type == "direct":
return peer_id in self._dm_allowlist
return peer_id in self._group_allowlist
def load_allowlists(self, account: dict) -> None:
self._dm_allowlist.clear()
self._group_allowlist.clear()
for entry in account.get("allow_from", []):
entry_type, entry_id = parse_feishu_allow_entry(str(entry))
if entry_type == "user":
self._dm_allowlist.add(entry_id)
elif entry_type == "wildcard":
pass
for entry in account.get("group_allow_from", []):
entry_type, entry_id = parse_feishu_allow_entry(str(entry))
if entry_type == "chat":
self._group_allowlist.add(entry_id)
elif entry_type == "wildcard":
pass
def add_dm_allow(self, peer_id: str) -> None:
self._dm_allowlist.add(peer_id)
def add_group_allow(self, chat_id: str) -> None:
self._group_allowlist.add(chat_id)
def check_dm_access(self, account: dict, sender_id: str) -> tuple[bool, str]:
dm_policy = account.get("dm_policy", "pairing")
allow_from = account.get("allow_from", [])
if dm_policy == "disabled":
return False, "DM is disabled"
if dm_policy == "open":
if "*" in allow_from:
return True, "open"
return False, "open DM requires '*' in allowFrom"
if dm_policy == "allowlist":
if sender_id in allow_from or any(
entry.strip() == sender_id for entry in allow_from
):
return True, "allowlist"
return False, f"sender {sender_id} not in allowlist"
if dm_policy == "pairing":
return True, "pairing"
return False, f"unknown dm_policy: {dm_policy}"
def check_group_access(
self,
account: dict,
chat_id: str,
sender_id: str,
mentioned_bot: bool = False,
require_mention: bool = True,
) -> tuple[bool, str]:
group_policy = account.get("group_policy", "allowlist")
group_allow_from = account.get("group_allow_from", [])
groups = account.get("groups", {})
if group_policy == "disabled":
return False, "Group chat is disabled"
if require_mention and not mentioned_bot:
return False, "@mention required"
if group_policy == "open":
return True, "open"
if group_policy == "allowlist":
chat_entries = [e for e in group_allow_from if e.startswith("chat:") or e.startswith("oc_")]
if any(chat_id in e for e in chat_entries):
return True, "allowlist"
if chat_id in groups:
group_cfg = groups[chat_id]
if not group_cfg.get("enabled", True):
return False, "group disabled"
group_allow = group_cfg.get("allow_from", [])
if group_allow and sender_id not in group_allow:
return False, f"sender {sender_id} not in group allow list"
return True, "explicit group config"
return False, f"chat {chat_id} not in group allow list"
return False, f"unknown group_policy: {group_policy}"
def check_mention_required(self, account: dict, chat_id: str | None = None) -> bool:
if chat_id:
groups = account.get("groups", {})
group_cfg = groups.get(chat_id, {})
if "require_mention" in group_cfg:
return group_cfg["require_mention"]
return account.get("require_mention", True)
def collect_warnings(self, config: dict, account_id: str | None = None, account: dict | None = None) -> list[str]:
warnings = []
group_policy = account.get("group_policy", "") if account else config.get("channels", {}).get("feishu", {}).get("groupPolicy", "")
if group_policy == "open" and not config.get("channels", {}).get("feishu", {}).get("groups"):
warnings.append("groupPolicy is 'open' without groups allowlist - consider setting groupPolicy='allowlist'")
return warnings