87 lines
3.0 KiB
Python
87 lines
3.0 KiB
Python
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/<numeric_id> 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/<id> 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 |