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