mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
feat: backend for VPP related global activities (#20484)
> Related issue: #19870 # 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://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [x] Added/updated tests - [x] Manual QA for all new/changed functionality
This commit is contained in:
parent
464c248f30
commit
5d2e40bc8b
7 changed files with 213 additions and 3 deletions
1
changes/19870-vpp-activities-backend
Normal file
1
changes/19870-vpp-activities-backend
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
- Adds global activity support for VPP related activities.
|
||||||
|
|
@ -1158,7 +1158,7 @@ Generated when a software installer is deleted from Fleet.
|
||||||
This activity contains the following fields:
|
This activity contains the following fields:
|
||||||
- "software_title": Name of the software.
|
- "software_title": Name of the software.
|
||||||
- "software_package": Filename of the installer.
|
- "software_package": Filename of the installer.
|
||||||
- "team_name": Name of the team to which this software was added. `null if it was added to no team.
|
- "team_name": Name of the team to which this software was added. `null` if it was added to no team.
|
||||||
- "team_id": The ID of the team to which this software was added. `null` if it was added to no team.
|
- "team_id": The ID of the team to which this software was added. `null` if it was added to no team.
|
||||||
- "self_service": Whether the software was available for installation by the end user.
|
- "self_service": Whether the software was available for installation by the end user.
|
||||||
|
|
||||||
|
|
@ -1174,6 +1174,83 @@ This activity contains the following fields:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## vpp_enabled
|
||||||
|
|
||||||
|
Generated when the VPP feature is enabled in Fleet.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## vpp_disabled
|
||||||
|
|
||||||
|
Generated when the VPP feature is disabled in Fleet.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## added_app_store_app
|
||||||
|
|
||||||
|
Generated when an App Store app is added to Fleet.
|
||||||
|
|
||||||
|
This activity contains the following fields:
|
||||||
|
- "software_title": Name of the App Store app.
|
||||||
|
- "app_store_id": ID of the app on the Apple App Store.
|
||||||
|
- "team_name": Name of the team to which this App Store app was added, or `null` if it was added to no team.
|
||||||
|
- "team_id": ID of the team to which this App Store app was added, or `null`if it was added to no team.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"software_title": "Logic Pro",
|
||||||
|
"app_store_id": "1234567",
|
||||||
|
"team_name": "Workstations",
|
||||||
|
"team_id": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## deleted_app_store_app
|
||||||
|
|
||||||
|
Generated when an App Store app is deleted from Fleet.
|
||||||
|
|
||||||
|
This activity contains the following fields:
|
||||||
|
- "software_title": Name of the App Store app.
|
||||||
|
- "app_store_id": ID of the app on the Apple App Store.
|
||||||
|
- "team_name": Name of the team from which this App Store app was deleted, or `null` if it was deleted from no team.
|
||||||
|
- "team_id": ID of the team from which this App Store app was deleted, or `null`if it was deleted from no team.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"software_title": "Logic Pro",
|
||||||
|
"app_store_id": "1234567",
|
||||||
|
"team_name": "Workstations",
|
||||||
|
"team_id": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## installed_app_store_app
|
||||||
|
|
||||||
|
Generated when an App Store app is installed on a device.
|
||||||
|
|
||||||
|
This activity contains the following fields:
|
||||||
|
- host_id: ID of the host on which the app was installed.
|
||||||
|
- host_display_name: Display name of the host.
|
||||||
|
- software_title: Name of the App Store app.
|
||||||
|
- app_store_id: ID of the app on the Apple App Store.
|
||||||
|
- command_uuid: UUID of the MDM command used to install the app.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"host_id": 42,
|
||||||
|
"host_display_name": "Anna's MacBook Pro",
|
||||||
|
"software_title": "Logic Pro",
|
||||||
|
"app_store_id": "1234567",
|
||||||
|
"command_uuid": "98765432-1234-1234-1234-1234567890ab"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
<meta name="title" value="Audit logs">
|
<meta name="title" value="Audit logs">
|
||||||
<meta name="pageOrderInSection" value="1400">
|
<meta name="pageOrderInSection" value="1400">
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/fleetdm/fleet/v4/server/authz"
|
||||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||||
"github.com/fleetdm/fleet/v4/server/mdm/apple/itunes"
|
"github.com/fleetdm/fleet/v4/server/mdm/apple/itunes"
|
||||||
|
|
@ -159,5 +160,15 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, adamID str
|
||||||
return ctxerr.Wrap(ctx, err, "writing VPP app to db")
|
return ctxerr.Wrap(ctx, err, "writing VPP app to db")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
act := fleet.ActivityAddedAppStoreApp{
|
||||||
|
AppStoreID: app.AdamID,
|
||||||
|
TeamName: &teamName,
|
||||||
|
SoftwareTitle: app.Name,
|
||||||
|
TeamID: teamID,
|
||||||
|
}
|
||||||
|
if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil {
|
||||||
|
return ctxerr.Wrap(ctx, err, "create activity for add app store app")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,11 @@ var ActivityDetailsList = []ActivityDetails{
|
||||||
ActivityTypeInstalledSoftware{},
|
ActivityTypeInstalledSoftware{},
|
||||||
ActivityTypeAddedSoftware{},
|
ActivityTypeAddedSoftware{},
|
||||||
ActivityTypeDeletedSoftware{},
|
ActivityTypeDeletedSoftware{},
|
||||||
|
ActivityEnabledVPP{},
|
||||||
|
ActivityDisabledVPP{},
|
||||||
|
ActivityAddedAppStoreApp{},
|
||||||
|
ActivityDeletedAppStoreApp{},
|
||||||
|
ActivityInstalledAppStoreApp{},
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActivityDetails interface {
|
type ActivityDetails interface {
|
||||||
|
|
@ -1507,7 +1512,7 @@ func (a ActivityTypeDeletedSoftware) Documentation() (string, string, string) {
|
||||||
return `Generated when a software installer is deleted from Fleet.`, `This activity contains the following fields:
|
return `Generated when a software installer is deleted from Fleet.`, `This activity contains the following fields:
|
||||||
- "software_title": Name of the software.
|
- "software_title": Name of the software.
|
||||||
- "software_package": Filename of the installer.
|
- "software_package": Filename of the installer.
|
||||||
- "team_name": Name of the team to which this software was added.` + " `null " + `if it was added to no team.
|
- "team_name": Name of the team to which this software was added.` + " `null` " + `if it was added to no team.
|
||||||
- "team_id": The ID of the team to which this software was added.` + " `null` " + `if it was added to no team.
|
- "team_id": The ID of the team to which this software was added.` + " `null` " + `if it was added to no team.
|
||||||
- "self_service": Whether the software was available for installation by the end user.`, `{
|
- "self_service": Whether the software was available for installation by the end user.`, `{
|
||||||
"software_title": "Falcon.app",
|
"software_title": "Falcon.app",
|
||||||
|
|
@ -1598,3 +1603,97 @@ func LogRoleChangeActivities(
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ActivityEnabledVPP struct{}
|
||||||
|
|
||||||
|
func (a ActivityEnabledVPP) ActivityName() string {
|
||||||
|
return "vpp_enabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ActivityEnabledVPP) Documentation() (activity string, details string, detailsExample string) {
|
||||||
|
return "Generated when the VPP feature is enabled in Fleet.", "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityDisabledVPP struct{}
|
||||||
|
|
||||||
|
func (a ActivityDisabledVPP) ActivityName() string {
|
||||||
|
return "vpp_disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ActivityDisabledVPP) Documentation() (activity string, details string, detailsExample string) {
|
||||||
|
return "Generated when the VPP feature is disabled in Fleet.", "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityAddedAppStoreApp struct {
|
||||||
|
SoftwareTitle string `json:"software_title"`
|
||||||
|
AppStoreID string `json:"app_store_id"`
|
||||||
|
TeamName *string `json:"team_name"`
|
||||||
|
TeamID *uint `json:"team_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ActivityAddedAppStoreApp) ActivityName() string {
|
||||||
|
return "added_app_store_app"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ActivityAddedAppStoreApp) Documentation() (activity string, details string, detailsExample string) {
|
||||||
|
return "Generated when an App Store app is added to Fleet.", `This activity contains the following fields:
|
||||||
|
- "software_title": Name of the App Store app.
|
||||||
|
- "app_store_id": ID of the app on the Apple App Store.
|
||||||
|
- "team_name": Name of the team to which this App Store app was added, or ` + "`null`" + ` if it was added to no team.
|
||||||
|
- "team_id": ID of the team to which this App Store app was added, or ` + "`null`" + `if it was added to no team.`, `{
|
||||||
|
"software_title": "Logic Pro",
|
||||||
|
"app_store_id": "1234567",
|
||||||
|
"team_name": "Workstations",
|
||||||
|
"team_id": 1
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityDeletedAppStoreApp struct {
|
||||||
|
SoftwareTitle string `json:"software_title"`
|
||||||
|
AppStoreID string `json:"app_store_id"`
|
||||||
|
TeamName string `json:"team_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ActivityDeletedAppStoreApp) ActivityName() string {
|
||||||
|
return "deleted_app_store_app"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ActivityDeletedAppStoreApp) Documentation() (activity string, details string, detailsExample string) {
|
||||||
|
return "Generated when an App Store app is deleted from Fleet.", `This activity contains the following fields:
|
||||||
|
- "software_title": Name of the App Store app.
|
||||||
|
- "app_store_id": ID of the app on the Apple App Store.
|
||||||
|
- "team_name": Name of the team from which this App Store app was deleted, or ` + "`null`" + ` if it was deleted from no team.
|
||||||
|
- "team_id": ID of the team from which this App Store app was deleted, or ` + "`null`" + `if it was deleted from no team.`, `{
|
||||||
|
"software_title": "Logic Pro",
|
||||||
|
"app_store_id": "1234567",
|
||||||
|
"team_name": "Workstations",
|
||||||
|
"team_id": 1
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityInstalledAppStoreApp struct {
|
||||||
|
HostID int `json:"host_id"`
|
||||||
|
HostDisplayName string `json:"host_display_name"`
|
||||||
|
SoftwareTitle string `json:"software_title"`
|
||||||
|
AppStoreID int `json:"app_store_id"`
|
||||||
|
CommandUUID string `json:"command_uuid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ActivityInstalledAppStoreApp) ActivityName() string {
|
||||||
|
return "installed_app_store_app"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ActivityInstalledAppStoreApp) Documentation() (string, string, string) {
|
||||||
|
return "Generated when an App Store app is installed on a device.", `This activity contains the following fields:
|
||||||
|
- host_id: ID of the host on which the app was installed.
|
||||||
|
- host_display_name: Display name of the host.
|
||||||
|
- software_title: Name of the App Store app.
|
||||||
|
- app_store_id: ID of the app on the Apple App Store.
|
||||||
|
- command_uuid: UUID of the MDM command used to install the app.`, `{
|
||||||
|
"host_id": 42,
|
||||||
|
"host_display_name": "Anna's MacBook Pro",
|
||||||
|
"software_title": "Logic Pro",
|
||||||
|
"app_store_id": "1234567",
|
||||||
|
"command_uuid": "98765432-1234-1234-1234-1234567890ab"
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10702,6 +10702,8 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
|
||||||
t.Setenv("FLEET_DEV_VPP_URL", s.appleVPPConfigSrv.URL)
|
t.Setenv("FLEET_DEV_VPP_URL", s.appleVPPConfigSrv.URL)
|
||||||
s.uploadDataViaForm("/api/latest/fleet/mdm/apple/vpp_token", "token", "token.vpptoken", []byte(base64.StdEncoding.EncodeToString([]byte(tokenJSON))), http.StatusAccepted, "")
|
s.uploadDataViaForm("/api/latest/fleet/mdm/apple/vpp_token", "token", "token.vpptoken", []byte(base64.StdEncoding.EncodeToString([]byte(tokenJSON))), http.StatusAccepted, "")
|
||||||
|
|
||||||
|
s.lastActivityMatches(fleet.ActivityEnabledVPP{}.ActivityName(), "", 0)
|
||||||
|
|
||||||
// Get the token
|
// Get the token
|
||||||
var resp getMDMAppleVPPTokenResponse
|
var resp getMDMAppleVPPTokenResponse
|
||||||
s.DoJSON("GET", "/api/latest/fleet/vpp", &getMDMAppleVPPTokenRequest{}, http.StatusOK, &resp)
|
s.DoJSON("GET", "/api/latest/fleet/vpp", &getMDMAppleVPPTokenRequest{}, http.StatusOK, &resp)
|
||||||
|
|
@ -10755,6 +10757,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
|
||||||
// Add an app store app to team 1
|
// Add an app store app to team 1
|
||||||
var addAppResp addAppStoreAppResponse
|
var addAppResp addAppStoreAppResponse
|
||||||
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", &addAppStoreAppRequest{TeamID: &team.ID, AppStoreID: appResp.AppStoreApps[0].AdamID}, http.StatusOK, &addAppResp)
|
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", &addAppStoreAppRequest{TeamID: &team.ID, AppStoreID: appResp.AppStoreApps[0].AdamID}, http.StatusOK, &addAppResp)
|
||||||
|
s.lastActivityMatches(fleet.ActivityAddedAppStoreApp{}.ActivityName(), fmt.Sprintf(`{"team_name": "%s", "software_title": "%s", "app_store_id": "%s", "team_id": %d}`, team.Name, appResp.AppStoreApps[0].Name, appResp.AppStoreApps[0].AdamID, team.ID), 0)
|
||||||
|
|
||||||
// Add an app store app to non-existent team
|
// Add an app store app to non-existent team
|
||||||
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", &addAppStoreAppRequest{TeamID: ptr.Uint(9999), AppStoreID: appResp.AppStoreApps[0].AdamID}, http.StatusNotFound, &addAppResp)
|
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", &addAppStoreAppRequest{TeamID: ptr.Uint(9999), AppStoreID: appResp.AppStoreApps[0].AdamID}, http.StatusNotFound, &addAppResp)
|
||||||
|
|
@ -10777,6 +10780,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
|
||||||
// Delete VPP token and check that it's not appearing anymore
|
// Delete VPP token and check that it's not appearing anymore
|
||||||
s.Do("DELETE", "/api/latest/fleet/mdm/apple/vpp_token", &deleteMDMAppleVPPTokenRequest{}, http.StatusNoContent)
|
s.Do("DELETE", "/api/latest/fleet/mdm/apple/vpp_token", &deleteMDMAppleVPPTokenRequest{}, http.StatusNoContent)
|
||||||
s.DoJSON("GET", "/api/latest/fleet/vpp", &getMDMAppleVPPTokenRequest{}, http.StatusNotFound, &resp)
|
s.DoJSON("GET", "/api/latest/fleet/vpp", &getMDMAppleVPPTokenRequest{}, http.StatusNotFound, &resp)
|
||||||
|
s.lastActivityMatches(fleet.ActivityDisabledVPP{}.ActivityName(), "", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. software title uploaded doesn't match existing title
|
// 1. software title uploaded doesn't match existing title
|
||||||
|
|
|
||||||
|
|
@ -2646,6 +2646,11 @@ func (svc *Service) UploadMDMAppleVPPToken(ctx context.Context, token io.ReadSee
|
||||||
return ctxerr.Wrap(ctx, err, "writing VPP token to db")
|
return ctxerr.Wrap(ctx, err, "writing VPP token to db")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
act := fleet.ActivityEnabledVPP{}
|
||||||
|
if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil {
|
||||||
|
return ctxerr.Wrap(ctx, err, "create activity for upload VPP token")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2729,5 +2734,14 @@ func (svc *Service) DeleteMDMAppleVPPToken(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc.ds.DeleteMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{fleet.MDMAssetVPPToken})
|
if err := svc.ds.DeleteMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{fleet.MDMAssetVPPToken}); err != nil {
|
||||||
|
return ctxerr.Wrap(ctx, err, "delete VPP token")
|
||||||
|
}
|
||||||
|
|
||||||
|
act := fleet.ActivityDisabledVPP{}
|
||||||
|
if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil {
|
||||||
|
return ctxerr.Wrap(ctx, err, "create activity for delete VPP token")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,10 @@ func TestMDMAppleAuthorization(t *testing.T) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
ds.DeleteMDMConfigAssetsByNameFunc = func(ctx context.Context, assetNames []fleet.MDMAssetName) error { return nil }
|
ds.DeleteMDMConfigAssetsByNameFunc = func(ctx context.Context, assetNames []fleet.MDMAssetName) error { return nil }
|
||||||
|
|
||||||
// use a custom implementation of checkAuthErr as the service call will fail
|
// use a custom implementation of checkAuthErr as the service call will fail
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue