Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/48805-byod-idp-enroll
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Fixed a bug where a user's BYOD selection was not persisted through IdP authentication
4 changes: 4 additions & 0 deletions server/service/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,10 @@ func initiateOTAEnrollSSO(svc fleet.Service, w http.ResponseWriter, r *http.Requ
if r.URL.Query().Get("fully_managed") == "true" {
requestURL += "&fully_managed=true"
}
// Same with the byod modifier parameter for Apple enrollments
if r.URL.Query().Get("byod") == "true" {
requestURL += "&byod=true"
}
ssnID, ssnDurationSecs, idpURL, err := svc.InitiateMDMSSO(r.Context(), fleet.SSOInitiatorOTAEnroll, requestURL, "")
if err != nil {
return err
Expand Down
70 changes: 70 additions & 0 deletions server/service/frontend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,76 @@ func TestServeEndUserEnrollOTA(t *testing.T) {
}
}

// ssoURLCaptureService captures the customOriginalURL passed to InitiateMDMSSO so
// tests can assert which query parameters survive into the SAML round-trip.
type ssoURLCaptureService struct {
fleet.Service
capturedOriginalURL string
}

func (s *ssoURLCaptureService) InitiateMDMSSO(_ context.Context, _, customOriginalURL, _ string) (string, int, string, error) {
s.capturedOriginalURL = customOriginalURL
return "session-id", 300, "https://idp.example.com/sso", nil
}

// The original URL is where the user lands after completing SAML auth, so any
// enrollment query parameter (fully_managed, byod) must be threaded through it or
// it is lost across the round-trip.
func TestInitiateOTAEnrollSSOPersistsQueryParams(t *testing.T) {
for _, tc := range []struct {
name string
query string
wantContains []string
wantExcludes []string
}{
{
name: "byod true is persisted",
query: "byod=true",
wantContains: []string{"&byod=true"},
},
{
name: "byod absent is not added",
query: "",
wantExcludes: []string{"byod"},
},
{
name: "byod false is not persisted",
query: "byod=false",
wantExcludes: []string{"byod"},
},
{
name: "byod and fully_managed both persisted",
query: "byod=true&fully_managed=true",
wantContains: []string{"&byod=true", "&fully_managed=true"},
},
} {
t.Run(tc.name, func(t *testing.T) {
svc := &ssoURLCaptureService{}
target := "/enroll?enroll_secret=foo"
if tc.query != "" {
target += "&" + tc.query
}
req := httptest.NewRequest(http.MethodGet, target, nil)
rec := httptest.NewRecorder()

err := initiateOTAEnrollSSO(svc, rec, req, "foo")
require.NoError(t, err)

require.Contains(t, svc.capturedOriginalURL, "enroll_secret=foo")
for _, want := range tc.wantContains {
require.Contains(t, svc.capturedOriginalURL, want)
}
for _, exclude := range tc.wantExcludes {
require.NotContains(t, svc.capturedOriginalURL, exclude)
}

// The handler should redirect the browser to the IdP.
require.Equal(t, http.StatusSeeOther, rec.Code)
require.Equal(t, "https://idp.example.com/sso", rec.Header().Get("Location"))
})
}
}

func TestServeEndUserEnrollOTAClearsCookieForFullyManaged(t *testing.T) {
if !hasBuildTag("full") {
t.Skip("This test requires running with -tags full")
Expand Down
Loading