From 5cfcd8be711347d66861afd3bb2d3c0c8a061872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Falconnier?= Date: Mon, 31 Jul 2023 18:00:56 +0200 Subject: [PATCH] Add MDM FileVault configurations --- goztl.go | 4 +- mdm_blueprints.go | 2 + mdm_blueprints_test.go | 6 + mdm_filevault_configs.go | 198 +++++++++++++++++++++++ mdm_filevault_configs_test.go | 293 ++++++++++++++++++++++++++++++++++ 5 files changed, 502 insertions(+), 1 deletion(-) create mode 100644 mdm_filevault_configs.go create mode 100644 mdm_filevault_configs_test.go diff --git a/goztl.go b/goztl.go index de9a1b5..599b815 100644 --- a/goztl.go +++ b/goztl.go @@ -15,7 +15,7 @@ import ( ) const ( - libraryVersion = "0.1.35" + libraryVersion = "0.1.36" userAgent = "goztl/" + libraryVersion mediaType = "application/json" ) @@ -41,6 +41,7 @@ type Client struct { MDMArtifacts MDMArtifactsService MDMBlueprints MDMBlueprintsService MDMBlueprintArtifacts MDMBlueprintArtifactsService + MDMFileVaultConfigs MDMFileVaultConfigsService MDMProfiles MDMProfilesService // Monolith MonolithCatalogs MonolithCatalogsService @@ -157,6 +158,7 @@ func NewClient(httpClient *http.Client, bu string, token string, opts ...ClientO c.MDMArtifacts = &MDMArtifactsServiceOp{client: c} c.MDMBlueprints = &MDMBlueprintsServiceOp{client: c} c.MDMBlueprintArtifacts = &MDMBlueprintArtifactsServiceOp{client: c} + c.MDMFileVaultConfigs = &MDMFileVaultConfigsServiceOp{client: c} c.MDMProfiles = &MDMProfilesServiceOp{client: c} // Monolith c.MonolithCatalogs = &MonolithCatalogsServiceOp{client: c} diff --git a/mdm_blueprints.go b/mdm_blueprints.go index af2e15e..e04e030 100644 --- a/mdm_blueprints.go +++ b/mdm_blueprints.go @@ -35,6 +35,7 @@ type MDMBlueprint struct { CollectApps int `json:"collect_apps"` CollectCertificates int `json:"collect_certificates"` CollectProfiles int `json:"collect_profiles"` + FileVaultConfigID *int `json:"filevault_config"` Created Timestamp `json:"created_at,omitempty"` Updated Timestamp `json:"updated_at,omitempty"` } @@ -50,6 +51,7 @@ type MDMBlueprintRequest struct { CollectApps int `json:"collect_apps"` CollectCertificates int `json:"collect_certificates"` CollectProfiles int `json:"collect_profiles"` + FileVaultConfigID *int `json:"filevault_config"` } type listMBOptions struct { diff --git a/mdm_blueprints_test.go b/mdm_blueprints_test.go index 09a8fe2..0c90c16 100644 --- a/mdm_blueprints_test.go +++ b/mdm_blueprints_test.go @@ -20,6 +20,7 @@ var mbListJSONResponse = ` "collect_apps": 0, "collect_certificates": 1, "collect_profiles": 2, + "filevault_config": null, "created_at": "2022-07-22T01:02:03.444444", "updated_at": "2022-07-22T01:02:03.444444" } @@ -34,6 +35,7 @@ var mbGetJSONResponse = ` "collect_apps": 0, "collect_certificates": 1, "collect_profiles": 2, + "filevault_config": 3, "created_at": "2022-07-22T01:02:03.444444", "updated_at": "2022-07-22T01:02:03.444444" } @@ -60,6 +62,7 @@ var mbUpdateJSONResponse = ` "collect_apps": 0, "collect_certificates": 1, "collect_profiles": 2, + "filevault_config": 3, "created_at": "2022-07-22T01:02:03.444444", "updated_at": "2022-07-22T01:02:03.444444" } @@ -121,6 +124,7 @@ func TestMDMBlueprintsService_GetByID(t *testing.T) { CollectApps: 0, CollectCertificates: 1, CollectProfiles: 2, + FileVaultConfigID: Int(3), Created: Timestamp{referenceTime}, Updated: Timestamp{referenceTime}, } @@ -218,6 +222,7 @@ func TestMDMBlueprintsService_Update(t *testing.T) { CollectApps: 0, CollectCertificates: 1, CollectProfiles: 2, + FileVaultConfigID: Int(3), } mux.HandleFunc("/mdm/blueprints/4/", func(w http.ResponseWriter, r *http.Request) { @@ -246,6 +251,7 @@ func TestMDMBlueprintsService_Update(t *testing.T) { CollectApps: 0, CollectCertificates: 1, CollectProfiles: 2, + FileVaultConfigID: Int(3), Created: Timestamp{referenceTime}, Updated: Timestamp{referenceTime}, } diff --git a/mdm_filevault_configs.go b/mdm_filevault_configs.go new file mode 100644 index 0000000..781e365 --- /dev/null +++ b/mdm_filevault_configs.go @@ -0,0 +1,198 @@ +package goztl + +import ( + "context" + "fmt" + "net/http" +) + +const mfcBasePath = "mdm/filevault_configs/" + +// MDMFileVaultConfigsService is an interface for interfacing with the MDM FileVault configuration +// endpoints of the Zentral API +type MDMFileVaultConfigsService interface { + List(context.Context, *ListOptions) ([]MDMFileVaultConfig, *Response, error) + GetByID(context.Context, int) (*MDMFileVaultConfig, *Response, error) + GetByName(context.Context, string) (*MDMFileVaultConfig, *Response, error) + Create(context.Context, *MDMFileVaultConfigRequest) (*MDMFileVaultConfig, *Response, error) + Update(context.Context, int, *MDMFileVaultConfigRequest) (*MDMFileVaultConfig, *Response, error) + Delete(context.Context, int) (*Response, error) +} + +// MDMFileVaultConfigsServiceOp handles communication with the MDM FileVault configurations related +// methods of the Zentral API. +type MDMFileVaultConfigsServiceOp struct { + client *Client +} + +var _ MDMFileVaultConfigsService = &MDMFileVaultConfigsServiceOp{} + +// MDMFileVaultConfig represents a Zentral MDM FileVault configuration +type MDMFileVaultConfig struct { + ID int `json:"id,omitempty"` + Name string `json:"name"` + EscrowLocationDisplayName string `json:"escrow_location_display_name"` + AtLoginOnly bool `json:"at_login_only"` + BypassAttempts int `json:"bypass_attempts"` + ShowRecoveryKey bool `json:"show_recovery_key"` + DestroyKeyOnStandby bool `json:"destroy_key_on_standby"` + PRKRotationIntervalDays int `json:"prk_rotation_interval_days"` + Created Timestamp `json:"created_at,omitempty"` + Updated Timestamp `json:"updated_at,omitempty"` +} + +func (mfc MDMFileVaultConfig) String() string { + return Stringify(mfc) +} + +// MDMFileVaultConfigRequest represents a request to create or update a MDM FileVault configuration +type MDMFileVaultConfigRequest struct { + Name string `json:"name"` + EscrowLocationDisplayName string `json:"escrow_location_display_name"` + AtLoginOnly bool `json:"at_login_only"` + BypassAttempts int `json:"bypass_attempts"` + ShowRecoveryKey bool `json:"show_recovery_key"` + DestroyKeyOnStandby bool `json:"destroy_key_on_standby"` + PRKRotationIntervalDays int `json:"prk_rotation_interval_days"` +} + +type listMFCOptions struct { + Name string `url:"name,omitempty"` +} + +// List lists all the MDM FileVault configurations. +func (s *MDMFileVaultConfigsServiceOp) List(ctx context.Context, opt *ListOptions) ([]MDMFileVaultConfig, *Response, error) { + return s.list(ctx, opt, nil) +} + +// GetByID retrieves a MDM FileVault configuration by id. +func (s *MDMFileVaultConfigsServiceOp) GetByID(ctx context.Context, mfcID int) (*MDMFileVaultConfig, *Response, error) { + if mfcID < 1 { + return nil, nil, NewArgError("mfcID", "cannot be less than 1") + } + + path := fmt.Sprintf("%s%d/", mfcBasePath, mfcID) + + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + mfc := new(MDMFileVaultConfig) + + resp, err := s.client.Do(ctx, req, mfc) + if err != nil { + return nil, resp, err + } + + return mfc, resp, err +} + +// GetByName retrieves a MDM FileVault configuration by name. +func (s *MDMFileVaultConfigsServiceOp) GetByName(ctx context.Context, name string) (*MDMFileVaultConfig, *Response, error) { + if len(name) < 1 { + return nil, nil, NewArgError("name", "cannot be blank") + } + + listMFCOpt := &listMFCOptions{Name: name} + + mfcs, resp, err := s.list(ctx, nil, listMFCOpt) + if err != nil { + return nil, resp, err + } + if len(mfcs) < 1 { + return nil, resp, nil + } + + return &mfcs[0], resp, err +} + +// Create a new MDM FileVault configuration. +func (s *MDMFileVaultConfigsServiceOp) Create(ctx context.Context, createRequest *MDMFileVaultConfigRequest) (*MDMFileVaultConfig, *Response, error) { + if createRequest == nil { + return nil, nil, NewArgError("createRequest", "cannot be nil") + } + + req, err := s.client.NewRequest(ctx, http.MethodPost, mfcBasePath, createRequest) + if err != nil { + return nil, nil, err + } + + mfc := new(MDMFileVaultConfig) + resp, err := s.client.Do(ctx, req, mfc) + if err != nil { + return nil, resp, err + } + + return mfc, resp, err +} + +// Update a MDM FileVault configuration. +func (s *MDMFileVaultConfigsServiceOp) Update(ctx context.Context, mfcID int, updateRequest *MDMFileVaultConfigRequest) (*MDMFileVaultConfig, *Response, error) { + if mfcID < 1 { + return nil, nil, NewArgError("mfcID", "cannot be less than 1") + } + + if updateRequest == nil { + return nil, nil, NewArgError("updateRequest", "cannot be nil") + } + + path := fmt.Sprintf("%s%d/", mfcBasePath, mfcID) + + req, err := s.client.NewRequest(ctx, http.MethodPut, path, updateRequest) + if err != nil { + return nil, nil, err + } + + mfc := new(MDMFileVaultConfig) + resp, err := s.client.Do(ctx, req, mfc) + if err != nil { + return nil, resp, err + } + + return mfc, resp, err +} + +// Delete a MDM FileVault configuration. +func (s *MDMFileVaultConfigsServiceOp) Delete(ctx context.Context, mfcID int) (*Response, error) { + if mfcID < 1 { + return nil, NewArgError("mfcID", "cannot be less than 1") + } + + path := fmt.Sprintf("%s%d/", mfcBasePath, mfcID) + + req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + + return resp, err +} + +// Helper method for listing MDM FileVault configurations +func (s *MDMFileVaultConfigsServiceOp) list(ctx context.Context, opt *ListOptions, mfcOpt *listMFCOptions) ([]MDMFileVaultConfig, *Response, error) { + path := mfcBasePath + path, err := addOptions(path, opt) + if err != nil { + return nil, nil, err + } + path, err = addOptions(path, mfcOpt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + var mfcs []MDMFileVaultConfig + resp, err := s.client.Do(ctx, req, &mfcs) + if err != nil { + return nil, resp, err + } + + return mfcs, resp, err +} diff --git a/mdm_filevault_configs_test.go b/mdm_filevault_configs_test.go new file mode 100644 index 0000000..966f7b3 --- /dev/null +++ b/mdm_filevault_configs_test.go @@ -0,0 +1,293 @@ +package goztl + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" +) + +var mfcListJSONResponse = ` +[ + { + "id": 4, + "name": "Default", + "escrow_location_display_name": "Escrow", + "at_login_only": true, + "bypass_attempts": 1, + "show_recovery_key": true, + "destroy_key_on_standby": true, + "prk_rotation_interval_days": 90, + "created_at": "2022-07-22T01:02:03.444444", + "updated_at": "2022-07-22T01:02:03.444444" + } +] +` + +var mfcGetJSONResponse = ` +{ + "id": 4, + "name": "Default", + "escrow_location_display_name": "Escrow", + "at_login_only": true, + "bypass_attempts": 1, + "show_recovery_key": true, + "destroy_key_on_standby": true, + "prk_rotation_interval_days": 90, + "created_at": "2022-07-22T01:02:03.444444", + "updated_at": "2022-07-22T01:02:03.444444" +} +` + +var mfcCreateJSONResponse = ` +{ + "id": 4, + "name": "Default", + "escrow_location_display_name": "Escrow", + "at_login_only": true, + "bypass_attempts": 1, + "show_recovery_key": true, + "destroy_key_on_standby": true, + "prk_rotation_interval_days": 90, + "created_at": "2022-07-22T01:02:03.444444", + "updated_at": "2022-07-22T01:02:03.444444" +} +` + +var mfcUpdateJSONResponse = ` +{ + "id": 4, + "name": "Default", + "escrow_location_display_name": "Escrow", + "at_login_only": true, + "bypass_attempts": 1, + "show_recovery_key": true, + "destroy_key_on_standby": true, + "prk_rotation_interval_days": 90, + "created_at": "2022-07-22T01:02:03.444444", + "updated_at": "2022-07-22T01:02:03.444444" +} +` + +func TestMDMFileVaultConfigsService_List(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc("/mdm/filevault_configs/", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", "application/json") + fmt.Fprint(w, mfcListJSONResponse) + }) + + ctx := context.Background() + got, _, err := client.MDMFileVaultConfigs.List(ctx, nil) + if err != nil { + t.Errorf("MDMFileVaultConfigs.List returned error: %v", err) + } + + want := []MDMFileVaultConfig{ + { + ID: 4, + Name: "Default", + EscrowLocationDisplayName: "Escrow", + AtLoginOnly: true, + BypassAttempts: 1, + ShowRecoveryKey: true, + DestroyKeyOnStandby: true, + PRKRotationIntervalDays: 90, + Created: Timestamp{referenceTime}, + Updated: Timestamp{referenceTime}, + }, + } + if !cmp.Equal(got, want) { + t.Errorf("MDMFileVaultConfigs.List returned %+v, want %+v", got, want) + } +} + +func TestMDMFileVaultConfigsService_GetByID(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc("/mdm/filevault_configs/4/", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", "application/json") + fmt.Fprint(w, mfcGetJSONResponse) + }) + + ctx := context.Background() + got, _, err := client.MDMFileVaultConfigs.GetByID(ctx, 4) + if err != nil { + t.Errorf("MDMFileVaultConfigs.GetByID returned error: %v", err) + } + + want := &MDMFileVaultConfig{ + ID: 4, + Name: "Default", + EscrowLocationDisplayName: "Escrow", + AtLoginOnly: true, + BypassAttempts: 1, + ShowRecoveryKey: true, + DestroyKeyOnStandby: true, + PRKRotationIntervalDays: 90, + Created: Timestamp{referenceTime}, + Updated: Timestamp{referenceTime}, + } + if !cmp.Equal(got, want) { + t.Errorf("MDMFileVaultConfigs.GetByID returned %+v, want %+v", got, want) + } +} + +func TestMDMFileVaultConfigsService_GetByName(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc("/mdm/filevault_configs/", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", "application/json") + testQueryArg(t, r, "name", "Default") + fmt.Fprint(w, mfcListJSONResponse) + }) + + ctx := context.Background() + got, _, err := client.MDMFileVaultConfigs.GetByName(ctx, "Default") + if err != nil { + t.Errorf("MDMFileVaultConfigs.GetByName returned error: %v", err) + } + + want := &MDMFileVaultConfig{ + ID: 4, + Name: "Default", + EscrowLocationDisplayName: "Escrow", + AtLoginOnly: true, + BypassAttempts: 1, + ShowRecoveryKey: true, + DestroyKeyOnStandby: true, + PRKRotationIntervalDays: 90, + Created: Timestamp{referenceTime}, + Updated: Timestamp{referenceTime}, + } + if !cmp.Equal(got, want) { + t.Errorf("MDMFileVaultConfigs.GetByName returned %+v, want %+v", got, want) + } +} + +func TestMDMFileVaultConfigsService_Create(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + createRequest := &MDMFileVaultConfigRequest{ + Name: "Default", + EscrowLocationDisplayName: "Escrow", + AtLoginOnly: true, + BypassAttempts: 1, + ShowRecoveryKey: true, + DestroyKeyOnStandby: true, + PRKRotationIntervalDays: 90, + } + + mux.HandleFunc("/mdm/filevault_configs/", func(w http.ResponseWriter, r *http.Request) { + v := new(MDMFileVaultConfigRequest) + err := json.NewDecoder(r.Body).Decode(v) + if err != nil { + t.Fatal(err) + } + testMethod(t, r, "POST") + testHeader(t, r, "Accept", "application/json") + testHeader(t, r, "Content-Type", "application/json") + assert.Equal(t, createRequest, v) + + fmt.Fprint(w, mfcCreateJSONResponse) + }) + + ctx := context.Background() + got, _, err := client.MDMFileVaultConfigs.Create(ctx, createRequest) + if err != nil { + t.Errorf("MDMFileVaultConfigs.Create returned error: %v", err) + } + + want := &MDMFileVaultConfig{ + ID: 4, + Name: "Default", + EscrowLocationDisplayName: "Escrow", + AtLoginOnly: true, + BypassAttempts: 1, + ShowRecoveryKey: true, + DestroyKeyOnStandby: true, + PRKRotationIntervalDays: 90, + Created: Timestamp{referenceTime}, + Updated: Timestamp{referenceTime}, + } + if !cmp.Equal(got, want) { + t.Errorf("MDMFileVaultConfigs.Create returned %+v, want %+v", got, want) + } +} + +func TestMDMFileVaultConfigsService_Update(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + updateRequest := &MDMFileVaultConfigRequest{ + Name: "Default", + EscrowLocationDisplayName: "Escrow", + AtLoginOnly: true, + BypassAttempts: 1, + ShowRecoveryKey: true, + DestroyKeyOnStandby: true, + PRKRotationIntervalDays: 90, + } + + mux.HandleFunc("/mdm/filevault_configs/4/", func(w http.ResponseWriter, r *http.Request) { + v := new(MDMFileVaultConfigRequest) + err := json.NewDecoder(r.Body).Decode(v) + if err != nil { + t.Fatal(err) + } + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", "application/json") + testHeader(t, r, "Content-Type", "application/json") + assert.Equal(t, updateRequest, v) + fmt.Fprint(w, mfcUpdateJSONResponse) + }) + + ctx := context.Background() + got, _, err := client.MDMFileVaultConfigs.Update(ctx, 4, updateRequest) + if err != nil { + t.Errorf("MDMFileVaultConfigs.Update returned error: %v", err) + } + + want := &MDMFileVaultConfig{ + ID: 4, + Name: "Default", + EscrowLocationDisplayName: "Escrow", + AtLoginOnly: true, + BypassAttempts: 1, + ShowRecoveryKey: true, + DestroyKeyOnStandby: true, + PRKRotationIntervalDays: 90, + Created: Timestamp{referenceTime}, + Updated: Timestamp{referenceTime}, + } + if !cmp.Equal(got, want) { + t.Errorf("MDMFileVaultConfigs.Update returned %+v, want %+v", got, want) + } +} + +func TestMDMFileVaultConfigsService_Delete(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc("/mdm/filevault_configs/4/", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + ctx := context.Background() + _, err := client.MDMFileVaultConfigs.Delete(ctx, 4) + if err != nil { + t.Errorf("MDMFileVaultConfigs.Delete returned error: %v", err) + } +}