fix(discord): harden DISCORD_ALLOWED_ROLES and cover gateway layer

Two follow-ups to the cherry-picked PR #9873 (`e3bcc819`):

1. `_is_allowed_user` now uses `getattr(self, '_allowed_*_ids', set())`
   so test fixtures that build the adapter via `object.__new__`
   (skipping __init__) don't crash with AttributeError.
   See AGENTS.md pitfall #17 — same pattern as gateway.run.

2. New 3-case regression coverage in test_discord_bot_auth_bypass.py:
   - role-only config bypasses the gateway 'no allowlists' branch
   - roles + users combined still authorizes user-allowlist matches
   - the role bypass does NOT leak to other platforms (Telegram, etc.)

3. Autouse fixture in test_discord_bot_auth_bypass.py clears all Discord
   auth env vars before each test so DISCORD_ALLOWED_ROLES leakage from
   a previous test in the session can't flip later 'should-reject' tests
   into false-pass.

Required because the bare cherry-pick of #9873 only added the adapter-
level role check — it didn't cover the gateway-level _is_user_authorized,
which still rejected role-only setups via the 'no allowlists configured'
branch.
This commit is contained in:
Teknium
2026-04-17 05:47:39 -07:00
committed by Teknium
parent 541a3e27d7
commit e5b880264b
2 changed files with 82 additions and 5 deletions

View File

@@ -1386,19 +1386,24 @@ class DiscordAdapter(BasePlatformAdapter):
When author is a Member, checks .roles directly; otherwise falls back
to scanning the bot's mutual guilds for a Member record.
"""
has_users = bool(self._allowed_user_ids)
has_roles = bool(self._allowed_role_ids)
# ``getattr`` fallbacks here guard against test fixtures that build
# an adapter via ``object.__new__(DiscordAdapter)`` and skip __init__
# (see AGENTS.md pitfall #17 — same pattern as gateway.run).
allowed_users = getattr(self, "_allowed_user_ids", set())
allowed_roles = getattr(self, "_allowed_role_ids", set())
has_users = bool(allowed_users)
has_roles = bool(allowed_roles)
if not has_users and not has_roles:
return True
# Check user ID allowlist
if has_users and user_id in self._allowed_user_ids:
if has_users and user_id in allowed_users:
return True
# Check role allowlist
if has_roles:
# Try direct role check from Member object
direct_roles = getattr(author, "roles", None) if author is not None else None
if direct_roles:
if any(getattr(r, "id", None) in self._allowed_role_ids for r in direct_roles):
if any(getattr(r, "id", None) in allowed_roles for r in direct_roles):
return True
# Fallback: scan mutual guilds for member's roles
if self._client is not None:
@@ -1412,7 +1417,7 @@ class DiscordAdapter(BasePlatformAdapter):
if m is None:
continue
m_roles = getattr(m, "roles", None) or []
if any(getattr(r, "id", None) in self._allowed_role_ids for r in m_roles):
if any(getattr(r, "id", None) in allowed_roles for r in m_roles):
return True
return False