from __future__ import annotations import logging import re logger = logging.getLogger(__name__) _EMAIL_PATTERN = re.compile(r".+@.+\..+") class GoogleChatSecurityAdapter: def __init__(self): self._dm_policy: str = "pairing" def resolve_dm_policy(self) -> dict: return {"mode": self._dm_policy, "allow_from": []} def check_allow_entry(self, entry: str) -> bool: if not entry: return False if entry.startswith("googlechat:"): entry = entry[len("googlechat:"):] if entry.startswith("users/") and _EMAIL_PATTERN.match(entry[6:]): return False return bool(entry) def normalize_allow_entry(self, entry: str) -> str: if not entry: return entry for prefix in ("googlechat:", "google-chat:", "gchat:"): if entry.lower().startswith(prefix): entry = entry[len(prefix):] break if entry.startswith("user:users/"): entry = entry[5:] elif entry.startswith("space:spaces/"): entry = entry[6:] return entry.lower() def collect_warnings(self, config: dict, account_id: str | None = None) -> list[str]: warnings = [] gc_config = config.get("channels", {}).get("googlechat", {}) group_policy = gc_config.get("groupPolicy", "allowlist") if group_policy == "open": warnings.append( "Group policy is 'open' - any Google Chat Space can trigger the bot. Consider using 'allowlist' for production." ) dm_config = gc_config.get("dm", {}) dm_policy = dm_config.get("policy", "pairing") if dm_policy == "open": warnings.append( "DM policy is 'open' - anyone can DM the bot. Consider using 'pairing' or 'allowlist' for production." ) allow_from = dm_config.get("allowFrom", []) for entry in allow_from: normalized = self.normalize_allow_entry(entry) if normalized.startswith("users/") and "@" in normalized: warnings.append( f"DM allowFrom entry '{entry}' uses email format. Email addresses are mutable; use users/ instead." ) groups = gc_config.get("groups", {}) for group_key in groups: if not group_key.startswith("spaces/") and group_key != "*": warnings.append( f"Group key '{group_key}' is not a stable spaces/ name. This may break routing." ) return warnings def check_dm_allowlist( self, sender_name: str, allow_from: list[str], dangerously_allow_name_matching: bool = False ) -> bool: if "*" in allow_from: return True for entry in allow_from: if "@" in entry and not dangerously_allow_name_matching: continue normalized = self.normalize_allow_entry(entry) if normalized == self.normalize_allow_entry(sender_name): return True return False