From fdc5aa57c2f48d5c6dbde0e52e84d3a87bd7bab8 Mon Sep 17 00:00:00 2001 From: Roberto Dip Date: Wed, 27 Mar 2024 09:53:43 -0300 Subject: [PATCH] add ddm declarations in the API (#17880) for #17409 --- server/fleet/activities.go | 50 ++++++++++++++++++++++++++ server/service/apple_mdm.go | 39 +++++++++++++++----- server/service/integration_mdm_test.go | 23 ++++++++++-- 3 files changed, 102 insertions(+), 10 deletions(-) diff --git a/server/fleet/activities.go b/server/fleet/activities.go index 2fb7a4dbc0..8dacdf6862 100644 --- a/server/fleet/activities.go +++ b/server/fleet/activities.go @@ -1317,6 +1317,56 @@ func (a ActivityTypeWipedHost) Documentation() (activity, details, detailsExampl }` } +type ActivityTypeCreatedDeclarationProfile struct { + ProfileName string `json:"profile_name"` + Identifier string `json:"identifier"` + TeamID *uint `json:"team_id"` + TeamName *string `json:"team_name"` +} + +func (a ActivityTypeCreatedDeclarationProfile) ActivityName() string { + return "created_declaration_profile" +} + +func (a ActivityTypeCreatedDeclarationProfile) Documentation() (activity string, details string, detailsExample string) { + return `Generated when a user adds a new macOS declaration to a team (or no team).`, + `This activity contains the following fields: +- "profile_name": Name of the declaration. +- "identifier": Identifier of the declaration. +- "team_id": The ID of the team that the declaration applies to, ` + "`null`" + ` if it applies to devices that are not in a team. +- "team_name": The name of the team that the declaration applies to, ` + "`null`" + ` if it applies to devices that are not in a team.`, `{ + "profile_name": "Passcode requirements", + "profile_identifier": "com.my.declaration", + "team_id": 123, + "team_name": "Workstations" +}` +} + +type ActivityTypeDeletedDeclarationProfile struct { + ProfileName string `json:"profile_name"` + Identifier string `json:"identifier"` + TeamID *uint `json:"team_id"` + TeamName *string `json:"team_name"` +} + +func (a ActivityTypeDeletedDeclarationProfile) ActivityName() string { + return "deleted_declaration_profile" +} + +func (a ActivityTypeDeletedDeclarationProfile) Documentation() (activity string, details string, detailsExample string) { + return `Generated when a user removes a macOS declaration from a team (or no team).`, + `This activity contains the following fields: +- "profile_name": Name of the declaration. +- "identifier": Identifier of the declaration. +- "team_id": The ID of the team that the declaration applies to, ` + "`null`" + ` if it applies to devices that are not in a team. +- "team_name": The name of the team that the declaration applies to, ` + "`null`" + ` if it applies to devices that are not in a team.`, `{ + "profile_name": "Passcode requirements", + "profile_identifier": "com.my.declaration", + "team_id": 123, + "team_name": "Workstations" +}` +} + // LogRoleChangeActivities logs activities for each role change, globally and one for each change in teams. func LogRoleChangeActivities(ctx context.Context, ds Datastore, adminUser *User, oldGlobalRole *string, oldTeamRoles []UserTeam, user *User) error { if user.GlobalRole != nil && (oldGlobalRole == nil || *oldGlobalRole != *user.GlobalRole) { diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index 49092b559e..1a05d51f57 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -406,6 +406,15 @@ func (svc *Service) NewMDMAppleDeclaration(ctx context.Context, teamID uint, r i return nil, err } + var teamName string + if teamID >= 1 { + tm, err := svc.EnterpriseOverrides.TeamByIDOrName(ctx, &teamID, nil) + if err != nil { + return nil, ctxerr.Wrap(ctx, err) + } + teamName = tm.Name + } + data, err := io.ReadAll(r) if err != nil { return nil, err @@ -442,6 +451,23 @@ func (svc *Service) NewMDMAppleDeclaration(ctx context.Context, teamID uint, r i return nil, err } + var ( + actTeamID *uint + actTeamName *string + ) + if teamID > 0 { + actTeamID = &teamID + actTeamName = &teamName + } + if err := svc.ds.NewActivity(ctx, authz.UserFromContext(ctx), &fleet.ActivityTypeCreatedDeclarationProfile{ + TeamID: actTeamID, + TeamName: actTeamName, + ProfileName: decl.Name, + Identifier: decl.Identifier, + }); err != nil { + return nil, ctxerr.Wrap(ctx, err, "logging activity for create mdm apple declaration") + } + return decl, nil } @@ -802,13 +828,10 @@ func (svc *Service) DeleteMDMAppleDeclaration(ctx context.Context, declUUID stri return ctxerr.Wrap(ctx, err) } - // TODO: confirm if bulk set pending host profiles is needed - // cannot use the profile ID as it is now deleted if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{teamID}, nil, nil); err != nil { return ctxerr.Wrap(ctx, err, "bulk set pending host profiles") } - // TODO: confirm activity type var ( actTeamID *uint actTeamName *string @@ -817,11 +840,11 @@ func (svc *Service) DeleteMDMAppleDeclaration(ctx context.Context, declUUID stri actTeamID = &teamID actTeamName = &teamName } - if err := svc.ds.NewActivity(ctx, authz.UserFromContext(ctx), &fleet.ActivityTypeDeletedMacosProfile{ - TeamID: actTeamID, - TeamName: actTeamName, - ProfileName: decl.Name, - ProfileIdentifier: decl.Identifier, + if err := svc.ds.NewActivity(ctx, authz.UserFromContext(ctx), &fleet.ActivityTypeDeletedDeclarationProfile{ + TeamID: actTeamID, + TeamName: actTeamName, + ProfileName: decl.Name, + Identifier: decl.Identifier, }); err != nil { return ctxerr.Wrap(ctx, err, "logging activity for delete mdm apple declaration") } diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index 8d68ebaf80..3820aff4f0 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -9331,6 +9331,20 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() { return uid } + createAppleDeclaration := func(name, ident string, teamID uint, labelNames []string) string { + uid := assertAppleDeclaration(name+".json", ident, teamID, labelNames, http.StatusOK, "") + + var wantJSON string + if teamID == 0 { + wantJSON = fmt.Sprintf(`{"team_id": null, "team_name": null, "profile_name": %q, "identifier": %q}`, name, ident) + } else { + wantJSON = fmt.Sprintf(`{"team_id": %d, "team_name": %q, "profile_name": %q, "identifier": %q}`, teamID, testTeam.Name, name, ident) + } + s.lastActivityOfTypeMatches(fleet.ActivityTypeCreatedDeclarationProfile{}.ActivityName(), wantJSON, 0) + + return uid + } + assertWindowsProfile := func(filename, locURI string, teamID uint, labelNames []string, wantStatus int, wantErrMsg string) string { fields := map[string][]string{ "labels": labelNames, @@ -9399,7 +9413,7 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() { assertAppleProfile("win-team-profile.mobileconfig", "win-team-profile", "test-team-ident-2", 0, nil, http.StatusOK, "") // add some macOS declarations - assertAppleDeclaration("apple-declaration.json", "test-declaration-ident", 0, nil, http.StatusOK, "") + createAppleDeclaration("apple-declaration", "test-declaration-ident", 0, nil) // identifier must be unique, it conflicts with existing declaration assertAppleDeclaration("apple-declaration.json", "test-declaration-ident", 0, nil, http.StatusConflict, "test-declaration-ident already exists") // name is pulled from filename, it conflicts with existing declaration @@ -9451,7 +9465,7 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() { // profiles with valid labels uuidAppleWithLabel := assertAppleProfile("apple-profile-with-labels.mobileconfig", "apple-profile-with-labels", "ident-with-labels", 0, []string{"foo"}, http.StatusOK, "") - uuidAppleDDMWithLabel := assertAppleDeclaration("apple-decl-with-labels.json", "ident-decl-with-labels", 0, []string{"foo"}, http.StatusOK, "") + uuidAppleDDMWithLabel := createAppleDeclaration("apple-decl-with-labels", "ident-decl-with-labels", 0, []string{"foo"}) uuidWindowsWithLabel := assertWindowsProfile("win-profile-with-labels.xml", "./Test", 0, []string{"foo", "bar"}, http.StatusOK, "") // verify that the label associations have been created @@ -9558,6 +9572,11 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() { // delete existing Apple declaration s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", uuidAppleDDMWithLabel), nil, http.StatusOK, &deleteResp) + s.lastActivityOfTypeMatches( + fleet.ActivityTypeDeletedDeclarationProfile{}.ActivityName(), + `{"profile_name": "apple-decl-with-labels", "identifier": "ident-decl-with-labels", "team_id": null, "team_name": null}`, + 0, + ) // delete non-existing Apple declaration s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", fmt.Sprintf("%sno-such-profile", fleet.MDMAppleDeclarationUUIDPrefix)), nil, http.StatusNotFound, &deleteResp) // delete existing Windows profiles