From 9a487adaa1ad075d5cd287d5f8e1b81493f52044 Mon Sep 17 00:00:00 2001 From: Eduardo Gomes Date: Mon, 6 May 2024 14:59:27 +0200 Subject: [PATCH] Add support for access reusable policies --- .changelog/1956.txt | 7 + access_application.go | 7 + access_application_test.go | 299 ++++++++++++++++++++++++++++++++++++- access_policy.go | 149 ++++++++++++------ access_policy_test.go | 79 ++++++++++ 5 files changed, 493 insertions(+), 48 deletions(-) create mode 100644 .changelog/1956.txt diff --git a/.changelog/1956.txt b/.changelog/1956.txt new file mode 100644 index 00000000000..2f149db7d08 --- /dev/null +++ b/.changelog/1956.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +access_policy: add support for reusable policies +``` + +```release-note:enhancement +access_application: add support for `policies` array +``` \ No newline at end of file diff --git a/access_application.go b/access_application.go index aeca100dd79..27f0c3f9195 100644 --- a/access_application.go +++ b/access_application.go @@ -58,6 +58,7 @@ type AccessApplication struct { CustomPages []string `json:"custom_pages,omitempty"` Tags []string `json:"tags,omitempty"` SCIMConfig *AccessApplicationSCIMConfig `json:"scim_config,omitempty"` + Policies []AccessPolicy `json:"policies,omitempty"` AccessAppLauncherCustomization } @@ -277,6 +278,8 @@ type CreateAccessApplicationParams struct { CustomPages []string `json:"custom_pages,omitempty"` Tags []string `json:"tags,omitempty"` SCIMConfig *AccessApplicationSCIMConfig `json:"scim_config,omitempty"` + // List of policy ids to link to this application in ascending order of precedence. + Policies []string `json:"policies,omitempty"` AccessAppLauncherCustomization } @@ -310,6 +313,10 @@ type UpdateAccessApplicationParams struct { CustomPages []string `json:"custom_pages,omitempty"` Tags []string `json:"tags,omitempty"` SCIMConfig *AccessApplicationSCIMConfig `json:"scim_config,omitempty"` + // List of policy ids to link to this application in ascending order of precedence. + // Can reference reusable policies and policies specific to this application. + // If this field is not provided, the existing policies will not be modified. + Policies *[]string `json:"policies,omitempty"` AccessAppLauncherCustomization } diff --git a/access_application_test.go b/access_application_test.go index 7b8da1d636d..11187ebb97b 100644 --- a/access_application_test.go +++ b/access_application_test.go @@ -3,6 +3,7 @@ package cloudflare import ( "context" "fmt" + "io" "net/http" "testing" "time" @@ -51,7 +52,25 @@ func TestAccessApplications(t *testing.T) { "custom_pages": ["480f4f69-1a28-4fdd-9240-1ed29f0ac1dc"], "tags": ["engineers"], "allow_authenticate_via_warp": true, - "options_preflight_bypass": false + "options_preflight_bypass": false, + "policies": [ + { + "id": "699d98642c564d2e855e9661899b7252", + "precedence": 1, + "reusable": true, + "decision": "allow", + "created_at": "2014-01-01T05:20:00.12345Z", + "updated_at": "2014-01-01T05:20:00.12345Z", + "name": "Allow devs", + "include": [ + { + "email": { + "email": "test@example.com" + } + } + ] + } + ] } ], "result_info": { @@ -93,6 +112,20 @@ func TestAccessApplications(t *testing.T) { CustomNonIdentityDenyURL: "https://blocked.com", AllowAuthenticateViaWarp: BoolPtr(true), OptionsPreflightBypass: BoolPtr(false), + Policies: []AccessPolicy{ + { + ID: "699d98642c564d2e855e9661899b7252", + Precedence: 1, + Reusable: BoolPtr(true), + Decision: "allow", + CreatedAt: &createdAt, + UpdatedAt: &updatedAt, + Name: "Allow devs", + Include: []any{ + map[string]interface{}{"email": map[string]interface{}{"email": "test@example.com"}}, + }, + }, + }, }} mux.HandleFunc("/accounts/"+testAccountID+"/access/apps", handler) @@ -146,7 +179,25 @@ func TestAccessApplication(t *testing.T) { "http_only_cookie_attribute": false, "path_cookie_attribute": false, "allow_authenticate_via_warp": false, - "options_preflight_bypass": false + "options_preflight_bypass": false, + "policies": [ + { + "id": "699d98642c564d2e855e9661899b7252", + "precedence": 1, + "reusable": true, + "decision": "allow", + "created_at": "2014-01-01T05:20:00.12345Z", + "updated_at": "2014-01-01T05:20:00.12345Z", + "name": "Allow devs", + "include": [ + { + "email": { + "email": "test@example.com" + } + } + ] + } + ] } } `) @@ -179,6 +230,20 @@ func TestAccessApplication(t *testing.T) { CustomNonIdentityDenyURL: "https://blocked.com", AllowAuthenticateViaWarp: BoolPtr(false), OptionsPreflightBypass: BoolPtr(false), + Policies: []AccessPolicy{ + { + ID: "699d98642c564d2e855e9661899b7252", + Precedence: 1, + Reusable: BoolPtr(true), + Decision: "allow", + CreatedAt: &createdAt, + UpdatedAt: &updatedAt, + Name: "Allow devs", + Include: []any{ + map[string]interface{}{"email": map[string]interface{}{"email": "test@example.com"}}, + }, + }, + }, } mux.HandleFunc("/accounts/"+testAccountID+"/access/apps/480f4f69-1a28-4fdd-9240-1ed29f0ac1db", handler) @@ -231,7 +296,25 @@ func TestCreateAccessApplications(t *testing.T) { "service_auth_401_redirect": true, "tags": ["engineers"], "allow_authenticate_via_warp": false, - "options_preflight_bypass": true + "options_preflight_bypass": true, + "policies": [ + { + "id": "699d98642c564d2e855e9661899b7252", + "precedence": 1, + "reusable": true, + "decision": "allow", + "created_at": "2014-01-01T05:20:00.12345Z", + "updated_at": "2014-01-01T05:20:00.12345Z", + "name": "Allow devs", + "include": [ + { + "email": { + "email": "test@example.com" + } + } + ] + } + ] } } `) @@ -262,6 +345,20 @@ func TestCreateAccessApplications(t *testing.T) { Tags: []string{"engineers"}, AllowAuthenticateViaWarp: BoolPtr(false), OptionsPreflightBypass: BoolPtr(true), + Policies: []AccessPolicy{ + { + ID: "699d98642c564d2e855e9661899b7252", + Precedence: 1, + Reusable: BoolPtr(true), + Decision: "allow", + CreatedAt: &createdAt, + UpdatedAt: &updatedAt, + Name: "Allow devs", + Include: []any{ + map[string]interface{}{"email": map[string]interface{}{"email": "test@example.com"}}, + }, + }, + }, } mux.HandleFunc("/accounts/"+testAccountID+"/access/apps", handler) @@ -270,6 +367,7 @@ func TestCreateAccessApplications(t *testing.T) { Name: "Admin Site", Domain: "test.example.com/admin", SessionDuration: "24h", + Policies: []string{"699d98642c564d2e855e9661899b7252"}, }) if assert.NoError(t, err) { @@ -282,6 +380,7 @@ func TestCreateAccessApplications(t *testing.T) { Name: "Admin Site", Domain: "test.example.com/admin", SessionDuration: "24h", + Policies: []string{"699d98642c564d2e855e9661899b7252"}, }) if assert.NoError(t, err) { @@ -322,7 +421,25 @@ func TestUpdateAccessApplication(t *testing.T) { "service_auth_401_redirect": true, "tags": ["engineers"], "allow_authenticate_via_warp": true, - "options_preflight_bypass": true + "options_preflight_bypass": true, + "policies": [ + { + "id": "699d98642c564d2e855e9661899b7252", + "precedence": 1, + "reusable": true, + "decision": "allow", + "created_at": "2014-01-01T05:20:00.12345Z", + "updated_at": "2014-01-01T05:20:00.12345Z", + "name": "Allow devs", + "include": [ + { + "email": { + "email": "test@example.com" + } + } + ] + } + ] } } `) @@ -351,6 +468,180 @@ func TestUpdateAccessApplication(t *testing.T) { OptionsPreflightBypass: BoolPtr(true), CreatedAt: &createdAt, UpdatedAt: &updatedAt, + Policies: []AccessPolicy{ + { + ID: "699d98642c564d2e855e9661899b7252", + Precedence: 1, + Reusable: BoolPtr(true), + Decision: "allow", + CreatedAt: &createdAt, + UpdatedAt: &updatedAt, + Name: "Allow devs", + Include: []any{ + map[string]interface{}{"email": map[string]interface{}{"email": "test@example.com"}}, + }, + }, + }, + } + + params := UpdateAccessApplicationParams{ + ID: "480f4f69-1a28-4fdd-9240-1ed29f0ac1db", + Name: "Admin Site", + Domain: "test.example.com/admin", + SelfHostedDomains: []string{"test.example.com/admin", "test.example.com/admin2"}, + Type: "self_hosted", + SessionDuration: "24h", + AUD: "737646a56ab1df6ec9bddc7e5ca84eaf3b0768850f3ffb5d74f1534911fe3893", + AllowedIdps: []string{"f174e90a-fafe-4643-bbbc-4a0ed4fc8415"}, + AutoRedirectToIdentity: BoolPtr(false), + EnableBindingCookie: BoolPtr(false), + AppLauncherVisible: BoolPtr(true), + ServiceAuth401Redirect: BoolPtr(true), + CustomDenyMessage: "denied!", + CustomDenyURL: "https://www.example.com", + LogoURL: "https://www.example.com/example.png", + SkipInterstitial: BoolPtr(true), + CustomNonIdentityDenyURL: "https://blocked.com", + Tags: []string{"engineers"}, + AllowAuthenticateViaWarp: BoolPtr(true), + OptionsPreflightBypass: BoolPtr(true), + Policies: &[]string{"699d98642c564d2e855e9661899b7252"}, + } + + mux.HandleFunc("/accounts/"+testAccountID+"/access/apps/480f4f69-1a28-4fdd-9240-1ed29f0ac1db", handler) + + actual, err := client.UpdateAccessApplication(context.Background(), AccountIdentifier(testAccountID), params) + + if assert.NoError(t, err) { + assert.Equal(t, fullAccessApplication, actual) + } + + mux.HandleFunc("/zones/"+testZoneID+"/access/apps/480f4f69-1a28-4fdd-9240-1ed29f0ac1db", handler) + + actual, err = client.UpdateAccessApplication(context.Background(), ZoneIdentifier(testZoneID), UpdateAccessApplicationParams{ + ID: "480f4f69-1a28-4fdd-9240-1ed29f0ac1db", + Name: "Admin Site", + Domain: "test.example.com/admin", + SelfHostedDomains: []string{"test.example.com/admin", "test.example.com/admin2"}, + Type: "self_hosted", + SessionDuration: "24h", + AUD: "737646a56ab1df6ec9bddc7e5ca84eaf3b0768850f3ffb5d74f1534911fe3893", + AllowedIdps: []string{"f174e90a-fafe-4643-bbbc-4a0ed4fc8415"}, + AutoRedirectToIdentity: BoolPtr(false), + EnableBindingCookie: BoolPtr(false), + AppLauncherVisible: BoolPtr(true), + ServiceAuth401Redirect: BoolPtr(true), + CustomDenyMessage: "denied!", + CustomDenyURL: "https://www.example.com", + LogoURL: "https://www.example.com/example.png", + SkipInterstitial: BoolPtr(true), + CustomNonIdentityDenyURL: "https://blocked.com", + OptionsPreflightBypass: BoolPtr(true), + Policies: &[]string{"699d98642c564d2e855e9661899b7252"}, + }) + + if assert.NoError(t, err) { + assert.Equal(t, fullAccessApplication, actual) + } +} + +func TestUpdateAccessApplicationOmitPolicies(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method) + reqBody, err := io.ReadAll(r.Body) + assert.NoError(t, err) + assert.NotContains(t, string(reqBody), "policies") + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": { + "id": "480f4f69-1a28-4fdd-9240-1ed29f0ac1db", + "created_at": "2014-01-01T05:20:00.12345Z", + "updated_at": "2014-01-01T05:20:00.12345Z", + "aud": "737646a56ab1df6ec9bddc7e5ca84eaf3b0768850f3ffb5d74f1534911fe3893", + "name": "Admin Site", + "domain": "test.example.com/admin", + "self_hosted_domains": ["test.example.com/admin", "test.example.com/admin2"], + "type": "self_hosted", + "session_duration": "24h", + "allowed_idps": ["f174e90a-fafe-4643-bbbc-4a0ed4fc8415"], + "auto_redirect_to_identity": false, + "enable_binding_cookie": false, + "custom_deny_url": "https://www.example.com", + "custom_deny_message": "denied!", + "custom_non_identity_deny_url": "https://blocked.com", + "logo_url": "https://www.example.com/example.png", + "skip_interstitial": true, + "app_launcher_visible": true, + "service_auth_401_redirect": true, + "tags": ["engineers"], + "allow_authenticate_via_warp": true, + "options_preflight_bypass": true, + "policies": [ + { + "id": "699d98642c564d2e855e9661899b7252", + "precedence": 1, + "reusable": true, + "decision": "allow", + "created_at": "2014-01-01T05:20:00.12345Z", + "updated_at": "2014-01-01T05:20:00.12345Z", + "name": "Allow devs", + "include": [ + { + "email": { + "email": "test@example.com" + } + } + ] + } + ] + } + } + `) + } + + fullAccessApplication := AccessApplication{ + ID: "480f4f69-1a28-4fdd-9240-1ed29f0ac1db", + Name: "Admin Site", + Domain: "test.example.com/admin", + SelfHostedDomains: []string{"test.example.com/admin", "test.example.com/admin2"}, + Type: "self_hosted", + SessionDuration: "24h", + AUD: "737646a56ab1df6ec9bddc7e5ca84eaf3b0768850f3ffb5d74f1534911fe3893", + AllowedIdps: []string{"f174e90a-fafe-4643-bbbc-4a0ed4fc8415"}, + AutoRedirectToIdentity: BoolPtr(false), + EnableBindingCookie: BoolPtr(false), + AppLauncherVisible: BoolPtr(true), + ServiceAuth401Redirect: BoolPtr(true), + CustomDenyMessage: "denied!", + CustomDenyURL: "https://www.example.com", + LogoURL: "https://www.example.com/example.png", + CustomNonIdentityDenyURL: "https://blocked.com", + Tags: []string{"engineers"}, + SkipInterstitial: BoolPtr(true), + AllowAuthenticateViaWarp: BoolPtr(true), + OptionsPreflightBypass: BoolPtr(true), + CreatedAt: &createdAt, + UpdatedAt: &updatedAt, + Policies: []AccessPolicy{ + { + ID: "699d98642c564d2e855e9661899b7252", + Precedence: 1, + Reusable: BoolPtr(true), + Decision: "allow", + CreatedAt: &createdAt, + UpdatedAt: &updatedAt, + Name: "Allow devs", + Include: []any{ + map[string]interface{}{"email": map[string]interface{}{"email": "test@example.com"}}, + }, + }, + }, } params := UpdateAccessApplicationParams{ diff --git a/access_policy.go b/access_policy.go index 0e1ae41c6d2..a70ceede50c 100644 --- a/access_policy.go +++ b/access_policy.go @@ -2,7 +2,6 @@ package cloudflare import ( "context" - "errors" "fmt" "net/http" "time" @@ -10,10 +9,6 @@ import ( "github.com/goccy/go-json" ) -var ( - ErrMissingApplicationID = errors.New("missing required application ID") -) - type AccessApprovalGroup struct { EmailListUuid string `json:"email_list_uuid,omitempty"` EmailAddresses []string `json:"email_addresses,omitempty"` @@ -23,11 +18,16 @@ type AccessApprovalGroup struct { // AccessPolicy defines a policy for allowing or disallowing access to // one or more Access applications. type AccessPolicy struct { - ID string `json:"id,omitempty"` + ID string `json:"id,omitempty"` + // Precedence is the order in which the policy is executed in an Access application. + // As a general rule, lower numbers take precedence over higher numbers. + // This field can only be zero when a reusable policy is requested outside the context + // of an Access application. Precedence int `json:"precedence"` Decision string `json:"decision"` CreatedAt *time.Time `json:"created_at"` UpdatedAt *time.Time `json:"updated_at"` + Reusable *bool `json:"reusable,omitempty"` Name string `json:"name"` IsolationRequired *bool `json:"isolation_required,omitempty"` @@ -68,18 +68,28 @@ type AccessPolicyDetailResponse struct { } type ListAccessPoliciesParams struct { + // ApplicationID is the application ID to list attached access policies for. + // If omitted, only reusable policies for the account are returned. ApplicationID string `json:"-"` ResultInfo } type GetAccessPolicyParams struct { + PolicyID string `json:"-"` + // ApplicationID is the application ID for which to scope the policy for. + // Optional, but if included, the policy returned will include its execution precedence within the application. ApplicationID string `json:"-"` - PolicyID string `json:"-"` } type CreateAccessPolicyParams struct { + // ApplicationID is the application ID for which to create the policy for. + // Pass an empty value to create a reusable policy. ApplicationID string `json:"-"` + // Precedence is the order in which the policy is executed in an Access application. + // As a general rule, lower numbers take precedence over higher numbers. + // This field is ignored when creating a reusable policy. + // Read more here https://developers.cloudflare.com/cloudflare-one/policies/access/#order-of-execution Precedence int `json:"precedence"` Decision string `json:"decision"` Name string `json:"name"` @@ -105,9 +115,14 @@ type CreateAccessPolicyParams struct { } type UpdateAccessPolicyParams struct { + // ApplicationID is the application ID that owns the existing policy. + // Pass an empty value if the existing policy is reusable. ApplicationID string `json:"-"` PolicyID string `json:"-"` + // Precedence is the order in which the policy is executed in an Access application. + // As a general rule, lower numbers take precedence over higher numbers. + // This field is ignored when updating a reusable policy. Precedence int `json:"precedence"` Decision string `json:"decision"` Name string `json:"name"` @@ -133,26 +148,33 @@ type UpdateAccessPolicyParams struct { } type DeleteAccessPolicyParams struct { + // ApplicationID is the application ID the policy belongs to. + // If the existing policy is reusable, this field must be omitted. Otherwise, it is required. ApplicationID string `json:"-"` PolicyID string `json:"-"` } -// ListAccessPolicies returns all access policies for an access application. +// ListAccessPolicies returns all access policies that match the parameters. // // Account API reference: https://developers.cloudflare.com/api/operations/access-policies-list-access-policies // Zone API reference: https://developers.cloudflare.com/api/operations/zone-level-access-policies-list-access-policies func (api *API) ListAccessPolicies(ctx context.Context, rc *ResourceContainer, params ListAccessPoliciesParams) ([]AccessPolicy, *ResultInfo, error) { - if params.ApplicationID == "" { - return []AccessPolicy{}, &ResultInfo{}, ErrMissingApplicationID + var baseURL string + if params.ApplicationID != "" { + baseURL = fmt.Sprintf( + "/%s/%s/access/apps/%s/policies", + rc.Level, + rc.Identifier, + params.ApplicationID, + ) + } else { + baseURL = fmt.Sprintf( + "/%s/%s/access/policies", + rc.Level, + rc.Identifier, + ) } - baseURL := fmt.Sprintf( - "/%s/%s/access/apps/%s/policies", - rc.Level, - rc.Identifier, - params.ApplicationID, - ) - autoPaginate := true if params.PerPage >= 1 || params.Page >= 1 { autoPaginate = false @@ -194,13 +216,23 @@ func (api *API) ListAccessPolicies(ctx context.Context, rc *ResourceContainer, p // Account API reference: https://developers.cloudflare.com/api/operations/access-policies-get-an-access-policy // Zone API reference: https://developers.cloudflare.com/api/operations/zone-level-access-policies-get-an-access-policy func (api *API) GetAccessPolicy(ctx context.Context, rc *ResourceContainer, params GetAccessPolicyParams) (AccessPolicy, error) { - uri := fmt.Sprintf( - "/%s/%s/access/apps/%s/policies/%s", - rc.Level, - rc.Identifier, - params.ApplicationID, - params.PolicyID, - ) + var uri string + if params.ApplicationID != "" { + uri = fmt.Sprintf( + "/%s/%s/access/apps/%s/policies/%s", + rc.Level, + rc.Identifier, + params.ApplicationID, + params.PolicyID, + ) + } else { + uri = fmt.Sprintf( + "/%s/%s/access/policies/%s", + rc.Level, + rc.Identifier, + params.PolicyID, + ) + } res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) if err != nil { @@ -221,12 +253,21 @@ func (api *API) GetAccessPolicy(ctx context.Context, rc *ResourceContainer, para // Account API reference: https://developers.cloudflare.com/api/operations/access-policies-create-an-access-policy // Zone API reference: https://developers.cloudflare.com/api/operations/zone-level-access-policies-create-an-access-policy func (api *API) CreateAccessPolicy(ctx context.Context, rc *ResourceContainer, params CreateAccessPolicyParams) (AccessPolicy, error) { - uri := fmt.Sprintf( - "/%s/%s/access/apps/%s/policies", - rc.Level, - rc.Identifier, - params.ApplicationID, - ) + var uri string + if params.ApplicationID != "" { + uri = fmt.Sprintf( + "/%s/%s/access/apps/%s/policies", + rc.Level, + rc.Identifier, + params.ApplicationID, + ) + } else { + uri = fmt.Sprintf( + "/%s/%s/access/policies", + rc.Level, + rc.Identifier, + ) + } res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params) if err != nil { @@ -251,13 +292,23 @@ func (api *API) UpdateAccessPolicy(ctx context.Context, rc *ResourceContainer, p return AccessPolicy{}, fmt.Errorf("access policy ID cannot be empty") } - uri := fmt.Sprintf( - "/%s/%s/access/apps/%s/policies/%s", - rc.Level, - rc.Identifier, - params.ApplicationID, - params.PolicyID, - ) + var uri string + if params.ApplicationID != "" { + uri = fmt.Sprintf( + "/%s/%s/access/apps/%s/policies/%s", + rc.Level, + rc.Identifier, + params.ApplicationID, + params.PolicyID, + ) + } else { + uri = fmt.Sprintf( + "/%s/%s/access/policies/%s", + rc.Level, + rc.Identifier, + params.PolicyID, + ) + } res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params) if err != nil { @@ -278,13 +329,23 @@ func (api *API) UpdateAccessPolicy(ctx context.Context, rc *ResourceContainer, p // Account API reference: https://developers.cloudflare.com/api/operations/access-policies-delete-an-access-policy // Zone API reference: https://developers.cloudflare.com/api/operations/zone-level-access-policies-delete-an-access-policy func (api *API) DeleteAccessPolicy(ctx context.Context, rc *ResourceContainer, params DeleteAccessPolicyParams) error { - uri := fmt.Sprintf( - "/%s/%s/access/apps/%s/policies/%s", - rc.Level, - rc.Identifier, - params.ApplicationID, - params.PolicyID, - ) + var uri string + if params.ApplicationID != "" { + uri = fmt.Sprintf( + "/%s/%s/access/apps/%s/policies/%s", + rc.Level, + rc.Identifier, + params.ApplicationID, + params.PolicyID, + ) + } else { + uri = fmt.Sprintf( + "/%s/%s/access/policies/%s", + rc.Level, + rc.Identifier, + params.PolicyID, + ) + } _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) if err != nil { diff --git a/access_policy_test.go b/access_policy_test.go index 6b9506023b0..75642138205 100644 --- a/access_policy_test.go +++ b/access_policy_test.go @@ -142,6 +142,23 @@ func TestAccessPolicies(t *testing.T) { if assert.NoError(t, err) { assert.Equal(t, []AccessPolicy{expectedAccessPolicy}, actual) } + + // Test Listing reusable policies + mux.HandleFunc("/accounts/"+testAccountID+"/access/policies", handler) + + actual, _, err = client.ListAccessPolicies(context.Background(), testAccountRC, ListAccessPoliciesParams{}) + + if assert.NoError(t, err) { + assert.Equal(t, []AccessPolicy{expectedAccessPolicy}, actual) + } + + mux.HandleFunc("/zones/"+testZoneID+"/access/policies", handler) + + actual, _, err = client.ListAccessPolicies(context.Background(), testZoneRC, ListAccessPoliciesParams{}) + + if assert.NoError(t, err) { + assert.Equal(t, []AccessPolicy{expectedAccessPolicy}, actual) + } } func TestAccessPolicy(t *testing.T) { @@ -218,6 +235,23 @@ func TestAccessPolicy(t *testing.T) { if assert.NoError(t, err) { assert.Equal(t, expectedAccessPolicy, actual) } + + // Test getting a reusable policy + mux.HandleFunc("/accounts/"+testAccountID+"/access/policies/"+accessPolicyID, handler) + + actual, err = client.GetAccessPolicy(context.Background(), testAccountRC, GetAccessPolicyParams{PolicyID: accessPolicyID}) + + if assert.NoError(t, err) { + assert.Equal(t, expectedAccessPolicy, actual) + } + + mux.HandleFunc("/zones/"+testZoneID+"/access/policies/"+accessPolicyID, handler) + + actual, err = client.GetAccessPolicy(context.Background(), testZoneRC, GetAccessPolicyParams{PolicyID: accessPolicyID}) + + if assert.NoError(t, err) { + assert.Equal(t, expectedAccessPolicy, actual) + } } func TestCreateAccessPolicy(t *testing.T) { @@ -328,6 +362,24 @@ func TestCreateAccessPolicy(t *testing.T) { if assert.NoError(t, err) { assert.Equal(t, expectedAccessPolicy, actual) } + + // Test creating a reusable policy + accessPolicy.ApplicationID = "" + mux.HandleFunc("/accounts/"+testAccountID+"/access/policies", handler) + + actual, err = client.CreateAccessPolicy(context.Background(), testAccountRC, accessPolicy) + + if assert.NoError(t, err) { + assert.Equal(t, expectedAccessPolicy, actual) + } + + mux.HandleFunc("/zones/"+testZoneID+"/access/policies", handler) + + actual, err = client.CreateAccessPolicy(context.Background(), testZoneRC, accessPolicy) + + if assert.NoError(t, err) { + assert.Equal(t, expectedAccessPolicy, actual) + } } func TestCreateAccessPolicyAuthContextRule(t *testing.T) { @@ -571,6 +623,22 @@ func TestUpdateAccessPolicy(t *testing.T) { if assert.NoError(t, err) { assert.Equal(t, expectedAccessPolicy, actual) } + + // Test updating reusable policies + accessPolicy.ApplicationID = "" + mux.HandleFunc("/accounts/"+testAccountID+"/access/policies/"+accessPolicyID, handler) + actual, err = client.UpdateAccessPolicy(context.Background(), testAccountRC, accessPolicy) + + if assert.NoError(t, err) { + assert.Equal(t, expectedAccessPolicy, actual) + } + + mux.HandleFunc("/zones/"+testZoneID+"/access/policies/"+accessPolicyID, handler) + actual, err = client.UpdateAccessPolicy(context.Background(), testZoneRC, accessPolicy) + + if assert.NoError(t, err) { + assert.Equal(t, expectedAccessPolicy, actual) + } } func TestUpdateAccessPolicyWithMissingID(t *testing.T) { @@ -611,4 +679,15 @@ func TestDeleteAccessPolicy(t *testing.T) { err = client.DeleteAccessPolicy(context.Background(), testZoneRC, DeleteAccessPolicyParams{ApplicationID: accessApplicationID, PolicyID: accessPolicyID}) assert.NoError(t, err) + + // Test deleting a reusable policy + mux.HandleFunc("/accounts/"+testAccountID+"/access/policies/"+accessPolicyID, handler) + err = client.DeleteAccessPolicy(context.Background(), testAccountRC, DeleteAccessPolicyParams{PolicyID: accessPolicyID}) + + assert.NoError(t, err) + + mux.HandleFunc("/zones/"+testZoneID+"/access/policies/"+accessPolicyID, handler) + err = client.DeleteAccessPolicy(context.Background(), testZoneRC, DeleteAccessPolicyParams{PolicyID: accessPolicyID}) + + assert.NoError(t, err) }