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:
Jahziel Villasana-Espinoza 2024-07-16 10:51:08 -04:00 committed by GitHub
parent 464c248f30
commit 5d2e40bc8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 213 additions and 3 deletions

View file

@ -0,0 +1 @@
- Adds global activity support for VPP related activities.

View file

@ -1158,7 +1158,7 @@ Generated when a software installer is deleted from Fleet.
This activity contains the following fields:
- "software_title": Name of the software.
- "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.
- "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="pageOrderInSection" value="1400">

View file

@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"github.com/fleetdm/fleet/v4/server/authz"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
"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")
}
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
}

View file

@ -99,6 +99,11 @@ var ActivityDetailsList = []ActivityDetails{
ActivityTypeInstalledSoftware{},
ActivityTypeAddedSoftware{},
ActivityTypeDeletedSoftware{},
ActivityEnabledVPP{},
ActivityDisabledVPP{},
ActivityAddedAppStoreApp{},
ActivityDeletedAppStoreApp{},
ActivityInstalledAppStoreApp{},
}
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:
- "software_title": Name of the software.
- "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.
- "self_service": Whether the software was available for installation by the end user.`, `{
"software_title": "Falcon.app",
@ -1598,3 +1603,97 @@ func LogRoleChangeActivities(
}
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"
}`
}

View file

@ -10702,6 +10702,8 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
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.lastActivityMatches(fleet.ActivityEnabledVPP{}.ActivityName(), "", 0)
// Get the token
var resp getMDMAppleVPPTokenResponse
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
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.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
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
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.lastActivityMatches(fleet.ActivityDisabledVPP{}.ActivityName(), "", 0)
}
// 1. software title uploaded doesn't match existing title

View file

@ -2646,6 +2646,11 @@ func (svc *Service) UploadMDMAppleVPPToken(ctx context.Context, token io.ReadSee
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
}
@ -2729,5 +2734,14 @@ func (svc *Service) DeleteMDMAppleVPPToken(ctx context.Context) error {
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
}

View file

@ -119,6 +119,10 @@ func TestMDMAppleAuthorization(t *testing.T) {
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 }
// use a custom implementation of checkAuthErr as the service call will fail