feat(mattermost): per-channel allow_from restricts who can address the bot in a listed channel - #89972
feat(mattermost): per-channel allow_from restricts who can address the bot in a listed channel#89972100yenadmin wants to merge 1 commit into
Conversation
Well-scoped feature reusing upstream config vocabulary (
No blocking issues found. — reviewer-b (automated review) |
080b293 to
d14c5b3
Compare
…e bot in a listed channel Enforce groups.<channel-id>.allow_from at Mattermost WebSocket intake without duplicating the gateway's existing DM allow_from and pairing authorization path. Exact, case-insensitive, and wildcard channel keys are supported. Scalar sender IDs normalize to one entry; malformed allowlists and non-mapping channel entries warn once per config key and fail closed for that channel. Unlisted channels and group messages retain existing behavior. Focused behavior proof: tests/gateway/test_mattermost.py is red against upstream/main and green on this change.
d14c5b3 to
56abbb9
Compare
|
Narrowed after re-verifying on current main (head 56abbb9, rebased onto 1ab32b2): the adapter-level DM filter is gone. Top-level What remains is the per-channel half: Focused |
What does this PR do?
Adds an optional sender allowlist for explicitly listed Mattermost channels. A channel entry under
extra.groups.<channel-id>can useallow_fromorallowFrom; exact channel keys win, followed by a case-insensitive match and then the"*"key fallback. A"*"allowlist member admits any sender. Sender IDs are compared as exact strings; the adapter does not resolve usernames.Why in the adapter: the shared gateway already recognizes this vocabulary —
_adapter_group_has_sender_allowlist(gateway/authz_mixin.py:355-378) resolvesgroups.<id>.allow_from/allowFromwith the same exact, case-insensitive and"*"lookup, and for adapters that enforce their own access policy the own-policy path treats a configured per-group list as proof that the adapter gated senders at intake (:398-399). The Mattermost adapter had no such gate between channel resolution and mention gating, so a configured per-channel list had no effect there. This PR adds exactly that gate, with the same lookup order.Malformed authorization config does not fail open. A bare integer or string is treated as one sender ID. Dicts, booleans, nested lists, and non-mapping channel entries log one warning per config key and deny everyone for that listed channel.
DM sender restriction already exists in the shared gateway: top-level
allow_fromis combined with the pairing store and platform-wide authorization. This PR deliberately does not duplicate that DM filter in the Mattermost adapter, so pairing andunauthorized_dm_behaviorcontinue to run through the existing gateway path.Unlisted channels—including Mattermost Group Messages (
channel_type "G")—retain upstream's existing behavior. Denials in listed channels remain visible at debug level; the first denial for each(channel_id, sender_id)pair is also logged at info level, with the in-adapter set capped at 512 entries.Related Issue
No open issue. This adds Mattermost enforcement for the per-group
groups.<id>.allow_fromvocabulary already recognized by shared gateway authorization.Type of Change
Changes Made
plugins/platforms/mattermost/adapter.py:66-130— normalizes scalar/flat-list sender IDs, resolves exact/case-insensitive/wildcard channel keys, warns once for malformed keys, and fails closed only for the affected listed channel.plugins/platforms/mattermost/adapter.py:192-193— keeps bounded denial-log state and once-per-key malformed-config warning state.plugins/platforms/mattermost/adapter.py:630-647— applies the sender restriction only inside the existing non-DM branch, before mention gating and downstream handling.tests/gateway/test_mattermost.py:397-511— covers listed sender allow/deny, unlisted Group Message behavior, empty-list inheritance, integer/string scalars, wildcard member/key behavior, case-insensitive channel keys, and malformed-shape fail-closed warnings.A question for you on the channel default
I chose inherit-existing for an absent or empty channel allowlist. A channel not present in
groups, a listed channel with noallow_fromkey, or a valid empty allowlist keeps the existing Mattermost behavior. Only an explicitly configured, non-empty allowlist narrows senders. Malformed configured values are different: they warn and fail closed.The stricter alternative is deny-empty for listed channels: once a channel appears in
groups, a missing or emptyallow_fromwould deny everyone there. That is safer for incomplete rollout config but changes the meaning of existing channel entries that carry other settings._channel_scope_allowsis the single decision point if maintainers prefer that policy.Example:
How to Test
upstream/main(1ab32b212b3828be8239bd68ac3687756bf7c5c2), retain this PR's test changes while restoringplugins/platforms/mattermost/adapter.pyfrom the base.scripts/run_tests.sh tests/gateway/test_mattermost.py -q. Expected before the change: 30 passed, 8 failed.56abbb9b94068d5ff1ad460fa4ac3bb3da905ca1) and rerun the same command. Expected: 38 passed, 0 failed.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass — focused file only, per repository local-test limitsDocumentation & Housekeeping
docs/, docstrings) — left open until maintainers settle the channel default question abovecli-config.yaml.exampleif I added/changed config keys — N/A; this reuses existingextra.groupsshapesCONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/AScreenshots / Logs
Not applicable; focused automated behavior proof is described above.
What this PR does NOT change: DM authorization (
allow_from, pairing store,unauthorized_dm_behavior, orMATTERMOST_ALLOWED_USERS);allowed_channels,require_mention, orfree_response_channels; unlisted channel behavior, including Group Messages; shared gateway authorization code; docs; or any live Mattermost/runtime deployment.