diff --git a/.changelog/1832.txt b/.changelog/1832.txt new file mode 100644 index 0000000000..4f09ea2c1b --- /dev/null +++ b/.changelog/1832.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +ruleset: add support for action parameters `fonts` and `disable_rum` +``` diff --git a/.changelog/1921.txt b/.changelog/1921.txt new file mode 100644 index 0000000000..a36bc61f6d --- /dev/null +++ b/.changelog/1921.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +access_application: add support for `scim_config` +``` \ No newline at end of file diff --git a/.changelog/1956.txt b/.changelog/1956.txt new file mode 100644 index 0000000000..2f149db7d0 --- /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/.changelog/1959.txt b/.changelog/1959.txt new file mode 100644 index 0000000000..a5ab17228e --- /dev/null +++ b/.changelog/1959.txt @@ -0,0 +1,3 @@ +```release-note:bug +access_application: fix scim configuration authentication json marshalling +``` \ No newline at end of file diff --git a/.changelog/1974.txt b/.changelog/1974.txt new file mode 100644 index 0000000000..15e7a6af0f --- /dev/null +++ b/.changelog/1974.txt @@ -0,0 +1,3 @@ +```release-note:dependency +deps: bumps golang.org/x/net from 0.24.0 to 0.25.0 +``` diff --git a/.changelog/1975.txt b/.changelog/1975.txt new file mode 100644 index 0000000000..a44e191529 --- /dev/null +++ b/.changelog/1975.txt @@ -0,0 +1,3 @@ +```release-note:dependency +deps: bumps golangci/golangci-lint-action from 5 to 6 +``` diff --git a/.changelog/1981.txt b/.changelog/1981.txt new file mode 100644 index 0000000000..ba98d72039 --- /dev/null +++ b/.changelog/1981.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +Add Refresh Token, Custom Claims, and PKCE Without Client Secret support to Access OIDC SaaS Applications +``` diff --git a/.changelog/1991.txt b/.changelog/1991.txt new file mode 100644 index 0000000000..d011b75902 --- /dev/null +++ b/.changelog/1991.txt @@ -0,0 +1,3 @@ +```release-note:dependency +deps: bumps bflad/action-milestone-comment from 1 to 2 +``` diff --git a/.changelog/1992.txt b/.changelog/1992.txt new file mode 100644 index 0000000000..d72844282c --- /dev/null +++ b/.changelog/1992.txt @@ -0,0 +1,3 @@ +```release-note:dependency +deps: bumps goreleaser/goreleaser-action from 5.0.0 to 5.1.0 +``` diff --git a/.changelog/1993.txt b/.changelog/1993.txt new file mode 100644 index 0000000000..86356e9429 --- /dev/null +++ b/.changelog/1993.txt @@ -0,0 +1,3 @@ +```release-note:dependency +deps: bumps github.com/hashicorp/go-retryablehttp from 0.7.5 to 0.7.6 +``` diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 59631923ba..ea44260830 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: path: ~/go/pkg/mod key: ${{ runner.os }}-go${{ matrix.go-version }}-${{ hashFiles('**/go.mod') }}-${{ hashFiles('**/go.sum') }} - name: golangci-lint - uses: golangci/golangci-lint-action@v5 + uses: golangci/golangci-lint-action@v6 with: version: latest args: "--config .golintci.yaml" diff --git a/.github/workflows/milestone-closed.yml b/.github/workflows/milestone-closed.yml index 3cd4cd68d6..bea4e2f766 100644 --- a/.github/workflows/milestone-closed.yml +++ b/.github/workflows/milestone-closed.yml @@ -15,7 +15,7 @@ jobs: ids: ${{ steps.milestone-comment.outputs.ids }} steps: - id: milestone-comment - uses: bflad/action-milestone-comment@v1 + uses: bflad/action-milestone-comment@v2 with: body: | This functionality has been released in [${{ github.event.milestone.title }}](https://github.com/${{ github.repository }}/releases/tag/${{ github.event.milestone.title }}). diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 096ac52428..c868169cbd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: with: go-version-file: 'go.mod' - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5.0.0 + uses: goreleaser/goreleaser-action@v5.1.0 with: version: latest args: release --rm-dist diff --git a/CHANGELOG.md b/CHANGELOG.md index 33dfc8b0d2..4ceaa6d970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,25 @@ -## 0.95.0 (Unreleased) +## 0.96.0 (Unreleased) + +## 0.95.0 (May 8th, 2024) + +ENHANCEMENTS: + +* access_application: add support for `policies` array ([#1956](https://github.com/cloudflare/cloudflare-go/issues/1956)) +* access_application: add support for `scim_config` ([#1921](https://github.com/cloudflare/cloudflare-go/issues/1921)) +* access_policy: add support for reusable policies ([#1956](https://github.com/cloudflare/cloudflare-go/issues/1956)) +* dlp: add support for zt risk behavior configuration ([#1887](https://github.com/cloudflare/cloudflare-go/issues/1887)) + +BUG FIXES: + +* access_application: fix scim configuration authentication json marshalling ([#1959](https://github.com/cloudflare/cloudflare-go/issues/1959)) + +DEPENDENCIES: + +* deps: bumps dependabot/fetch-metadata from 2.0.0 to 2.1.0 ([#1839](https://github.com/cloudflare/cloudflare-go/issues/1839)) +* deps: bumps github.com/urfave/cli/v2 from 2.27.1 to 2.27.2 ([#1861](https://github.com/cloudflare/cloudflare-go/issues/1861)) +* deps: bumps golang.org/x/net from 0.24.0 to 0.25.0 ([#1974](https://github.com/cloudflare/cloudflare-go/issues/1974)) +* deps: bumps golangci/golangci-lint-action from 4 to 5 ([#1845](https://github.com/cloudflare/cloudflare-go/issues/1845)) +* deps: bumps golangci/golangci-lint-action from 5 to 6 ([#1975](https://github.com/cloudflare/cloudflare-go/issues/1975)) ## 0.94.0 (April 24th, 2024) diff --git a/access_application.go b/access_application.go index fa426643ed..75ef4f31cb 100644 --- a/access_application.go +++ b/access_application.go @@ -2,6 +2,7 @@ package cloudflare import ( "context" + "errors" "fmt" "net/http" "time" @@ -56,6 +57,8 @@ type AccessApplication struct { OptionsPreflightBypass *bool `json:"options_preflight_bypass,omitempty"` CustomPages []string `json:"custom_pages,omitempty"` Tags []string `json:"tags,omitempty"` + SCIMConfig *AccessApplicationSCIMConfig `json:"scim_config,omitempty"` + Policies []AccessPolicy `json:"policies,omitempty"` AccessAppLauncherCustomization } @@ -76,6 +79,96 @@ type AccessApplicationCorsHeaders struct { MaxAge int `json:"max_age,omitempty"` } +// AccessApplicationSCIMConfig represents the configuration for provisioning to an Access Application via SCIM. +type AccessApplicationSCIMConfig struct { + Enabled *bool `json:"enabled,omitempty"` + RemoteURI string `json:"remote_uri,omitempty"` + Authentication *AccessApplicationScimAuthenticationJson `json:"authentication,omitempty"` + IdPUID string `json:"idp_uid,omitempty"` + DeactivateOnDelete *bool `json:"deactivate_on_delete,omitempty"` + Mappings []*AccessApplicationScimMapping `json:"mappings,omitempty"` +} + +type AccessApplicationScimAuthenticationScheme string + +const ( + AccessApplicationScimAuthenticationSchemeHttpBasic AccessApplicationScimAuthenticationScheme = "httpbasic" + AccessApplicationScimAuthenticationSchemeOauthBearerToken AccessApplicationScimAuthenticationScheme = "oauthbearertoken" + AccessApplicationScimAuthenticationSchemeOauth2 AccessApplicationScimAuthenticationScheme = "oauth2" +) + +type AccessApplicationScimAuthenticationJson struct { + Value AccessApplicationScimAuthentication +} + +func (a *AccessApplicationScimAuthenticationJson) UnmarshalJSON(buf []byte) error { + var scheme baseScimAuthentication + if err := json.Unmarshal(buf, &scheme); err != nil { + return err + } + + switch scheme.Scheme { + case AccessApplicationScimAuthenticationSchemeHttpBasic: + a.Value = new(AccessApplicationScimAuthenticationHttpBasic) + case AccessApplicationScimAuthenticationSchemeOauthBearerToken: + a.Value = new(AccessApplicationScimAuthenticationOauthBearerToken) + case AccessApplicationScimAuthenticationSchemeOauth2: + a.Value = new(AccessApplicationScimAuthenticationOauth2) + default: + return errors.New("invalid authentication scheme") + } + + return json.Unmarshal(buf, a.Value) +} + +func (a *AccessApplicationScimAuthenticationJson) MarshalJSON() ([]byte, error) { + return json.Marshal(a.Value) +} + +type AccessApplicationScimAuthentication interface { + isScimAuthentication() +} + +type baseScimAuthentication struct { + Scheme AccessApplicationScimAuthenticationScheme `json:"scheme"` +} + +func (baseScimAuthentication) isScimAuthentication() {} + +type AccessApplicationScimAuthenticationHttpBasic struct { + baseScimAuthentication + User string `json:"user"` + Password string `json:"password"` +} + +type AccessApplicationScimAuthenticationOauthBearerToken struct { + baseScimAuthentication + Token string `json:"token"` +} + +type AccessApplicationScimAuthenticationOauth2 struct { + baseScimAuthentication + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + AuthorizationURL string `json:"authorization_url"` + TokenURL string `json:"token_url"` + Scopes []string `json:"scopes,omitempty"` +} + +type AccessApplicationScimMapping struct { + Schema string `json:"schema"` + Enabled *bool `json:"enabled,omitempty"` + Filter string `json:"filter,omitempty"` + TransformJsonata string `json:"transform_jsonata,omitempty"` + Operations *AccessApplicationScimMappingOperations `json:"operations,omitempty"` +} + +type AccessApplicationScimMappingOperations struct { + Create *bool `json:"create,omitempty"` + Update *bool `json:"update,omitempty"` + Delete *bool `json:"delete,omitempty"` +} + // AccessApplicationListResponse represents the response from the list // access applications endpoint. type AccessApplicationListResponse struct { @@ -106,6 +199,17 @@ type SAMLAttributeConfig struct { Source SourceConfig `json:"source"` } +type OIDCClaimConfig struct { + Name string `json:"name,omitempty"` + Source SourceConfig `json:"source"` + Required *bool `json:"required,omitempty"` + Scope string `json:"scope,omitempty"` +} + +type RefreshTokenOptions struct { + Lifetime string `json:"lifetime,omitempty"` +} + type SaasApplication struct { // Items common to both SAML and OIDC AppID string `json:"app_id,omitempty"` @@ -126,13 +230,16 @@ type SaasApplication struct { SamlAttributeTransformJsonata string `json:"saml_attribute_transform_jsonata"` // OIDC saas app - ClientID string `json:"client_id,omitempty"` - ClientSecret string `json:"client_secret,omitempty"` - RedirectURIs []string `json:"redirect_uris,omitempty"` - GrantTypes []string `json:"grant_types,omitempty"` - Scopes []string `json:"scopes,omitempty"` - AppLauncherURL string `json:"app_launcher_url,omitempty"` - GroupFilterRegex string `json:"group_filter_regex,omitempty"` + ClientID string `json:"client_id,omitempty"` + ClientSecret string `json:"client_secret,omitempty"` + RedirectURIs []string `json:"redirect_uris,omitempty"` + GrantTypes []string `json:"grant_types,omitempty"` + Scopes []string `json:"scopes,omitempty"` + AppLauncherURL string `json:"app_launcher_url,omitempty"` + GroupFilterRegex string `json:"group_filter_regex,omitempty"` + CustomClaims []OIDCClaimConfig `json:"custom_claims,omitempty"` + AllowPKCEWithoutClientSecret *bool `json:"allow_pkce_without_client_secret,omitempty"` + RefreshTokenOptions *RefreshTokenOptions `json:"refresh_token_options,omitempty"` } type AccessAppLauncherCustomization struct { @@ -155,6 +262,7 @@ type AccessLandingPageDesign struct { ButtonColor string `json:"button_color"` ButtonTextColor string `json:"button_text_color"` } + type ListAccessApplicationsParams struct { ResultInfo } @@ -187,6 +295,9 @@ type CreateAccessApplicationParams struct { AllowAuthenticateViaWarp *bool `json:"allow_authenticate_via_warp,omitempty"` 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 } @@ -219,6 +330,11 @@ type UpdateAccessApplicationParams struct { OptionsPreflightBypass *bool `json:"options_preflight_bypass,omitempty"` 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 c7f443381e..086479c95e 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,185 @@ 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" + } + } + ] + } + ] + } + } + `) + } + + 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{ + 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" + } + } + ] + } + ] } } `) @@ -351,6 +628,20 @@ 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{ @@ -824,7 +1115,18 @@ func TestCreateOIDCSaasAccessApplications(t *testing.T) { "grant_types": ["authorization_code"], "scopes": ["openid", "email", "profile", "groups"], "app_launcher_url": "https://saas.example.com", - "group_filter_regex": ".*" + "group_filter_regex": ".*", + "allow_pkce_without_client_secret": false, + "custom_claims": [ + { + "name": "test1", + "source": { + "name": "test1" + }, + "required": true, + "scope": "profile" + } + ] } } } @@ -850,14 +1152,23 @@ func TestCreateOIDCSaasAccessApplications(t *testing.T) { LogoURL: "https://www.example.com/example.png", SkipInterstitial: BoolPtr(true), SaasApplication: &SaasApplication{ - AuthType: "oidc", - ClientID: "737646a56ab1df6ec9bddc7e5ca84eaf3b0768850f3ffb5d74f1534911fe3893", - ClientSecret: "secret", - RedirectURIs: []string{"https://saas.example.com"}, - GrantTypes: []string{"authorization_code"}, - Scopes: []string{"openid", "email", "profile", "groups"}, - AppLauncherURL: "https://saas.example.com", - GroupFilterRegex: ".*", + AuthType: "oidc", + ClientID: "737646a56ab1df6ec9bddc7e5ca84eaf3b0768850f3ffb5d74f1534911fe3893", + ClientSecret: "secret", + RedirectURIs: []string{"https://saas.example.com"}, + GrantTypes: []string{"authorization_code"}, + Scopes: []string{"openid", "email", "profile", "groups"}, + AppLauncherURL: "https://saas.example.com", + GroupFilterRegex: ".*", + AllowPKCEWithoutClientSecret: BoolPtr(false), + CustomClaims: []OIDCClaimConfig{ + { + Name: "test1", + Source: SourceConfig{Name: "test1"}, + Required: BoolPtr(true), + Scope: "profile", + }, + }, }, CreatedAt: &createdAt, UpdatedAt: &updatedAt, @@ -870,10 +1181,19 @@ func TestCreateOIDCSaasAccessApplications(t *testing.T) { actual, err := client.CreateAccessApplication(context.Background(), AccountIdentifier(testAccountID), CreateAccessApplicationParams{ Name: "Admin Saas Site", SaasApplication: &SaasApplication{ - AuthType: "oidc", - RedirectURIs: []string{"https://saas.example.com"}, - AppLauncherURL: "https://saas.example.com", - GroupFilterRegex: ".*", + AuthType: "oidc", + RedirectURIs: []string{"https://saas.example.com"}, + AppLauncherURL: "https://saas.example.com", + GroupFilterRegex: ".*", + AllowPKCEWithoutClientSecret: BoolPtr(false), + CustomClaims: []OIDCClaimConfig{ + { + Name: "test1", + Source: SourceConfig{Name: "test1"}, + Required: BoolPtr(true), + Scope: "profile", + }, + }, }, SessionDuration: "24h", }) @@ -887,10 +1207,19 @@ func TestCreateOIDCSaasAccessApplications(t *testing.T) { actual, err = client.CreateAccessApplication(context.Background(), ZoneIdentifier(testZoneID), CreateAccessApplicationParams{ Name: "Admin Saas Site", SaasApplication: &SaasApplication{ - AuthType: "oidc", - RedirectURIs: []string{"https://saas.example.com"}, - AppLauncherURL: "https://saas.example.com", - GroupFilterRegex: ".*", + AuthType: "oidc", + RedirectURIs: []string{"https://saas.example.com"}, + AppLauncherURL: "https://saas.example.com", + GroupFilterRegex: ".*", + AllowPKCEWithoutClientSecret: BoolPtr(false), + CustomClaims: []OIDCClaimConfig{ + { + Name: "test1", + Source: SourceConfig{Name: "test1"}, + Required: BoolPtr(true), + Scope: "profile", + }, + }, }, SessionDuration: "24h", }) @@ -1013,3 +1342,182 @@ func TestCreateApplicationWithAccessAppLauncherCustomization(t *testing.T) { assert.Equal(t, fullAccessApplication, actual) } } + +func TestCreateAccessApplicationWithSCIMProvisioning(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": { + "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 SCIM App", + "domain": "example.cloudflareaccess.com/cdn-cgi/access/sso/oidc/737646a56ab1df6ec9bddc7e5ca84eaf3b0768850f3ffb5d74f1534911fe3893", + "type": "saas", + "session_duration": "24h", + "allowed_idps": [], + "auto_redirect_to_identity": false, + "enable_binding_cookie": false, + "custom_deny_url": "https://www.example.com", + "custom_deny_message": "denied!", + "logo_url": "https://www.example.com/example.png", + "skip_interstitial": true, + "app_launcher_visible": true, + "service_auth_401_redirect": true, + "custom_non_identity_deny_url": "https://blocked.com", + "tags": ["engineers"], + "scim_config": { + "enabled": true, + "remote_uri": "https://scim.com", + "authentication": { + "scheme": "oauthbearertoken", + "token": "1234567890" + }, + "idp_uid": "1234567", + "deactivate_on_delete": true, + "mappings": [ + { + "schema": "urn:ietf:params:scim:schemas:core:2.0:User", + "enabled": true, + "filter": "title pr or userType eq \"Intern\"", + "transform_jsonata": "$merge([$, {'userName': $substringBefore($.userName, '@') & '+test@' & $substringAfter($.userName, '@')}])", + "operations": { + "create": true, + "update": true, + "delete": true + } + } + ] + } + } + } + `) + } + + createdAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z") + updatedAt, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00.12345Z") + fullAccessApplication := AccessApplication{ + ID: "480f4f69-1a28-4fdd-9240-1ed29f0ac1db", + Name: "Admin SCIM App", + Domain: "example.cloudflareaccess.com/cdn-cgi/access/sso/oidc/737646a56ab1df6ec9bddc7e5ca84eaf3b0768850f3ffb5d74f1534911fe3893", + Type: "saas", + SessionDuration: "24h", + AUD: "737646a56ab1df6ec9bddc7e5ca84eaf3b0768850f3ffb5d74f1534911fe3893", + AllowedIdps: []string{}, + 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), + CreatedAt: &createdAt, + UpdatedAt: &updatedAt, + CustomNonIdentityDenyURL: "https://blocked.com", + Tags: []string{"engineers"}, + SCIMConfig: &AccessApplicationSCIMConfig{ + Enabled: BoolPtr(true), + RemoteURI: "https://scim.com", + Authentication: &AccessApplicationScimAuthenticationJson{ + Value: &AccessApplicationScimAuthenticationOauthBearerToken{ + Token: "1234567890", + baseScimAuthentication: baseScimAuthentication{Scheme: AccessApplicationScimAuthenticationSchemeOauthBearerToken}, + }, + }, + IdPUID: "1234567", + DeactivateOnDelete: BoolPtr(true), + Mappings: []*AccessApplicationScimMapping{ + { + Schema: "urn:ietf:params:scim:schemas:core:2.0:User", + Enabled: BoolPtr(true), + Filter: "title pr or userType eq \"Intern\"", + TransformJsonata: "$merge([$, {'userName': $substringBefore($.userName, '@') & '+test@' & $substringAfter($.userName, '@')}])", + Operations: &AccessApplicationScimMappingOperations{ + Create: BoolPtr(true), + Update: BoolPtr(true), + Delete: BoolPtr(true), + }, + }, + }, + }, + } + + mux.HandleFunc("/accounts/"+testAccountID+"/access/apps", handler) + + actual, err := client.CreateAccessApplication(context.Background(), AccountIdentifier(testAccountID), CreateAccessApplicationParams{ + Name: "Admin Saas Site", + SCIMConfig: &AccessApplicationSCIMConfig{ + Enabled: BoolPtr(true), + RemoteURI: "https://scim.com", + Authentication: &AccessApplicationScimAuthenticationJson{ + Value: &AccessApplicationScimAuthenticationOauthBearerToken{ + Token: "1234567890", + baseScimAuthentication: baseScimAuthentication{Scheme: AccessApplicationScimAuthenticationSchemeOauthBearerToken}, + }, + }, + IdPUID: "1234567", + DeactivateOnDelete: BoolPtr(true), + Mappings: []*AccessApplicationScimMapping{ + { + Schema: "urn:ietf:params:scim:schemas:core:2.0:User", + Enabled: BoolPtr(true), + Filter: "title pr or userType eq \"Intern\"", + TransformJsonata: "$merge([$, {'userName': $substringBefore($.userName, '@') & '+test@' & $substringAfter($.userName, '@')}])", + Operations: &AccessApplicationScimMappingOperations{ + Create: BoolPtr(true), + Update: BoolPtr(true), + Delete: BoolPtr(true), + }, + }, + }, + }, + }) + + if assert.NoError(t, err) { + assert.Equal(t, fullAccessApplication, actual) + } + + mux.HandleFunc("/zones/"+testZoneID+"/access/apps", handler) + + actual, err = client.CreateAccessApplication(context.Background(), ZoneIdentifier(testZoneID), CreateAccessApplicationParams{ + Name: "Admin SCIM Site", + SCIMConfig: &AccessApplicationSCIMConfig{ + Enabled: BoolPtr(true), + RemoteURI: "https://scim.com", + Authentication: &AccessApplicationScimAuthenticationJson{ + Value: &AccessApplicationScimAuthenticationOauthBearerToken{ + Token: "1234567890", + baseScimAuthentication: baseScimAuthentication{Scheme: AccessApplicationScimAuthenticationSchemeOauthBearerToken}, + }, + }, + IdPUID: "1234567", + DeactivateOnDelete: BoolPtr(true), + Mappings: []*AccessApplicationScimMapping{ + { + Schema: "urn:ietf:params:scim:schemas:core:2.0:User", + Enabled: BoolPtr(true), + Filter: "title pr or userType eq \"Intern\"", + TransformJsonata: "$merge([$, {'userName': $substringBefore($.userName, '@') & '+test@' & $substringAfter($.userName, '@')}])", + Operations: &AccessApplicationScimMappingOperations{ + Create: BoolPtr(true), + Update: BoolPtr(true), + Delete: BoolPtr(true), + }, + }, + }, + }, + }) + + if assert.NoError(t, err) { + assert.Equal(t, fullAccessApplication, actual) + } +} diff --git a/access_policy.go b/access_policy.go index 0e1ae41c6d..a70ceede50 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 6b9506023b..7564213820 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) } diff --git a/go.mod b/go.mod index fe3fe2fc12..7c67586df6 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.19 require ( github.com/goccy/go-json v0.10.2 github.com/google/go-querystring v1.1.0 - github.com/hashicorp/go-retryablehttp v0.7.5 + github.com/hashicorp/go-retryablehttp v0.7.6 github.com/olekukonko/tablewriter v0.0.5 github.com/stretchr/testify v1.9.0 github.com/urfave/cli/v2 v2.27.2 - golang.org/x/net v0.24.0 + golang.org/x/net v0.25.0 golang.org/x/time v0.5.0 ) @@ -18,19 +18,15 @@ require gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect require ( github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fatih/color v1.13.0 // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.2.0 // indirect github.com/kr/pretty v0.3.0 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/text v0.15.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 73bbc2da40..9df8e0d049 100644 --- a/go.sum +++ b/go.sum @@ -3,9 +3,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -15,11 +13,9 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= -github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= -github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-retryablehttp v0.7.6 h1:TwRYfx2z2C4cLbXmT8I5PgP/xmuqASDyiVuGYfs9GZM= +github.com/hashicorp/go-retryablehttp v0.7.6/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -28,15 +24,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -52,24 +41,17 @@ github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XF github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/rulesets.go b/rulesets.go index c8a476251a..8b1ad430a5 100644 --- a/rulesets.go +++ b/rulesets.go @@ -246,7 +246,9 @@ type RulesetRuleActionParameters struct { DisableApps *bool `json:"disable_apps,omitempty"` DisableZaraz *bool `json:"disable_zaraz,omitempty"` DisableRailgun *bool `json:"disable_railgun,omitempty"` + DisableRUM *bool `json:"disable_rum,omitempty"` EmailObfuscation *bool `json:"email_obfuscation,omitempty"` + Fonts *bool `json:"fonts,omitempty"` Mirage *bool `json:"mirage,omitempty"` OpportunisticEncryption *bool `json:"opportunistic_encryption,omitempty"` Polish *Polish `json:"polish,omitempty"` diff --git a/rulesets_test.go b/rulesets_test.go index 91f432529e..dbaf2b4f13 100644 --- a/rulesets_test.go +++ b/rulesets_test.go @@ -403,7 +403,9 @@ func TestGetRuleset_SetConfig(t *testing.T) { "server_side_excludes":true, "ssl":"off", "sxg":true, - "hotlink_protection":true + "hotlink_protection":true, + "fonts":true, + "disable_rum":true }, "description": "Set all available config rules in one rule", "last_updated": "2020-12-18T09:28:09.655749Z", @@ -440,7 +442,9 @@ func TestGetRuleset_SetConfig(t *testing.T) { DisableApps: BoolPtr(true), DisableZaraz: BoolPtr(true), DisableRailgun: BoolPtr(true), + DisableRUM: BoolPtr(true), EmailObfuscation: BoolPtr(true), + Fonts: BoolPtr(true), Mirage: BoolPtr(true), OpportunisticEncryption: BoolPtr(true), Polish: PolishOff.IntoRef(),