Skip to content

MM-67866 Improving connection logic#980

Merged
avasconcelos114 merged 3 commits intomasterfrom
MM-67866
Mar 18, 2026
Merged

MM-67866 Improving connection logic#980
avasconcelos114 merged 3 commits intomasterfrom
MM-67866

Conversation

@avasconcelos114
Copy link
Contributor

@avasconcelos114 avasconcelos114 commented Mar 13, 2026

Summary

This PR refactors part of the oauth callback flows to better match plugin configurations

Ticket Link

Fixes https://mattermost.atlassian.net/browse/MM-67866

Change Impact: 🔴 High

Reasoning: The PR alters OAuth callback handling and adds OAuth scope validation in the authentication flow, and updates the GitHubUserInfo struct — changes that affect core authentication/authorization and public data structures used across the plugin.

Regression Risk: High — introducing stricter scope validation can block legitimate connections; modifications to a public struct increase risk of incompatibilities and ripple effects. While unit tests were added, broader end-to-end coverage with real GitHub OAuth responses is limited.

QA Recommendation: Perform thorough manual QA of end-to-end OAuth flows (public-only and private repo scenarios), validate behavior with existing tokens of varied scopes, and test plugin upgrade paths for stored user info. Manual QA is strongly recommended and skipping it is not advised.
Generated by CodeRabbitAI

@avasconcelos114 avasconcelos114 self-assigned this Mar 13, 2026
@avasconcelos114 avasconcelos114 requested a review from a team as a code owner March 13, 2026 16:23
@avasconcelos114 avasconcelos114 added 2: Dev Review Requires review by a core committer 3: Security Review Review requested from Security Team labels Mar 13, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0b4ee15a-b56a-45fd-819d-50821921b7e7

📥 Commits

Reviewing files that changed from the base of the PR and between 6b0d4f0 and 12230b5.

📒 Files selected for processing (3)
  • server/plugin/api.go
  • server/plugin/plugin.go
  • server/plugin/utils_test.go

📝 Walkthrough

Walkthrough

Adds OAuth scope parsing and validation to the GitHub OAuth flow, enforces the EnablePrivateRepo setting when "private" is requested, extends GitHub user info struct, and adds unit tests covering scope parsing, validation, and the private-repo-disabled API path.

Changes

Cohort / File(s) Summary
OAuth scope helpers & types
server/plugin/plugin.go
Adds oauthScopesHeader, validateOAuthScopes(resp, privateAllowed) and parseScopes(header); expands GitHubUserInfo with new fields (GitHubUsername, LastToDoPostAt, Settings, AllowedPrivateRepos, MM34646ResetTokenDone).
API enforcement & flow
server/plugin/api.go
Enforces EnablePrivateRepo when ?private=true is requested in connectUserToGitHub(); in completeConnectUserToGitHub() captures GitHub response and validates OAuth scopes, aborting with 403 on mismatch.
Tests
server/plugin/api_test.go, server/plugin/utils_test.go
Adds TestConnectUserToGitHub_PrivateRepoDisabled; adds TestParseScopes and TestValidateOAuthScopes with table-driven cases and helper response construction.
Messaging fix
server/plugin/command.go
Minor grammar correction in the private repository disabled message string.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as Server API
    participant GitHub as GitHub OAuth

    Client->>API: GET /oauth/connect?private=true
    API->>API: Check EnablePrivateRepo flag
    alt Private Repos Disabled
        API-->>Client: 403 Forbidden (private repos disabled)
    else Private Repos Enabled
        API->>GitHub: Exchange code / Fetch authenticated user
        GitHub-->>API: User data + X-OAuth-Scopes header
        API->>API: parseScopes() -> validateOAuthScopes(resp, privateAllowed)
        alt Scopes Valid
            API-->>Client: 200 OK (authentication complete)
        else Scopes Invalid
            API-->>Client: 403 Forbidden (scope validation failed)
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through headers, trimmed each scope,

With tiny paws I checked each rope,
Private doors now need the key,
Tokens vetted, safe and free,
A joyful hop for secure repo!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'MM-67866 Improving connection logic' is vague and generic, using non-descriptive terms like 'Improving connection logic' that don't convey the specific nature of the changes (OAuth scope validation, private repo enforcement). Use a more specific title that describes the main change, such as 'Add OAuth scope validation and private repo enforcement' or 'Validate OAuth scopes on GitHub connection'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch MM-67866
📝 Coding Plan
  • Generate coding plan for human review comments

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
server/plugin/plugin.go (2)

612-616: Use slices.Contains for cleaner iteration.

The static analysis tool flagged this loop as simplifiable. Since the slices package is already imported, this can be modernized.

