diff --git a/ee/server/service/certificate_authorities.go b/ee/server/service/certificate_authorities.go index ff731f5759..db31010970 100644 --- a/ee/server/service/certificate_authorities.go +++ b/ee/server/service/certificate_authorities.go @@ -866,6 +866,10 @@ func (svc *Service) UpdateCertificateAuthority(ctx context.Context, id uint, p f var caActivityName string if p.DigiCertCAUpdatePayload != nil { + if p.DigiCertCAUpdatePayload.IsEmpty() { + return &fleet.BadRequestError{Message: fmt.Sprintf("%sDigiCert CA update payload is empty", errPrefix)} + } + if err := p.DigiCertCAUpdatePayload.ValidateRelatedFields(errPrefix, *oldCA.Name); err != nil { return err } @@ -890,6 +894,10 @@ func (svc *Service) UpdateCertificateAuthority(ctx context.Context, id uint, p f activity = fleet.ActivityEditedDigiCert{Name: caActivityName} } if p.HydrantCAUpdatePayload != nil { + if p.HydrantCAUpdatePayload.IsEmpty() { + return &fleet.BadRequestError{Message: fmt.Sprintf("%sHydrant CA update payload is empty", errPrefix)} + } + if err := p.HydrantCAUpdatePayload.ValidateRelatedFields(errPrefix, *oldCA.Name); err != nil { return err } @@ -910,6 +918,10 @@ func (svc *Service) UpdateCertificateAuthority(ctx context.Context, id uint, p f activity = fleet.ActivityEditedHydrant{Name: caActivityName} } if p.NDESSCEPProxyCAUpdatePayload != nil { + if p.NDESSCEPProxyCAUpdatePayload.IsEmpty() { + return &fleet.BadRequestError{Message: fmt.Sprintf("%sNDES SCEP Proxy CA update payload is empty", errPrefix)} + } + if err := p.NDESSCEPProxyCAUpdatePayload.ValidateRelatedFields(errPrefix, *oldCA.Name); err != nil { return err } @@ -930,6 +942,10 @@ func (svc *Service) UpdateCertificateAuthority(ctx context.Context, id uint, p f activity = fleet.ActivityEditedNDESSCEPProxy{} } if p.CustomSCEPProxyCAUpdatePayload != nil { + if p.CustomSCEPProxyCAUpdatePayload.IsEmpty() { + return &fleet.BadRequestError{Message: fmt.Sprintf("%sCustom SCEP Proxy CA update payload is empty", errPrefix)} + } + if err := p.CustomSCEPProxyCAUpdatePayload.ValidateRelatedFields(errPrefix, *oldCA.Name); err != nil { return err } diff --git a/ee/server/service/certificate_authorities_test.go b/ee/server/service/certificate_authorities_test.go index 7b6f78355a..2b43e6f7c7 100644 --- a/ee/server/service/certificate_authorities_test.go +++ b/ee/server/service/certificate_authorities_test.go @@ -1009,6 +1009,30 @@ func TestUpdatingCertificateAuthorities(t *testing.T) { require.EqualError(t, err, "Couldn't edit certificate authority. Certificate authority with ID 999 does not exist.") }) + t.Run("Errors on empty inner update payload", func(t *testing.T) { + svc, ctx := baseSetupForCATests() + payloadMap := map[uint]fleet.CertificateAuthorityUpdatePayload{ + digicertID: { + DigiCertCAUpdatePayload: &fleet.DigiCertCAUpdatePayload{}, + }, + hydrantID: { + HydrantCAUpdatePayload: &fleet.HydrantCAUpdatePayload{}, + }, + scepID: { + CustomSCEPProxyCAUpdatePayload: &fleet.CustomSCEPProxyCAUpdatePayload{}, + }, + ndesID: { + NDESSCEPProxyCAUpdatePayload: &fleet.NDESSCEPProxyCAUpdatePayload{}, + }, + } + + for id, payload := range payloadMap { + + err := svc.UpdateCertificateAuthority(ctx, id, payload) + require.Contains(t, err.Error(), "update payload is empty") + } + }) + t.Run("Errors on wrong existing CA type", func(t *testing.T) { svc, ctx := baseSetupForCATests() diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tests.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tests.tsx index 78d24f3d96..b9befc7214 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tests.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tests.tsx @@ -40,7 +40,7 @@ describe("CustomSCEPForm", () => { /> ); - // data is valid, submit should be enabled + // data is valid, so submit should be enabled expect(screen.getByRole("button", { name: "Submit" })).toBeEnabled(); // name input is invalidated, submit should be disabled @@ -62,4 +62,36 @@ describe("CustomSCEPForm", () => { expect(screen.getByRole("button", { name: "Submit" })).toBeDisabled(); }); + + it("submit button is disabled if isDirty is false", () => { + render( + + ); + + expect(screen.getByRole("button", { name: "Submit" })).toBeDisabled(); + }); + + it("submit button is enabled if isDirty", () => { + render( + + ); + + expect(screen.getByRole("button", { name: "Submit" })).toBeEnabled(); + }); }); diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx index 337d2aa96a..d936aa656e 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx @@ -27,6 +27,7 @@ interface ICustomSCEPFormProps { submitBtnText: string; isSubmitting: boolean; isEditing?: boolean; + isDirty?: boolean; onChange: (update: { name: string; value: string }) => void; onSubmit: () => void; onCancel: () => void; @@ -38,6 +39,7 @@ const CustomSCEPForm = ({ submitBtnText, isSubmitting, isEditing = false, + isDirty = true, onChange, onSubmit, onCancel, @@ -113,7 +115,7 @@ const CustomSCEPForm = ({ diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tests.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tests.tsx index 6e3c0d8450..b4c5b68e9a 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tests.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tests.tsx @@ -44,7 +44,7 @@ describe("DigicertForm", () => { /> ); - // data is valid, submit should be enabled + // data is valid, so submit should be enabled expect(screen.getByRole("button", { name: "Submit" })).toBeEnabled(); // name input is invalidated, submit should be disabled @@ -66,4 +66,36 @@ describe("DigicertForm", () => { expect(screen.getByRole("button", { name: "Submit" })).toBeDisabled(); }); + + it("submit button is disabled if isDirty is false", () => { + render( + + ); + + expect(screen.getByRole("button", { name: "Submit" })).toBeDisabled(); + }); + + it("submit button is enabled if isDirty", () => { + render( + + ); + + expect(screen.getByRole("button", { name: "Submit" })).toBeEnabled(); + }); }); diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx index 58af394941..18521b8d9e 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx @@ -31,6 +31,7 @@ interface IDigicertFormProps { submitBtnText: string; isSubmitting: boolean; isEditing?: boolean; + isDirty?: boolean; onChange: (update: { name: string; value: string }) => void; onSubmit: () => void; onCancel: () => void; @@ -42,6 +43,7 @@ const DigicertForm = ({ submitBtnText, isSubmitting, isEditing = false, + isDirty = true, onChange, onSubmit, onCancel, @@ -166,7 +168,7 @@ const DigicertForm = ({ > diff --git a/server/fleet/certificate_authorities.go b/server/fleet/certificate_authorities.go index c1eb17a210..5e23680eec 100644 --- a/server/fleet/certificate_authorities.go +++ b/server/fleet/certificate_authorities.go @@ -207,6 +207,13 @@ type DigiCertCAUpdatePayload struct { CertificateSeatID *string `json:"certificate_seat_id"` } +// IsEmpty checks if the struct only has all empty values +func (dcp DigiCertCAUpdatePayload) IsEmpty() bool { + return dcp.Name == nil && dcp.URL == nil && dcp.APIToken == nil && + dcp.ProfileID == nil && dcp.CertificateCommonName == nil && + dcp.CertificateUserPrincipalNames == nil && dcp.CertificateSeatID == nil +} + // ValidateRelatedFields verifies that fields that are related to each other are set correctly. // For example if the URL is provided then the API token must also be provided. func (dcp *DigiCertCAUpdatePayload) ValidateRelatedFields(errPrefix string, certName string) error { @@ -236,6 +243,11 @@ type NDESSCEPProxyCAUpdatePayload struct { Password *string `json:"password"` } +// IsEmpty checks if the struct only has all empty values +func (ndesp *NDESSCEPProxyCAUpdatePayload) IsEmpty() bool { + return ndesp.URL == nil && ndesp.AdminURL == nil && ndesp.Username == nil && ndesp.Password == nil +} + // ValidateRelatedFields verifies that fields that are related to each other are set correctly. // For example if the Admin URL is provided then the Password must also be provided. func (ndesp *NDESSCEPProxyCAUpdatePayload) ValidateRelatedFields(errPrefix string, certName string) error { @@ -264,6 +276,11 @@ type CustomSCEPProxyCAUpdatePayload struct { Challenge *string `json:"challenge"` } +// IsEmpty checks if the struct only has all empty values +func (cscepp CustomSCEPProxyCAUpdatePayload) IsEmpty() bool { + return cscepp.Name == nil && cscepp.URL == nil && cscepp.Challenge == nil +} + // ValidateRelatedFields verifies that fields that are related to each other are set correctly. // For example if the Name is provided then the Challenge must also be provided. func (cscepp *CustomSCEPProxyCAUpdatePayload) ValidateRelatedFields(errPrefix string, certName string) error { @@ -290,6 +307,11 @@ type HydrantCAUpdatePayload struct { ClientSecret *string `json:"client_secret"` } +// IsEmpty checks if the struct only has all empty values +func (hp HydrantCAUpdatePayload) IsEmpty() bool { + return hp.Name == nil && hp.URL == nil && hp.ClientID == nil && hp.ClientSecret == nil +} + // ValidateRelatedFields verifies that fields that are related to each other are set correctly. // For example if the Name is provided then the Client ID and Client Secret must also be provided func (hp *HydrantCAUpdatePayload) ValidateRelatedFields(errPrefix string, certName string) error {