mirror of
https://github.com/fleetdm/fleet
synced 2026-05-22 00:18:27 +00:00
For #22875. # Checklist for submitter If some of the following don't apply, delete the relevant line. <!-- Note that API documentation changes are now addressed by the product design team. --> - [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/Committing-Changes.md#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated tests - [x] If database migrations are included, checked table schema to confirm autoupdate - For database migrations: - [x] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [x] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [x] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [x] Manual QA for all new/changed functionality
225 lines
5.7 KiB
Go
225 lines
5.7 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/authz"
|
|
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/mock"
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestPreProcessUninstallScript(t *testing.T) {
|
|
t.Parallel()
|
|
input := `
|
|
blah$PACKAGE_IDS
|
|
pkgids=$PACKAGE_ID
|
|
they are $PACKAGE_ID, right $MY_SECRET?
|
|
quotes for "$PACKAGE_ID"
|
|
blah${PACKAGE_ID}withConcat
|
|
quotes and braces for "${PACKAGE_ID}"
|
|
${PACKAGE_ID}`
|
|
|
|
payload := fleet.UploadSoftwareInstallerPayload{
|
|
Extension: "exe",
|
|
UninstallScript: input,
|
|
PackageIDs: []string{"com.foo"},
|
|
}
|
|
|
|
preProcessUninstallScript(&payload)
|
|
expected := `
|
|
blah$PACKAGE_IDS
|
|
pkgids="com.foo"
|
|
they are "com.foo", right $MY_SECRET?
|
|
quotes for "com.foo"
|
|
blah"com.foo"withConcat
|
|
quotes and braces for "com.foo"
|
|
"com.foo"`
|
|
assert.Equal(t, expected, payload.UninstallScript)
|
|
|
|
payload = fleet.UploadSoftwareInstallerPayload{
|
|
Extension: "pkg",
|
|
UninstallScript: input,
|
|
PackageIDs: []string{"com.foo", "com.bar"},
|
|
}
|
|
preProcessUninstallScript(&payload)
|
|
expected = `
|
|
blah$PACKAGE_IDS
|
|
pkgids=(
|
|
"com.foo"
|
|
"com.bar"
|
|
)
|
|
they are (
|
|
"com.foo"
|
|
"com.bar"
|
|
), right $MY_SECRET?
|
|
quotes for (
|
|
"com.foo"
|
|
"com.bar"
|
|
)
|
|
blah(
|
|
"com.foo"
|
|
"com.bar"
|
|
)withConcat
|
|
quotes and braces for (
|
|
"com.foo"
|
|
"com.bar"
|
|
)
|
|
(
|
|
"com.foo"
|
|
"com.bar"
|
|
)`
|
|
assert.Equal(t, expected, payload.UninstallScript)
|
|
}
|
|
|
|
func TestInstallUninstallAuth(t *testing.T) {
|
|
t.Parallel()
|
|
ds := new(mock.Store)
|
|
svc := newTestService(t, ds)
|
|
|
|
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
|
return &fleet.AppConfig{
|
|
ServerSettings: fleet.ServerSettings{ScriptsDisabled: true}, // global scripts being disabled shouldn't impact (un)installs
|
|
}, nil
|
|
}
|
|
ds.HostFunc = func(ctx context.Context, id uint) (*fleet.Host, error) {
|
|
return &fleet.Host{
|
|
OrbitNodeKey: ptr.String("orbit_key"),
|
|
Platform: "darwin",
|
|
TeamID: ptr.Uint(1),
|
|
}, nil
|
|
}
|
|
ds.GetSoftwareInstallerMetadataByTeamAndTitleIDFunc = func(ctx context.Context, teamID *uint, titleID uint,
|
|
withScriptContents bool,
|
|
) (*fleet.SoftwareInstaller, error) {
|
|
return &fleet.SoftwareInstaller{
|
|
Name: "installer.pkg",
|
|
Platform: "darwin",
|
|
TeamID: ptr.Uint(1),
|
|
}, nil
|
|
}
|
|
ds.GetHostLastInstallDataFunc = func(ctx context.Context, hostID uint, installerID uint) (*fleet.HostLastInstallData, error) {
|
|
return nil, nil
|
|
}
|
|
ds.InsertSoftwareInstallRequestFunc = func(ctx context.Context, hostID uint, softwareInstallerID uint, selfService bool, policyID *uint) (string,
|
|
error,
|
|
) {
|
|
return "request_id", nil
|
|
}
|
|
ds.GetAnyScriptContentsFunc = func(ctx context.Context, id uint) ([]byte, error) {
|
|
return []byte("script"), nil
|
|
}
|
|
ds.NewInternalScriptExecutionRequestFunc = func(ctx context.Context, request *fleet.HostScriptRequestPayload) (*fleet.HostScriptResult,
|
|
error) {
|
|
return &fleet.HostScriptResult{
|
|
ExecutionID: "execution_id",
|
|
}, nil
|
|
}
|
|
ds.InsertSoftwareUninstallRequestFunc = func(ctx context.Context, executionID string, hostID uint, softwareInstallerID uint) error {
|
|
return nil
|
|
}
|
|
|
|
ds.IsSoftwareInstallerLabelScopedFunc = func(ctx context.Context, installerID, hostID uint) (bool, error) {
|
|
return true, nil
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
user *fleet.User
|
|
shouldFail bool
|
|
}{
|
|
{
|
|
"global admin",
|
|
&fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)},
|
|
false,
|
|
},
|
|
{
|
|
"global maintainer",
|
|
&fleet.User{GlobalRole: ptr.String(fleet.RoleMaintainer)},
|
|
false,
|
|
},
|
|
{
|
|
"global observer",
|
|
&fleet.User{GlobalRole: ptr.String(fleet.RoleObserver)},
|
|
true,
|
|
},
|
|
{
|
|
"team admin",
|
|
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin}}},
|
|
false,
|
|
},
|
|
{
|
|
"team maintainer",
|
|
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleMaintainer}}},
|
|
false,
|
|
},
|
|
{
|
|
"team observer",
|
|
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver}}},
|
|
true,
|
|
},
|
|
}
|
|
for _, tt := range testCases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := viewer.NewContext(context.Background(), viewer.Viewer{User: tt.user})
|
|
checkAuthErr(t, tt.shouldFail, svc.InstallSoftwareTitle(ctx, 1, 10))
|
|
checkAuthErr(t, tt.shouldFail, svc.UninstallSoftwareTitle(ctx, 1, 10))
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestUninstallSoftwareTitle is mostly tested in enterprise integration test. This test hits a few edge cases.
|
|
func TestUninstallSoftwareTitle(t *testing.T) {
|
|
t.Parallel()
|
|
ds := new(mock.Store)
|
|
svc := newTestService(t, ds)
|
|
|
|
host := &fleet.Host{
|
|
OrbitNodeKey: ptr.String("orbit_key"),
|
|
Platform: "darwin",
|
|
TeamID: ptr.Uint(1),
|
|
}
|
|
|
|
ds.HostFunc = func(ctx context.Context, id uint) (*fleet.Host, error) {
|
|
return host, nil
|
|
}
|
|
|
|
// Global scripts disabled (doesn't matter)
|
|
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
|
return &fleet.AppConfig{
|
|
ServerSettings: fleet.ServerSettings{
|
|
ScriptsDisabled: true,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// Host scripts disabled
|
|
host.ScriptsEnabled = ptr.Bool(false)
|
|
require.ErrorContains(t, svc.UninstallSoftwareTitle(context.Background(), 1, 10), fleet.RunScriptsOrbitDisabledErrMsg)
|
|
}
|
|
|
|
func checkAuthErr(t *testing.T, shouldFail bool, err error) {
|
|
t.Helper()
|
|
if shouldFail {
|
|
require.Error(t, err)
|
|
var forbiddenError *authz.Forbidden
|
|
require.ErrorAs(t, err, &forbiddenError)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
func newTestService(t *testing.T, ds fleet.Datastore) *Service {
|
|
t.Helper()
|
|
authorizer, err := authz.NewAuthorizer()
|
|
require.NoError(t, err)
|
|
svc := &Service{
|
|
authz: authorizer,
|
|
ds: ds,
|
|
}
|
|
return svc
|
|
}
|