diff --git a/cmd/fleetctl/gitops_test.go b/cmd/fleetctl/gitops_test.go index 1723abb23e..daf71e069d 100644 --- a/cmd/fleetctl/gitops_test.go +++ b/cmd/fleetctl/gitops_test.go @@ -741,7 +741,6 @@ team_settings: assert.Empty(t, savedTeam.Config.MDM.MacOSSetup.BootstrapPackage.Value) assert.False(t, savedTeam.Config.MDM.EnableDiskEncryption) assert.Equal(t, filepath.Base(tmpFile.Name()), *savedTeam.Filename) - } func TestBasicGlobalAndTeamGitOps(t *testing.T) { @@ -1152,7 +1151,7 @@ func TestTeamVPPAppsGitOps(t *testing.T) { {"testdata/gitops/team_vpp_valid_app.yml", "", time.Now().Add(24 * time.Hour)}, {"testdata/gitops/team_vpp_valid_empty.yml", "", time.Now().Add(24 * time.Hour)}, {"testdata/gitops/team_vpp_valid_empty.yml", "", time.Now().Add(-24 * time.Hour)}, - {"testdata/gitops/team_vpp_valid_app.yml", "vpp token expired", time.Now().Add(-24 * time.Hour)}, + {"testdata/gitops/team_vpp_valid_app.yml", "VPP token expired", time.Now().Add(-24 * time.Hour)}, {"testdata/gitops/team_vpp_invalid_app.yml", "app not available on vpp account", time.Now().Add(24 * time.Hour)}, } diff --git a/ee/server/service/software_installers.go b/ee/server/service/software_installers.go index 659348f7bc..7412f7df7b 100644 --- a/ee/server/service/software_installers.go +++ b/ee/server/service/software_installers.go @@ -292,7 +292,7 @@ func (svc *Service) InstallSoftwareTitle(ctx context.Context, hostID uint, softw // request error if fleet.IsNotFound(err) { return &fleet.BadRequestError{ - Message: "Software title has no package or VPP app added. Please add software package or VPP app to install.", + Message: "Couldn't install software. Software title is not available for install. Please add software package or App Store app to install.", InternalErr: ctxerr.WrapWithData( ctx, err, "couldn't find an installer or VPP app for software title", map[string]any{"host_id": host.ID, "team_id": host.TeamID, "title_id": softwareTitleID}, @@ -317,6 +317,15 @@ func (svc *Service) installSoftwareFromVPP(ctx context.Context, host *fleet.Host } } + config, err := svc.ds.AppConfig(ctx) + if err != nil { + return ctxerr.Wrap(ctx, err, "fetching config to check MDM status") + } + + if !config.MDM.EnabledAndConfigured { + return fleet.NewUserMessageError(errors.New("Couldn't install. MDM is turned off. Please make sure that MDM is turned on to install App Store apps."), http.StatusUnprocessableEntity) + } + mdmConnected, err := svc.ds.IsHostConnectedToFleetMDM(ctx, host) if err != nil { return ctxerr.Wrapf(ctx, err, "checking MDM status for host %d", host.ID) diff --git a/ee/server/service/vpp.go b/ee/server/service/vpp.go index e76393111f..cd9871d7f0 100644 --- a/ee/server/service/vpp.go +++ b/ee/server/service/vpp.go @@ -4,6 +4,7 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "fmt" "net/http" "strings" @@ -16,6 +17,8 @@ import ( "github.com/fleetdm/fleet/v4/server/mdm/apple/vpp" ) +// getVPPToken returns the base64 encoded VPP token, ready for use in requests to Apple's VPP API. +// It returns an error if the token is expired. func (svc *Service) getVPPToken(ctx context.Context) (string, error) { configMap, err := svc.ds.GetAllMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{fleet.MDMAssetVPPToken}) if err != nil { @@ -44,7 +47,7 @@ func (svc *Service) getVPPToken(ctx context.Context) (string, error) { } if time.Now().After(exp) { - return "", ctxerr.Errorf(ctx, "vpp token expired on %s", exp.String()) + return "", fleet.NewUserMessageError(errors.New("Couldn't install. VPP token expired."), http.StatusUnprocessableEntity) } return vppTokenData.Token, nil diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 2cad9d187a..ca17eb7eab 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -11485,3 +11485,29 @@ func (s *integrationEnterpriseTestSuite) TestCalendarCallback() { require.NoError(t, err) assert.Empty(t, team1CalendarEvents) } + +func (s *integrationEnterpriseTestSuite) TestVPPAppsWithoutMDM() { + t := s.T() + ctx := context.Background() + + // Create host + orbitHost := createOrbitEnrolledHost(t, "darwin", "nonmdm", s.ds) + + // Create team and add host to team + var newTeamResp teamResponse + s.DoJSON("POST", "/api/latest/fleet/teams", &createTeamRequest{TeamPayload: fleet.TeamPayload{Name: ptr.String("Team 1")}}, http.StatusOK, &newTeamResp) + team := newTeamResp.Team + + s.Do("POST", "/api/latest/fleet/hosts/transfer", &addHostsToTeamRequest{HostIDs: []uint{orbitHost.ID}, TeamID: &team.ID}, http.StatusOK) + + // Add an app so that we don't get a not found error + app, err := s.ds.InsertVPPAppWithTeam(ctx, &fleet.VPPApp{ + Name: "App " + t.Name(), + BundleIdentifier: "bid_" + t.Name(), + AdamID: "adam_" + t.Name(), + }, &team.ID) + require.NoError(t, err) + + r := s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/install/%d", orbitHost.ID, app.TitleID), &installSoftwareRequest{}, http.StatusUnprocessableEntity) + require.Contains(t, extractServerErrorText(r.Body), "Couldn't install. MDM is turned off. Please make sure that MDM is turned on to install App Store apps.") +} diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index f0d60d122f..1d9661da8f 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -9798,10 +9798,24 @@ func (s *integrationMDMTestSuite) TestVPPApps() { errTitleID := listSw.SoftwareTitles[1].ID // attempt to install a VPP app on the non-MDM enrolled host - installResp := installSoftwareResponse{} s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/install/%d", orbitHost.ID, titleID), &installSoftwareRequest{}, http.StatusBadRequest, &installResp) + // Spoof an expired VPP token and attempt to install VPP app + tokenJSON = fmt.Sprintf(`{"expDate":"%s","token":"%s","orgName":"%s"}`, "2020-06-24T15:50:50+0000", token, orgName) + s.uploadDataViaForm("/api/latest/fleet/mdm/apple/vpp_token", "token", "token.vpptoken", []byte(base64.StdEncoding.EncodeToString([]byte(tokenJSON))), http.StatusAccepted, "") + + r := s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/install/%d", mdmHost.ID, errTitleID), &installSoftwareRequest{}, http.StatusUnprocessableEntity) + require.Contains(t, extractServerErrorText(r.Body), "VPP token expired") + + // Put a valid token back in + tokenJSON = fmt.Sprintf(`{"expDate":"%s","token":"%s","orgName":"%s"}`, "2050-06-24T15:50:50+0000", token, orgName) + s.uploadDataViaForm("/api/latest/fleet/mdm/apple/vpp_token", "token", "token.vpptoken", []byte(base64.StdEncoding.EncodeToString([]byte(tokenJSON))), http.StatusAccepted, "") + + // Attempt to install non-existent app + r = s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/install/%d", mdmHost.ID, 99999), &installSoftwareRequest{}, http.StatusBadRequest) + require.Contains(t, extractServerErrorText(r.Body), "Couldn't install software. Software title is not available for install. Please add software package or App Store app to install.") + // Trigger install to the host installResp = installSoftwareResponse{} s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/install/%d", mdmHost.ID, errTitleID), &installSoftwareRequest{}, http.StatusAccepted, &installResp)