diff --git a/ee/server/service/mdm.go b/ee/server/service/mdm.go
index 7992d2f52b..f64cd3db9f 100644
--- a/ee/server/service/mdm.go
+++ b/ee/server/service/mdm.go
@@ -712,7 +712,7 @@ func (svc *Service) DeleteMDMAppleSetupAssistant(ctx context.Context, teamID *ui
const appleMDMAccountDrivenEnrollmentUrl = "/api/mdm/apple/account_driven_enroll"
-func (svc *Service) InitiateMDMSSO(ctx context.Context, initiator, customOriginalURL string) (sessionID string, sessionDurationSeconds int, idpURL string, err error) {
+func (svc *Service) InitiateMDMSSO(ctx context.Context, initiator, customOriginalURL string, hostUUID string) (sessionID string, sessionDurationSeconds int, idpURL string, err error) {
// skipauth: User context does not yet exist. Unauthenticated users may
// initiate SSO.
svc.authz.SkipAuthorization(ctx)
@@ -771,6 +771,10 @@ func (svc *Service) InitiateMDMSSO(ctx context.Context, initiator, customOrigina
sessionID, idpURL, err = sso.CreateAuthorizationRequest(ctx,
samlProvider, svc.ssoSessionStore, originalURL,
uint(sessionDurationSeconds), //nolint:gosec // dismiss G115
+ sso.SSORequestData{
+ HostUUID: hostUUID,
+ Initiator: initiator,
+ },
)
if err != nil {
return "", 0, "", ctxerr.Wrap(ctx, err, "InitiateMDMSSO creating authorization")
@@ -786,13 +790,13 @@ func (svc *Service) MDMSSOCallback(ctx context.Context, sessionID string, samlRe
logging.WithLevel(logging.WithNoUser(ctx), level.Info)
- profileToken, enrollmentRef, eulaToken, originalURL, err := svc.mdmSSOHandleCallbackAuth(ctx, sessionID, samlResponse)
+ profileToken, enrollmentRef, eulaToken, originalURL, ssoRequestData, err := svc.mdmSSOHandleCallbackAuth(ctx, sessionID, samlResponse)
if err != nil {
logging.WithErr(ctx, err)
return apple_mdm.FleetUISSOCallbackPath + "?error=true", ""
}
- if !strings.HasPrefix(originalURL, "/enroll?") {
+ if !strings.HasPrefix(originalURL, "/enroll?") && ssoRequestData.Initiator != "setup_experience" {
// for flows other than the /enroll BYOD, we have to ensure that Apple MDM
// is enabled (this was previously done in a middleware on the route, but
// we do it here now so the middleware is disabled for the BYOD flow, which
@@ -812,6 +816,8 @@ func (svc *Service) MDMSSOCallback(ctx context.Context, sessionID string, samlRe
q.Add("eula_token", eulaToken)
}
+ q.Add("initiator", ssoRequestData.Initiator)
+
switch {
case originalURL == appleMDMAccountDrivenEnrollmentUrl:
// For account driven enrollment we have to use this special protocol URL scheme to pass the
@@ -845,17 +851,17 @@ func (svc *Service) mdmSSOHandleCallbackAuth(
sessionID string,
samlResponse []byte,
) (profileToken string, enrollmentReference string,
- eulaToken string, originalURL string, err error,
+ eulaToken string, originalURL string, ssoRequestData sso.SSORequestData, err error,
) {
appConfig, err := svc.ds.AppConfig(ctx)
if err != nil {
- return "", "", "", "", ctxerr.Wrap(ctx, err, "get config for sso")
+ return "", "", "", "", sso.SSORequestData{}, ctxerr.Wrap(ctx, err, "get config for sso")
}
serverURL := appConfig.MDMUrl()
acsURL, err := url.Parse(serverURL + svc.config.Server.URLPrefix + "/api/v1/fleet/mdm/sso/callback")
if err != nil {
- return "", "", "", "", ctxerr.Wrap(ctx, err, "failed to parse ACS URL")
+ return "", "", "", "", sso.SSORequestData{}, ctxerr.Wrap(ctx, err, "failed to parse ACS URL")
}
mdmSSOSettings := appConfig.MDM.EndUserAuthentication.SSOProviderSettings
@@ -866,7 +872,7 @@ func (svc *Service) mdmSSOHandleCallbackAuth(
// this means some teams may not use SSO even if it is configured.
if mdmSSOSettings.IsEmpty() {
err := &fleet.BadRequestError{Message: "organization not configured to use sso"}
- return "", "", "", "", ctxerr.Wrap(ctx, err, "get config for mdm sso callback")
+ return "", "", "", "", sso.SSORequestData{}, ctxerr.Wrap(ctx, err, "get config for mdm sso callback")
}
expectedAudiences := []string{
@@ -874,11 +880,11 @@ func (svc *Service) mdmSSOHandleCallbackAuth(
appConfig.MDMUrl(),
appConfig.MDMUrl() + svc.config.Server.URLPrefix + "/api/v1/fleet/mdm/sso/callback",
}
- samlProvider, requestID, originalURL, err := sso.SAMLProviderFromSession(
+ samlProvider, requestID, originalURL, ssoRequestData, err := sso.SAMLProviderFromSession(
ctx, sessionID, svc.ssoSessionStore, acsURL, mdmSSOSettings.EntityID, expectedAudiences,
)
if err != nil {
- return "", "", "", "", ctxerr.Wrap(ctx, err, "failed to create provider from metadata")
+ return "", "", "", "", sso.SSORequestData{}, ctxerr.Wrap(ctx, err, "failed to create provider from metadata")
}
// Parse and verify SAMLResponse (verifies fields, expected IDs and signature).
@@ -886,7 +892,7 @@ func (svc *Service) mdmSSOHandleCallbackAuth(
if err != nil {
// We actually don't return 401 to clients and instead return an HTML page with /login?status=error,
// but to be consistent we will return fleet.AuthFailedError which is used for unauthorized access.
- return "", "", "", "", ctxerr.Wrap(ctx, fleet.NewAuthFailedError(err.Error()))
+ return "", "", "", "", sso.SSORequestData{}, ctxerr.Wrap(ctx, fleet.NewAuthFailedError(err.Error()))
}
// Store information for automatic account population/creation
@@ -902,12 +908,13 @@ func (svc *Service) mdmSSOHandleCallbackAuth(
}
err = svc.ds.InsertMDMIdPAccount(ctx, &fleet.MDMIdPAccount{
+ UUID: ssoRequestData.HostUUID,
Username: username,
Fullname: auth.UserDisplayName(),
Email: auth.UserID(),
})
if err != nil {
- return "", "", "", "", ctxerr.Wrap(ctx, err, "saving account data from IdP")
+ return "", "", "", "", sso.SSORequestData{}, ctxerr.Wrap(ctx, err, "saving account data from IdP")
}
idpAcc, err := svc.ds.GetMDMIdPAccountByEmail(
@@ -917,12 +924,21 @@ func (svc *Service) mdmSSOHandleCallbackAuth(
auth.UserID(),
)
if err != nil {
- return "", "", "", "", ctxerr.Wrap(ctx, err, "retrieving new account data from IdP")
+ return "", "", "", "", sso.SSORequestData{}, ctxerr.Wrap(ctx, err, "retrieving new account data from IdP")
+ }
+
+ // If the initiator is "setup_experience", we can insert the host idp account record
+ // right away, as the host uuid is provided in the SSO request data.
+ if ssoRequestData.Initiator == "setup_experience" && ssoRequestData.HostUUID != "" {
+ err = svc.ds.AssociateHostMDMIdPAccountDB(ctx, ssoRequestData.HostUUID, idpAcc.UUID)
+ if err != nil {
+ return "", "", "", "", sso.SSORequestData{}, ctxerr.Wrap(ctx, err, "saving host-account link from IdP")
+ }
}
eula, err := svc.ds.MDMGetEULAMetadata(ctx)
if err != nil && !fleet.IsNotFound(err) {
- return "", "", "", "", ctxerr.Wrap(ctx, err, "getting EULA metadata")
+ return "", "", "", "", sso.SSORequestData{}, ctxerr.Wrap(ctx, err, "getting EULA metadata")
}
if eula != nil {
@@ -931,22 +947,22 @@ func (svc *Service) mdmSSOHandleCallbackAuth(
// If this is account driven enrollment there is no need to fetch the profile
if originalURL == appleMDMAccountDrivenEnrollmentUrl {
- return "", idpAcc.UUID, eulaToken, originalURL, nil
+ return "", idpAcc.UUID, eulaToken, originalURL, ssoRequestData, nil
}
// get the automatic profile to access the authentication token.
depProf, err := svc.getAutomaticEnrollmentProfile(ctx)
if err != nil {
- return "", "", "", "", ctxerr.Wrap(ctx, err, "listing profiles")
+ return "", "", "", "", sso.SSORequestData{}, ctxerr.Wrap(ctx, err, "listing profiles")
}
if depProf == nil {
- return "", "", "", "", ctxerr.Wrap(ctx, err, "missing profile")
+ return "", "", "", "", sso.SSORequestData{}, ctxerr.Wrap(ctx, err, "missing profile")
}
// using the idp token as a reference just because that's the
// only thing we're referencing later on during enrollment.
- return depProf.Token, idpAcc.UUID, eulaToken, originalURL, nil
+ return depProf.Token, idpAcc.UUID, eulaToken, originalURL, ssoRequestData, nil
}
func (svc *Service) mdmAppleSyncDEPProfiles(ctx context.Context) error {
diff --git a/frontend/pages/MDMAppleSSOCallbackPage/MDMAppleSSOCallbackPage.tsx b/frontend/pages/MDMAppleSSOCallbackPage/MDMAppleSSOCallbackPage.tsx
index b9a985c66e..7ae9f88bec 100644
--- a/frontend/pages/MDMAppleSSOCallbackPage/MDMAppleSSOCallbackPage.tsx
+++ b/frontend/pages/MDMAppleSSOCallbackPage/MDMAppleSSOCallbackPage.tsx
@@ -7,6 +7,8 @@ import Spinner from "components/Spinner/Spinner";
import SSOError from "components/MDM/SSOError";
import Button from "components/buttons/Button";
+import AuthenticationFormWrapper from "components/AuthenticationFormWrapper";
+
const baseClass = "mdm-apple-sso-callback-page";
const RedirectTo = ({ url }: { url: string }) => {
@@ -18,6 +20,7 @@ interface IEnrollmentGateProps {
profileToken?: string;
eulaToken?: string;
enrollmentReference?: string;
+ initiator?: string;
error?: boolean;
}
@@ -25,6 +28,7 @@ const EnrollmentGate = ({
profileToken,
eulaToken,
enrollmentReference,
+ initiator,
error,
}: IEnrollmentGateProps) => {
const [showEULA, setShowEULA] = useState(Boolean(eulaToken));
@@ -35,6 +39,16 @@ const EnrollmentGate = ({
return ;
}
+ if (initiator === "setup_experience") {
+ return (
+
+
+
You’re done! You may now close this window.
+
+
+ );
+ }
+
if (showEULA && eulaToken) {
return (
@@ -70,6 +84,7 @@ interface IMDMSSOCallbackQuery {
eula_token?: string;
profile_token?: string;
enrollment_reference?: string;
+ initiator?: string;
error?: boolean;
}
@@ -80,6 +95,7 @@ const MDMAppleSSOCallbackPage = (
eula_token,
profile_token,
enrollment_reference,
+ initiator,
error,
} = props.location.query;
return (
@@ -88,6 +104,7 @@ const MDMAppleSSOCallbackPage = (
eulaToken={eula_token}
profileToken={profile_token}
enrollmentReference={enrollment_reference}
+ initiator={initiator}
error={error}
/>
diff --git a/frontend/pages/MDMAppleSSOCallbackPage/_styles.scss b/frontend/pages/MDMAppleSSOCallbackPage/_styles.scss
index 5140589b92..9f7e1b7d0e 100644
--- a/frontend/pages/MDMAppleSSOCallbackPage/_styles.scss
+++ b/frontend/pages/MDMAppleSSOCallbackPage/_styles.scss
@@ -19,4 +19,8 @@
&__agree-btn {
width: 80%;
}
+
+ &.form {
+ height: auto;
+ }
}
diff --git a/frontend/pages/MDMAppleSSOPage/MDMAppleSSOPage.tsx b/frontend/pages/MDMAppleSSOPage/MDMAppleSSOPage.tsx
index df3ae27e82..88b0cc3066 100644
--- a/frontend/pages/MDMAppleSSOPage/MDMAppleSSOPage.tsx
+++ b/frontend/pages/MDMAppleSSOPage/MDMAppleSSOPage.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useState } from "react";
import { useQuery } from "react-query";
import { AxiosError } from "axios";
import { WithRouterProps } from "react-router";
@@ -7,22 +7,29 @@ import mdmAPI, { IMDMSSOParams } from "services/entities/mdm";
import SSOError from "components/MDM/SSOError";
import Spinner from "components/Spinner/Spinner";
+import Button from "components/buttons/Button";
+import CustomLink from "components/CustomLink";
import { IMdmSSOReponse } from "interfaces/mdm";
+import AuthenticationFormWrapper from "components/AuthenticationFormWrapper";
const baseClass = "mdm-apple-sso-page";
const DEPSSOLoginPage = ({
location: { pathname, query },
}: WithRouterProps