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
10 changed files with 266 additions and 27 deletions
+22 -12
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")