diff --git a/contributors/emails/nyaruko@hermes b/contributors/emails/nyaruko@hermes new file mode 100644 index 000000000000..430861e13081 --- /dev/null +++ b/contributors/emails/nyaruko@hermes @@ -0,0 +1 @@ +tsuk1nose diff --git a/gateway/authz_mixin.py b/gateway/authz_mixin.py index 7cd3dc1eb069..24910144d4ef 100644 --- a/gateway/authz_mixin.py +++ b/gateway/authz_mixin.py @@ -43,6 +43,21 @@ def _auth_env(name: str, default: str = "") -> str: return (os.getenv(name) or default).strip() +def _coerce_allow_set(raw) -> set[str]: + """Parse allowlist values from config or env var into a set of strings. + + Handles both list inputs (YAML sequences) and comma-separated string + inputs (env vars or scalar YAML values). A scalar string is split on + commas so ``allow_from: "123,456"`` yields ``{"123", "456"}``, not + ``{"1", "2", "3", ",", ...}``. + """ + if raw is None: + return set() + if isinstance(raw, list): + return {str(part).strip() for part in raw if str(part).strip()} + return {part.strip() for part in str(raw).split(",") if part.strip()} + + class GatewayAuthorizationMixin: """User/chat authorization methods for ``GatewayRunner``.""" @@ -369,7 +384,7 @@ class GatewayAuthorizationMixin: extra = getattr(getattr(adapter, "config", None), "extra", None) or {} adapter_group_allowed = extra.get("group_allowed_chats") if adapter_group_allowed: - allowed = {str(c).strip() for c in adapter_group_allowed if str(c).strip()} + allowed = _coerce_allow_set(adapter_group_allowed) if "*" in allowed or source.chat_id in allowed: return True except Exception: @@ -550,12 +565,12 @@ class GatewayAuthorizationMixin: adapter = self._adapter_for_source(source) if adapter is not None: extra = getattr(getattr(adapter, "config", None), "extra", None) or {} - if source.chat_type in {"group", "forum"}: + if source.chat_type in {"group", "forum", "channel"}: adapter_allow = extra.get("group_allow_from") else: adapter_allow = extra.get("allow_from") if adapter_allow: - allowed = {str(u).strip() for u in adapter_allow if str(u).strip()} + allowed = _coerce_allow_set(adapter_allow) if user_id in allowed or "*" in allowed: return True # No allowlists configured -- check global allow-all flag diff --git a/plugins/platforms/telegram/adapter.py b/plugins/platforms/telegram/adapter.py index b4a46878e64e..05883c0e2ffe 100644 --- a/plugins/platforms/telegram/adapter.py +++ b/plugins/platforms/telegram/adapter.py @@ -250,6 +250,7 @@ import sys from pathlib import Path as _Path sys.path.insert(0, str(_Path(__file__).resolve().parents[3])) +from gateway.authz_mixin import _coerce_allow_set from gateway.config import Platform, PlatformConfig from gateway.platforms.base import ( BasePlatformAdapter, @@ -1016,12 +1017,12 @@ class TelegramAdapter(BasePlatformAdapter): # Adapter-level allow_from / group_allow_from: when set, they are the # sole authority. Group chats use group_allow_from; DMs use allow_from. chat_type = source.chat_type or "" - if chat_type in ("group", "forum"): + if chat_type in ("group", "forum", "channel"): adapter_allow_from = self.config.extra.get("group_allow_from") else: adapter_allow_from = self.config.extra.get("allow_from") if adapter_allow_from is not None: - allowed = {str(u).strip() for u in adapter_allow_from if str(u).strip()} + allowed = _coerce_allow_set(adapter_allow_from) return user_id in allowed or "*" in allowed # Test/custom injection only. The class method named @@ -9410,7 +9411,7 @@ def _apply_yaml_config(yaml_cfg: dict, telegram_cfg: dict) -> dict | None: if isinstance(group_allowed_users, list): group_allowed_users = ",".join(str(v) for v in group_allowed_users) os.environ["TELEGRAM_GROUP_ALLOWED_USERS"] = str(group_allowed_users) - group_allowed_chats = telegram_cfg.get("group_allowed_chats") + group_allowed_chats = telegram_cfg.get("group_allowed_chats") or _telegram_extra.get("group_allowed_chats") if group_allowed_chats is not None and not os.getenv("TELEGRAM_GROUP_ALLOWED_CHATS"): if isinstance(group_allowed_chats, list): group_allowed_chats = ",".join(str(v) for v in group_allowed_chats) diff --git a/tests/gateway/test_telegram_group_gating.py b/tests/gateway/test_telegram_group_gating.py index 9f69132d86eb..1dc9a13a6f2d 100644 --- a/tests/gateway/test_telegram_group_gating.py +++ b/tests/gateway/test_telegram_group_gating.py @@ -346,7 +346,9 @@ def test_observed_group_context_preserves_slash_command_text_for_dispatch(): assert attributed.text == "/new@hermes_bot" assert attributed.get_command() == "new" - assert attributed.source.user_id is None + # Commands preserve sender identity for slash-access control (#67816). + assert attributed.source.user_id == "111" + assert attributed.source.user_name == "Alice" assert "observed Telegram group context" in attributed.channel_prompt