Skip to content

MM-69450: Surface SAML SSO authorization failures to users#1030

Closed
jgheithcock wants to merge 1 commit into
masterfrom
mm-69450
Closed

MM-69450: Surface SAML SSO authorization failures to users#1030
jgheithcock wants to merge 1 commit into
masterfrom
mm-69450

Conversation

@jgheithcock

@jgheithcock jgheithcock commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Detect GitHub API responses that indicate missing SAML SSO authorization (403 body, X-GitHub-SSO header, GraphQL errors)
  • Notify users with a deduplicated bot DM instructing them to /github disconnect and /github connect again
  • Show the same guidance in the GitHub RHS panel and on link hover previews when PR/issue fetch fails due to SAML

Test plan

  • Connect a test user to a GitHub account with access to a SAML-protected org
  • Revoke SSO authorization for the Mattermost OAuth app on GitHub for that org
  • Open the GitHub sidebar — confirm warning message appears and a bot DM is received (once)
  • Paste a PR link and hover — confirm tooltip shows the SAML guidance message
  • Run /github disconnect then /github connect, re-authorize SAML on GitHub — confirm warnings clear and data loads again
  • Unit tests: go test ./server/githuberrors/... ./server/plugin/... -run SAML
  • make check-style passes

Change Impact: High 🔴

Reasoning: This change affects GitHub authentication/authorization handling and surfaces failures across critical user-facing flows. It also spans backend error detection, notification persistence, API responses, and multiple UI entry points, increasing the blast radius.

Regression Risk: Moderate to high, since the new SAML SSO handling path is adjacent to existing GitHub request/error handling and introduces behavior changes in sidebar data loading, link previews, and bot DM notifications.

** QA Recommendation:** Manual QA is strongly recommended, especially for revoked/partial SSO authorization flows, repeated notifications, sidebar messaging, and link hover behavior. Skipping manual QA is not advised.
Generated by CodeRabbitAI

When GitHub rejects API calls due to missing SAML SSO authorization, notify
users via a deduplicated bot DM and show guidance in the RHS and link tooltips.
@jgheithcock jgheithcock requested a review from a team as a code owner June 24, 2026 18:39
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds GitHub SAML SSO error detection, threads SAML-required state through server data and API responses, sends one-time notifications, and renders SAML messages in the sidebar and link tooltip UI.

Changes

SAML SSO handling

