add support for displaying EULA during ADE/DEP (#11532)

Related to #11350 and the sub-tasks for stuff that happens in setup
assistant: #11477 and #11479

This adds back-end and UI logic to show an EULA during DEP enrollment if
one was uploaded via the UI, if an EULA wasn't uploaded, we just proceed
to enroll the device right after authentication.


https://user-images.githubusercontent.com/4419992/236316655-282ee74a-5f79-4095-a950-82b77b80a5c0.mov
This commit is contained in:
Roberto Dip
2023-05-05 14:36:13 -03:00
committed by GitHub
parent c3d960a036
commit 33d788caed
21 changed files with 319 additions and 107 deletions
+1
View File
@@ -0,0 +1 @@
* Added support to add an EULA as part of the AEP/DEP unboxing flow.
+57 -1
View File
@@ -527,7 +527,8 @@ The MDM endpoints exist to support the related command-line interface sub-comman
- [Generate Apple DEP Key Pair](#generate-apple-dep-key-pair)
- [Request Certificate Signing Request (CSR)](#request-certificate-signing-request-csr)
- [Batch-apply Apple MDM custom settings](#batch-apply-apple-mdm-custom-settings)
- [Download an enrollment profile using IdP authentication](#download-an-enrollment-profile-using-idp-authentication)
- [Initiate SSO during DEP enrollment](#initiate-sso-during-dep-enrollment)
- [Complete SSO during DEP enrollment](#complete-sso-during-dep-enrollment)
### Generate Apple DEP Key Pair
@@ -602,6 +603,61 @@ If no team (id or name) is provided, the profiles are applied for all hosts (for
`204`
### Initiate SSO during DEP enrollment
This endpoint initiates the SSO flow, the response contains an URL that the client can use to redirect the user to initiate the SSO flow in the configured IdP.
`POST /api/v1/fleet/mdm/sso`
#### Parameters
None.
#### Example
`POST /api/v1/fleet/mdm/sso`
##### Default response
```
{
"url": "https://idp-provider.com/saml?SAMLRequest=...",
}
```
### Complete SSO during DEP enrollment
This is the callback endpoint that the identity provider will use to send security assertions to Fleet. This is where Fleet receives and processes the response from the identify provider.
`POST /api/v1/fleet/mdm/sso/callback`
#### Parameters
| Name | Type | In | Description |
| ------------ | ------ | ---- | ----------------------------------------------------------- |
| SAMLResponse | string | body | **Required**. The SAML response from the identity provider. |
#### Example
`POST /api/v1/fleet/mdm/sso/callback`
##### Request body
```json
{
"SAMLResponse": "<SAML response from IdP>"
}
```
##### Default response
`Status: 302`
If the credentials are valid, the server redirects the client to the Fleet UI. The URL contains the following query parameters that can be used to complete the DEP enrollment flow:
- `profile_token` is a token that can be used to download an enrollment profile (.mobileconfig).
- `eula_token` (optional) if an EULA was uploaded, this contains a token that can be used to view the EULA document.
## Get or apply configuration files
These API routes are used by the `fleetctl` CLI tool. Users can manage Fleet with `fleetctl` and [configuration files in YAML syntax](https://fleetdm.com/docs/using-fleet/configuration-files/).
+50 -26
View File
@@ -8,6 +8,7 @@ import (
"encoding/json"
"errors"
"io"
"net/url"
"github.com/fleetdm/fleet/v4/pkg/file"
"github.com/fleetdm/fleet/v4/server/authz"
@@ -508,7 +509,7 @@ func (svc *Service) InitiateMDMAppleSSO(ctx context.Context) (string, error) {
}
func (svc *Service) InitiateMDMAppleSSOCallback(ctx context.Context, auth fleet.Auth) ([]byte, error) {
func (svc *Service) InitiateMDMAppleSSOCallback(ctx context.Context, auth fleet.Auth) (string, error) {
// skipauth: User context does not yet exist. Unauthenticated users may
// hit the SSO callback.
svc.authz.SkipAuthorization(ctx)
@@ -517,12 +518,12 @@ func (svc *Service) InitiateMDMAppleSSOCallback(ctx context.Context, auth fleet.
appConfig, err := svc.ds.AppConfig(ctx)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "get config for sso")
return "", ctxerr.Wrap(ctx, err, "get config for sso")
}
_, metadata, err := svc.ssoSessionStore.Fullfill(auth.RequestID())
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "validate request in session")
return "", ctxerr.Wrap(ctx, err, "validate request in session")
}
err = sso.ValidateAudiences(
@@ -534,35 +535,35 @@ func (svc *Service) InitiateMDMAppleSSOCallback(ctx context.Context, auth fleet.
)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "validating sso response")
return "", ctxerr.Wrap(ctx, err, "validating sso response")
}
return apple_mdm.GenerateEnrollmentProfileMobileconfig(
appConfig.OrgInfo.OrgName,
appConfig.ServerSettings.ServerURL,
svc.config.MDM.AppleSCEPChallenge,
svc.mdmPushCertTopic,
)
eula, err := svc.ds.MDMAppleGetEULAMetadata(ctx)
if err != nil && !fleet.IsNotFound(err) {
return "", ctxerr.Wrap(ctx, err, "getting EULA metadata")
}
depProf, err := svc.getAutomaticEnrollmentProfile(ctx)
if err != nil {
return "", ctxerr.Wrap(ctx, err, "listing profiles")
}
if depProf == nil {
return "", ctxerr.Wrap(ctx, err, "missing profile")
}
q := url.Values{"profile_token": {depProf.Token}}
if eula != nil {
q.Add("eula_token", eula.Token)
}
return appConfig.ServerSettings.ServerURL + "/mdm/sso/callback?" + q.Encode(), nil
}
func (svc *Service) mdmAppleSyncDEPProfile(ctx context.Context) error {
profiles, err := svc.ds.ListMDMAppleEnrollmentProfiles(ctx)
depProf, err := svc.getAutomaticEnrollmentProfile(ctx)
if err != nil {
return ctxerr.Wrap(ctx, err, "listing profiles")
}
// Grab the first automatic enrollment profile we find, the current
// behavior is that the last enrollment profile that was uploaded is
// the one assigned to newly enrolled devices.
//
// TODO: this will change after #10995 where there can be a DEP profile
// per team.
var depProf *fleet.MDMAppleEnrollmentProfile
for _, prof := range profiles {
if prof.Type == "automatic" {
depProf = prof
break
}
return ctxerr.Wrap(ctx, err, "fetching enrollment profile")
}
if depProf == nil {
@@ -586,3 +587,26 @@ func (svc *Service) mdmAppleSyncDEPProfile(ctx context.Context) error {
return svc.depService.RegisterProfileWithAppleDEPServer(ctx, jsonProf, enrollURL)
}
func (svc *Service) getAutomaticEnrollmentProfile(ctx context.Context) (*fleet.MDMAppleEnrollmentProfile, error) {
profiles, err := svc.ds.ListMDMAppleEnrollmentProfiles(ctx)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "listing profiles")
}
// Grab the first automatic enrollment profile we find, the current
// behavior is that the last enrollment profile that was uploaded is
// the one assigned to newly enrolled devices.
//
// TODO: this will change after #10995 where there can be a DEP profile
// per team.
var depProf *fleet.MDMAppleEnrollmentProfile
for _, prof := range profiles {
if prof.Type == "automatic" {
depProf = prof
break
}
}
return depProf, nil
}
@@ -0,0 +1,22 @@
import React from "react";
import classnames from "classnames";
import DataError from "components/DataError";
const baseClass = "mdm-sso-error";
interface ISSOErrorProps {
className?: string;
}
const SSOError = ({ className }: ISSOErrorProps) => {
const classNames = classnames(baseClass, className);
return (
<DataError className={classNames}>
<p>Please contact your IT admin at +1-(415)-651-2575.</p>
</DataError>
);
};
export default SSOError;
@@ -0,0 +1,6 @@
.mdm-sso-error {
p {
font-size: $x-small;
margin: 12px 0 0;
}
}
@@ -0,0 +1 @@
export { default } from "./SSOError";
@@ -30,6 +30,7 @@ export default {
"unstyled-modal-query",
"contextual-nav-item",
"small-text-icon",
"oversized",
],
control: "select",
},
@@ -21,7 +21,8 @@ export type ButtonVariant =
| "unstyled"
| "unstyled-modal-query"
| "contextual-nav-item"
| "small-text-icon";
| "small-text-icon"
| "oversized";
export interface IButtonProps {
autofocus?: boolean;
@@ -377,4 +377,11 @@ $base-class: "button";
display: flex;
justify-content: space-between;
}
&--oversized {
background-color: $core-fleet-black;
padding: $pad-large $pad-small;
font-size: $medium;
width: 100%;
}
}
@@ -0,0 +1,70 @@
import React, { useState } from "react";
import { WithRouterProps } from "react-router";
import endpoints from "utilities/endpoints";
import Spinner from "components/Spinner/Spinner";
import SSOError from "components/MDM/SSOError";
import Button from "components/buttons/Button";
const baseClass = "mdm-apple-sso-callback-page";
const RedirectTo = ({ url }: { url: string }) => {
window.location.href = url;
return <Spinner />;
};
interface IEnrollmentGateProps {
profileToken?: string;
eulaToken?: string;
}
const EnrollmentGate = ({ profileToken, eulaToken }: IEnrollmentGateProps) => {
const [showEULA, setShowEULA] = useState(Boolean(eulaToken));
if (!profileToken) {
return <SSOError />;
}
if (showEULA && eulaToken) {
return (
<div className={`${baseClass}__eula-wrapper`}>
<h3>Terms and conditions</h3>
<iframe
src={`/api/${endpoints.MDM_APPLE_EULA_FILE(eulaToken)}`}
width="100%"
title="eula"
/>
<Button
onClick={() => setShowEULA(false)}
variant="oversized"
className={`${baseClass}__agree-btn`}
>
Agree and continue
</Button>
</div>
);
}
return (
<RedirectTo url={endpoints.MDM_APPLE_ENROLLMENT_PROFILE(profileToken)} />
);
};
interface IMDMSSOCallbackQuery {
eula_token?: string;
profile_token?: string;
}
const MDMAppleSSOCallbackPage = (
props: WithRouterProps<object, IMDMSSOCallbackQuery>
) => {
const { eula_token, profile_token } = props.location.query;
return (
<div className={baseClass}>
<EnrollmentGate eulaToken={eula_token} profileToken={profile_token} />
</div>
);
};
export default MDMAppleSSOCallbackPage;
@@ -0,0 +1,20 @@
.mdm-apple-sso-callback-page {
height: 100vh; // expend height to make entire viewport a white background
background-color: $core-white;
display: flex;
align-items: center;
justify-content: center;
&__eula-wrapper {
width: 80vw;
text-align: center;
iframe {
height: 65vh;
}
}
&__agree-btn {
width: 80%;
}
}
@@ -0,0 +1 @@
export { default } from "./MDMAppleSSOCallbackPage";
@@ -4,20 +4,12 @@ import { AxiosError } from "axios";
import mdmAPI from "services/entities/mdm";
import DataError from "components/DataError";
import SSOError from "components/MDM/SSOError";
import Spinner from "components/Spinner/Spinner";
import { IMdmSSOReponse } from "interfaces/mdm";
const baseClass = "mdm-apple-sso-page";
const SSOError = () => {
return (
<DataError className={`${baseClass}__sso-error`}>
<p>Please contact your IT admin at +1-(415)-651-2575.</p>
</DataError>
);
};
const DEPSSOLoginPage = () => {
const { error } = useQuery<void, AxiosError, IMdmSSOReponse>(
["dep_sso"],
@@ -4,11 +4,4 @@
display: flex;
align-items: center;
justify-content: center;
&__sso-error {
p {
font-size: $x-small;
margin: 12px 0 0;
}
}
}
+2
View File
@@ -40,6 +40,7 @@ import QueryPage from "pages/queries/QueryPage";
import RegistrationPage from "pages/RegistrationPage";
import ResetPasswordPage from "pages/ResetPasswordPage";
import MDMAppleSSOPage from "pages/MDMAppleSSOPage";
import MDMAppleSSOCallbackPage from "pages/MDMAppleSSOCallbackPage";
import SoftwareDetailsPage from "pages/software/SoftwareDetailsPage";
import ApiOnlyUser from "pages/ApiOnlyUser";
import Fleet403 from "pages/errors/Fleet403";
@@ -98,6 +99,7 @@ const routes = (
/>
<Route path="login/forgot" component={ForgotPasswordPage} />
<Route path="login/reset" component={ResetPasswordPage} />
<Route path="mdm/sso/callback" component={MDMAppleSSOCallbackPage} />
<Route path="mdm/sso" component={MDMAppleSSOPage} />
</Route>
</Route>
+4
View File
@@ -48,6 +48,10 @@ export default {
MDM_PROFILES_AGGREGATE_STATUSES: `/${API_VERSION}/fleet/mdm/apple/profiles/summary`,
MDM_APPLE_DISK_ENCRYPTION_AGGREGATE: `/${API_VERSION}/fleet/mdm/apple/filevault/summary`,
MDM_APPLE_SSO: `/${API_VERSION}/fleet/mdm/sso`,
MDM_APPLE_EULA_FILE: (token: string) =>
`/${API_VERSION}/fleet/mdm/apple/setup/eula/${token}`,
MDM_APPLE_ENROLLMENT_PROFILE: (token: string) =>
`/api/mdm/apple/enroll?token=${token}`,
MDM_BOOTSTRAP_PACKAGE_METADATA: (teamId: number) =>
`/${API_VERSION}/fleet/mdm/apple/bootstrap/${teamId}/metadata`,
MDM_BOOTSTRAP_PACKAGE: `/${API_VERSION}/fleet/mdm/apple/bootstrap`,
+1 -2
View File
@@ -168,8 +168,7 @@ type Service interface {
// InitSSOCallback handles the IDP response and ensures the credentials
// are valid, then responds with an enrollment profile.
// TODO: add support for EULAs too
InitiateMDMAppleSSOCallback(ctx context.Context, auth Auth) ([]byte, error)
InitiateMDMAppleSSOCallback(ctx context.Context, auth Auth) (string, error)
// GetSSOUser handles retrieval of an user that is trying to authenticate
// via SSO
+12 -21
View File
@@ -1161,6 +1161,8 @@ type mdmAppleEnrollResponse struct {
func (r mdmAppleEnrollResponse) hijackRender(ctx context.Context, w http.ResponseWriter) {
w.Header().Set("Content-Length", strconv.FormatInt(int64(len(r.Profile)), 10))
w.Header().Set("Content-Type", "application/x-apple-aspen-config")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Content-Disposition", "attachment;fleet-enrollment-profile.mobileconfig")
// OK to just log the error here as writing anything on
// `http.ResponseWriter` sets the status code to 200 (and it can't be
@@ -2112,44 +2114,33 @@ func (callbackMDMAppleSSORequest) DecodeRequest(ctx context.Context, r *http.Req
type callbackMDMAppleSSOResponse struct {
Err error `json:"error,omitempty"`
// used in hijackRender for the response
profile []byte
redirectURL string
}
func (r callbackMDMAppleSSOResponse) hijackRender(ctx context.Context, w http.ResponseWriter) {
w.Header().Set("Location", r.redirectURL)
w.WriteHeader(http.StatusTemporaryRedirect)
}
func (r callbackMDMAppleSSOResponse) error() error { return r.Err }
func (r callbackMDMAppleSSOResponse) hijackRender(ctx context.Context, w http.ResponseWriter) {
w.Header().Set("Content-Length", strconv.FormatInt(int64(len(r.profile)), 10))
w.Header().Set("Content-Type", "application/x-apple-aspen-config")
w.Header().Add("Content-Disposition", `attachment; filename="fleet-mdm-enrollment-profile.mobileconfig"`)
w.Header().Set("X-Content-Type-Options", "nosniff")
// OK to just log the error here as writing anything on
// `http.ResponseWriter` sets the status code to 200 (and it can't be
// changed.) Clients should rely on matching content-length with the
// header provided.
if n, err := w.Write(r.profile); err != nil {
logging.WithExtras(ctx, "err", err, "written", n)
}
}
func callbackMDMAppleSSOEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
auth := request.(fleet.Auth)
// validate that the SSO response is valid
profile, err := svc.InitiateMDMAppleSSOCallback(ctx, auth)
redirectURL, err := svc.InitiateMDMAppleSSOCallback(ctx, auth)
if err != nil {
return callbackMDMAppleSSOResponse{Err: err}, nil
}
return callbackMDMAppleSSOResponse{profile: profile}, nil
return callbackMDMAppleSSOResponse{redirectURL: redirectURL}, nil
}
func (svc *Service) InitiateMDMAppleSSOCallback(ctx context.Context, auth fleet.Auth) ([]byte, error) {
func (svc *Service) InitiateMDMAppleSSOCallback(ctx context.Context, auth fleet.Auth) (string, error) {
// skipauth: No authorization check needed due to implementation
// returning only license error.
svc.authz.SkipAuthorization(ctx)
return nil, fleet.ErrMissingLicense
return "", fleet.ErrMissingLicense
}
////////////////////////////////////////////////////////////////////////////////
+57 -35
View File
@@ -17,6 +17,7 @@ import (
"mime/multipart"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"sort"
@@ -611,25 +612,7 @@ func (s *integrationMDMTestSuite) TestDeviceMDMManualEnroll() {
s.DoRaw("GET", "/api/latest/fleet/device/invalid_token/mdm/apple/manual_enrollment_profile", nil, http.StatusUnauthorized)
// valid token downloads the profile
resp := s.DoRaw("GET", "/api/latest/fleet/device/"+token+"/mdm/apple/manual_enrollment_profile", nil, http.StatusOK)
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
require.NoError(t, err)
require.Contains(t, resp.Header, "Content-Disposition")
require.Contains(t, resp.Header, "Content-Type")
require.Contains(t, resp.Header, "X-Content-Type-Options")
require.Contains(t, resp.Header.Get("Content-Disposition"), "attachment;")
require.Contains(t, resp.Header.Get("Content-Type"), "application/x-apple-aspen-config")
require.Contains(t, resp.Header.Get("X-Content-Type-Options"), "nosniff")
headerLen, err := strconv.Atoi(resp.Header.Get("Content-Length"))
require.NoError(t, err)
require.Equal(t, len(body), headerLen)
var profile struct {
PayloadIdentifier string `plist:"PayloadIdentifier"`
}
require.NoError(t, plist.Unmarshal(body, &profile))
require.Equal(t, apple_mdm.FleetPayloadIdentifier, profile.PayloadIdentifier)
s.downloadAndVerifyEnrollmentProfile("/api/latest/fleet/device/" + token + "/mdm/apple/manual_enrollment_profile")
}
func (s *integrationMDMTestSuite) TestAppleMDMDeviceEnrollment() {
@@ -3875,25 +3858,41 @@ func (s *integrationMDMTestSuite) TestSSO() {
require.Equal(t, acResp.ServerSettings.ServerURL+"/mdm/sso", lastSubmittedProfile.ConfigurationWebURL)
res := s.LoginMDMSSOUser("sso_user", "user123#")
require.NotEmpty(t, res.Header.Get("Location"))
require.Equal(t, http.StatusTemporaryRedirect, res.StatusCode)
body, err := io.ReadAll(res.Body)
u, err := url.Parse(res.Header.Get("Location"))
require.NoError(t, err)
defer res.Body.Close()
require.Contains(t, res.Header, "Content-Disposition")
require.Contains(t, res.Header, "Content-Type")
require.Contains(t, res.Header, "X-Content-Type-Options")
require.Contains(t, res.Header.Get("Content-Disposition"), "attachment;")
require.Contains(t, res.Header.Get("Content-Type"), "application/x-apple-aspen-config")
require.Contains(t, res.Header.Get("X-Content-Type-Options"), "nosniff")
headerLen, err := strconv.Atoi(res.Header.Get("Content-Length"))
require.NoError(t, err)
require.Equal(t, len(body), headerLen)
q := u.Query()
// without an EULA uploaded, only the profile token is provided
require.False(t, q.Has("eula_token"))
require.True(t, q.Has("profile_token"))
// the url retrieves a valid profile
s.downloadAndVerifyEnrollmentProfile("/api/mdm/apple/enroll?token=" + q.Get("profile_token"))
var profile struct {
PayloadIdentifier string `plist:"PayloadIdentifier"`
}
require.NoError(t, plist.Unmarshal(body, &profile))
require.Equal(t, apple_mdm.FleetPayloadIdentifier, profile.PayloadIdentifier)
// upload an EULA
pdfBytes := []byte("%PDF-1.pdf-contents")
pdfName := "eula.pdf"
s.uploadEULA(&fleet.MDMAppleEULA{Bytes: pdfBytes, Name: pdfName}, http.StatusOK, "")
res = s.LoginMDMSSOUser("sso_user", "user123#")
require.NotEmpty(t, res.Header.Get("Location"))
require.Equal(t, http.StatusTemporaryRedirect, res.StatusCode)
u, err = url.Parse(res.Header.Get("Location"))
require.NoError(t, err)
q = u.Query()
// with an EULA uploaded, both values are present
require.True(t, q.Has("eula_token"))
require.True(t, q.Has("profile_token"))
// the url retrieves a valid profile
s.downloadAndVerifyEnrollmentProfile("/api/mdm/apple/enroll?token=" + q.Get("profile_token"))
// the url retrieves a valid EULA
resp := s.DoRaw("GET", "/api/latest/fleet/mdm/apple/setup/eula/"+q.Get("eula_token"), nil, http.StatusOK)
require.EqualValues(t, len(pdfBytes), resp.ContentLength)
require.Equal(t, "application/pdf", resp.Header.Get("content-type"))
respBytes, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.EqualValues(t, pdfBytes, respBytes)
// changing the server URL also updates the remote DEP profile
acResp = appConfigResponse{}
@@ -3903,3 +3902,26 @@ func (s *integrationMDMTestSuite) TestSSO() {
require.Contains(t, lastSubmittedProfile.URL, "https://example.com/api/mdm/apple/enroll?token=")
require.Equal(t, "https://example.com/mdm/sso", lastSubmittedProfile.ConfigurationWebURL)
}
func (s *integrationMDMTestSuite) downloadAndVerifyEnrollmentProfile(path string) {
t := s.T()
resp := s.DoRaw("GET", path, nil, http.StatusOK)
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
require.NoError(t, err)
require.Contains(t, resp.Header, "Content-Disposition")
require.Contains(t, resp.Header, "Content-Type")
require.Contains(t, resp.Header, "X-Content-Type-Options")
require.Contains(t, resp.Header.Get("Content-Disposition"), "attachment;")
require.Contains(t, resp.Header.Get("Content-Type"), "application/x-apple-aspen-config")
require.Contains(t, resp.Header.Get("X-Content-Type-Options"), "nosniff")
headerLen, err := strconv.Atoi(resp.Header.Get("Content-Length"))
require.NoError(t, err)
require.Equal(t, len(body), headerLen)
var profile struct {
PayloadIdentifier string `plist:"PayloadIdentifier"`
}
require.NoError(t, plist.Unmarshal(body, &profile))
require.Equal(t, apple_mdm.FleetPayloadIdentifier, profile.PayloadIdentifier)
}
-1
View File
@@ -295,7 +295,6 @@ func (r getMDMAppleEULAResponse) hijackRender(ctx context.Context, w http.Respon
w.Header().Set("Content-Length", strconv.Itoa(len(r.eula.Bytes)))
w.Header().Set("Content-Type", "application/pdf")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment;filename="%s.pdf"`, r.eula.Name))
// OK to just log the error here as writing anything on
// `http.ResponseWriter` sets the status code to 200 (and it can't be
+4 -4
View File
@@ -280,7 +280,7 @@ func (ts *withServer) getConfig() *appConfigResponse {
func (ts *withServer) LoginSSOUser(username, password string) (fleet.Auth, string) {
t := ts.s.T()
auth, res := ts.loginSSOUser(username, password, "/api/v1/fleet/sso")
auth, res := ts.loginSSOUser(username, password, "/api/v1/fleet/sso", http.StatusOK)
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
require.NoError(t, err)
@@ -288,11 +288,11 @@ func (ts *withServer) LoginSSOUser(username, password string) (fleet.Auth, strin
}
func (ts *withServer) LoginMDMSSOUser(username, password string) *http.Response {
_, res := ts.loginSSOUser(username, password, "/api/v1/fleet/mdm/sso")
_, res := ts.loginSSOUser(username, password, "/api/v1/fleet/mdm/sso", http.StatusTemporaryRedirect)
return res
}
func (ts *withServer) loginSSOUser(username, password string, basePath string) (fleet.Auth, *http.Response) {
func (ts *withServer) loginSSOUser(username, password string, basePath string, callbackStatus int) (fleet.Auth, *http.Response) {
t := ts.s.T()
if _, ok := os.LookupEnv("SAML_IDP_TEST"); !ok {
@@ -338,7 +338,7 @@ func (ts *withServer) loginSSOUser(username, password string, basePath string) (
auth, err := sso.DecodeAuthResponse(rawSSOResp)
require.NoError(t, err)
q := url.QueryEscape(rawSSOResp)
res := ts.DoRawNoAuth("POST", basePath+"/callback?SAMLResponse="+q, nil, http.StatusOK)
res := ts.DoRawNoAuth("POST", basePath+"/callback?SAMLResponse="+q, nil, callbackStatus)
return auth, res
}