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:
parent
0cc9426c6d
commit
fef0b2d600
2 changed files with 59 additions and 1 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue