diff --git a/.changelog/2748.txt b/.changelog/2754.txt similarity index 51% rename from .changelog/2748.txt rename to .changelog/2754.txt index c7e0cc22650..82df31b4e61 100644 --- a/.changelog/2748.txt +++ b/.changelog/2754.txt @@ -1,3 +1,3 @@ ```release-note:breaking-change -teams: gateway: rename TeamsCertificate in TeamsAccountConfiguration to TeamsCertificateSetting +teams: gateway: rename TeamsCertificate in TeamsAccountConfiguration to TeamsCertificateSetting, add TeamsCertificate resource to manage gateway certificates ``` diff --git a/teams_accounts.go b/teams_accounts.go index 73210da9974..dbded095329 100644 --- a/teams_accounts.go +++ b/teams_accounts.go @@ -47,7 +47,7 @@ type TeamsAccountSettings struct { BodyScanning *TeamsBodyScanning `json:"body_scanning,omitempty"` ExtendedEmailMatching *TeamsExtendedEmailMatching `json:"extended_email_matching,omitempty"` CustomCertificate *TeamsCustomCertificate `json:"custom_certificate,omitempty"` - Certificate *TeamsCertificateSetting `json:"certificate,omitempty"` + Certificate *TeamsCertificateSetting `json:"certificate,omitempty"` } type BrowserIsolation struct { diff --git a/teams_certificates.go b/teams_certificates.go new file mode 100644 index 00000000000..74267866944 --- /dev/null +++ b/teams_certificates.go @@ -0,0 +1,158 @@ +package cloudflare + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/goccy/go-json" +) + +type TeamsCertificate struct { + Enabled *bool `json:"enabled"` + ID string `json:"id"` + BindingStatus string `json:"binding_status"` + QsPackId string `json:"qs_pack_id"` + Type string `json:"type"` + UpdatedAt *time.Time `json:"updated_at"` + UploadedOn *time.Time `json:"uploaded_on"` + CreatedAt *time.Time `json:"created_at"` + ExpiresOn *time.Time `json:"expires_on"` +} + +type TeamsCertificateCreateRequest struct { + ValidityPeriodDays int `json:"validity_period_days,omitempty"` +} + +const DEFAULT_VALIDITY_PERIOD_DAYS = 1826 + +// TeamsCertificateResponse is the API response, containing a single certificate. +type TeamsCertificateResponse struct { + Response + Result TeamsCertificate `json:"result"` +} + +// TeamsCertificatesResponse is the API response, containing an array of certificates. +type TeamsCertificatesResponse struct { + Response + Result []TeamsCertificate `json:"result"` +} + +// TeamsCertificates returns all certificates in an account +// +// API reference: https://developers.cloudflare.com/api/operations/zero-trust-certificates-list-zero-trust-certificates +func (api *API) TeamsCertificates(ctx context.Context, accountID string) ([]TeamsCertificate, error) { + uri := fmt.Sprintf("/accounts/%s/gateway/certificates", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []TeamsCertificate{}, err + } + + var teamsCertificatesResponse TeamsCertificatesResponse + err = json.Unmarshal(res, &teamsCertificatesResponse) + if err != nil { + return []TeamsCertificate{}, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + + return teamsCertificatesResponse.Result, nil +} + +// TeamsCertificate returns teams account certificate. +// +// API reference: https://developers.cloudflare.com/api/operations/zero-trust-certificates-zero-trust-certificate-details +func (api *API) TeamsCertificate(ctx context.Context, accountID string, certificateId string) (TeamsCertificate, error) { + uri := fmt.Sprintf("/accounts/%s/gateway/certificates/%s", accountID, certificateId) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return TeamsCertificate{}, err + } + + var teamsCertificateResponse TeamsCertificateResponse + err = json.Unmarshal(res, &teamsCertificateResponse) + if err != nil { + return TeamsCertificate{}, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + + return teamsCertificateResponse.Result, nil +} + +// TeamsGenerateCertificate generates a new gateway managed certificate +// +// API reference: https://developers.cloudflare.com/api/operations/zero-trust-certificates-create-zero-trust-certificate +func (api *API) TeamsGenerateCertificate(ctx context.Context, accountID string, certificateRequest TeamsCertificateCreateRequest) (TeamsCertificate, error) { + uri := fmt.Sprintf("/accounts/%s/gateway/certificates", accountID) + + if certificateRequest.ValidityPeriodDays == 0 { + certificateRequest.ValidityPeriodDays = DEFAULT_VALIDITY_PERIOD_DAYS + } + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, certificateRequest) + if err != nil { + return TeamsCertificate{}, err + } + + var teamsCertResponse TeamsCertificateResponse + err = json.Unmarshal(res, &teamsCertResponse) + if err != nil { + return TeamsCertificate{}, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + + return teamsCertResponse.Result, nil +} + +// TeamsActivateCertificate activates a certificate +// +// API reference: https://developers.cloudflare.com/api/operations/zero-trust-certificates-activate-zero-trust-certificate +func (api *API) TeamsActivateCertificate(ctx context.Context, accountID string, certificateId string) (TeamsCertificate, error) { + uri := fmt.Sprintf("/accounts/%s/gateway/certificates/%s/activate", accountID, certificateId) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil) + if err != nil { + return TeamsCertificate{}, err + } + + var teamsCertResponse TeamsCertificateResponse + err = json.Unmarshal(res, &teamsCertResponse) + if err != nil { + return TeamsCertificate{}, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + + return teamsCertResponse.Result, nil +} + +// TeamsDectivateCertificate deactivates a certificate +// +// API reference: https://developers.cloudflare.com/api/operations/zero-trust-certificates-deactivate-zero-trust-certificate +func (api *API) TeamsDeactivateCertificate(ctx context.Context, accountID string, certificateId string) (TeamsCertificate, error) { + uri := fmt.Sprintf("/accounts/%s/gateway/certificates/%s/deactivate", accountID, certificateId) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil) + if err != nil { + return TeamsCertificate{}, err + } + + var teamsCertResponse TeamsCertificateResponse + err = json.Unmarshal(res, &teamsCertResponse) + if err != nil { + return TeamsCertificate{}, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + + return teamsCertResponse.Result, nil +} + +// TeamsDeleteCertificate deletes a certificate. +// +// API reference: https://developers.cloudflare.com/api/operations/zero-trust-certificates-delete-zero-trust-certificate +func (api *API) TeamsDeleteCertificate(ctx context.Context, accountID string, certificateId string) error { + uri := fmt.Sprintf("/accounts/%s/gateway/certificates/%s", accountID, certificateId) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} diff --git a/teams_certificates_test.go b/teams_certificates_test.go new file mode 100644 index 00000000000..8fc7155bbf4 --- /dev/null +++ b/teams_certificates_test.go @@ -0,0 +1,229 @@ +package cloudflare + +import ( + "context" + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTeamsCertificate(t *testing.T) { + setup() + defer teardown() + + testCertID := "80c8a54e-d55c-46c6-86bb-e8a3c90472f4" + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": { + "enabled": false, + "id": "80c8a54e-d55c-46c6-86bb-e8a3c90472f4", + "binding_status": "inactive", + "qs_pack_id": "00000000-0000-0000-0000-000000000000", + "type": "gateway_managed", + "updated_at": "0001-01-01T00:00:00Z", + "uploaded_on": "0001-01-01T00:00:00Z", + "created_at": "2024-06-19T08:28:34.235742Z", + "expires_on": "2029-06-19T08:24:00Z" + } + } + `) + } + + mux.HandleFunc("/accounts/"+testAccountID+"/gateway/certificates/"+testCertID, handler) + + actual, err := client.TeamsCertificate(context.Background(), testAccountID, testCertID) + + if assert.NoError(t, err) { + assert.Equal(t, *actual.Enabled, false) + assert.Equal(t, actual.ID, testCertID) + assert.Equal(t, actual.BindingStatus, "inactive") + assert.Equal(t, actual.Type, "gateway_managed") + } +} + +func TestTeamsCertificatesList(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": [ + { + "enabled": false, + "id": "43a36083-987b-4321-95ab-4052771c4e6f", + "binding_status": "inactive", + "qs_pack_id": "00000000-0000-0000-0000-000000000000", + "type": "custom", + "updated_at": "0001-01-01T00:00:00Z", + "uploaded_on": "2022-12-08T22:58:13.646596Z", + "created_at": "0001-01-01T00:00:00Z", + "expires_on": "2122-10-29T16:59:47Z" + }, + { + "enabled": false, + "id": "4a9d6ecf-0fdd-4676-818a-ee6b45f17f9b", + "binding_status": "inactive", + "qs_pack_id": "00000000-0000-0000-0000-000000000000", + "type": "gateway_managed", + "updated_at": "0001-01-01T00:00:00Z", + "uploaded_on": "0001-01-01T00:00:00Z", + "created_at": "2024-05-29T01:32:21.133597Z", + "expires_on": "2029-05-29T01:27:00Z" + } + ] + } + `) + } + + mux.HandleFunc("/accounts/"+testAccountID+"/gateway/certificates", handler) + + actual, err := client.TeamsCertificates(context.Background(), testAccountID) + + if assert.NoError(t, err) { + assert.Equal(t, len(actual), 2) + assert.Equal(t, actual[0].ID, "43a36083-987b-4321-95ab-4052771c4e6f") + assert.Equal(t, actual[1].ID, "4a9d6ecf-0fdd-4676-818a-ee6b45f17f9b") + } +} + +func TestTeamsAccountGenerateCertificate(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method, "Expected method 'post', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": { + "enabled": false, + "id": "80c8a54e-d55c-46c6-86bb-e8a3c90472f4", + "binding_status": "inactive", + "qs_pack_id": "00000000-0000-0000-0000-000000000000", + "type": "gateway_managed", + "updated_at": "0001-01-01T00:00:00Z", + "uploaded_on": "0001-01-01T00:00:00Z", + "created_at": "2024-06-19T08:28:34.235742Z", + "expires_on": "2029-06-19T08:24:00Z" + } + } + `) + } + + mux.HandleFunc("/accounts/"+testAccountID+"/gateway/certificates", handler) + + createRequest := TeamsCertificateCreateRequest{ + ValidityPeriodDays: 1826, + } + actual, err := client.TeamsGenerateCertificate(context.Background(), testAccountID, createRequest) + + if assert.NoError(t, err) { + assert.Equal(t, actual.ID, "80c8a54e-d55c-46c6-86bb-e8a3c90472f4") + } +} + +func TestTeamsActivateCertificate(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": { + "enabled": false, + "id": "80c8a54e-d55c-46c6-86bb-e8a3c90472f4", + "binding_status": "pending_deployment", + "qs_pack_id": "00000000-0000-0000-0000-000000000000", + "type": "gateway_managed", + "updated_at": "0001-01-01T00:00:00Z", + "uploaded_on": "0001-01-01T00:00:00Z", + "created_at": "2024-06-19T08:28:34.235742Z", + "expires_on": "2029-06-19T08:24:00Z" + } + } + `) + } + + mux.HandleFunc("/accounts/"+testAccountID+"/gateway/certificates/80c8a54e-d55c-46c6-86bb-e8a3c90472f4/activate", handler) + + cert, err := client.TeamsActivateCertificate(context.Background(), testAccountID, "80c8a54e-d55c-46c6-86bb-e8a3c90472f4") + + assert.NoError(t, err) + assert.Equal(t, cert.BindingStatus, "pending_deployment") +} + +func TestTeamsDeactivateCertificate(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": { + "enabled": false, + "id": "80c8a54e-d55c-46c6-86bb-e8a3c90472f4", + "binding_status": "pending_deletion", + "qs_pack_id": "00000000-0000-0000-0000-000000000000", + "type": "gateway_managed", + "updated_at": "0001-01-01T00:00:00Z", + "uploaded_on": "0001-01-01T00:00:00Z", + "created_at": "2024-06-19T08:28:34.235742Z", + "expires_on": "2029-06-19T08:24:00Z" + } + } + `) + } + + mux.HandleFunc("/accounts/"+testAccountID+"/gateway/certificates/80c8a54e-d55c-46c6-86bb-e8a3c90472f4/deactivate", handler) + + cert, err := client.TeamsDeactivateCertificate(context.Background(), testAccountID, "80c8a54e-d55c-46c6-86bb-e8a3c90472f4") + + assert.NoError(t, err) + assert.Equal(t, cert.BindingStatus, "pending_deletion") +} + +func TestTeamsDeleteCertificate(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'Delete', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": nil + } + `) + } + + mux.HandleFunc("/accounts/"+testAccountID+"/gateway/certificates/80c8a54e-d55c-46c6-86bb-e8a3c90472f4", handler) + + err := client.TeamsDeleteCertificate(context.Background(), testAccountID, "80c8a54e-d55c-46c6-86bb-e8a3c90472f4") + + assert.NoError(t, err) +}