diff --git a/gateway/run.py b/gateway/run.py index ba7ea43ad..4cb5a38e9 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -2618,6 +2618,9 @@ class GatewayRunner: Platform.BLUEBUBBLES: "BLUEBUBBLES_ALLOWED_USERS", Platform.QQBOT: "QQ_ALLOWED_USERS", } + platform_group_env_map = { + Platform.QQBOT: "QQ_GROUP_ALLOWED_USERS", + } platform_allow_all_map = { Platform.TELEGRAM: "TELEGRAM_ALLOW_ALL_USERS", Platform.DISCORD: "DISCORD_ALLOW_ALL_USERS", @@ -2649,12 +2652,23 @@ class GatewayRunner: # Check platform-specific and global allowlists platform_allowlist = os.getenv(platform_env_map.get(source.platform, ""), "").strip() + group_allowlist = "" + if source.chat_type == "group": + group_allowlist = os.getenv(platform_group_env_map.get(source.platform, ""), "").strip() global_allowlist = os.getenv("GATEWAY_ALLOWED_USERS", "").strip() - if not platform_allowlist and not global_allowlist: + if not platform_allowlist and not group_allowlist and not global_allowlist: # No allowlists configured -- check global allow-all flag return os.getenv("GATEWAY_ALLOW_ALL_USERS", "").lower() in ("true", "1", "yes") + # Some platforms authorize group traffic by chat ID rather than sender ID. + if group_allowlist and source.chat_type == "group" and source.chat_id: + allowed_group_ids = { + chat_id.strip() for chat_id in group_allowlist.split(",") if chat_id.strip() + } + if "*" in allowed_group_ids or source.chat_id in allowed_group_ids: + return True + # Check if user is in any allowlist allowed_ids = set() if platform_allowlist: diff --git a/tests/gateway/test_unauthorized_dm_behavior.py b/tests/gateway/test_unauthorized_dm_behavior.py index 5f898b5e6..627723915 100644 --- a/tests/gateway/test_unauthorized_dm_behavior.py +++ b/tests/gateway/test_unauthorized_dm_behavior.py @@ -21,6 +21,7 @@ def _clear_auth_env(monkeypatch) -> None: "MATTERMOST_ALLOWED_USERS", "MATRIX_ALLOWED_USERS", "DINGTALK_ALLOWED_USERS", "FEISHU_ALLOWED_USERS", "WECOM_ALLOWED_USERS", + "QQ_ALLOWED_USERS", "QQ_GROUP_ALLOWED_USERS", "GATEWAY_ALLOWED_USERS", "TELEGRAM_ALLOW_ALL_USERS", "DISCORD_ALLOW_ALL_USERS", @@ -32,6 +33,7 @@ def _clear_auth_env(monkeypatch) -> None: "MATTERMOST_ALLOW_ALL_USERS", "MATRIX_ALLOW_ALL_USERS", "DINGTALK_ALLOW_ALL_USERS", "FEISHU_ALLOW_ALL_USERS", "WECOM_ALLOW_ALL_USERS", + "QQ_ALLOW_ALL_USERS", "GATEWAY_ALLOW_ALL_USERS", ): monkeypatch.delenv(key, raising=False) @@ -130,6 +132,46 @@ def test_star_wildcard_works_for_any_platform(monkeypatch): assert runner._is_user_authorized(source) is True +def test_qq_group_allowlist_authorizes_group_chat_without_user_allowlist(monkeypatch): + _clear_auth_env(monkeypatch) + monkeypatch.setenv("QQ_GROUP_ALLOWED_USERS", "group-openid-1") + + runner, _adapter = _make_runner( + Platform.QQBOT, + GatewayConfig(platforms={Platform.QQBOT: PlatformConfig(enabled=True)}), + ) + + source = SessionSource( + platform=Platform.QQBOT, + user_id="member-openid-999", + chat_id="group-openid-1", + user_name="tester", + chat_type="group", + ) + + assert runner._is_user_authorized(source) is True + + +def test_qq_group_allowlist_does_not_authorize_other_groups(monkeypatch): + _clear_auth_env(monkeypatch) + monkeypatch.setenv("QQ_GROUP_ALLOWED_USERS", "group-openid-1") + + runner, _adapter = _make_runner( + Platform.QQBOT, + GatewayConfig(platforms={Platform.QQBOT: PlatformConfig(enabled=True)}), + ) + + source = SessionSource( + platform=Platform.QQBOT, + user_id="member-openid-999", + chat_id="group-openid-2", + user_name="tester", + chat_type="group", + ) + + assert runner._is_user_authorized(source) is False + + @pytest.mark.asyncio async def test_unauthorized_dm_pairs_by_default(monkeypatch): _clear_auth_env(monkeypatch)