35493 Android software configurations API endpoints (#36096)

**Related issue:** Resolves #35493
Notes: 
- Currently this adds a new function `updateAndroidAppConfigurationTx`
that uses a passed transaction to stay consistent with how
uploading/editing vpp apps treats display names and custom icons.
- In some places configuration uses `omitempty` to use `json.RawMessage`
but avoid it being set to "null" in requests/respones.
# Checklist for submitter

## Testing

- [x] Added/updated automated tests
- [ ] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [x] QA'd all new/changed functionality manually
- Can add an app with empty configuration {}
- Can delete the app, and configuration deletes as well
- Can't add app with invalid configuration
- "reason": "Couldn't update configuration. Only
\"managedConfiguration\" and \"workProfileWidgets\" are supported as
top-level keys."
- Can add an app with a good configuration
- Can edit app and change the configuration to something valid, invalid
gives error

For unreleased bug fixes in a release candidate, one of:

- [ ] Confirmed that the fix is not expected to adversely impact load
test results
- [ ] Alerted the release DRI if additional load testing is needed
This commit is contained in:
Jonathan Katz 2025-11-25 11:21:14 -05:00 committed by GitHub
parent 8ddb92de23
commit c274ebc63b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 266 additions and 27 deletions

View file

@ -449,6 +449,9 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, appID flee
assetMD := assetMetadata[asset.AdamID]
// Configuration is an Android only feature
appID.Configuration = nil
platforms := getPlatformsFromSupportedDevices(assetMD.SupportedDevices)
if _, ok := platforms[appID.Platform]; !ok {
return 0, fleet.NewInvalidArgumentError("app_store_id", fmt.Sprintf("%s isn't available for %s", assetMD.TrackName, appID.Platform))
@ -512,6 +515,7 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, appID flee
SelfService: app.SelfService,
LabelsIncludeAny: actLabelsIncl,
LabelsExcludeAny: actLabelsExcl,
Configuration: app.Configuration,
}
if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil {
@ -604,7 +608,7 @@ func getVPPAppsMetadata(ctx context.Context, ids []fleet.VPPAppTeam) ([]*fleet.V
return apps, nil
}
func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID *uint, selfService *bool, labelsIncludeAny, labelsExcludeAny, categories []string, displayName *string) (*fleet.VPPAppStoreApp, error) {
func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID *uint, payload fleet.AppStoreAppUpdatePayload) (*fleet.VPPAppStoreApp, error) {
if err := svc.authz.Authorize(ctx, &fleet.VPPApp{TeamID: teamID}, fleet.ActionWrite); err != nil {
return nil, err
}
@ -623,9 +627,9 @@ func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID
}
var validatedLabels *fleet.LabelIdentsWithScope
if labelsExcludeAny != nil || labelsIncludeAny != nil {
if payload.LabelsExcludeAny != nil || payload.LabelsIncludeAny != nil {
var err error
validatedLabels, err = ValidateSoftwareLabels(ctx, svc, labelsIncludeAny, labelsExcludeAny)
validatedLabels, err = ValidateSoftwareLabels(ctx, svc, payload.LabelsIncludeAny, payload.LabelsExcludeAny)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "UpdateAppStoreApp: validating software labels")
}
@ -637,8 +641,8 @@ func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID
}
selfServiceVal := meta.SelfService
if selfService != nil {
selfServiceVal = *selfService
if payload.SelfService != nil {
selfServiceVal = *payload.SelfService
}
appToWrite := &fleet.VPPApp{
@ -648,7 +652,8 @@ func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID
},
SelfService: selfServiceVal,
ValidatedLabels: validatedLabels,
DisplayName: displayName,
DisplayName: payload.DisplayName,
Configuration: payload.Configuration,
},
TeamID: teamID,
TitleID: titleID,
@ -660,23 +665,27 @@ func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID
appToWrite.IconURL = *meta.IconURL
}
if categories != nil {
categories = server.RemoveDuplicatesFromSlice(categories)
catIDs, err := svc.ds.GetSoftwareCategoryIDs(ctx, categories)
if payload.Categories != nil {
payload.Categories = server.RemoveDuplicatesFromSlice(payload.Categories)
catIDs, err := svc.ds.GetSoftwareCategoryIDs(ctx, payload.Categories)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "getting software category ids")
}
if len(catIDs) != len(categories) {
if len(catIDs) != len(payload.Categories) {
return nil, &fleet.BadRequestError{
Message: "some or all of the categories provided don't exist",
InternalErr: fmt.Errorf("categories provided: %v", categories),
InternalErr: fmt.Errorf("categories provided: %v", payload.Categories),
}
}
appToWrite.CategoryIDs = catIDs
}
if payload.Configuration != nil {
appToWrite.Configuration = payload.Configuration
}
// check if labels have changed
var existingLabels fleet.LabelIdentsWithScope
switch {
@ -738,7 +747,7 @@ func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID
actLabelsIncl, actLabelsExcl := activitySoftwareLabelsFromValidatedLabels(validatedLabels)
displayNameVal := ptr.ValOrZero(displayName)
displayNameVal := ptr.ValOrZero(payload.DisplayName)
act := fleet.ActivityEditedAppStoreApp{
TeamName: &teamName,
@ -752,6 +761,7 @@ func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID
LabelsExcludeAny: actLabelsExcl,
SoftwareIconURL: meta.IconURL,
SoftwareDisplayName: displayNameVal,
Configuration: appToWrite.Configuration,
}
if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil {
return nil, ctxerr.Wrap(ctx, err, "create activity for update app store app")

View file

@ -1624,3 +1624,35 @@ func (ds *Datastore) DeleteAndroidAppConfiguration(ctx context.Context, appID st
return nil
}
// updateAndroidAppConfigurationTx inserts or updates an app configuration using a transaction
func (ds *Datastore) updateAndroidAppConfigurationTx(ctx context.Context, tx sqlx.ExtContext, teamID *uint, appID string, config json.RawMessage) error {
err := fleet.ValidateAndroidAppConfiguration(config)
if err != nil {
return ctxerr.Wrap(ctx, err, "validating android app configuration")
}
var tid *uint
var globalOrTeamID uint
if teamID != nil {
globalOrTeamID = *teamID
if *teamID > 0 {
tid = teamID
}
}
stmt := `
INSERT INTO
android_app_configurations (application_id, team_id, global_or_team_id, configuration)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
configuration = VALUES(configuration)
`
_, err = tx.ExecContext(ctx, stmt, appID, tid, globalOrTeamID, config)
if err != nil {
return ctxerr.Wrap(ctx, err, "updateAndroidAppConfiguration")
}
return nil
}

View file

@ -59,6 +59,7 @@ func TestAndroid(t *testing.T) {
{"InsertAndroidAppConfiguration_Duplicate", testInsertAndroidAppConfigurationDuplicate},
{"AndroidAppConfiguration_CascadeDeleteTeam", testAndroidAppConfigurationCascadeDeleteTeam},
{"AndroidAppConfiguration_GlobalVsTeam", testAndroidAppConfigurationGlobalVsTeam},
{"AddDeleteAndroidAppWithConfiguration", testAddDeleteAndroidAppWithConfiguration},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
@ -2538,3 +2539,71 @@ func testAndroidAppConfigurationGlobalVsTeam(t *testing.T, ds *Datastore) {
require.Equal(t, teamID, *retrievedTeam.TeamID)
require.Equal(t, teamID, retrievedTeam.GlobalOrTeamID)
}
func testAddDeleteAndroidAppWithConfiguration(t *testing.T, ds *Datastore) {
ctx := context.Background()
team1, err := ds.NewTeam(ctx, &fleet.Team{Name: "team1"})
require.NoError(t, err)
test.CreateInsertGlobalVPPToken(t, ds)
testConfig := json.RawMessage(`{"ManagedConfiguration": {"DisableShareScreen": true, "DisableComputerAudio": true}}`)
// Create android and VPP apps
app1, err := ds.InsertVPPAppWithTeam(ctx, &fleet.VPPApp{
Name: "android1", BundleIdentifier: "android1",
VPPAppTeam: fleet.VPPAppTeam{VPPAppID: fleet.VPPAppID{AdamID: "something_android_app_1", Platform: fleet.AndroidPlatform},
Configuration: testConfig,
}}, &team1.ID)
require.NoError(t, err)
app2, err := ds.InsertVPPAppWithTeam(ctx, &fleet.VPPApp{
Name: "vpp1", BundleIdentifier: "com.app.vpp1",
VPPAppTeam: fleet.VPPAppTeam{VPPAppID: fleet.VPPAppID{AdamID: "adam_vpp_app_forapple_1", Platform: fleet.IOSPlatform},
Configuration: json.RawMessage(`{"ManagedConfiguration": {"ios app shouldn't have configuration": true}}`),
}}, &team1.ID)
require.NoError(t, err)
// Get android app without team
meta, err := ds.GetVPPAppMetadataByTeamAndTitleID(ctx, nil, app1.TitleID)
require.NoError(t, err)
require.Zero(t, meta.Configuration)
// Get android app and configuration
meta, err = ds.GetVPPAppMetadataByTeamAndTitleID(ctx, &team1.ID, app1.TitleID)
require.NoError(t, err)
require.NotZero(t, meta.VPPAppsTeamsID)
require.NotZero(t, meta.Configuration)
require.Equal(t, "android1", meta.BundleIdentifier)
require.Equal(t, testConfig, meta.Configuration)
// Get ios app
meta2, err := ds.GetVPPAppMetadataByTeamAndTitleID(ctx, nil, app2.TitleID)
require.NoError(t, err)
require.NotZero(t, meta2.VPPAppsTeamsID)
// Edit android app
newConfig := json.RawMessage(`{"workProfileWidgets": "WORK_PROFILE_WIDGETS_ALLOWED"}`)
app1.VPPAppTeam.Configuration = newConfig
_, err = ds.InsertVPPAppWithTeam(ctx, app1, &team1.ID)
require.NoError(t, err)
// Check that configuration was changed
meta, err = ds.GetVPPAppMetadataByTeamAndTitleID(ctx, &team1.ID, app1.TitleID)
require.NoError(t, err)
require.NotZero(t, meta.VPPAppsTeamsID)
require.Equal(t, newConfig, meta.Configuration)
// Add invalid configuration
badConfig := json.RawMessage(`"-": "-"`)
app1.VPPAppTeam.Configuration = badConfig
_, err = ds.InsertVPPAppWithTeam(ctx, app1, &team1.ID)
require.Error(t, err)
// Delete app, should delete configuration
require.NoError(t, ds.DeleteVPPAppFromTeam(ctx, &team1.ID, app1.VPPAppID))
_, err = ds.GetVPPAppMetadataByTeamAndTitleID(ctx, &team1.ID, app1.TitleID)
require.ErrorContains(t, err, "not found")
_, err = ds.GetAndroidAppConfiguration(ctx, app1.AdamID, team1.ID)
require.ErrorContains(t, err, "not found")
}

View file

@ -96,6 +96,14 @@ WHERE
app.DisplayName = displayName
config, err := ds.GetAndroidAppConfiguration(ctx, app.AdamID, tmID) // tmID can be used as globalOrTeamID
if err != nil && !fleet.IsNotFound(err) {
return nil, ctxerr.Wrap(ctx, err, "get android configuration for app store app")
}
if config != nil && config.Configuration != nil {
app.Configuration = config.Configuration
}
if teamID != nil {
policies, err := ds.getPoliciesBySoftwareTitleIDs(ctx, []uint{titleID}, *teamID)
if err != nil {
@ -642,6 +650,12 @@ func (ds *Datastore) InsertVPPAppWithTeam(ctx context.Context, app *fleet.VPPApp
}
}
if app.Configuration != nil && app.Platform == fleet.AndroidPlatform {
if err := ds.updateAndroidAppConfigurationTx(ctx, tx, teamID, app.AdamID, app.Configuration); err != nil {
return ctxerr.Wrap(ctx, err, "setting configuration for android app")
}
}
return nil
})
if err != nil {
@ -905,6 +919,14 @@ func (ds *Datastore) DeleteVPPAppFromTeam(ctx context.Context, teamID *uint, app
return notFound("VPPApp").WithMessage(fmt.Sprintf("adam id %s platform %s for team id %d", appID.AdamID, appID.Platform,
globalOrTeamID))
}
if appID.Platform == fleet.AndroidPlatform {
err := ds.DeleteAndroidAppConfiguration(ctx, appID.AdamID, globalOrTeamID)
if err != nil && !fleet.IsNotFound(err) {
return ctxerr.Wrap(ctx, err, "deleting android app configuration")
}
}
return nil
}

View file

@ -2262,6 +2262,7 @@ type ActivityAddedAppStoreApp struct {
SelfService bool `json:"self_service"`
LabelsIncludeAny []ActivitySoftwareLabel `json:"labels_include_any,omitempty"`
LabelsExcludeAny []ActivitySoftwareLabel `json:"labels_exclude_any,omitempty"`
Configuration json.RawMessage `json:"configuration,omitempty"`
}
func (a ActivityAddedAppStoreApp) ActivityName() string {
@ -2413,6 +2414,7 @@ type ActivityEditedAppStoreApp struct {
LabelsIncludeAny []ActivitySoftwareLabel `json:"labels_include_any,omitempty"`
LabelsExcludeAny []ActivitySoftwareLabel `json:"labels_exclude_any,omitempty"`
SoftwareDisplayName string `json:"software_display_name"`
Configuration json.RawMessage `json:"configuration,omitempty"`
}
func (a ActivityEditedAppStoreApp) ActivityName() string {

View file

@ -728,7 +728,7 @@ type Service interface {
// AddAppStoreApp persists a VPP app onto a team and returns the resulting title ID
AddAppStoreApp(ctx context.Context, teamID *uint, appTeam VPPAppTeam) (uint, error)
UpdateAppStoreApp(ctx context.Context, titleID uint, teamID *uint, selfService *bool, labelsIncludeAny, labelsExcludeAny, categories []string, displayName *string) (*VPPAppStoreApp, error)
UpdateAppStoreApp(ctx context.Context, titleID uint, teamID *uint, payload AppStoreAppUpdatePayload) (*VPPAppStoreApp, error)
// GetInHouseAppManifest returns a manifest XML file that points at the download URL for the given in-house app.
GetInHouseAppManifest(ctx context.Context, titleID uint, teamID *uint) ([]byte, error)

View file

@ -1,6 +1,7 @@
package fleet
import (
"encoding/json"
"fmt"
"time"
)
@ -47,6 +48,9 @@ type VPPAppTeam struct {
// app creation if AddAutoInstallPolicy is true.
AddedAutomaticInstallPolicy *Policy `json:"-"`
DisplayName *string `json:"display_name"`
// Configuration is a json file used to customize Android app
// behavior/settings. Relevant to Android only.
Configuration json.RawMessage `json:"configuration,omitempty"`
}
// VPPApp represents a VPP (Volume Purchase Program) application,
@ -102,8 +106,9 @@ type VPPAppStoreApp struct {
AddedAt time.Time `db:"added_at" json:"created_at"`
// Categories is the list of categories to which this software belongs: e.g. "Productivity",
// "Browsers", etc.
Categories []string `json:"categories"`
DisplayName string `json:"display_name"`
Categories []string `json:"categories"`
DisplayName string `json:"display_name"`
Configuration json.RawMessage `json:"configuration"`
}
// VPPAppStatusSummary represents aggregated status metrics for a VPP app.
@ -138,3 +143,12 @@ const (
DefaultVPPInstallVerifyTimeout = 10 * time.Minute
DefaultVPPVerifyRequestDelay = 5 * time.Second
)
type AppStoreAppUpdatePayload struct {
SelfService *bool
LabelsIncludeAny []string
LabelsExcludeAny []string
Categories []string
DisplayName *string
Configuration json.RawMessage
}

View file

@ -470,7 +470,7 @@ type GetAppStoreAppsFunc func(ctx context.Context, teamID *uint) ([]*fleet.VPPAp
type AddAppStoreAppFunc func(ctx context.Context, teamID *uint, appTeam fleet.VPPAppTeam) (uint, error)
type UpdateAppStoreAppFunc func(ctx context.Context, titleID uint, teamID *uint, selfService *bool, labelsIncludeAny []string, labelsExcludeAny []string, categories []string, displayName *string) (*fleet.VPPAppStoreApp, error)
type UpdateAppStoreAppFunc func(ctx context.Context, titleID uint, teamID *uint, payload fleet.AppStoreAppUpdatePayload) (*fleet.VPPAppStoreApp, error)
type GetInHouseAppManifestFunc func(ctx context.Context, titleID uint, teamID *uint) ([]byte, error)
@ -3719,11 +3719,11 @@ func (s *Service) AddAppStoreApp(ctx context.Context, teamID *uint, appTeam flee
return s.AddAppStoreAppFunc(ctx, teamID, appTeam)
}
func (s *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID *uint, selfService *bool, labelsIncludeAny []string, labelsExcludeAny []string, categories []string, displayName *string) (*fleet.VPPAppStoreApp, error) {
func (s *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID *uint, payload fleet.AppStoreAppUpdatePayload) (*fleet.VPPAppStoreApp, error) {
s.mu.Lock()
s.UpdateAppStoreAppFuncInvoked = true
s.mu.Unlock()
return s.UpdateAppStoreAppFunc(ctx, titleID, teamID, selfService, labelsIncludeAny, labelsExcludeAny, categories, displayName)
return s.UpdateAppStoreAppFunc(ctx, titleID, teamID, payload)
}
func (s *Service) GetInHouseAppManifest(ctx context.Context, titleID uint, teamID *uint) ([]byte, error) {

View file

@ -2,6 +2,7 @@ package service
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
@ -221,4 +222,82 @@ func (s *integrationMDMTestSuite) TestAndroidAppSelfService() {
// Should have hit the android API endpoint
s.Assert().True(s.androidAPIClient.EnterprisesPoliciesModifyPolicyApplicationsFuncInvoked)
// Test Android app configurations
// Android app with configuration
appConfiguration := json.RawMessage(`{"workProfileWidgets": "WORK_PROFILE_WIDGETS_ALLOWED"}`)
androidAppWithConfig := &fleet.VPPApp{
VPPAppTeam: fleet.VPPAppTeam{
VPPAppID: fleet.VPPAppID{
AdamID: "com.fooooooo",
Platform: fleet.AndroidPlatform,
},
Configuration: appConfiguration,
},
Name: "foo",
BundleIdentifier: "com.fooooooo",
IconURL: "https://example.com/images/2",
}
// Add Android app
var appWithConfigResp addAppStoreAppResponse
s.DoJSON(
"POST",
"/api/latest/fleet/software/app_store_apps",
&addAppStoreAppRequest{
AppStoreID: androidAppWithConfig.AdamID,
Platform: androidAppWithConfig.VPPAppID.Platform,
Configuration: androidAppWithConfig.Configuration,
},
http.StatusOK,
&appWithConfigResp,
)
// Verify that activity includes configuration
s.lastActivityMatches(fleet.ActivityAddedAppStoreApp{}.ActivityName(),
fmt.Sprintf(`{"team_name": "%s", "software_title": "%s", "software_title_id": %d, "app_store_id": "%s", "team_id": %s, "platform": "%s", "self_service": true,"configuration": %s}`,
"", "Test App", appWithConfigResp.TitleID, androidAppWithConfig.AdamID, "null", androidAppWithConfig.Platform, androidAppWithConfig.Configuration), 0)
// Should see it in host software library
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host1.ID), nil, http.StatusOK, &getHostSw, "available_for_install", "true")
assert.Len(t, getHostSw.Software, 2)
s.Assert().NotNil(getHostSw.Software[1].AppStoreApp)
s.Assert().Equal(androidAppWithConfig.AdamID, getHostSw.Software[1].AppStoreApp.AppStoreID)
// Edit app without changing configuration
s.DoJSON(
"PATCH",
fmt.Sprintf("/api/latest/fleet/software/titles/%d/app_store_app", appWithConfigResp.TitleID),
&updateAppStoreAppRequest{},
http.StatusOK,
&addAppResp,
)
s.lastActivityMatches(fleet.ActivityEditedAppStoreApp{}.ActivityName(), "", 0)
var titleWithConfigResp getSoftwareTitleResponse
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d", appWithConfigResp.TitleID), &getSoftwareTitleRequest{
ID: appWithConfigResp.TitleID,
TeamID: nil,
}, http.StatusOK, &titleWithConfigResp)
var responseConf map[string]any
require.NoError(t, json.Unmarshal(titleWithConfigResp.SoftwareTitle.AppStoreApp.Configuration, &responseConf))
require.Contains(t, responseConf, "workProfileWidgets")
// Edit app and change configuration
newConfig := json.RawMessage(`{"managedConfiguration": {"key": "value"}}`)
s.DoJSON(
"PATCH",
fmt.Sprintf("/api/latest/fleet/software/titles/%d/app_store_app", appWithConfigResp.TitleID),
&updateAppStoreAppRequest{
Configuration: newConfig,
},
http.StatusOK,
&addAppResp,
)
// Verify that configuration changed and last activity is correct
s.lastActivityMatches(fleet.ActivityEditedAppStoreApp{}.ActivityName(),
fmt.Sprintf(`{"team_name": "%s", "software_title": "%s", "software_icon_url":"https://example.com/1.jpg", "software_title_id": %d, "app_store_id": "%s", "team_id": %s, "software_display_name":"", "platform": "%s", "self_service": true,"configuration": %s}`,
"", "Test App", appWithConfigResp.TitleID, androidAppWithConfig.AdamID, "null", androidAppWithConfig.Platform, newConfig), 0)
}

View file

@ -2,6 +2,7 @@ package service
import (
"context"
"encoding/json"
"io"
"mime/multipart"
"net/http"
@ -58,6 +59,7 @@ type addAppStoreAppRequest struct {
LabelsIncludeAny []string `json:"labels_include_any"`
LabelsExcludeAny []string `json:"labels_exclude_any"`
Categories []string `json:"categories"`
Configuration json.RawMessage `json:"configuration,omitempty"`
}
type addAppStoreAppResponse struct {
@ -76,6 +78,7 @@ func addAppStoreAppEndpoint(ctx context.Context, request interface{}, svc fleet.
LabelsExcludeAny: req.LabelsExcludeAny,
AddAutoInstallPolicy: req.AutomaticInstall,
Categories: req.Categories,
Configuration: req.Configuration,
})
if err != nil {
return &addAppStoreAppResponse{Err: err}, nil
@ -97,13 +100,14 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, _ *uint, _ fleet.VPPAppT
//////////////////////////////////////////////////////////////////////////////
type updateAppStoreAppRequest struct {
TitleID uint `url:"title_id"`
TeamID *uint `json:"team_id"`
SelfService *bool `json:"self_service"`
LabelsIncludeAny []string `json:"labels_include_any"`
LabelsExcludeAny []string `json:"labels_exclude_any"`
Categories []string `json:"categories"`
DisplayName *string `json:"display_name"`
TitleID uint `url:"title_id"`
TeamID *uint `json:"team_id"`
SelfService *bool `json:"self_service"`
LabelsIncludeAny []string `json:"labels_include_any"`
LabelsExcludeAny []string `json:"labels_exclude_any"`
Categories []string `json:"categories"`
Configuration json.RawMessage `json:"configuration,omitempty"`
DisplayName *string `json:"display_name"`
}
type updateAppStoreAppResponse struct {
@ -116,7 +120,14 @@ func (r updateAppStoreAppResponse) Error() error { return r.Err }
func updateAppStoreAppEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) {
req := request.(*updateAppStoreAppRequest)
updatedApp, err := svc.UpdateAppStoreApp(ctx, req.TitleID, req.TeamID, req.SelfService, req.LabelsIncludeAny, req.LabelsExcludeAny, req.Categories, req.DisplayName)
updatedApp, err := svc.UpdateAppStoreApp(ctx, req.TitleID, req.TeamID, fleet.AppStoreAppUpdatePayload{
SelfService: req.SelfService,
LabelsIncludeAny: req.LabelsIncludeAny,
LabelsExcludeAny: req.LabelsExcludeAny,
Categories: req.Categories,
Configuration: req.Configuration,
DisplayName: req.DisplayName,
})
if err != nil {
return updateAppStoreAppResponse{Err: err}, nil
}
@ -124,7 +135,7 @@ func updateAppStoreAppEndpoint(ctx context.Context, request interface{}, svc fle
return updateAppStoreAppResponse{AppStoreApp: updatedApp}, nil
}
func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID *uint, selfService *bool, labelsIncludeAny, labelsExcludeAny, categories []string, displayName *string) (*fleet.VPPAppStoreApp, error) {
func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID *uint, payload fleet.AppStoreAppUpdatePayload) (*fleet.VPPAppStoreApp, error) {
// skipauth: No authorization check needed due to implementation returning
// only license error.
svc.authz.SkipAuthorization(ctx)