fix(qq): send pairing codes for unauthorized C2C users

This commit is contained in:
yorkhellen 2026-06-03 23:35:03 +08:00 committed by Xubin Ren
parent facdc41a16
commit 7c3808327f
3 changed files with 97 additions and 5 deletions

View File

@ -490,14 +490,24 @@ class QQChannel(BaseChannel):
content = (data.content or "").strip() content = (data.content or "").strip()
if not self.is_allowed(user_id):
return
if data.id in self._processed_ids: if data.id in self._processed_ids:
return return
self._processed_ids.append(data.id) self._processed_ids.append(data.id)
self._chat_type_cache[chat_id] = chat_type self._chat_type_cache[chat_id] = chat_type
# Early permission check — avoid attachment downloads and ack side effects
# for unauthorized users. C2C messages can receive pairing codes;
# group messages remain silently ignored.
if not self.is_allowed(user_id):
if not is_group:
await self._handle_message(
sender_id=user_id,
chat_id=chat_id,
content="",
is_dm=True,
)
return
# the data used by tests don't contain attachments property # the data used by tests don't contain attachments property
# so we use getattr with a default of [] to avoid AttributeError in tests # so we use getattr with a default of [] to avoid AttributeError in tests
attachments = getattr(data, "attachments", None) or [] attachments = getattr(data, "attachments", None) or []
@ -538,6 +548,7 @@ class QQChannel(BaseChannel):
"message_id": data.id, "message_id": data.id,
"attachments": att_meta, "attachments": att_meta,
}, },
is_dm=not is_group,
) )
except Exception: except Exception:
self.logger.exception("Error handling inbound message id={}", getattr(data, "id", "?")) self.logger.exception("Error handling inbound message id={}", getattr(data, "id", "?"))

View File

@ -1,7 +1,7 @@
import tempfile import tempfile
from pathlib import Path from pathlib import Path
from types import SimpleNamespace from types import SimpleNamespace
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock
import pytest import pytest
@ -58,6 +58,51 @@ async def test_on_group_message_routes_to_group_chat_id() -> None:
assert msg.chat_id == "group123" assert msg.chat_id == "group123"
@pytest.mark.asyncio
async def test_on_c2c_message_passes_is_dm_true_to_base_handler() -> None:
channel = QQChannel(QQConfig(app_id="app", secret="secret", allow_from=["user1"]), MessageBus())
channel._handle_message = AsyncMock()
data = SimpleNamespace(
id="msg-c2c",
content="hello",
author=SimpleNamespace(user_openid="user1"),
attachments=[],
)
await channel._on_message(data, is_group=False)
channel._handle_message.assert_awaited_once()
kwargs = channel._handle_message.await_args.kwargs
assert kwargs["sender_id"] == "user1"
assert kwargs["chat_id"] == "user1"
assert kwargs["content"] == "hello"
assert kwargs["is_dm"] is True
@pytest.mark.asyncio
async def test_on_group_message_passes_is_dm_false_to_base_handler() -> None:
channel = QQChannel(QQConfig(app_id="app", secret="secret", allow_from=["user1"]), MessageBus())
channel._handle_message = AsyncMock()
data = SimpleNamespace(
id="msg-group",
content="hello",
group_openid="group123",
author=SimpleNamespace(member_openid="user1"),
attachments=[],
)
await channel._on_message(data, is_group=True)
channel._handle_message.assert_awaited_once()
kwargs = channel._handle_message.await_args.kwargs
assert kwargs["sender_id"] == "user1"
assert kwargs["chat_id"] == "group123"
assert kwargs["content"] == "hello"
assert kwargs["is_dm"] is False
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_send_group_message_uses_plain_text_group_api_with_msg_seq() -> None: async def test_send_group_message_uses_plain_text_group_api_with_msg_seq() -> None:
channel = QQChannel(QQConfig(app_id="app", secret="secret", allow_from=["*"]), MessageBus()) channel = QQChannel(QQConfig(app_id="app", secret="secret", allow_from=["*"]), MessageBus())

View File

@ -183,7 +183,7 @@ async def test_send_media_failure_falls_back_to_text() -> None:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_on_message_ignores_unauthorized_sender_before_attachments_and_ack() -> None: async def test_on_message_unauthorized_c2c_pairs_before_attachments_and_ack() -> None:
channel = QQChannel( channel = QQChannel(
QQConfig( QQConfig(
app_id="app", app_id="app",
@ -206,9 +206,45 @@ async def test_on_message_ignores_unauthorized_sender_before_attachments_and_ack
await channel._on_message(data, is_group=False) await channel._on_message(data, is_group=False)
channel._handle_attachments.assert_not_awaited()
channel._handle_message.assert_awaited_once_with(
sender_id="blocked-user",
chat_id="blocked-user",
content="",
is_dm=True,
)
assert channel._client.api.c2c_calls == []
@pytest.mark.asyncio
async def test_on_message_ignores_unauthorized_group_before_attachments_and_ack() -> None:
channel = QQChannel(
QQConfig(
app_id="app",
secret="secret",
allow_from=["allowed-user"],
ack_message="Processing...",
),
MessageBus(),
)
channel._client = _FakeClient()
channel._handle_attachments = AsyncMock(return_value=(["/tmp/a.png"], ["file"], []))
channel._handle_message = AsyncMock()
data = SimpleNamespace(
id="msg-blocked-group",
content="hello",
group_openid="group123",
author=SimpleNamespace(member_openid="blocked-user"),
attachments=[SimpleNamespace(filename="a.png")],
)
await channel._on_message(data, is_group=True)
channel._handle_attachments.assert_not_awaited() channel._handle_attachments.assert_not_awaited()
channel._handle_message.assert_not_awaited() channel._handle_message.assert_not_awaited()
assert channel._client.api.c2c_calls == [] assert channel._client.api.c2c_calls == []
assert channel._client.api.group_calls == []
# ── _on_message() exception handling ──────────────────────────────── # ── _on_message() exception handling ────────────────────────────────