fix(gateway): scope secondary-adapter auth callback to its own profile

Subset of PR #61985: _make_adapter_auth_check gains a profile_name
parameter and secondary-profile adapters (started in
_start_one_profile_adapters) bind it, so the auth callback's
SessionSource resolves the routed profile's adapter and pairing store
instead of silently falling back to the default profile. This is the
gap left open by the #65629 merge — adapter-internal auth checks (e.g.
Slack thread-context fetch) fire outside the wrapped message handler.

The PR's authz_mixin.py hunks are dropped: main's _auth_env (merged via
PR #65629) already covers the scoped allowlist reads they targeted.
This commit is contained in:
rlaehddus302 2026-07-16 06:36:02 -07:00 committed by Teknium
parent 0cc9426c6d
commit fef0b2d600
2 changed files with 59 additions and 1 deletions

View file

@ -8875,7 +8875,9 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
adapter.set_session_store(self.session_store)
adapter.set_busy_session_handler(self._handle_active_session_busy_message)
adapter.set_topic_recovery_fn(self._recover_telegram_topic_thread_id)
adapter.set_authorization_check(self._make_adapter_auth_check(adapter.platform))
adapter.set_authorization_check(
self._make_adapter_auth_check(adapter.platform, profile_name=profile_name)
)
adapter._busy_text_mode = self._busy_text_mode
try:
@ -9092,6 +9094,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
def _make_adapter_auth_check(
self,
platform: Platform,
profile_name: Optional[str] = None,
) -> Callable[[str, Optional[str], Optional[str]], bool]:
"""Build a platform-bound auth callback for adapter use.
@ -9104,6 +9107,10 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
The returned callback delegates to :meth:`_is_user_authorized` so the
full auth chain platform allowlists, group allowlists, pairing
store, allow-all flags stays the single source of truth.
``profile_name`` binds the callback to the secondary adapter's own
multiplex profile, so its ``SessionSource`` resolves that profile's
secret scope instead of falling back to the active profile.
"""
def check(
user_id: str,
@ -9117,6 +9124,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
chat_id=chat_id or "",
chat_type=chat_type or "group",
user_id=user_id,
profile=profile_name,
)
return self._is_user_authorized(source)
return check

View file

@ -139,6 +139,56 @@ def test_secondary_allowlist_dm_behavior_ignores_unauthorized(monkeypatch):
assert runner._get_unauthorized_dm_behavior(Platform.WECOM) == "ignore"
def test_adapter_auth_check_stamps_secondary_profile(monkeypatch):
"""The adapter auth-check callback must stamp its own secondary profile.
Regression for the gap where ``_make_adapter_auth_check`` built a
profile-less ``SessionSource``, so a secondary adapter's external-context
authorization (e.g. Slack/Discord thread-reply lookups) silently
resolved the *active* profile's allowlist scope instead of its own.
"""
from gateway.run import GatewayRunner
_clear_auth_env(monkeypatch)
runner = object.__new__(GatewayRunner)
runner.config = GatewayConfig(multiplex_profiles=True)
captured: dict = {}
def fake_is_user_authorized(source):
captured["profile"] = source.profile
return True
runner._is_user_authorized = fake_is_user_authorized
check = runner._make_adapter_auth_check(Platform.WECOM, profile_name="coder")
assert check("some-user", "dm", "dm-chat") is True
assert captured["profile"] == "coder"
def test_adapter_auth_check_defaults_to_active_profile(monkeypatch):
"""Primary-adapter callbacks (no profile_name) still resolve the active profile."""
from gateway.run import GatewayRunner
_clear_auth_env(monkeypatch)
runner = object.__new__(GatewayRunner)
runner.config = GatewayConfig(multiplex_profiles=True)
captured: dict = {}
def fake_is_user_authorized(source):
captured["profile"] = source.profile
return True
runner._is_user_authorized = fake_is_user_authorized
check = runner._make_adapter_auth_check(Platform.WECOM)
assert check("some-user", "dm", "dm-chat") is True
assert captured["profile"] is None
def test_secondary_open_policy_fails_startup_guard(monkeypatch):
"""Secondary profiles must pass the same open-policy startup guard."""
from gateway.run import _own_policy_open_startup_violation