from __future__ import annotations import logging import re logger = logging.getLogger(__name__) class MattermostSecurityAdapter: def resolve_dm_policy(self, account: dict) -> str: return account.get("dm_policy", "pairing") def resolve_group_policy(self, account: dict) -> str: return account.get("group_policy", "allowlist") def is_sender_allowed( self, sender_id: str, allow_list: list[str], dangerously_allow_name_matching: bool = False, sender_name: str | None = None, ) -> bool: normalized = [normalize_allow_entry(e) for e in allow_list] if sender_id in normalized: return True if "*" in normalized: return True if dangerously_allow_name_matching and sender_name: raw_entries = [str(e).strip() for e in allow_list] return f"@{sender_name}" in raw_entries or sender_name in raw_entries return False def check_sender_access( self, chat_type: str, sender_id: str, account: dict, sender_name: str | None = None, ) -> tuple[bool, str]: if chat_type == "direct": return self._check_dm_access(sender_id, account, sender_name) return self._check_group_access(sender_id, account, sender_name) def _check_dm_access( self, sender_id: str, account: dict, sender_name: str | None = None, ) -> tuple[bool, str]: policy = self.resolve_dm_policy(account) if policy == "open": allow_from = account.get("allow_from", []) if "*" in [str(e).strip() for e in allow_from]: return True, "ok" return self._check_allowlist(sender_id, account, sender_name, "dm") if policy == "pairing": allow_from = account.get("allow_from", []) if self.is_sender_allowed( sender_id, allow_from, account.get("dangerously_allow_name_matching", False), sender_name, ): return True, "ok" return False, "pairing_required" if policy == "allowlist": return self._check_allowlist(sender_id, account, sender_name, "dm") if policy == "disabled": return False, "dm_disabled" return False, "unknown_policy" def _check_group_access( self, sender_id: str, account: dict, sender_name: str | None = None, ) -> tuple[bool, str]: policy = self.resolve_group_policy(account) if policy == "open": return True, "ok" if policy == "allowlist": return self._check_allowlist(sender_id, account, sender_name, "group") if policy == "disabled": return False, "group_disabled" return False, "unknown_policy" def _check_allowlist( self, sender_id: str, account: dict, sender_name: str | None, scope: str, ) -> tuple[bool, str]: allow_key = "group_allow_from" if scope == "group" else "allow_from" allow_list = account.get(allow_key, []) if not allow_list: allow_list = account.get("allow_from", []) if self.is_sender_allowed( sender_id, allow_list, account.get("dangerously_allow_name_matching", False), sender_name, ): return True, "ok" return False, f"{scope}_not_allowed" def normalize_allow_entry(raw: str | int) -> str: entry = str(raw).strip() for prefix in ["mattermost:", "user:", "@"]: if entry.startswith(prefix): entry = entry[len(prefix):] return entry def is_valid_mattermost_id(s: str) -> bool: return bool(re.fullmatch(r"[a-z0-9]{26}", s, re.IGNORECASE)) def normalize_allow_list(raw_entries: list[str | int]) -> list[str]: return [normalize_allow_entry(e) for e in raw_entries]