Layer / File(s) Summary
GitHub SAML detection
server/githuberrors/saml.go, server/githuberrors/saml_test.go
Adds IsSAMLSSORequired plus helpers for GitHub SAML enforcement messages, with unit coverage for header, status, and message cases.
Notification helpers and plugin hooks
server/plugin/saml_sso.go, server/plugin/saml_sso_test.go, server/plugin/api.go, server/plugin/plugin.go
Adds per-user SAML notification state, bot DM notification handling, SAML-specific API error writing, and notification cleanup on connect, disconnect, and GitHub client errors.
Sidebar SAML state propagation
server/plugin/graphql/lhs_request.go, server/plugin/api.go
Threads a samlSSORequired flag through LHS data loading and sidebar assembly, and adds saml_sso_message to the sidebar payload.
GitHub API SAML responses
server/plugin/api.go
PR details and issue/PR fetch failures now route through SAML-specific handling before the existing error path.
Sidebar warning message
webapp/src/types/github_types.ts, webapp/src/selectors.ts, webapp/src/components/sidebar_right/*
Carries the sidebar SAML message through webapp state and renders it as a warning alert in the right sidebar.
Link tooltip error state
webapp/src/components/link_tooltip/link_tooltip.jsx
Stores SAML-related fetch errors, skips retries when one is present, and renders an error tooltip instead of fetched link data.

Sequence Diagram(s)

sequenceDiagram
  participant getIssueByNumber
  participant writeSAMLSSOErrorIfNeeded
  participant notifySAMLSSORequired
  participant store
  getIssueByNumber->>writeSAMLSSOErrorIfNeeded: pass GitHub error
  writeSAMLSSOErrorIfNeeded->>notifySAMLSSORequired: SAML SSO required
  notifySAMLSSORequired->>store: Get and Set notification key
  writeSAMLSSOErrorIfNeeded-->>getIssueByNumber: return true after 403
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I hopped through SAML moonlit snow,
And shared the warning soft and low.
The sidebar glowed, the tooltip sighed,
With tiny hops the errors hid no more inside.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: surfacing SAML SSO authorization failures to users.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mm-69450

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/plugin/saml_sso.go`:
- Around line 19-29: The SAML SSO notification flow in notifySAMLSSORequired is
racy because it does Get, then CreateBotDMPost, then Set, allowing concurrent
callers from the API path to send duplicate DMs. Update the logic to atomically
claim the samlSSONotifiedKey before posting, or otherwise serialize access
around the check-and-set sequence, so only one goroutine can proceed to
CreateBotDMPost. Keep the existing store and logging flow in the Plugin method,
but make the notification state transition happen as a single guarded operation.

In `@webapp/src/components/link_tooltip/link_tooltip.jsx`:
- Around line 70-75: The tooltip’s fetch effect in link_tooltip.jsx is being
short-circuited by a stale errorMessage, so reconnects never trigger initData
again. Update the logic around the effect and the data/error state handling in
LinkTooltip so a successful reconnect clears or ignores the previous error
before retrying, and ensure the early return only blocks when the current state
truly prevents a refetch. Apply the same stale-error handling to the related
tooltip rendering paths in the other affected section as well.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 10fb2f16-3aaf-421a-8f09-dbae26a68f16

📥 Commits

Reviewing files that changed from the base of the PR and between 9d74db1 and 0bdf45e.

📒 Files selected for processing (12)
  • server/githuberrors/saml.go
  • server/githuberrors/saml_test.go
  • server/plugin/api.go
  • server/plugin/graphql/lhs_request.go
  • server/plugin/plugin.go
  • server/plugin/saml_sso.go
  • server/plugin/saml_sso_test.go
  • webapp/src/components/link_tooltip/link_tooltip.jsx
  • webapp/src/components/sidebar_right/index.jsx
  • webapp/src/components/sidebar_right/sidebar_right.jsx
  • webapp/src/selectors.ts
  • webapp/src/types/github_types.ts

Comment thread server/plugin/saml_sso.go
Comment on lines +19 to +29
func (p *Plugin) notifySAMLSSORequired(userID string) {
key := userID + samlSSONotifiedKey
var notified bool
if err := p.store.Get(key, &notified); err == nil && notified {
return
}

p.CreateBotDMPost(userID, samlSSOUserMessage, "custom_git_saml_sso")
if _, err := p.store.Set(key, true); err != nil {
p.client.Log.Warn("Failed to store SAML SSO notification state", "userID", userID, "error", err.Error())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Make the notification claim atomic.

notifySAMLSSORequired does GetCreateBotDMPostSet, so two concurrent SAML failures can both observe "not notified" and each send a DM before either writes the flag. server/plugin/api.go, Lines 818-845 already invoke this path from parallel goroutines, so the dedup guarantee is racy. Claim the KV key atomically, or otherwise serialize this section across callers, before posting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/plugin/saml_sso.go` around lines 19 - 29, The SAML SSO notification
flow in notifySAMLSSORequired is racy because it does Get, then CreateBotDMPost,
then Set, allowing concurrent callers from the API path to send duplicate DMs.
Update the logic to atomically claim the samlSSONotifiedKey before posting, or
otherwise serialize access around the check-and-set sequence, so only one
goroutine can proceed to CreateBotDMPost. Keep the existing store and logging
flow in the Plugin method, but make the notification state transition happen as
a single guarded operation.

Comment on lines +70 to +75
if (!connected || data || errorMessage || ((typeof (show) !== 'undefined' || show != null) && !show)) {
return;
}

initData();
}, [connected, data, href, show, enterpriseURL]);
}, [connected, data, errorMessage, href, show, enterpriseURL]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Stale errorMessage blocks re-fetch after reconnect

Line 70 short-circuits when errorMessage is set, but nothing clears that state on later retries. After /github connect, this tooltip can remain stuck showing the old SAML error until remount.

Suggested fix
 useEffect(() => {
+    // reset stale state when input context changes
+    setErrorMessage(null);
+    setData(null);
+
     const initData = async () => {
         ...
     };

     // show is not provided for Mattermost Server < 5.28
-    if (!connected || data || errorMessage || ((typeof (show) !== 'undefined' || show != null) && !show)) {
+    if (!connected || data || ((typeof (show) !== 'undefined' || show != null) && !show)) {
         return;
     }

     initData();
 }, [connected, data, errorMessage, href, show, enterpriseURL]);

Also applies to: 137-154

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webapp/src/components/link_tooltip/link_tooltip.jsx` around lines 70 - 75,
The tooltip’s fetch effect in link_tooltip.jsx is being short-circuited by a
stale errorMessage, so reconnects never trigger initData again. Update the logic
around the effect and the data/error state handling in LinkTooltip so a
successful reconnect clears or ignores the previous error before retrying, and
ensure the early return only blocks when the current state truly prevents a
refetch. Apply the same stale-error handling to the related tooltip rendering
paths in the other affected section as well.

@jgheithcock jgheithcock marked this pull request as draft June 24, 2026 18:56
@jgheithcock

Copy link
Copy Markdown
Contributor Author

Closing as this is too large of a change for what we are looking to resolve.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant