Skip to content

Clear idp cookie after succesful SSO#47569

Open
ksykulev wants to merge 1 commit into
mainfrom
47343-idp-cookie
Open

Clear idp cookie after succesful SSO#47569
ksykulev wants to merge 1 commit into
mainfrom
47343-idp-cookie

Conversation

@ksykulev

@ksykulev ksykulev commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Related issue: Resolves #47343

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information.

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually

Summary by CodeRabbit

  • New Features
    • Added support for identity provider information during enrollment.
    • SSO authentication cookies are now cleared after successful authentication.

Copilot AI review requested due to automatic review settings June 13, 2026 22:29
@ksykulev ksykulev requested review from a team as code owners June 13, 2026 22:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

  • Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.

Pull request overview

This PR updates the end-user OTA enrollment flow to prevent reusing a stale IdP identity across enrollments by clearing the BYOD IdP cookie after successful SSO, and by plumbing an IdP UUID into the Android enrollment token request when needed.

Changes:

  • Clears the BYOD IdP cookie after creating an Android enrollment token (via SetCookies on the response).
  • Passes an IdpUUID value through the /enroll HTML template and into the Android enrollment token fetch as idp_uuid.
  • Updates the Android enrollment token request decoder to accept idp_uuid from the query string before falling back to the cookie.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
server/service/frontend.go Passes IdpUUID into the enroll template and clears the BYOD IdP cookie before rendering the enroll page (needs adjustment to avoid breaking iOS/iPadOS flows).
server/mdm/android/service/service.go Accepts idp_uuid query param for enrollment token requests before checking the cookie.
server/mdm/android/service.go Adds SetCookies to clear the BYOD IdP cookie after successfully creating an enrollment token; introduces a cookie name constant.
frontend/templates/enroll-ota.html Appends idp_uuid to the Android enrollment token fetch URL when present.
changes/47343-idp-cookie User-visible change entry (content excluded from review).
Files excluded by content exclusion policy (1)
  • changes/47343-idp-cookie

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +175 to +179
// Clear the BYOD IdP cookie now that we are about to render the enrollment page.
var idpUUID string
if authRequired {
idpUUID = r.URL.Query().Get("enrollment_reference")
http.SetCookie(w, &http.Cookie{
"google.golang.org/api/androidmanagement/v1"
)

const byodIdpCookieName = "__Host-FLEETBYODIDP"
@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.27027% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.18%. Comparing base (268c918) to head (f988110).

Files with missing lines Patch % Lines
server/mdm/android/service/service.go 14.28% 5 Missing and 1 partial ⚠️
server/service/frontend.go 83.33% 2 Missing and 1 partial ⚠️
server/mdm/android/service.go 83.33% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #47569      +/-   ##
==========================================
- Coverage   67.19%   67.18%   -0.01%     
==========================================
  Files        3616     3616              
  Lines      229016   229048      +32     
  Branches    11785    11929     +144     
==========================================
+ Hits       153885   153890       +5     
- Misses      61303    61319      +16     
- Partials    13828    13839      +11     
Flag Coverage Δ
backend 68.83% <70.27%> (-0.01%) ⬇️
frontend 57.97% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This change updates Android enrollment to carry an IdP UUID from the server-rendered enrollment page into the enrollment-token request. The frontend handler now passes IdpUUID into the template and clears the BYOD IdP cookie after authentication. The client template appends idp_uuid to the token request URL when present. On the backend, token request decoding prefers the idp_uuid query parameter over the BYOD cookie, and successful token responses clear the BYOD IdP cookie. A change note documents the cookie-clearing behavior.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: clearing the IdP cookie after successful SSO for Android enrollment.
Linked Issues check ✅ Passed The code updates cookie clearing and IdP UUID handling to restore the EUA prompt and identity linking for fully-managed Android enrollment.
Out of Scope Changes check ✅ Passed The changes appear focused on the enrollment/SSO flow described by the issue, with no obvious unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description matches the required template, includes the related issue, and covers changes file and testing checkboxes.

✏️ 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 47343-idp-cookie

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
server/mdm/android/service.go (1)

10-10: ⚡ Quick win

Use the shared BYOD IdP cookie constant instead of duplicating the literal.

byodIdpCookieName duplicates a cross-layer contract value. If the canonical cookie name changes, this cookie-clearing path can silently break.

Proposed change
 import (
 	"context"
 	"net/http"
 
+	shared_mdm "github.com/fleetdm/fleet/v4/pkg/mdm"
 	"google.golang.org/api/androidmanagement/v1"
 )
-
-const byodIdpCookieName = "__Host-FLEETBYODIDP"
@@
 	http.SetCookie(w, &http.Cookie{
-		Name:     byodIdpCookieName,
+		Name:     shared_mdm.BYODIdpCookieName,
 		Value:    "",
 		Path:     "/",
🤖 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/mdm/android/service.go` at line 10, The constant byodIdpCookieName is
currently defined as a local literal value "__Host-FLEETBYODIDP" in this file,
which duplicates the canonical definition elsewhere in the codebase. Remove the
local constant definition and instead import and use the shared
byodIdpCookieName constant from its canonical location (likely in a shared
utilities or constants package) to ensure consistency across layers and prevent
the cookie-clearing logic from breaking if the canonical cookie name is updated.
🤖 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/mdm/android/service/service.go`:
- Around line 500-506: The idp_uuid query parameter is being accepted directly
without server-side validation, allowing any caller with an enroll_secret to
bind an enrollment to any IdP UUID without fresh authentication. Remove or
significantly restrict the code block that extracts idp_uuid from the query
parameter and directly populates it in the enrollmentTokenRequest struct.
Instead, only accept idp_uuid when it is cryptographically bound to
server-trusted state, such as validation against an active session cookie or a
signed one-time token generated by the server during a prior authenticated
operation. Ensure that the enrollmentTokenRequest construction validates the
identity proof before incorporating any IdP identifier.

---

Nitpick comments:
In `@server/mdm/android/service.go`:
- Line 10: The constant byodIdpCookieName is currently defined as a local
literal value "__Host-FLEETBYODIDP" in this file, which duplicates the canonical
definition elsewhere in the codebase. Remove the local constant definition and
instead import and use the shared byodIdpCookieName constant from its canonical
location (likely in a shared utilities or constants package) to ensure
consistency across layers and prevent the cookie-clearing logic from breaking if
the canonical cookie name is updated.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e13046bb-cb8e-4cf9-85df-43df31ff1b85

📥 Commits

Reviewing files that changed from the base of the PR and between 268c918 and f988110.

📒 Files selected for processing (5)
  • changes/47343-idp-cookie
  • frontend/templates/enroll-ota.html
  • server/mdm/android/service.go
  • server/mdm/android/service/service.go
  • server/service/frontend.go

Comment on lines +500 to +506
if idpUUID := r.URL.Query().Get("idp_uuid"); idpUUID != "" {
return &enrollmentTokenRequest{
EnrollSecret: enrollSecret,
IdpUUID: idpUUID,
FullyManaged: fullyManaged,
}, nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Do not treat raw idp_uuid query input as authenticated identity proof.

This early return bypasses cookie/session validation. A caller with enroll_secret and any valid IdP UUID can mint enrollment tokens without fresh SSO and bind the enrollment to that identity. idp_uuid should be accepted only when bound to server-trusted state (e.g., validated session/cookie match or signed one-time server token).

🤖 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/mdm/android/service/service.go` around lines 500 - 506, The idp_uuid
query parameter is being accepted directly without server-side validation,
allowing any caller with an enroll_secret to bind an enrollment to any IdP UUID
without fresh authentication. Remove or significantly restrict the code block
that extracts idp_uuid from the query parameter and directly populates it in the
enrollmentTokenRequest struct. Instead, only accept idp_uuid when it is
cryptographically bound to server-trusted state, such as validation against an
active session cookie or a signed one-time token generated by the server during
a prior authenticated operation. Ensure that the enrollmentTokenRequest
construction validates the identity proof before incorporating any IdP
identifier.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: aggregate-result

Failed stage: Check for failures [❌]

Failed test name: integration-enterprise-mysql8.0.44

Failure summary:

The action failed because at least one test job reported a failed status:
- The status file
./integration-enterprise-mysql8.0.44-status/status contained fail, so the workflow flagged
integration-enterprise-mysql8.0.44 as failed (lines 179-180).
- The aggregator step then exited with
code 1 after detecting non-empty failed_tests (lines 162-165, 189-190).

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

124:  Artifact download completed successfully.
125:  Extracting artifact entry: /home/runner/work/fleet/fleet/integration-enterprise-mysql8.0.44-status/status
126:  Extracting artifact entry: /home/runner/work/fleet/fleet/fast-status/status
127:  Artifact download completed successfully.
128:  Artifact download completed successfully.
129:  Extracting artifact entry: /home/runner/work/fleet/fleet/vuln-mysql8.0.44-status/status
130:  Extracting artifact entry: /home/runner/work/fleet/fleet/integration-core-mysql8.0.44-status/status
131:  Artifact download completed successfully.
132:  Artifact download completed successfully.
133:  Extracting artifact entry: /home/runner/work/fleet/fleet/service-mysql8.0.44-status/status
134:  Artifact download completed successfully.
135:  Extracting artifact entry: /home/runner/work/fleet/fleet/scripts-status/status
136:  Artifact download completed successfully.
137:  Total of 10 artifact(s) downloaded
138:  Download artifact has finished successfully
139:  ##[group]Run failed_tests=""
140:  �[36;1mfailed_tests=""�[0m
141:  �[36;1mstatus_count=0�[0m
142:  �[36;1m# Find all status files (they are in directories like 'fleetctl-mysql8.0.44-status/status')�[0m
143:  �[36;1mfor status_file in $(find ./ -type f -name 'status'); do�[0m
144:  �[36;1m  status_count=$((status_count + 1))�[0m
145:  �[36;1m  # Extract test name from parent directory (e.g., 'fleetctl-mysql8.0.44-status')�[0m
146:  �[36;1m  test_dir=$(basename $(dirname "$status_file"))�[0m
147:  �[36;1m  # Remove '-status' suffix to get the test name�[0m
148:  �[36;1m  test_name="${test_dir%-status}"�[0m
149:  �[36;1m  status_content=$(cat "$status_file")�[0m
150:  �[36;1m  echo "Processing: $status_file (Test: $test_name) with status content: $status_content"�[0m
151:  �[36;1m  if grep -q "fail" "$status_file"; then�[0m
152:  �[36;1m    echo "  ❌ Test failed: $test_name"�[0m
153:  �[36;1m    failed_tests="${failed_tests}${test_name}, "�[0m
154:  �[36;1m  else�[0m
155:  �[36;1m    echo "  ✅ Test passed: $test_name"�[0m
156:  �[36;1m  fi�[0m
157:  �[36;1mdone�[0m
158:  �[36;1mif [[ $status_count -eq 0 ]]; then�[0m
159:  �[36;1m  echo "❌ ERROR: No status files found! This indicates a workflow issue."�[0m
160:  �[36;1m  exit 1�[0m
161:  �[36;1mfi�[0m
162:  �[36;1mif [[ -n "$failed_tests" ]]; then�[0m
163:  �[36;1m  echo "❌ One or more test jobs failed: ${failed_tests%, }"�[0m
164:  �[36;1m  exit 1�[0m
165:  �[36;1mfi�[0m
166:  �[36;1mecho "✅ All test jobs succeeded."�[0m
167:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
168:  ##[endgroup]
169:  Processing: ./fleetctl-mysql8.0.44-status/status (Test: fleetctl-mysql8.0.44) with status content: success
170:  ✅ Test passed: fleetctl-mysql8.0.44
171:  Processing: ./vuln-mysql8.0.44-status/status (Test: vuln-mysql8.0.44) with status content: success
172:  ✅ Test passed: vuln-mysql8.0.44
173:  Processing: ./service-mysql8.0.44-status/status (Test: service-mysql8.0.44) with status content: success
174:  ✅ Test passed: service-mysql8.0.44
175:  Processing: ./integration-core-mysql8.0.44-status/status (Test: integration-core-mysql8.0.44) with status content: success
176:  ✅ Test passed: integration-core-mysql8.0.44
177:  Processing: ./mysql-mysql8.0.44-status/status (Test: mysql-mysql8.0.44) with status content: success
178:  ✅ Test passed: mysql-mysql8.0.44
179:  Processing: ./integration-enterprise-mysql8.0.44-status/status (Test: integration-enterprise-mysql8.0.44) with status content: fail
180:  ❌ Test failed: integration-enterprise-mysql8.0.44
181:  Processing: ./integration-mdm-mysql8.0.44-status/status (Test: integration-mdm-mysql8.0.44) with status content: success
182:  ✅ Test passed: integration-mdm-mysql8.0.44
183:  Processing: ./scripts-status/status (Test: scripts) with status content: success
184:  ✅ Test passed: scripts
185:  Processing: ./fast-status/status (Test: fast) with status content: success
186:  ✅ Test passed: fast
187:  Processing: ./main-mysql8.0.44-status/status (Test: main-mysql8.0.44) with status content: success
188:  ✅ Test passed: main-mysql8.0.44
189:  ❌ One or more test jobs failed: integration-enterprise-mysql8.0.44
190:  ##[error]Process completed with exit code 1.
191:  Post job cleanup.

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.

Fully-managed Android devices aren't seeing EUA prompt during the enrollment

2 participants