Android config: policy reconciliation/application (#36627)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
* Applied the Android app configuration to the devices.
|
||||
+35
-13
@@ -506,6 +506,7 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, appID flee
|
||||
isAndroidAppID := androidApplicationID.MatchString(appID.AdamID)
|
||||
|
||||
var app *fleet.VPPApp
|
||||
var androidEnterpriseName string
|
||||
|
||||
// Different flows based on platform
|
||||
switch appID.Platform {
|
||||
@@ -520,8 +521,9 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, appID flee
|
||||
if err != nil {
|
||||
return 0, &fleet.BadRequestError{Message: "Android MDM is not enabled", InternalErr: err}
|
||||
}
|
||||
androidEnterpriseName = enterprise.Name()
|
||||
|
||||
androidApp, err := svc.androidModule.EnterprisesApplications(ctx, enterprise.Name(), appID.AdamID)
|
||||
androidApp, err := svc.androidModule.EnterprisesApplications(ctx, androidEnterpriseName, appID.AdamID)
|
||||
if err != nil {
|
||||
if fleet.IsNotFound(err) {
|
||||
return 0, fleet.NewInvalidArgumentError("app_store_id", "Couldn't add software. The application ID isn't available in Play Store. Please find ID on the Play Store and try again.")
|
||||
@@ -537,11 +539,6 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, appID flee
|
||||
TeamID: teamID,
|
||||
}
|
||||
|
||||
err = worker.QueueMakeAndroidAppAvailableJob(context.Background(), svc.ds, svc.logger, appID.AdamID, app.AppTeamID, enterprise.Name())
|
||||
if err != nil {
|
||||
return 0, ctxerr.Wrap(ctx, err, "enqueuing job to make android app available")
|
||||
}
|
||||
|
||||
default:
|
||||
if isAndroidAppID {
|
||||
return 0, fleet.NewInvalidArgumentError(
|
||||
@@ -623,13 +620,18 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, appID flee
|
||||
Name: assetMD.TrackName,
|
||||
LatestVersion: assetMD.Version,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
addedApp, err := svc.ds.InsertVPPAppWithTeam(ctx, app, teamID)
|
||||
if err != nil {
|
||||
return 0, ctxerr.Wrap(ctx, err, "writing VPP app to db")
|
||||
}
|
||||
if appID.Platform == fleet.AndroidPlatform {
|
||||
err := worker.QueueMakeAndroidAppAvailableJob(ctx, svc.ds, svc.logger, appID.AdamID, addedApp.AppTeamID, androidEnterpriseName)
|
||||
if err != nil {
|
||||
return 0, ctxerr.Wrap(ctx, err, "enqueuing job to make android app available")
|
||||
}
|
||||
}
|
||||
|
||||
actLabelsIncl, actLabelsExcl := activitySoftwareLabelsFromValidatedLabels(addedApp.ValidatedLabels)
|
||||
|
||||
@@ -663,7 +665,6 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, appID flee
|
||||
}
|
||||
|
||||
return addedApp.TitleID, nil
|
||||
|
||||
}
|
||||
|
||||
func getVPPAppsMetadata(ctx context.Context, ids []fleet.VPPAppTeam) ([]*fleet.VPPApp, error) {
|
||||
@@ -784,6 +785,9 @@ func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID
|
||||
if payload.SelfService != nil && meta.Platform != fleet.AndroidPlatform {
|
||||
selfServiceVal = *payload.SelfService
|
||||
}
|
||||
if payload.Configuration != nil && meta.Platform != fleet.AndroidPlatform {
|
||||
payload.Configuration = nil
|
||||
}
|
||||
|
||||
appToWrite := &fleet.VPPApp{
|
||||
VPPAppTeam: fleet.VPPAppTeam{
|
||||
@@ -822,10 +826,6 @@ func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID
|
||||
appToWrite.CategoryIDs = catIDs
|
||||
}
|
||||
|
||||
if payload.Configuration != nil {
|
||||
appToWrite.Configuration = payload.Configuration
|
||||
}
|
||||
|
||||
// check if labels have changed
|
||||
var existingLabels fleet.LabelIdentsWithScope
|
||||
switch {
|
||||
@@ -857,12 +857,34 @@ func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID
|
||||
}
|
||||
}
|
||||
|
||||
var androidConfigChanged bool
|
||||
if !labelsChanged && meta.Platform == fleet.AndroidPlatform {
|
||||
// check if configuration has changed
|
||||
androidConfigChanged, err = svc.ds.HasAndroidAppConfigurationChanged(ctx, meta.AdamID, ptr.ValOrZero(teamID), payload.Configuration)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "UpdateAppStoreApp: checking if android app configuration changed")
|
||||
}
|
||||
}
|
||||
|
||||
// Update the app
|
||||
_, err = svc.ds.InsertVPPAppWithTeam(ctx, appToWrite, teamID)
|
||||
insertedApp, err := svc.ds.InsertVPPAppWithTeam(ctx, appToWrite, teamID)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "UpdateAppStoreApp: write app to db")
|
||||
}
|
||||
|
||||
// if labelsChanged, new hosts may require having the app made available, and if config
|
||||
// changed, the app policy must be updated.
|
||||
if meta.Platform == fleet.AndroidPlatform && (labelsChanged || androidConfigChanged) {
|
||||
enterprise, err := svc.ds.GetEnterprise(ctx)
|
||||
if err != nil {
|
||||
return nil, &fleet.BadRequestError{Message: "Android MDM is not enabled", InternalErr: err}
|
||||
}
|
||||
err = worker.QueueMakeAndroidAppAvailableJob(ctx, svc.ds, svc.logger, appToWrite.AdamID, insertedApp.AppTeamID, enterprise.Name())
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "enqueuing job to make android app available")
|
||||
}
|
||||
}
|
||||
|
||||
if labelsChanged {
|
||||
// Get the hosts that are now IN label scope (after the update)
|
||||
hostsInScope, err := svc.ds.GetIncludedHostIDMapForVPPApp(ctx, meta.VPPAppsTeamsID)
|
||||
|
||||
@@ -1632,6 +1632,37 @@ WHERE
|
||||
})
|
||||
}
|
||||
|
||||
// HasAndroidAppConfigurationChanged checks if the new configuration for an Android app
|
||||
// identified by adam_id and global_or_team_id is different from the existing one. This
|
||||
// is a datastore method so that we rely on mysql's canonicalisation of JSON for comparison.
|
||||
func (ds *Datastore) HasAndroidAppConfigurationChanged(ctx context.Context, applicationID string, globalOrTeamID uint, newConfig json.RawMessage) (bool, error) {
|
||||
const stmt = `
|
||||
SELECT
|
||||
CAST(? AS JSON) != configuration AS has_changed
|
||||
FROM
|
||||
android_app_configurations
|
||||
WHERE
|
||||
application_id = ? AND
|
||||
global_or_team_id = ?
|
||||
`
|
||||
|
||||
newConfigStr := string(newConfig)
|
||||
if len(newConfigStr) == 0 {
|
||||
newConfigStr = "{}" // consider an empty config as an empty JSON for comparison's sake
|
||||
}
|
||||
|
||||
var hasChanged bool
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &hasChanged, stmt, newConfigStr, applicationID, globalOrTeamID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
// old config does not exist, so old one is changed if not empty
|
||||
return len(newConfig) > 0, nil
|
||||
}
|
||||
return false, ctxerr.Wrap(ctx, err, "compare android app configuration")
|
||||
}
|
||||
return hasChanged, nil
|
||||
}
|
||||
|
||||
// GetAndroidAppConfiguration retrieves the configuration for an Android app
|
||||
// identified by adam_id and global_or_team_id.
|
||||
func (ds *Datastore) GetAndroidAppConfiguration(ctx context.Context, adamID string, globalOrTeamID uint) (*fleet.AndroidAppConfiguration, error) {
|
||||
@@ -1660,6 +1691,65 @@ func (ds *Datastore) GetAndroidAppConfiguration(ctx context.Context, adamID stri
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetAndroidAppConfigurationByAppTeamID(ctx context.Context, vppAppTeamID uint) (*fleet.AndroidAppConfiguration, error) {
|
||||
stmt := `
|
||||
SELECT
|
||||
aac.id,
|
||||
aac.application_id,
|
||||
aac.team_id,
|
||||
aac.global_or_team_id,
|
||||
aac.configuration,
|
||||
aac.created_at,
|
||||
aac.updated_at
|
||||
FROM android_app_configurations aac
|
||||
JOIN vpp_apps_teams vat
|
||||
ON vat.adam_id = aac.application_id AND vat.global_or_team_id = aac.global_or_team_id
|
||||
WHERE vat.id = ?
|
||||
`
|
||||
|
||||
var config fleet.AndroidAppConfiguration
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &config, stmt, vppAppTeamID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ctxerr.Wrap(ctx, notFound("AndroidAppConfiguration"))
|
||||
}
|
||||
return nil, ctxerr.Wrap(ctx, err, "get android app configuration")
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) BulkGetAndroidAppConfigurations(ctx context.Context, appIDs []string, globalOrTeamID uint) (map[string]json.RawMessage, error) {
|
||||
const bulkGetStmt = `
|
||||
SELECT
|
||||
application_id,
|
||||
configuration
|
||||
FROM android_app_configurations
|
||||
WHERE application_id IN (?) AND global_or_team_id = ?
|
||||
`
|
||||
|
||||
if len(appIDs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
stmt, args, err := sqlx.In(bulkGetStmt, appIDs, globalOrTeamID)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "building bulk get android app configurations query")
|
||||
}
|
||||
|
||||
var configs []*fleet.AndroidAppConfiguration
|
||||
err = sqlx.SelectContext(ctx, ds.reader(ctx), &configs, stmt, args...)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "bulk get android app configurations")
|
||||
}
|
||||
|
||||
m := make(map[string]json.RawMessage, len(configs))
|
||||
for _, c := range configs {
|
||||
m[c.ApplicationID] = c.Configuration
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// InsertAndroidAppConfiguration creates a new Android app configuration entry.
|
||||
func (ds *Datastore) InsertAndroidAppConfiguration(ctx context.Context, config *fleet.AndroidAppConfiguration) error {
|
||||
stmt := `
|
||||
|
||||
@@ -60,6 +60,7 @@ func TestAndroid(t *testing.T) {
|
||||
{"AndroidAppConfiguration_CascadeDeleteTeam", testAndroidAppConfigurationCascadeDeleteTeam},
|
||||
{"AndroidAppConfiguration_GlobalVsTeam", testAndroidAppConfigurationGlobalVsTeam},
|
||||
{"AddDeleteAndroidAppWithConfiguration", testAddDeleteAndroidAppWithConfiguration},
|
||||
{"HasAndroidAppConfigurationChanged", testHasAndroidAppConfigurationChanged},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@@ -2366,6 +2367,18 @@ func testInsertAndGetAndroidAppConfiguration(t *testing.T, ds *Datastore) {
|
||||
require.NotZero(t, retrieved.ID)
|
||||
require.NotZero(t, retrieved.CreatedAt)
|
||||
require.NotZero(t, retrieved.UpdatedAt)
|
||||
|
||||
// test bulk-get configuration
|
||||
configsByAppID, err := ds.BulkGetAndroidAppConfigurations(testCtx(), []string{appID}, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, configsByAppID, 1)
|
||||
require.Equal(t, string(retrieved.Configuration), string(configsByAppID[appID]))
|
||||
|
||||
// bulk-get configuration returns any known app config, ignores others
|
||||
configsByAppID, err = ds.BulkGetAndroidAppConfigurations(testCtx(), []string{appID, "no-such-app"}, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, configsByAppID, 1)
|
||||
require.Equal(t, string(retrieved.Configuration), string(configsByAppID[appID]))
|
||||
}
|
||||
|
||||
func testUpdateAndroidAppConfiguration(t *testing.T, ds *Datastore) {
|
||||
@@ -2607,3 +2620,94 @@ func testAddDeleteAndroidAppWithConfiguration(t *testing.T, ds *Datastore) {
|
||||
_, err = ds.GetAndroidAppConfiguration(ctx, app1.AdamID, team1.ID)
|
||||
require.ErrorContains(t, err, "not found")
|
||||
}
|
||||
|
||||
func testHasAndroidAppConfigurationChanged(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
|
||||
appID := "com.example.testapp"
|
||||
setupTestApp(t, ds, appID)
|
||||
|
||||
config := &fleet.AndroidAppConfiguration{
|
||||
ApplicationID: appID,
|
||||
TeamID: nil,
|
||||
GlobalOrTeamID: 0,
|
||||
Configuration: json.RawMessage(`{"managedConfiguration": {"a": 1}}`),
|
||||
}
|
||||
err := ds.InsertAndroidAppConfiguration(ctx, config)
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
newConfig string
|
||||
compareAppID string
|
||||
changed bool
|
||||
}{
|
||||
{
|
||||
desc: "empty new config",
|
||||
newConfig: "",
|
||||
compareAppID: appID,
|
||||
changed: true,
|
||||
},
|
||||
{
|
||||
desc: "empty object",
|
||||
newConfig: "{}",
|
||||
compareAppID: appID,
|
||||
changed: true,
|
||||
},
|
||||
{
|
||||
desc: "boolean instead of object",
|
||||
newConfig: "false",
|
||||
compareAppID: appID,
|
||||
changed: true,
|
||||
},
|
||||
{
|
||||
desc: "empty managedConfiguration",
|
||||
newConfig: `{"managedConfiguration": {}}`,
|
||||
compareAppID: appID,
|
||||
changed: true,
|
||||
},
|
||||
{
|
||||
desc: "same config",
|
||||
newConfig: `{"managedConfiguration": {"a":1}}`,
|
||||
compareAppID: appID,
|
||||
changed: false,
|
||||
},
|
||||
{
|
||||
desc: "slightly different config",
|
||||
newConfig: `{"managedConfiguration": {"a":"b"}}`,
|
||||
compareAppID: appID,
|
||||
changed: true,
|
||||
},
|
||||
{
|
||||
desc: "expanded different config",
|
||||
newConfig: `{"managedConfiguration": {"a":1, "b":2}}`,
|
||||
compareAppID: appID,
|
||||
changed: true,
|
||||
},
|
||||
{
|
||||
desc: "very different config",
|
||||
newConfig: `{"workProfileWidgets": "WORK_PROFILE_WIDGETS_ALLOWED"}`,
|
||||
compareAppID: appID,
|
||||
changed: true,
|
||||
},
|
||||
{
|
||||
desc: "empty compared to non-existing",
|
||||
newConfig: ``,
|
||||
compareAppID: "com.no-such.app",
|
||||
changed: false,
|
||||
},
|
||||
{
|
||||
desc: "some config compared to non-existing",
|
||||
newConfig: `{"workProfileWidgets": "WORK_PROFILE_WIDGETS_ALLOWED"}`,
|
||||
compareAppID: "com.no-such.app",
|
||||
changed: true,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
got, err := ds.HasAndroidAppConfigurationChanged(ctx, c.compareAppID, 0, json.RawMessage(c.newConfig))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.changed, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1947,11 +1947,11 @@ WHERE (unique_identifier, source, extension_for) IN (%s)
|
||||
`
|
||||
|
||||
const getSoftwareTitle = `
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
software_titles
|
||||
WHERE
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
software_titles
|
||||
WHERE
|
||||
unique_identifier = ? AND source = ? AND extension_for = ''
|
||||
`
|
||||
|
||||
@@ -2167,7 +2167,7 @@ INSERT INTO software_installers (
|
||||
install_during_setup,
|
||||
fleet_maintained_app_id
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||
(SELECT name FROM users WHERE id = ?), (SELECT email FROM users WHERE id = ?), ?, ?, COALESCE(?, false), ?
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
|
||||
@@ -193,7 +193,7 @@ func ValidateAndroidAppConfiguration(config json.RawMessage) error {
|
||||
|
||||
type androidAppConfig struct {
|
||||
ManagedConfiguration json.RawMessage `json:"managedConfiguration"`
|
||||
WorkProfileWidgets json.RawMessage `json:"workProfileWidgets"`
|
||||
WorkProfileWidgets string `json:"workProfileWidgets"`
|
||||
}
|
||||
|
||||
var cfg androidAppConfig
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestValidateAndroidAppConfiguration(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "valid - both keys",
|
||||
config: json.RawMessage(`{"managedConfiguration": {"key": "value"}, "workProfileWidgets": true}`),
|
||||
config: json.RawMessage(`{"managedConfiguration": {"key": "value"}, "workProfileWidgets": ""}`),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
@@ -34,9 +34,15 @@ func TestValidateAndroidAppConfiguration(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "valid - workProfileWidgets only",
|
||||
config: json.RawMessage(`{"workProfileWidgets": false}`),
|
||||
config: json.RawMessage(`{"workProfileWidgets": "WORK_PROFILE_WIDGETS_ALLOWED"}`),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid - workProfileWidgets bad type",
|
||||
config: json.RawMessage(`{"workProfileWidgets": false}`),
|
||||
expectError: true,
|
||||
errorMsg: "Couldn't update configuration. Invalid JSON.",
|
||||
},
|
||||
{
|
||||
name: "valid - empty object",
|
||||
config: json.RawMessage(`{}`),
|
||||
|
||||
@@ -2376,6 +2376,12 @@ type Datastore interface {
|
||||
// GetAndroidAppConfiguration retrieves the configuration for an Android app
|
||||
// identified by adam_id and global_or_team_id.
|
||||
GetAndroidAppConfiguration(ctx context.Context, adamID string, globalOrTeamID uint) (*AndroidAppConfiguration, error)
|
||||
GetAndroidAppConfigurationByAppTeamID(ctx context.Context, vppAppTeamID uint) (*AndroidAppConfiguration, error)
|
||||
HasAndroidAppConfigurationChanged(ctx context.Context, applicationID string, globalOrTeamID uint, newConfig json.RawMessage) (bool, error)
|
||||
|
||||
// BulkGetAndroidAppConfigurations retrieves Android app configurations for
|
||||
// all provided apps and returns them indexed by the app id.
|
||||
BulkGetAndroidAppConfigurations(ctx context.Context, appIDs []string, globalOrTeamID uint) (map[string]json.RawMessage, error)
|
||||
|
||||
// InsertAndroidAppConfiguration creates a new Android app configuration entry.
|
||||
InsertAndroidAppConfiguration(ctx context.Context, config *AndroidAppConfiguration) error
|
||||
|
||||
@@ -22,9 +22,11 @@ type Service interface {
|
||||
UnenrollAndroidHost(ctx context.Context, hostID uint) error
|
||||
|
||||
EnterprisesApplications(ctx context.Context, enterpriseName, applicationID string) (*androidmanagement.Application, error)
|
||||
AddAppsToAndroidPolicy(ctx context.Context, enterpriseName string, applicationIDs []string, hostUUIDs map[string]string, installType string) (map[string]*MDMAndroidPolicyRequest, error)
|
||||
AddAppsToAndroidPolicy(ctx context.Context, enterpriseName string, appPolicies []*androidmanagement.ApplicationPolicy, hostUUIDs map[string]string) (map[string]*MDMAndroidPolicyRequest, error)
|
||||
// SetAppsForAndroidPolicy sets the available apps for the given hosts' Android MDM policy to the given list of apps.
|
||||
SetAppsForAndroidPolicy(ctx context.Context, enterpriseName string, applicationIDs []string, hostUUIDs map[string]string, installType string) error
|
||||
// Note that unlike AddAppsToAndroidPolicy, this method replaces the existing app list with the given one, it is
|
||||
// not additive/PATCH semantics.
|
||||
SetAppsForAndroidPolicy(ctx context.Context, enterpriseName string, appPolicies []*androidmanagement.ApplicationPolicy, hostUUIDs map[string]string) error
|
||||
AddFleetAgentToAndroidPolicy(ctx context.Context, enterpriseName string, hostConfigs map[string]AgentManagedConfiguration) error
|
||||
// BuildAndSendFleetAgentConfig builds the complete AgentManagedConfiguration for the given hosts
|
||||
// (including certificate templates) and sends it to the Android Management API.
|
||||
|
||||
@@ -870,15 +870,7 @@ func (svc *Service) EnterprisesApplications(ctx context.Context, enterpriseName,
|
||||
|
||||
// Adds the specified apps to the host-specific Android policy of the provided hosts, and
|
||||
// returns a map of host UUID to the policy request object of their updated policy on success.
|
||||
func (svc *Service) AddAppsToAndroidPolicy(ctx context.Context, enterpriseName string, applicationIDs []string, hostUUIDs map[string]string, installType string) (map[string]*android.MDMAndroidPolicyRequest, error) {
|
||||
var appPolicies []*androidmanagement.ApplicationPolicy
|
||||
for _, a := range applicationIDs {
|
||||
appPolicies = append(appPolicies, &androidmanagement.ApplicationPolicy{
|
||||
PackageName: a,
|
||||
InstallType: installType,
|
||||
})
|
||||
}
|
||||
|
||||
func (svc *Service) AddAppsToAndroidPolicy(ctx context.Context, enterpriseName string, appPolicies []*androidmanagement.ApplicationPolicy, hostUUIDs map[string]string) (map[string]*android.MDMAndroidPolicyRequest, error) {
|
||||
var errs []error
|
||||
hostToPolicyRequest := make(map[string]*android.MDMAndroidPolicyRequest, len(hostUUIDs))
|
||||
for uuid, policyID := range hostUUIDs {
|
||||
@@ -1291,20 +1283,28 @@ func (svc *Service) BuildAndSendFleetAgentConfig(ctx context.Context, enterprise
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) SetAppsForAndroidPolicy(ctx context.Context, enterpriseName string, applicationIDs []string, hostUUIDs map[string]string, installType string) error {
|
||||
var appPolicies []*androidmanagement.ApplicationPolicy
|
||||
for _, a := range applicationIDs {
|
||||
appPolicies = append(appPolicies, &androidmanagement.ApplicationPolicy{PackageName: a, InstallType: "AVAILABLE"})
|
||||
}
|
||||
for _, policyID := range hostUUIDs {
|
||||
|
||||
policy := &androidmanagement.Policy{Applications: appPolicies}
|
||||
// SetAppsForAndroidPolicy sets the available apps for the given hosts' Android MDM policy to the given list of apps.
|
||||
// Note that unlike AddAppsToAndroidPolicy, this method replaces the existing app list with the given one, it is
|
||||
// not additive/PATCH semantics.
|
||||
func (svc *Service) SetAppsForAndroidPolicy(ctx context.Context, enterpriseName string, appPolicies []*androidmanagement.ApplicationPolicy, hostUUIDs map[string]string) error {
|
||||
var errs []error
|
||||
for uuid, policyID := range hostUUIDs {
|
||||
policyName := fmt.Sprintf("%s/policies/%s", enterpriseName, policyID)
|
||||
|
||||
_, err := svc.androidAPIClient.EnterprisesPoliciesPatch(ctx, policyName, policy, androidmgmt.PoliciesPatchOpts{OnlyUpdateApps: true})
|
||||
policyRequest, err := newAndroidPolicyApplicationsRequest(policyID, policyName, appPolicies)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "setting apps list for android policy")
|
||||
return ctxerr.Wrapf(ctx, err, "prepare policy request %s", policyName)
|
||||
}
|
||||
|
||||
var apiErr error
|
||||
policy := &androidmanagement.Policy{Applications: appPolicies}
|
||||
policy, apiErr = svc.androidAPIClient.EnterprisesPoliciesPatch(ctx, policyName, policy, androidmgmt.PoliciesPatchOpts{OnlyUpdateApps: true})
|
||||
if _, err := recordAndroidRequestResult(ctx, svc.fleetDS, policyRequest, policy, nil, apiErr); err != nil {
|
||||
return ctxerr.Wrapf(ctx, err, "save android policy request for host %s", uuid)
|
||||
}
|
||||
|
||||
if apiErr != nil {
|
||||
errs = append(errs, ctxerr.Wrapf(ctx, apiErr, "google api: modify policy applications for host %s", uuid))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
@@ -1547,6 +1547,12 @@ type InsertAndroidSetupExperienceSoftwareInstallFunc func(ctx context.Context, p
|
||||
|
||||
type GetAndroidAppConfigurationFunc func(ctx context.Context, adamID string, globalOrTeamID uint) (*fleet.AndroidAppConfiguration, error)
|
||||
|
||||
type GetAndroidAppConfigurationByAppTeamIDFunc func(ctx context.Context, vppAppTeamID uint) (*fleet.AndroidAppConfiguration, error)
|
||||
|
||||
type HasAndroidAppConfigurationChangedFunc func(ctx context.Context, applicationID string, globalOrTeamID uint, newConfig json.RawMessage) (bool, error)
|
||||
|
||||
type BulkGetAndroidAppConfigurationsFunc func(ctx context.Context, appIDs []string, globalOrTeamID uint) (map[string]json.RawMessage, error)
|
||||
|
||||
type InsertAndroidAppConfigurationFunc func(ctx context.Context, config *fleet.AndroidAppConfiguration) error
|
||||
|
||||
type UpdateAndroidAppConfigurationFunc func(ctx context.Context, config *fleet.AndroidAppConfiguration) error
|
||||
@@ -3952,6 +3958,15 @@ type DataStore struct {
|
||||
GetAndroidAppConfigurationFunc GetAndroidAppConfigurationFunc
|
||||
GetAndroidAppConfigurationFuncInvoked bool
|
||||
|
||||
GetAndroidAppConfigurationByAppTeamIDFunc GetAndroidAppConfigurationByAppTeamIDFunc
|
||||
GetAndroidAppConfigurationByAppTeamIDFuncInvoked bool
|
||||
|
||||
HasAndroidAppConfigurationChangedFunc HasAndroidAppConfigurationChangedFunc
|
||||
HasAndroidAppConfigurationChangedFuncInvoked bool
|
||||
|
||||
BulkGetAndroidAppConfigurationsFunc BulkGetAndroidAppConfigurationsFunc
|
||||
BulkGetAndroidAppConfigurationsFuncInvoked bool
|
||||
|
||||
InsertAndroidAppConfigurationFunc InsertAndroidAppConfigurationFunc
|
||||
InsertAndroidAppConfigurationFuncInvoked bool
|
||||
|
||||
@@ -9466,6 +9481,27 @@ func (s *DataStore) GetAndroidAppConfiguration(ctx context.Context, adamID strin
|
||||
return s.GetAndroidAppConfigurationFunc(ctx, adamID, globalOrTeamID)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetAndroidAppConfigurationByAppTeamID(ctx context.Context, vppAppTeamID uint) (*fleet.AndroidAppConfiguration, error) {
|
||||
s.mu.Lock()
|
||||
s.GetAndroidAppConfigurationByAppTeamIDFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.GetAndroidAppConfigurationByAppTeamIDFunc(ctx, vppAppTeamID)
|
||||
}
|
||||
|
||||
func (s *DataStore) HasAndroidAppConfigurationChanged(ctx context.Context, applicationID string, globalOrTeamID uint, newConfig json.RawMessage) (bool, error) {
|
||||
s.mu.Lock()
|
||||
s.HasAndroidAppConfigurationChangedFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.HasAndroidAppConfigurationChangedFunc(ctx, applicationID, globalOrTeamID, newConfig)
|
||||
}
|
||||
|
||||
func (s *DataStore) BulkGetAndroidAppConfigurations(ctx context.Context, appIDs []string, globalOrTeamID uint) (map[string]json.RawMessage, error) {
|
||||
s.mu.Lock()
|
||||
s.BulkGetAndroidAppConfigurationsFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.BulkGetAndroidAppConfigurationsFunc(ctx, appIDs, globalOrTeamID)
|
||||
}
|
||||
|
||||
func (s *DataStore) InsertAndroidAppConfiguration(ctx context.Context, config *fleet.AndroidAppConfiguration) error {
|
||||
s.mu.Lock()
|
||||
s.InsertAndroidAppConfigurationFuncInvoked = true
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/api/androidmanagement/v1"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
func (s *integrationMDMTestSuite) TestSetupExperienceScript() {
|
||||
@@ -3766,6 +3767,187 @@ func (s *integrationMDMTestSuite) TestSetupExperienceAndroidCancelOnUnenroll() {
|
||||
require.Equal(t, 0, countOther)
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) TestAndroidAppConfiguration() {
|
||||
t := s.T()
|
||||
s.setSkipWorkerJobs(t)
|
||||
|
||||
s.enableAndroidMDM(t)
|
||||
|
||||
// add some android apps
|
||||
app1 := &fleet.VPPApp{
|
||||
VPPAppTeam: fleet.VPPAppTeam{
|
||||
VPPAppID: fleet.VPPAppID{
|
||||
AdamID: "com.test1",
|
||||
Platform: fleet.AndroidPlatform,
|
||||
},
|
||||
},
|
||||
Name: "Test1",
|
||||
BundleIdentifier: "com.test1",
|
||||
IconURL: "https://example.com/1",
|
||||
}
|
||||
app2 := &fleet.VPPApp{
|
||||
VPPAppTeam: fleet.VPPAppTeam{
|
||||
VPPAppID: fleet.VPPAppID{
|
||||
AdamID: "com.test2",
|
||||
Platform: fleet.AndroidPlatform,
|
||||
},
|
||||
},
|
||||
Name: "Test2",
|
||||
BundleIdentifier: "com.test2",
|
||||
IconURL: "https://example.com/2",
|
||||
}
|
||||
app3 := &fleet.VPPApp{
|
||||
VPPAppTeam: fleet.VPPAppTeam{
|
||||
VPPAppID: fleet.VPPAppID{
|
||||
AdamID: "com.test3",
|
||||
Platform: fleet.AndroidPlatform,
|
||||
},
|
||||
},
|
||||
Name: "Test3",
|
||||
BundleIdentifier: "com.test3",
|
||||
IconURL: "https://example.com/3",
|
||||
}
|
||||
|
||||
androidApps := []*fleet.VPPApp{app1, app2, app3}
|
||||
s.androidAPIClient.EnterprisesApplicationsFunc = func(ctx context.Context, enterpriseName string, packageName string) (*androidmanagement.Application, error) {
|
||||
for _, app := range androidApps {
|
||||
if app.AdamID == packageName {
|
||||
return &androidmanagement.Application{IconUrl: app.IconURL, Title: app.Name}, nil
|
||||
}
|
||||
}
|
||||
return nil, ¬FoundError{}
|
||||
}
|
||||
|
||||
// vars have no need for a mutex, protected via runWorkerUntilDone
|
||||
var (
|
||||
// records the appPolicies received in the ModifyPolicyApplications calls
|
||||
patchAppsPolicies [][]*androidmanagement.ApplicationPolicy
|
||||
patchAppsCallCount int
|
||||
)
|
||||
s.androidAPIClient.EnterprisesPoliciesModifyPolicyApplicationsFunc = func(ctx context.Context, policyName string, appPolicies []*androidmanagement.ApplicationPolicy) (*androidmanagement.Policy, error) {
|
||||
patchAppsCallCount++
|
||||
patchAppsPolicies = append(patchAppsPolicies, appPolicies)
|
||||
|
||||
return &androidmanagement.Policy{Version: int64(patchAppsCallCount)}, nil
|
||||
}
|
||||
|
||||
// add Android app 1
|
||||
var addAppResp addAppStoreAppResponse
|
||||
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", &addAppStoreAppRequest{
|
||||
AppStoreID: app1.AdamID,
|
||||
Platform: fleet.AndroidPlatform,
|
||||
}, http.StatusOK, &addAppResp)
|
||||
app1TitleID := addAppResp.TitleID
|
||||
|
||||
// add Android app 2
|
||||
addAppResp = addAppStoreAppResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", &addAppStoreAppRequest{
|
||||
AppStoreID: app2.AdamID,
|
||||
Platform: fleet.AndroidPlatform,
|
||||
}, http.StatusOK, &addAppResp)
|
||||
app2TitleID := addAppResp.TitleID
|
||||
|
||||
require.NotEqual(t, app1TitleID, app2TitleID)
|
||||
|
||||
s.runWorkerUntilDone()
|
||||
|
||||
// worker should have done nothing (no host to add apps to yet)
|
||||
require.Len(t, patchAppsPolicies, 0)
|
||||
patchAppsPolicies = nil
|
||||
|
||||
var patchAppResp updateAppStoreAppResponse
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/software/titles/%d/app_store_app", app1TitleID), &updateAppStoreAppRequest{
|
||||
TeamID: nil,
|
||||
Configuration: json.RawMessage(`{"managedConfiguration": 1}`),
|
||||
}, http.StatusOK, &patchAppResp)
|
||||
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/software/titles/%d/app_store_app", app2TitleID), &updateAppStoreAppRequest{
|
||||
TeamID: nil,
|
||||
Configuration: json.RawMessage(`{"managedConfiguration": 2}`),
|
||||
}, http.StatusOK, &patchAppResp)
|
||||
|
||||
// add app 1 and 2 to Android setup experience
|
||||
var putResp putSetupExperienceSoftwareResponse
|
||||
s.DoJSON("PUT", "/api/latest/fleet/setup_experience/software", &putSetupExperienceSoftwareRequest{
|
||||
Platform: string(fleet.AndroidPlatform),
|
||||
TeamID: 0,
|
||||
TitleIDs: []uint{app1TitleID, app2TitleID},
|
||||
}, http.StatusOK, &putResp)
|
||||
|
||||
s.createAndEnrollAndroidDevice(t, "test-android", nil)
|
||||
|
||||
s.runWorkerUntilDone()
|
||||
|
||||
// worker should have:
|
||||
// 1. made each app available to the included hosts (for self-service), so 2 entries for that (from the PATCH apps to set the config)
|
||||
// (this is because I made the worker run after host enrollment, if there were no host, the task would have nothing to do)
|
||||
// 2. made all apps available to the enrolled host (for self-service), from the host enrollment
|
||||
// 3. installed the apps, from the host enrollment
|
||||
require.Len(t, patchAppsPolicies, 4)
|
||||
require.ElementsMatch(t, []*androidmanagement.ApplicationPolicy{
|
||||
{PackageName: app1.VPPAppID.AdamID, InstallType: "AVAILABLE", ManagedConfiguration: googleapi.RawMessage(`1`)},
|
||||
}, patchAppsPolicies[0])
|
||||
require.ElementsMatch(t, []*androidmanagement.ApplicationPolicy{
|
||||
{PackageName: app2.VPPAppID.AdamID, InstallType: "AVAILABLE", ManagedConfiguration: googleapi.RawMessage(`2`)},
|
||||
}, patchAppsPolicies[1])
|
||||
require.ElementsMatch(t, []*androidmanagement.ApplicationPolicy{
|
||||
{PackageName: app1.VPPAppID.AdamID, InstallType: "AVAILABLE", ManagedConfiguration: googleapi.RawMessage(`1`)},
|
||||
{PackageName: app2.VPPAppID.AdamID, InstallType: "AVAILABLE", ManagedConfiguration: googleapi.RawMessage(`2`)},
|
||||
}, patchAppsPolicies[2])
|
||||
require.ElementsMatch(t, []*androidmanagement.ApplicationPolicy{
|
||||
{PackageName: app1.VPPAppID.AdamID, InstallType: "PREINSTALLED", ManagedConfiguration: googleapi.RawMessage(`1`)},
|
||||
{PackageName: app2.VPPAppID.AdamID, InstallType: "PREINSTALLED", ManagedConfiguration: googleapi.RawMessage(`2`)},
|
||||
}, patchAppsPolicies[3])
|
||||
|
||||
patchAppsPolicies = nil
|
||||
|
||||
// add app3 to Fleet
|
||||
addAppResp = addAppStoreAppResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", &addAppStoreAppRequest{
|
||||
AppStoreID: app3.AdamID,
|
||||
Platform: fleet.AndroidPlatform,
|
||||
}, http.StatusOK, &addAppResp)
|
||||
app3TitleID := addAppResp.TitleID
|
||||
|
||||
s.runWorkerUntilDone()
|
||||
|
||||
// worker should have:
|
||||
// 1. made the apps available to the host (for self-service)
|
||||
require.Len(t, patchAppsPolicies, 1)
|
||||
require.ElementsMatch(t, []*androidmanagement.ApplicationPolicy{
|
||||
{PackageName: app3.VPPAppID.AdamID, InstallType: "AVAILABLE"},
|
||||
}, patchAppsPolicies[0])
|
||||
|
||||
patchAppsPolicies = nil
|
||||
|
||||
// set a configuration for the app3
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/software/titles/%d/app_store_app", app3TitleID), &updateAppStoreAppRequest{
|
||||
TeamID: nil,
|
||||
Configuration: json.RawMessage(`{"managedConfiguration": 3}`),
|
||||
}, http.StatusOK, &patchAppResp)
|
||||
|
||||
s.runWorkerUntilDone()
|
||||
|
||||
// worker should have:
|
||||
// 1. made the app available with its config
|
||||
require.Len(t, patchAppsPolicies, 1)
|
||||
require.ElementsMatch(t, []*androidmanagement.ApplicationPolicy{
|
||||
{PackageName: app3.VPPAppID.AdamID, InstallType: "AVAILABLE", ManagedConfiguration: googleapi.RawMessage(`3`)},
|
||||
}, patchAppsPolicies[0])
|
||||
|
||||
patchAppsPolicies = nil
|
||||
|
||||
// patch but no change to the configuration for the app3
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/software/titles/%d/app_store_app", app3TitleID), &updateAppStoreAppRequest{
|
||||
TeamID: nil,
|
||||
Configuration: json.RawMessage(`{"managedConfiguration": 3}`),
|
||||
}, http.StatusOK, &patchAppResp)
|
||||
|
||||
s.runWorkerUntilDone()
|
||||
|
||||
require.Len(t, patchAppsPolicies, 0)
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) createAndEnrollAndroidDevice(t *testing.T, name string, teamID *uint) (host *fleet.Host, deviceInfo androidmanagement.Device, pubSubToken fleet.MDMConfigAsset) {
|
||||
ctx := t.Context()
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@ import (
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mdm/android"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
kitlog "github.com/go-kit/log"
|
||||
"github.com/go-kit/log/level"
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/api/androidmanagement/v1"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
const softwareWorkerJobName = "software_worker"
|
||||
@@ -41,8 +43,10 @@ type softwareWorkerArgs struct {
|
||||
ApplicationID string `json:"application_id,omitempty"`
|
||||
ApplicationIDs []string `json:"application_ids,omitempty"`
|
||||
EnterpriseName string `json:"enterprise_name,omitempty"`
|
||||
AppTeamID uint `json:"app_team_id,omitempty"`
|
||||
HostID uint `json:"host_id,omitempty"`
|
||||
// AppTeamID is *not* a team ID, it is the vpp_apps_teams.id value. This is a bit confusing
|
||||
// as a name, but that is what is expected in this field.
|
||||
AppTeamID uint `json:"app_team_id,omitempty"`
|
||||
HostID uint `json:"host_id,omitempty"`
|
||||
|
||||
// HostEnrollTeamID is the team ID associated with the host at the time
|
||||
// of enrollment, which is the one used to run the setup experience.
|
||||
@@ -102,14 +106,33 @@ func (v *SoftwareWorker) Run(ctx context.Context, argsJSON json.RawMessage) erro
|
||||
}
|
||||
}
|
||||
|
||||
// this is called when a new app is added to Fleet and when an existing app is updated
|
||||
// (either its scope of affected hosts changed due to labels conditions, or its
|
||||
// configuration changed).
|
||||
func (v *SoftwareWorker) makeAndroidAppAvailable(ctx context.Context, applicationID string, appTeamID uint, enterpriseName string) error {
|
||||
hosts, err := v.Datastore.GetIncludedHostUUIDMapForAppStoreApp(ctx, appTeamID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "add app store app: getting android hosts in scope")
|
||||
}
|
||||
|
||||
config, err := v.Datastore.GetAndroidAppConfigurationByAppTeamID(ctx, appTeamID)
|
||||
if err != nil && !fleet.IsNotFound(err) {
|
||||
return ctxerr.Wrap(ctx, err, "get android app configuration")
|
||||
}
|
||||
var configByAppID map[string]json.RawMessage
|
||||
if config != nil && config.Configuration != nil {
|
||||
configByAppID = map[string]json.RawMessage{
|
||||
applicationID: config.Configuration,
|
||||
}
|
||||
}
|
||||
|
||||
appPolicies, err := buildApplicationPolicyWithConfig(ctx, []string{applicationID}, configByAppID, "AVAILABLE")
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "building application policies with config")
|
||||
}
|
||||
|
||||
// Update Android MDM policy to include the app in self service
|
||||
_, err = v.AndroidModule.AddAppsToAndroidPolicy(ctx, enterpriseName, []string{applicationID}, hosts, "AVAILABLE")
|
||||
_, err = v.AndroidModule.AddAppsToAndroidPolicy(ctx, enterpriseName, appPolicies, hosts)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "add app store app: add app to android policy")
|
||||
}
|
||||
@@ -117,25 +140,7 @@ func (v *SoftwareWorker) makeAndroidAppAvailable(ctx context.Context, applicatio
|
||||
return nil
|
||||
}
|
||||
|
||||
func QueueMakeAndroidAppAvailableJob(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger, applicationID string, appTeamID uint, enterpriseName string) error {
|
||||
args := &softwareWorkerArgs{
|
||||
Task: makeAndroidAppAvailableTask,
|
||||
ApplicationID: applicationID,
|
||||
AppTeamID: appTeamID,
|
||||
EnterpriseName: enterpriseName,
|
||||
}
|
||||
|
||||
job, err := QueueJob(ctx, ds, softwareWorkerJobName, args)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "queueing job")
|
||||
}
|
||||
|
||||
level.Debug(logger).Log("job_id", job.ID, "job_name", softwareWorkerJobName, "task", makeAndroidAppAvailableTask)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *SoftwareWorker) makeAndroidAppsAvailableForHost(ctx context.Context, hostUUID string, hostID uint, enterpriseName, policyID string) error {
|
||||
|
||||
func (v *SoftwareWorker) ensureHostSpecificPolicyIsApplied(ctx context.Context, hostUUID string, enterpriseName, policyID string) error {
|
||||
if policyID == fmt.Sprint(android.DefaultAndroidPolicyID) {
|
||||
var policy androidmanagement.Policy
|
||||
policy.StatusReportingSettings = &androidmanagement.StatusReportingSettings{
|
||||
@@ -186,6 +191,18 @@ func (v *SoftwareWorker) makeAndroidAppsAvailableForHost(ctx context.Context, ho
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *SoftwareWorker) makeAndroidAppsAvailableForHost(ctx context.Context, hostUUID string, hostID uint, enterpriseName, policyID string) error {
|
||||
if err := v.ensureHostSpecificPolicyIsApplied(ctx, hostUUID, enterpriseName, policyID); err != nil {
|
||||
return ctxerr.Wrapf(ctx, err, "ensuring host-specific policy is applied for host %s", hostUUID)
|
||||
}
|
||||
|
||||
androidHost, err := v.Datastore.AndroidHostLiteByHostUUID(ctx, hostUUID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrapf(ctx, err, "get android host by host UUID %s", hostUUID)
|
||||
}
|
||||
|
||||
appIDs, err := v.Datastore.GetAndroidAppsInScopeForHost(ctx, hostID)
|
||||
if err != nil {
|
||||
@@ -196,7 +213,17 @@ func (v *SoftwareWorker) makeAndroidAppsAvailableForHost(ctx context.Context, ho
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = v.AndroidModule.AddAppsToAndroidPolicy(ctx, enterpriseName, appIDs, map[string]string{hostUUID: hostUUID}, "AVAILABLE")
|
||||
configsByAppID, err := v.Datastore.BulkGetAndroidAppConfigurations(ctx, appIDs, ptr.ValOrZero(androidHost.TeamID))
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "bulk get android app configurations")
|
||||
}
|
||||
|
||||
appPolicies, err := buildApplicationPolicyWithConfig(ctx, appIDs, configsByAppID, "AVAILABLE")
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "building application policies with config")
|
||||
}
|
||||
|
||||
_, err = v.AndroidModule.AddAppsToAndroidPolicy(ctx, enterpriseName, appPolicies, map[string]string{hostUUID: hostUUID})
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "add app store app: add app to android policy")
|
||||
}
|
||||
@@ -240,8 +267,22 @@ func (v *SoftwareWorker) runAndroidSetupExperience(ctx context.Context,
|
||||
}
|
||||
|
||||
if len(appIDs) > 0 {
|
||||
// NOTE: from my tests, we do need to re-apply the app configs when installing apps,
|
||||
// even if they were already applied when making the apps available for self-service.
|
||||
// However, once installed, if the app config changes it is applied automatically by the
|
||||
// policy change (no need to re-install).
|
||||
configsByAppID, err := v.Datastore.BulkGetAndroidAppConfigurations(ctx, appIDs, hostEnrollTeamID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "bulk get android app configurations")
|
||||
}
|
||||
|
||||
appPolicies, err := buildApplicationPolicyWithConfig(ctx, appIDs, configsByAppID, "PREINSTALLED")
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "building application policies with config")
|
||||
}
|
||||
|
||||
// assign those apps to the host's Android policy
|
||||
hostToPolicyRequest, err := v.AndroidModule.AddAppsToAndroidPolicy(ctx, enterpriseName, appIDs, map[string]string{hostUUID: hostUUID}, "PREINSTALLED")
|
||||
hostToPolicyRequest, err := v.AndroidModule.AddAppsToAndroidPolicy(ctx, enterpriseName, appPolicies, map[string]string{hostUUID: hostUUID})
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "add app store app: add app to android policy")
|
||||
}
|
||||
@@ -276,6 +317,56 @@ func (v *SoftwareWorker) runAndroidSetupExperience(ctx context.Context,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *SoftwareWorker) bulkMakeAndroidAppsAvailableForHost(ctx context.Context, hostUUID, policyID string, applicationIDs []string, enterpriseName string) error {
|
||||
host, err := v.Datastore.AndroidHostLiteByHostUUID(ctx, hostUUID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrapf(ctx, err, "getting android host lite by uuid %s", hostUUID)
|
||||
}
|
||||
|
||||
configsByAppID, err := v.Datastore.BulkGetAndroidAppConfigurations(ctx, applicationIDs, ptr.ValOrZero(host.Host.TeamID))
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "bulk get android app configurations")
|
||||
}
|
||||
|
||||
appPolicies, err := buildApplicationPolicyWithConfig(ctx, applicationIDs, configsByAppID, "AVAILABLE")
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "building application policies with config")
|
||||
}
|
||||
|
||||
// Update Android MDM policy to include the apps in self service
|
||||
err = v.AndroidModule.SetAppsForAndroidPolicy(ctx, enterpriseName, appPolicies, map[string]string{hostUUID: policyID})
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "make android apps available")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildApplicationPolicyWithConfig(ctx context.Context, appIDs []string,
|
||||
configsByAppID map[string]json.RawMessage, installType string) ([]*androidmanagement.ApplicationPolicy, error) {
|
||||
|
||||
appPolicies := make([]*androidmanagement.ApplicationPolicy, 0, len(appIDs))
|
||||
for _, appID := range appIDs {
|
||||
var androidAppConfig struct {
|
||||
ManagedConfiguration json.RawMessage `json:"managedConfiguration"`
|
||||
WorkProfileWidgets string `json:"workProfileWidgets"`
|
||||
}
|
||||
if config := configsByAppID[appID]; config != nil {
|
||||
if err := json.Unmarshal(config, &androidAppConfig); err != nil {
|
||||
// should never happen, as it is stored as json in the db and is pre-validated
|
||||
return nil, ctxerr.Wrap(ctx, err, "unmarshal android app configuration")
|
||||
}
|
||||
}
|
||||
appPolicies = append(appPolicies, &androidmanagement.ApplicationPolicy{
|
||||
PackageName: appID,
|
||||
InstallType: installType,
|
||||
ManagedConfiguration: googleapi.RawMessage(androidAppConfig.ManagedConfiguration),
|
||||
WorkProfileWidgets: androidAppConfig.WorkProfileWidgets,
|
||||
})
|
||||
}
|
||||
return appPolicies, nil
|
||||
}
|
||||
|
||||
func QueueRunAndroidSetupExperience(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger,
|
||||
hostUUID string, hostEnrollTeamID *uint, enterpriseName string) error {
|
||||
|
||||
@@ -299,13 +390,20 @@ func QueueRunAndroidSetupExperience(ctx context.Context, ds fleet.Datastore, log
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *SoftwareWorker) bulkMakeAndroidAppsAvailableForHost(ctx context.Context, hostUUID, policyID string, applicationIDs []string, enterpriseName string) error {
|
||||
// Update Android MDM policy to include the apps in self service
|
||||
err := v.AndroidModule.SetAppsForAndroidPolicy(ctx, enterpriseName, applicationIDs, map[string]string{hostUUID: policyID}, "AVAILABLE")
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "make android apps available")
|
||||
func QueueMakeAndroidAppAvailableJob(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger, applicationID string, appTeamID uint, enterpriseName string) error {
|
||||
args := &softwareWorkerArgs{
|
||||
Task: makeAndroidAppAvailableTask,
|
||||
ApplicationID: applicationID,
|
||||
AppTeamID: appTeamID,
|
||||
EnterpriseName: enterpriseName,
|
||||
}
|
||||
|
||||
job, err := QueueJob(ctx, ds, softwareWorkerJobName, args)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "queueing job")
|
||||
}
|
||||
|
||||
level.Debug(logger).Log("job_id", job.ID, "job_name", softwareWorkerJobName, "task", args.Task)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user