♻️ Suggested refactor
-	for _, s := range granted {
-		if s == string(github.ScopeRepo) {
-			return fmt.Errorf("token was granted %q scope but only %q was requested", github.ScopeRepo, github.ScopePublicRepo)
-		}
-	}
+	if slices.Contains(granted, string(github.ScopeRepo)) {
+		return fmt.Errorf("token was granted %q scope but only %q was requested", github.ScopeRepo, github.ScopePublicRepo)
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/plugin/plugin.go` around lines 612 - 616, Replace the manual loop that
checks for github.ScopeRepo in the granted slice with slices.Contains to
simplify the check: in the block that iterates over granted and compares s ==
string(github.ScopeRepo), use slices.Contains(granted, string(github.ScopeRepo))
and keep the existing error return (fmt.Errorf("token was granted %q scope but
only %q was requested", github.ScopeRepo, github.ScopePublicRepo)) when true; no
other logic changes required.

621-630: Use strings.SplitSeq for more efficient ranging.

The static analysis tool flagged that ranging over SplitSeq is more efficient than splitting into a slice first.

♻️ Suggested refactor
 func parseScopes(header string) []string {
 	var scopes []string
-	for _, s := range strings.Split(header, ",") {
+	for s := range strings.SplitSeq(header, ",") {
 		s = strings.TrimSpace(s)
 		if s != "" {
 			scopes = append(scopes, s)
 		}
 	}
 	return scopes
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/plugin/plugin.go` around lines 621 - 630, The parseScopes function
currently builds a full slice via strings.Split then ranges it; change it to
iterate the strings.SplitSeq for the header instead to avoid allocating the
intermediate slice. Replace the strings.Split(header, ",") loop in parseScopes
with a loop over strings.SplitSeq(header, ",") (using the sequence's iteration
API to obtain each chunk), call strings.TrimSpace on each chunk, and append
non-empty results to scopes so the function behavior remains the same while
avoiding the temporary slice allocation.
server/plugin/api.go (1)

432-444: Good security enhancement with OAuth scope validation.

The scope validation ensures tokens don't have broader permissions than requested, preventing potential privilege escalation. The error handling and user messaging are clear.

One minor note: the static analysis flagged that err at line 439 shadows the declaration at line 389. While this is functionally correct (the shadowed variable is from a different scope), you could rename it for clarity.

♻️ Optional: Rename shadowed variable for clarity
-	if err := p.validateOAuthScopes(resp, state.PrivateAllowed); err != nil {
-		c.Log.WithError(err).Warnf("Mismatching OAuth scopes, rejecting connection")
+	if scopeErr := p.validateOAuthScopes(resp, state.PrivateAllowed); scopeErr != nil {
+		c.Log.WithError(scopeErr).Warnf("Mismatching OAuth scopes, rejecting connection")
 		rErr = errors.New("OAuth token scope does not match the requested permissions")
 		p.writeAPIError(w, &APIErrorResponse{Message: "OAuth token scope does not match the requested permissions. Please reconnect your GitHub account.", StatusCode: http.StatusForbidden})
 		return
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/plugin/api.go` around lines 432 - 444, The variable err returned by
githubClient.Users.Get shadows an earlier err; rename this inner error variable
to something non-shadowing (e.g., getUserErr) in the block that calls
githubClient.Users.Get and update the subsequent error checks and logging
(c.Log.WithError and any references) to use that new name; ensure you similarly
rename the err used in the validateOAuthScopes check if it conflicts, keeping
calls to validateOAuthScopes, p.writeAPIError, rErr assignment, gitUser and resp
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/plugin/utils_test.go`:
- Around line 322-328: The struct literal assigned to the variable "tests" in
utils_test.go is misformatted; run "gofmt -w server/plugin/utils_test.go" (or
apply gofmt formatting) to fix spacing/alignment for the fields (name, resp,
privateAllowed, enablePrivateRepo, expectError) so the file compiles and the
pipeline passes; ensure tabs/spaces and field alignment match gofmt's output for
the "tests" declaration and save the file.

---

Nitpick comments:
In `@server/plugin/api.go`:
- Around line 432-444: The variable err returned by githubClient.Users.Get
shadows an earlier err; rename this inner error variable to something
non-shadowing (e.g., getUserErr) in the block that calls githubClient.Users.Get
and update the subsequent error checks and logging (c.Log.WithError and any
references) to use that new name; ensure you similarly rename the err used in
the validateOAuthScopes check if it conflicts, keeping calls to
validateOAuthScopes, p.writeAPIError, rErr assignment, gitUser and resp
unchanged.

In `@server/plugin/plugin.go`:
- Around line 612-616: Replace the manual loop that checks for github.ScopeRepo
in the granted slice with slices.Contains to simplify the check: in the block
that iterates over granted and compares s == string(github.ScopeRepo), use
slices.Contains(granted, string(github.ScopeRepo)) and keep the existing error
return (fmt.Errorf("token was granted %q scope but only %q was requested",
github.ScopeRepo, github.ScopePublicRepo)) when true; no other logic changes
required.
- Around line 621-630: The parseScopes function currently builds a full slice
via strings.Split then ranges it; change it to iterate the strings.SplitSeq for
the header instead to avoid allocating the intermediate slice. Replace the
strings.Split(header, ",") loop in parseScopes with a loop over
strings.SplitSeq(header, ",") (using the sequence's iteration API to obtain each
chunk), call strings.TrimSpace on each chunk, and append non-empty results to
scopes so the function behavior remains the same while avoiding the temporary
slice allocation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3216030c-c755-4531-ac80-b5537aad36fa

📥 Commits

Reviewing files that changed from the base of the PR and between c4c0b47 and 6b0d4f0.

📒 Files selected for processing (5)
  • server/plugin/api.go
  • server/plugin/api_test.go
  • server/plugin/command.go
  • server/plugin/plugin.go
  • server/plugin/utils_test.go

Copy link
Contributor

@nevyangelova nevyangelova left a comment

Choose a reason for hiding this comment

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

LGTM

@avasconcelos114 avasconcelos114 removed the 2: Dev Review Requires review by a core committer label Mar 16, 2026
@edgarbellot edgarbellot requested review from hmohammed-prodsec and removed request for edgarbellot March 16, 2026 08:26
@hmohammed-prodsec hmohammed-prodsec removed the 3: Security Review Review requested from Security Team label Mar 18, 2026
@avasconcelos114 avasconcelos114 merged commit 6e6b740 into master Mar 18, 2026
19 checks passed
@avasconcelos114 avasconcelos114 deleted the MM-67866 branch March 18, 2026 13:22
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.

3 participants