mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Update software title names on FMA sync and upload (#42647)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #41710 Updates (only) macOS software title names on FMA catalog sync. Updates software title names on installer upload for Windows FMAs with an upgrade code. # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. - [ ] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes ## 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
This commit is contained in:
parent
8d63bf2bbe
commit
13f94af560
5 changed files with 125 additions and 7 deletions
1
changes/41710-overwrite-software-title
Normal file
1
changes/41710-overwrite-software-title
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Updated Fleet-maintained apps to overwrite software title names on sync and when adding an FMA installer.
|
||||
|
|
@ -128,12 +128,13 @@ func (svc *Service) AddFleetMaintainedApp(
|
|||
maintainedAppID = nil // don't set app as maintained if scripts have been modified
|
||||
}
|
||||
|
||||
// For platforms other than macOS, installer name has to match what we see in software inventory,
|
||||
// so we have the UniqueIdentifier field to indicate what that should be (independent of the name we
|
||||
// display when listing the FMA). For macOS, unique identifier is bundle name, and we use bundle
|
||||
// identifier to link installers with inventory, so we set the name to the FMA's display name instead.
|
||||
// For Windows, installer name has to match what we see in software inventory, so we have the
|
||||
// UniqueIdentifier field to indicate what that should be (independent of the FMA's display name).
|
||||
// If we have an upgrade code to match inventory with, we can set the installer name to the FMA's
|
||||
// display name instead. For macOS, unique identifier is bundle name, and we use bundle identifier
|
||||
// to link installers with inventory, so we set the name to the FMA's display name instead.
|
||||
appName := app.UniqueIdentifier
|
||||
if app.Platform == "darwin" || appName == "" {
|
||||
if app.Platform == "darwin" || appName == "" || app.UpgradeCode != "" {
|
||||
appName = app.Name
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package mysql
|
|||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
maintained_apps "github.com/fleetdm/fleet/v4/server/mdm/maintainedapps"
|
||||
|
|
@ -25,6 +26,7 @@ func TestMaintainedApps(t *testing.T) {
|
|||
{"SyncAndRemoveApps", testSyncAndRemoveApps},
|
||||
{"GetMaintainedAppBySlug", testGetMaintainedAppBySlug},
|
||||
{"ListAvailableAppsWindows", testListAvailableAppsWindows},
|
||||
{"SoftwareTitleRenamingWindows", testSoftwareTitleRenamingWindows},
|
||||
{"GetFMANamesByIdentifier", testGetFMANamesByIdentifier},
|
||||
{"UpsertMaintainedAppUpdatesSoftware", testUpsertMaintainedAppUpdatesSoftware},
|
||||
}
|
||||
|
|
@ -632,6 +634,87 @@ func testListAvailableAppsWindows(t *testing.T, ds *Datastore) {
|
|||
require.Nil(t, apps[1].TitleID)
|
||||
}
|
||||
|
||||
func testSoftwareTitleRenamingWindows(t *testing.T, ds *Datastore) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
user := test.NewUser(t, ds, "Alice", "alice@example.com", true)
|
||||
host1 := test.NewHost(t, ds, "host1", "", "host1key", "host1uuid", time.Now())
|
||||
|
||||
software1 := []fleet.Software{
|
||||
{Name: "Goodbye 1.00 (x64)", Version: "1.0", Source: "programs"},
|
||||
{Name: "Hello 1.00 (x64)", Version: "1.0", Source: "programs", UpgradeCode: ptr.String("{123456}")},
|
||||
}
|
||||
_, err := ds.UpdateHostSoftware(ctx, host1.ID, software1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, ds.SyncHostsSoftware(ctx, time.Now()))
|
||||
require.NoError(t, ds.SyncHostsSoftwareTitles(ctx, time.Now()))
|
||||
|
||||
opts := fleet.SoftwareTitleListOptions{ListOptions: fleet.ListOptions{OrderKey: "name"}}
|
||||
sw, _, _, err := ds.ListSoftwareTitles(ctx, opts, fleet.TeamFilter{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sw, 2)
|
||||
require.Equal(t, "Goodbye 1.00 (x64)", sw[0].Name)
|
||||
require.Equal(t, "Hello 1.00 (x64)", sw[1].Name)
|
||||
|
||||
maintained3, err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{
|
||||
Name: "goodbye",
|
||||
Slug: "goodbye/windows",
|
||||
Platform: "windows",
|
||||
UniqueIdentifier: "Goodbye 1.00 (x64)",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
maintained4, err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{
|
||||
Name: "Hello",
|
||||
Slug: "hello/windows",
|
||||
Platform: "windows",
|
||||
UniqueIdentifier: "Hello 1.00 (x64)",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
sw, _, _, err = ds.ListSoftwareTitles(ctx, opts, fleet.TeamFilter{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sw, 2)
|
||||
require.Equal(t, "Goodbye 1.00 (x64)", sw[0].Name)
|
||||
require.Equal(t, "Hello 1.00 (x64)", sw[1].Name)
|
||||
|
||||
_, _, err = ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
|
||||
Title: "Goodbye 1.00 (x64)",
|
||||
Source: "programs",
|
||||
StorageID: "storageid1",
|
||||
Filename: "goodbye.msi",
|
||||
Extension: "msi",
|
||||
Platform: "windows",
|
||||
Version: "1.0",
|
||||
UserID: user.ID,
|
||||
ValidatedLabels: &fleet.LabelIdentsWithScope{},
|
||||
FleetMaintainedAppID: new(maintained3.ID),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, _, err = ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
|
||||
Title: "Hello",
|
||||
UpgradeCode: "{123456}",
|
||||
Source: "programs",
|
||||
StorageID: "storageid2",
|
||||
Filename: "hello.msi",
|
||||
Extension: "msi",
|
||||
Platform: "windows",
|
||||
Version: "1.0",
|
||||
UserID: user.ID,
|
||||
ValidatedLabels: &fleet.LabelIdentsWithScope{},
|
||||
FleetMaintainedAppID: new(maintained4.ID),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// After uploading installers, Goodbye 1.00 (x64) has no upgrade code so it
|
||||
// keeps its name, and Hello 1.00 (x64) updates to just Hello as it has one.
|
||||
sw, _, _, err = ds.ListSoftwareTitles(ctx, opts, fleet.TeamFilter{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sw, 2)
|
||||
require.Equal(t, "Goodbye 1.00 (x64)", sw[0].Name)
|
||||
require.Equal(t, "Hello", sw[1].Name)
|
||||
}
|
||||
|
||||
func testUpsertMaintainedAppUpdatesSoftware(t *testing.T, ds *Datastore) {
|
||||
ctx := t.Context()
|
||||
|
||||
|
|
|
|||
|
|
@ -553,6 +553,14 @@ func (ds *Datastore) getOrGenerateSoftwareInstallerTitleID(ctx context.Context,
|
|||
if payload.Source == "programs" && payload.UpgradeCode != "" {
|
||||
updateStmt := `UPDATE software_titles SET upgrade_code = ? WHERE id = ?`
|
||||
updateArgs := []any{payload.UpgradeCode, titleID}
|
||||
|
||||
// Update the software title name if this is a Windows FMA with an upgrade code. We already update
|
||||
// software titles with macOS FMA names on FMA catalog sync, so we only do Windows here.
|
||||
if payload.FleetMaintainedAppID != nil {
|
||||
updateStmt = `UPDATE software_titles SET name = ?, upgrade_code = ? WHERE id = ?`
|
||||
updateArgs = []any{payload.Title, payload.UpgradeCode, titleID}
|
||||
}
|
||||
|
||||
_, err := ds.writer(ctx).ExecContext(ctx, updateStmt, updateArgs...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
|
|
|||
|
|
@ -2496,6 +2496,7 @@ func testGetOrGenerateSoftwareInstallerTitleID(t *testing.T, ds *Datastore) {
|
|||
{Name: "Existing Title", Version: "v0.0.2", Source: "apps", BundleIdentifier: "existing.title"},
|
||||
{Name: "Existing Title", Version: "0.0.3", Source: "apps", BundleIdentifier: "existing.title"},
|
||||
{Name: "Existing Title Without Bundle", Version: "0.0.3", Source: "apps"},
|
||||
{Name: "FMA Old Name", Version: "1.0", Source: "apps", BundleIdentifier: "com.fma"},
|
||||
}
|
||||
software3 := []fleet.Software{
|
||||
{Name: "Win Title 1", Version: "11.0", Source: "programs", UpgradeCode: ptr.String("")},
|
||||
|
|
@ -2503,6 +2504,7 @@ func testGetOrGenerateSoftwareInstallerTitleID(t *testing.T, ds *Datastore) {
|
|||
{Name: "Win Title 3", Version: "11.0", Source: "programs", UpgradeCode: ptr.String("")},
|
||||
{Name: "Win Title 4", Version: "11.0", Source: "programs", UpgradeCode: ptr.String("12345")},
|
||||
{Name: "Win Title 5", Version: "11.0", Source: "programs", UpgradeCode: ptr.String("ABCDEF")},
|
||||
{Name: "Win Title 6", Version: "11.0", Source: "programs", UpgradeCode: ptr.String("GHIJKL")},
|
||||
}
|
||||
|
||||
_, err := ds.UpdateHostSoftware(ctx, host1.ID, software1)
|
||||
|
|
@ -2591,7 +2593,18 @@ func testGetOrGenerateSoftwareInstallerTitleID(t *testing.T, ds *Datastore) {
|
|||
expectedSource: "ios_apps",
|
||||
},
|
||||
{
|
||||
name: "installer: no upgrade code, existing title: same name, no upgrade code",
|
||||
name: "don't rename macos FMA titles",
|
||||
payload: &fleet.UploadSoftwareInstallerPayload{
|
||||
Title: "FMA New Name",
|
||||
Source: "apps",
|
||||
BundleIdentifier: "com.fma",
|
||||
FleetMaintainedAppID: ptr.Uint(2),
|
||||
},
|
||||
expectedName: "FMA Old Name",
|
||||
expectedSource: "apps",
|
||||
},
|
||||
{
|
||||
name: "installer: no upgrade code, existing title: same name, no upgrade code",
|
||||
payload: &fleet.UploadSoftwareInstallerPayload{
|
||||
Title: "Win Title 1",
|
||||
Source: "programs",
|
||||
|
|
@ -2601,7 +2614,7 @@ func testGetOrGenerateSoftwareInstallerTitleID(t *testing.T, ds *Datastore) {
|
|||
expectedUpgradeCode: ptr.String(""),
|
||||
},
|
||||
{
|
||||
name: "installer: no upgrade code, existing title: same name, has upgrade code",
|
||||
name: "installer: no upgrade code, existing title: same name, has upgrade code",
|
||||
payload: &fleet.UploadSoftwareInstallerPayload{
|
||||
Title: "Win Title 2",
|
||||
Source: "programs",
|
||||
|
|
@ -2643,6 +2656,18 @@ func testGetOrGenerateSoftwareInstallerTitleID(t *testing.T, ds *Datastore) {
|
|||
expectedSource: "programs",
|
||||
expectedUpgradeCode: ptr.String("ABCDEF"),
|
||||
},
|
||||
{
|
||||
name: "installer: has upgrade code and FMA, existing title: different name, same upgrade code",
|
||||
payload: &fleet.UploadSoftwareInstallerPayload{
|
||||
Title: "New Name",
|
||||
Source: "programs",
|
||||
UpgradeCode: "GHIJKL",
|
||||
FleetMaintainedAppID: ptr.Uint(1), // FMAs should overwrite name for upgrade code
|
||||
},
|
||||
expectedName: "New Name",
|
||||
expectedSource: "programs",
|
||||
expectedUpgradeCode: ptr.String("GHIJKL"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
|||
Loading…
Reference in a new issue