diff --git a/.changelog/4734.txt b/.changelog/4734.txt
new file mode 100644
index 0000000000..223d86cc92
--- /dev/null
+++ b/.changelog/4734.txt
@@ -0,0 +1,3 @@
+```release-note:new-resource
+cloudflare_content_scanning_expression
+```
\ No newline at end of file
diff --git a/.changelog/4743.txt b/.changelog/4743.txt
new file mode 100644
index 0000000000..5d6ea14b94
--- /dev/null
+++ b/.changelog/4743.txt
@@ -0,0 +1,3 @@
+```release-note:enhancement
+resource/access_application: support multi-valued + Access service token authentication for SCIM provisioning to Access applications
+```
diff --git a/.changelog/4814.txt b/.changelog/4814.txt
new file mode 100644
index 0000000000..d037303bca
--- /dev/null
+++ b/.changelog/4814.txt
@@ -0,0 +1,3 @@
+```release-note:bug
+resource/cloudflare_ruleset: handle when `disable_stale_while_updating` is an empty object but not nil
+```
diff --git a/.changelog/4817.txt b/.changelog/4817.txt
new file mode 100644
index 0000000000..1405728ab7
--- /dev/null
+++ b/.changelog/4817.txt
@@ -0,0 +1,3 @@
+```release-note:note
+resource/cloudflare_teams_location: remove unusable `policy_ids` attribute
+```
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 67a986f4ff..2fb4638ef8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,30 @@
-## 4.49.0 (Unreleased)
+## 4.50.0 (Unreleased)
+
+## 4.49.0 (December 25th, 2025)
+
+NOTES:
+
+* resource/cloudflare_teams_location: remove unusable `policy_ids` attribute ([#4817](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4817))
+
+FEATURES:
+
+* **New Resource:** `cloudflare_content_scanning_expression` ([#4734](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4734))
+
+ENHANCEMENTS:
+
+* resource/access_application: support multi-valued + Access service token authentication for SCIM provisioning to Access applications ([#4743](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4743))
+
+BUG FIXES:
+
+* resource/cloudflare_ruleset: handle when `disable_stale_while_updating` is an empty object but not nil ([#4814](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4814))
+
+DEPENDENCIES:
+
+* provider: bump github.com/cloudflare/cloudflare-go from 0.111.0 to 0.112.0 ([#4803](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4803))
+* provider: bump github.com/hashicorp/terraform-plugin-framework-validators from 0.15.0 to 0.16.0 ([#4762](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4762))
+* provider: bump golang.org/x/crypto from 0.21.0 to 0.31.0 in /tools ([#4755](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4755))
+* provider: bump golang.org/x/crypto from 0.30.0 to 0.31.0 ([#4756](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4756))
+* provider: bump golang.org/x/net from 0.32.0 to 0.33.0 ([#4802](https://github.com/cloudflare/terraform-provider-cloudflare/issues/4802))
## 4.48.0 (December 11th, 2024)
diff --git a/docs/resources/access_application.md b/docs/resources/access_application.md
index f86241e53d..e481556f54 100644
--- a/docs/resources/access_application.md
+++ b/docs/resources/access_application.md
@@ -281,7 +281,7 @@ Required:
Optional:
-- `authentication` (Block List, Max: 1) Attributes for configuring HTTP Basic, OAuth Bearer token, or OAuth 2 authentication schemes for SCIM provisioning to an application. (see [below for nested schema](#nestedblock--scim_config--authentication))
+- `authentication` (Block List) Attributes for configuring HTTP Basic, OAuth Bearer token, or OAuth 2 authentication schemes for SCIM provisioning to an application. (see [below for nested schema](#nestedblock--scim_config--authentication))
- `deactivate_on_delete` (Boolean) If false, propagates DELETE requests to the target application for SCIM resources. If true, sets 'active' to false on the SCIM resource. Note: Some targets do not support DELETE operations.
- `enabled` (Boolean) Whether SCIM provisioning is turned on for this application.
- `mappings` (Block List) A list of mappings to apply to SCIM resources before provisioning them in this application. These can transform or filter the resources to be provisioned. (see [below for nested schema](#nestedblock--scim_config--mappings))
@@ -295,14 +295,14 @@ Required:
Optional:
-- `authorization_url` (String) URL used to generate the auth code used during token generation. Required when using `scim_config.0.authentication.0.client_secret`, `scim_config.0.authentication.0.client_id`, `scim_config.0.authentication.0.token_url`. Conflicts with `scim_config.0.authentication.0.user`, `scim_config.0.authentication.0.password`, `scim_config.0.authentication.0.token`.
-- `client_id` (String) Client ID used to authenticate when generating a token for authenticating with the remote SCIM service. Required when using `scim_config.0.authentication.0.client_secret`, `scim_config.0.authentication.0.authorization_url`, `scim_config.0.authentication.0.token_url`. Conflicts with `scim_config.0.authentication.0.user`, `scim_config.0.authentication.0.password`, `scim_config.0.authentication.0.token`.
-- `client_secret` (String) Secret used to authenticate when generating a token for authenticating with the remove SCIM service. Required when using `scim_config.0.authentication.0.client_id`, `scim_config.0.authentication.0.authorization_url`, `scim_config.0.authentication.0.token_url`. Conflicts with `scim_config.0.authentication.0.user`, `scim_config.0.authentication.0.password`, `scim_config.0.authentication.0.token`.
-- `password` (String) Required when using `scim_config.0.authentication.0.user`. Conflicts with `scim_config.0.authentication.0.token`, `scim_config.0.authentication.0.client_id`, `scim_config.0.authentication.0.client_secret`, `scim_config.0.authentication.0.authorization_url`, `scim_config.0.authentication.0.token_url`, `scim_config.0.authentication.0.scopes`.
-- `scopes` (Set of String) The authorization scopes to request when generating the token used to authenticate with the remove SCIM service. Conflicts with `scim_config.0.authentication.0.user`, `scim_config.0.authentication.0.password`, `scim_config.0.authentication.0.token`.
-- `token` (String) Token used to authenticate with the remote SCIM service. Conflicts with `scim_config.0.authentication.0.user`, `scim_config.0.authentication.0.password`, `scim_config.0.authentication.0.client_id`, `scim_config.0.authentication.0.client_secret`, `scim_config.0.authentication.0.authorization_url`, `scim_config.0.authentication.0.token_url`, `scim_config.0.authentication.0.scopes`.
-- `token_url` (String) URL used to generate the token used to authenticate with the remote SCIM service. Required when using `scim_config.0.authentication.0.client_secret`, `scim_config.0.authentication.0.authorization_url`, `scim_config.0.authentication.0.client_id`. Conflicts with `scim_config.0.authentication.0.user`, `scim_config.0.authentication.0.password`, `scim_config.0.authentication.0.token`.
-- `user` (String) User name used to authenticate with the remote SCIM service. Required when using `scim_config.0.authentication.0.password`. Conflicts with `scim_config.0.authentication.0.token`, `scim_config.0.authentication.0.client_id`, `scim_config.0.authentication.0.client_secret`, `scim_config.0.authentication.0.authorization_url`, `scim_config.0.authentication.0.token_url`, `scim_config.0.authentication.0.scopes`.
+- `authorization_url` (String) URL used to generate the auth code used during token generation.
+- `client_id` (String) Client ID used to authenticate when generating a token for authenticating with the remote SCIM service.
+- `client_secret` (String) Secret used to authenticate when generating a token for authenticating with the remove SCIM service.
+- `password` (String)
+- `scopes` (Set of String) The authorization scopes to request when generating the token used to authenticate with the remove SCIM service.
+- `token` (String) Token used to authenticate with the remote SCIM service.
+- `token_url` (String) URL used to generate the token used to authenticate with the remote SCIM service.
+- `user` (String) User name used to authenticate with the remote SCIM service.
diff --git a/docs/resources/cloud_connector_rules.md b/docs/resources/cloud_connector_rules.md
index e4d2ee0cae..49f53fbf13 100644
--- a/docs/resources/cloud_connector_rules.md
+++ b/docs/resources/cloud_connector_rules.md
@@ -2,7 +2,7 @@
page_title: "cloudflare_cloud_connector_rules Resource - Cloudflare"
subcategory: ""
description: |-
- The Cloud Connector Rules resource allows you to create and manage cloud connector rules for a zone.
+ The Cloud Connector Rules https://developers.cloudflare.com/rules/cloud-connector/ resource allows you to create and manage cloud connector rules for a zone.
---
# cloudflare_cloud_connector_rules (Resource)
diff --git a/docs/resources/content_scanning_expression.md b/docs/resources/content_scanning_expression.md
new file mode 100644
index 0000000000..0828c46b66
--- /dev/null
+++ b/docs/resources/content_scanning_expression.md
@@ -0,0 +1,49 @@
+---
+page_title: "cloudflare_content_scanning_expression Resource - Cloudflare"
+subcategory: ""
+description: |-
+ Provides a Cloudflare Content Scanning Expression resource for managing custom scan expression within a specific zone.
+---
+
+# cloudflare_content_scanning_expression (Resource)
+
+Provides a Cloudflare Content Scanning Expression resource for managing custom scan expression within a specific zone.
+
+## Example Usage
+
+```terraform
+# Enable Content Scanning before trying to add custom scan expressions
+resource "cloudflare_content_scanning" "example" {
+ zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b"
+ enabled = true
+}
+
+resource "cloudflare_content_scanning_expression" "first_example" {
+ zone_id = cloudflare_content_scanning.example.zone_id
+ payload = "lookup_json_string(http.request.body.raw, \"file\")"
+}
+
+resource "cloudflare_content_scanning_expression" "second_example" {
+ zone_id = cloudflare_content_scanning.example.zone_id
+ payload = "lookup_json_string(http.request.body.raw, \"document\")"
+}
+```
+
+## Schema
+
+### Required
+
+- `payload` (String) Custom scan expression to tell the content scanner where to find the content objects.
+- `zone_id` (String) The zone identifier to target for the resource.
+
+### Read-Only
+
+- `id` (String) The identifier of this resource.
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+terraform import cloudflare_content_scanning_expression.example /
+```
diff --git a/docs/resources/teams_location.md b/docs/resources/teams_location.md
index 7574ed4e25..fa7cf986c2 100644
--- a/docs/resources/teams_location.md
+++ b/docs/resources/teams_location.md
@@ -50,7 +50,6 @@ resource "cloudflare_teams_location" "example" {
- `id` (String) The ID of this resource.
- `ip` (String) Client IP address.
- `ipv4_destination` (String) IP to direct all IPv4 DNS queries to.
-- `policy_ids` (List of String)
### Nested Schema for `networks`
diff --git a/docs/resources/zero_trust_access_application.md b/docs/resources/zero_trust_access_application.md
index c2762f49c6..1c508626fd 100644
--- a/docs/resources/zero_trust_access_application.md
+++ b/docs/resources/zero_trust_access_application.md
@@ -262,7 +262,7 @@ Required:
Optional:
-- `authentication` (Block List, Max: 1) Attributes for configuring HTTP Basic, OAuth Bearer token, or OAuth 2 authentication schemes for SCIM provisioning to an application. (see [below for nested schema](#nestedblock--scim_config--authentication))
+- `authentication` (Block List) Attributes for configuring HTTP Basic, OAuth Bearer token, or OAuth 2 authentication schemes for SCIM provisioning to an application. (see [below for nested schema](#nestedblock--scim_config--authentication))
- `deactivate_on_delete` (Boolean) If false, propagates DELETE requests to the target application for SCIM resources. If true, sets 'active' to false on the SCIM resource. Note: Some targets do not support DELETE operations.
- `enabled` (Boolean) Whether SCIM provisioning is turned on for this application.
- `mappings` (Block List) A list of mappings to apply to SCIM resources before provisioning them in this application. These can transform or filter the resources to be provisioned. (see [below for nested schema](#nestedblock--scim_config--mappings))
@@ -276,14 +276,14 @@ Required:
Optional:
-- `authorization_url` (String) URL used to generate the auth code used during token generation. Required when using `scim_config.0.authentication.0.client_secret`, `scim_config.0.authentication.0.client_id`, `scim_config.0.authentication.0.token_url`. Conflicts with `scim_config.0.authentication.0.user`, `scim_config.0.authentication.0.password`, `scim_config.0.authentication.0.token`.
-- `client_id` (String) Client ID used to authenticate when generating a token for authenticating with the remote SCIM service. Required when using `scim_config.0.authentication.0.client_secret`, `scim_config.0.authentication.0.authorization_url`, `scim_config.0.authentication.0.token_url`. Conflicts with `scim_config.0.authentication.0.user`, `scim_config.0.authentication.0.password`, `scim_config.0.authentication.0.token`.
-- `client_secret` (String) Secret used to authenticate when generating a token for authenticating with the remove SCIM service. Required when using `scim_config.0.authentication.0.client_id`, `scim_config.0.authentication.0.authorization_url`, `scim_config.0.authentication.0.token_url`. Conflicts with `scim_config.0.authentication.0.user`, `scim_config.0.authentication.0.password`, `scim_config.0.authentication.0.token`.
-- `password` (String) Required when using `scim_config.0.authentication.0.user`. Conflicts with `scim_config.0.authentication.0.token`, `scim_config.0.authentication.0.client_id`, `scim_config.0.authentication.0.client_secret`, `scim_config.0.authentication.0.authorization_url`, `scim_config.0.authentication.0.token_url`, `scim_config.0.authentication.0.scopes`.
-- `scopes` (Set of String) The authorization scopes to request when generating the token used to authenticate with the remove SCIM service. Conflicts with `scim_config.0.authentication.0.user`, `scim_config.0.authentication.0.password`, `scim_config.0.authentication.0.token`.
-- `token` (String) Token used to authenticate with the remote SCIM service. Conflicts with `scim_config.0.authentication.0.user`, `scim_config.0.authentication.0.password`, `scim_config.0.authentication.0.client_id`, `scim_config.0.authentication.0.client_secret`, `scim_config.0.authentication.0.authorization_url`, `scim_config.0.authentication.0.token_url`, `scim_config.0.authentication.0.scopes`.
-- `token_url` (String) URL used to generate the token used to authenticate with the remote SCIM service. Required when using `scim_config.0.authentication.0.client_secret`, `scim_config.0.authentication.0.authorization_url`, `scim_config.0.authentication.0.client_id`. Conflicts with `scim_config.0.authentication.0.user`, `scim_config.0.authentication.0.password`, `scim_config.0.authentication.0.token`.
-- `user` (String) User name used to authenticate with the remote SCIM service. Required when using `scim_config.0.authentication.0.password`. Conflicts with `scim_config.0.authentication.0.token`, `scim_config.0.authentication.0.client_id`, `scim_config.0.authentication.0.client_secret`, `scim_config.0.authentication.0.authorization_url`, `scim_config.0.authentication.0.token_url`, `scim_config.0.authentication.0.scopes`.
+- `authorization_url` (String) URL used to generate the auth code used during token generation.
+- `client_id` (String) Client ID used to authenticate when generating a token for authenticating with the remote SCIM service.
+- `client_secret` (String) Secret used to authenticate when generating a token for authenticating with the remove SCIM service.
+- `password` (String)
+- `scopes` (Set of String) The authorization scopes to request when generating the token used to authenticate with the remove SCIM service.
+- `token` (String) Token used to authenticate with the remote SCIM service.
+- `token_url` (String) URL used to generate the token used to authenticate with the remote SCIM service.
+- `user` (String) User name used to authenticate with the remote SCIM service.
diff --git a/docs/resources/zero_trust_dns_location.md b/docs/resources/zero_trust_dns_location.md
index 7d8d806280..1b71ebc83b 100644
--- a/docs/resources/zero_trust_dns_location.md
+++ b/docs/resources/zero_trust_dns_location.md
@@ -50,7 +50,6 @@ resource "cloudflare_zero_trust_dns_location" "example" {
- `id` (String) The ID of this resource.
- `ip` (String) Client IP address.
- `ipv4_destination` (String) IP to direct all IPv4 DNS queries to.
-- `policy_ids` (List of String)
### Nested Schema for `networks`
diff --git a/examples/resources/cloudflare_content_scanning_expression/import.sh b/examples/resources/cloudflare_content_scanning_expression/import.sh
new file mode 100644
index 0000000000..3591b3ef76
--- /dev/null
+++ b/examples/resources/cloudflare_content_scanning_expression/import.sh
@@ -0,0 +1 @@
+terraform import cloudflare_content_scanning_expression.example /
diff --git a/examples/resources/cloudflare_content_scanning_expression/resource.tf b/examples/resources/cloudflare_content_scanning_expression/resource.tf
new file mode 100644
index 0000000000..3faf65ca79
--- /dev/null
+++ b/examples/resources/cloudflare_content_scanning_expression/resource.tf
@@ -0,0 +1,15 @@
+# Enable Content Scanning before trying to add custom scan expressions
+resource "cloudflare_content_scanning" "example" {
+ zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b"
+ enabled = true
+}
+
+resource "cloudflare_content_scanning_expression" "first_example" {
+ zone_id = cloudflare_content_scanning.example.zone_id
+ payload = "lookup_json_string(http.request.body.raw, \"file\")"
+}
+
+resource "cloudflare_content_scanning_expression" "second_example" {
+ zone_id = cloudflare_content_scanning.example.zone_id
+ payload = "lookup_json_string(http.request.body.raw, \"document\")"
+}
diff --git a/internal/framework/provider/provider.go b/internal/framework/provider/provider.go
index 97561762a1..1b8247356b 100644
--- a/internal/framework/provider/provider.go
+++ b/internal/framework/provider/provider.go
@@ -19,6 +19,7 @@ import (
"github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/api_token_permissions_groups"
"github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/cloud_connector_rules"
"github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/content_scanning"
+ "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/content_scanning_expression"
"github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/d1"
"github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/dcv_delegation"
"github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/dlp_datasets"
@@ -397,6 +398,7 @@ func (p *CloudflareProvider) Resources(ctx context.Context) []func() resource.Re
leaked_credential_check.NewResource,
leaked_credential_check_rule.NewResource,
content_scanning.NewResource,
+ content_scanning_expression.NewResource,
}
}
diff --git a/internal/framework/service/content_scanning_expression/model.go b/internal/framework/service/content_scanning_expression/model.go
new file mode 100644
index 0000000000..100019c401
--- /dev/null
+++ b/internal/framework/service/content_scanning_expression/model.go
@@ -0,0 +1,9 @@
+package content_scanning_expression
+
+import "github.com/hashicorp/terraform-plugin-framework/types"
+
+type ContentScanningExpressionModel struct {
+ ZoneID types.String `tfsdk:"zone_id"`
+ ID types.String `tfsdk:"id"`
+ Payload types.String `tfsdk:"payload"`
+}
diff --git a/internal/framework/service/content_scanning_expression/resource.go b/internal/framework/service/content_scanning_expression/resource.go
new file mode 100644
index 0000000000..089d9d6ef3
--- /dev/null
+++ b/internal/framework/service/content_scanning_expression/resource.go
@@ -0,0 +1,196 @@
+package content_scanning_expression
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/cloudflare/cloudflare-go"
+ "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/muxclient"
+
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+var (
+ _ resource.Resource = &ContentScanningExpressionResource{}
+ _ resource.ResourceWithImportState = &ContentScanningExpressionResource{}
+)
+
+func NewResource() resource.Resource {
+ return &ContentScanningExpressionResource{}
+}
+
+type ContentScanningExpressionResource struct {
+ client *muxclient.Client
+}
+
+func (r *ContentScanningExpressionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_content_scanning_expression"
+}
+
+func (r *ContentScanningExpressionResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ client, ok := req.ProviderData.(*muxclient.Client)
+
+ if !ok {
+ resp.Diagnostics.AddError(
+ "unexpected resource configure type",
+ fmt.Sprintf("Expected *muxclient.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ r.client = client
+}
+
+func (r *ContentScanningExpressionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var data ContentScanningExpressionModel
+ diags := req.Plan.Get(ctx, &data)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ params := cloudflare.ContentScanningAddCustomExpressionsParams{
+ Payloads: []cloudflare.ContentScanningCustomPayload{
+ {
+ Payload: data.Payload.ValueString(),
+ },
+ },
+ }
+ expressions, err := r.client.V1.ContentScanningAddCustomExpressions(ctx, cloudflare.ZoneIdentifier(data.ZoneID.ValueString()), params)
+ if err != nil {
+ resp.Diagnostics.AddError("Error creating a custom scan expression for Content Scanning", err.Error())
+ return
+ }
+
+ // The Add API returns a list of all exiting custom scan expression
+ // loop until we find the newly created one, matching on payload
+ // payload uniqueness is enforced by the service
+ for _, exp := range expressions {
+ if exp.Payload == data.Payload.ValueString() {
+ data.ID = types.StringValue(exp.ID)
+ break
+ }
+ }
+
+ diags = resp.State.Set(ctx, &data)
+ resp.Diagnostics.Append(diags...)
+}
+
+func (r *ContentScanningExpressionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var state ContentScanningExpressionModel
+ diags := req.State.Get(ctx, &state)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ zoneID := state.ZoneID.ValueString()
+ var foundExp cloudflare.ContentScanningCustomExpression
+ expressions, err := r.client.V1.ContentScanningListCustomExpressions(ctx, cloudflare.ZoneIdentifier(zoneID), cloudflare.ContentScanningListCustomExpressionsParams{})
+ if err != nil {
+ resp.Diagnostics.AddError("Error listing customs scan expressions for Content Scanning", err.Error())
+ return
+ }
+
+ // content scanning doens't offer a single get operation so
+ // loop until we find the matching ID.
+ for _, exp := range expressions {
+ if exp.ID == state.ID.ValueString() {
+ foundExp = exp
+ break
+ }
+ }
+
+ state.ID = types.StringValue(foundExp.ID)
+ state.Payload = types.StringValue(foundExp.Payload)
+
+ diags = resp.State.Set(ctx, &state)
+ resp.Diagnostics.Append(diags...)
+}
+
+func (r *ContentScanningExpressionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var plan ContentScanningExpressionModel
+ diags := req.Plan.Get(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ var state ContentScanningExpressionModel
+ diags = req.State.Get(ctx, &state)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ zoneID := cloudflare.ZoneIdentifier(plan.ZoneID.ValueString())
+ plan.ID = state.ID
+
+ // API does not offer an update operation so we use delete/create
+ delParams := cloudflare.ContentScanningDeleteCustomExpressionsParams{ID: plan.ID.ValueString()}
+ _, err := r.client.V1.ContentScanningDeleteCustomExpression(ctx, zoneID, delParams)
+ if err != nil {
+ resp.Diagnostics.AddError("Error in Update while deleting custom scan expression for Content Scanning", err.Error())
+ return
+ }
+ createParams := cloudflare.ContentScanningAddCustomExpressionsParams{
+ Payloads: []cloudflare.ContentScanningCustomPayload{
+ {
+ Payload: plan.Payload.ValueString(),
+ },
+ },
+ }
+ expressions, err := r.client.V1.ContentScanningAddCustomExpressions(ctx, zoneID, createParams)
+ if err != nil {
+ resp.Diagnostics.AddError("Error in Update while creating a custom scan expression for Content Scanning", err.Error())
+ return
+ }
+
+ // The Add API returns a list of all exiting custom scan expression
+ // loop until we find the newly created one, matching on payload
+ // payload uniqueness is enforced by the service
+ for _, exp := range expressions {
+ if exp.Payload == plan.Payload.ValueString() {
+ plan.ID = types.StringValue(exp.ID)
+ break
+ }
+ }
+
+ diags = resp.State.Set(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
+}
+
+func (r *ContentScanningExpressionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var state ContentScanningExpressionModel
+ diags := req.State.Get(ctx, &state)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ zoneID := cloudflare.ZoneIdentifier(state.ZoneID.ValueString())
+ deleteParam := cloudflare.ContentScanningDeleteCustomExpressionsParams{ID: state.ID.ValueString()}
+ _, err := r.client.V1.ContentScanningDeleteCustomExpression(ctx, zoneID, deleteParam)
+ if err != nil {
+ resp.Diagnostics.AddError("Error deleting custom scan expression for Content Scanning", err.Error())
+ return
+ }
+}
+
+func (r *ContentScanningExpressionResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ idparts := strings.Split(req.ID, "/")
+ if len(idparts) != 2 {
+ resp.Diagnostics.AddError("error importing content scanning custom expression", "invalid ID specified. Please specify the ID as \"zone_id/resource_id\"")
+ return
+ }
+ resp.Diagnostics.Append(resp.State.SetAttribute(
+ ctx, path.Root("zone_id"), idparts[0],
+ )...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(
+ ctx, path.Root("id"), idparts[1],
+ )...)
+}
diff --git a/internal/framework/service/content_scanning_expression/resource_test.go b/internal/framework/service/content_scanning_expression/resource_test.go
new file mode 100644
index 0000000000..4b74ec74ca
--- /dev/null
+++ b/internal/framework/service/content_scanning_expression/resource_test.go
@@ -0,0 +1,104 @@
+package content_scanning_expression_test
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "testing"
+
+ cfv1 "github.com/cloudflare/cloudflare-go"
+ "github.com/cloudflare/terraform-provider-cloudflare/internal/acctest"
+ "github.com/cloudflare/terraform-provider-cloudflare/internal/utils"
+
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func init() {
+ resource.AddTestSweepers("cloudflare_content_scanning_expression", &resource.Sweeper{
+ Name: "cloudflare_content_scanning_expression",
+ F: testSweepCloudflareCSExpression,
+ })
+}
+
+func testSweepCloudflareCSExpression(r string) error {
+ ctx := context.Background()
+ client, clientErr := acctest.SharedV1Client()
+ if clientErr != nil {
+ tflog.Error(ctx, fmt.Sprintf("Sweeper: failed to create Cloudflare client: %s", clientErr))
+ }
+
+ zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
+ if zoneID == "" {
+ return errors.New("CLOUDFLARE_ZONE_ID must be set")
+ }
+ // fetch existing expressions from API
+ expressions, err := client.ContentScanningListCustomExpressions(ctx, cfv1.ZoneIdentifier(zoneID), cfv1.ContentScanningListCustomExpressionsParams{})
+ if err != nil {
+ tflog.Error(ctx, fmt.Sprintf("Sweeper: error listing customs scan expressions for Content Scanning: %s", err))
+ return err
+ }
+ for _, exp := range expressions {
+ deleteParam := cfv1.ContentScanningDeleteCustomExpressionsParams{ID: exp.ID}
+ _, err := client.ContentScanningDeleteCustomExpression(ctx, cfv1.ZoneIdentifier(zoneID), deleteParam)
+ if err != nil {
+ tflog.Error(ctx, fmt.Sprintf("Sweeper: error deleting custom scan expression for Content Scanning: %s", err))
+ }
+ }
+
+ return nil
+}
+
+func TestAccCloudflareContentScanningExpression_Basic(t *testing.T) {
+ rnd := utils.GenerateRandomResourceName()
+ name := fmt.Sprintf("cloudflare_content_scanning_expression.%s", rnd)
+ zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ acctest.TestAccPreCheck(t)
+ },
+ ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccBasicConfig(rnd, zoneID),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(name+"_first", "zone_id", zoneID),
+ resource.TestCheckResourceAttr(name+"_first", "payload", "lookup_json_string(http.request.body.raw, \"file\")"),
+
+ resource.TestCheckResourceAttr(name+"_second", "zone_id", zoneID),
+ resource.TestCheckResourceAttr(name+"_second", "payload", "lookup_json_string(http.request.body.raw, \"document\")"),
+ ),
+ },
+ {
+ Config: testAccBasicConfigChange(rnd, zoneID),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(name+"_second", "zone_id", zoneID),
+ resource.TestCheckResourceAttr(name+"_second", "payload", "lookup_json_string(http.request.body.raw, \"txt\")"),
+ ),
+ },
+ },
+ })
+}
+
+func testAccBasicConfig(name, zoneID string) string {
+ return fmt.Sprintf(`
+ resource "cloudflare_content_scanning_expression" "%[1]s_first" {
+ zone_id = "%[2]s"
+ payload = "lookup_json_string(http.request.body.raw, \"file\")"
+ }
+
+ resource "cloudflare_content_scanning_expression" "%[1]s_second" {
+ zone_id = "%[2]s"
+ payload = "lookup_json_string(http.request.body.raw, \"document\")"
+ }`, name, zoneID)
+}
+
+func testAccBasicConfigChange(name, zoneID string) string {
+ return fmt.Sprintf(`
+ resource "cloudflare_content_scanning_expression" "%[1]s_second" {
+ zone_id = "%[2]s"
+ payload = "lookup_json_string(http.request.body.raw, \"txt\")"
+ }`, name, zoneID)
+}
diff --git a/internal/framework/service/content_scanning_expression/schema.go b/internal/framework/service/content_scanning_expression/schema.go
new file mode 100644
index 0000000000..398426d18b
--- /dev/null
+++ b/internal/framework/service/content_scanning_expression/schema.go
@@ -0,0 +1,29 @@
+package content_scanning_expression
+
+import (
+ "context"
+
+ "github.com/cloudflare/terraform-provider-cloudflare/internal/consts"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+)
+
+func (r *ContentScanningExpressionResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Provides a Cloudflare Content Scanning Expression resource for managing custom scan expression within a specific zone.",
+ Attributes: map[string]schema.Attribute{
+ consts.IDSchemaKey: schema.StringAttribute{
+ Description: consts.IDSchemaDescription,
+ Computed: true,
+ },
+ consts.ZoneIDSchemaKey: schema.StringAttribute{
+ Description: consts.ZoneIDSchemaDescription,
+ Required: true,
+ },
+ "payload": schema.StringAttribute{
+ Description: "Custom scan expression to tell the content scanner where to find the content objects.",
+ Required: true,
+ },
+ },
+ }
+}
diff --git a/internal/framework/service/rulesets/resource.go b/internal/framework/service/rulesets/resource.go
index 84902ef3dc..6de78078a0 100644
--- a/internal/framework/service/rulesets/resource.go
+++ b/internal/framework/service/rulesets/resource.go
@@ -623,9 +623,11 @@ func toRulesetResourceModel(ctx context.Context, zoneID, accountID basetypes.Str
}
if ruleResponse.ActionParameters.ServeStale != nil {
- rule.ActionParameters[0].ServeStale = []*ActionParameterServeStaleModel{{
- DisableStaleWhileUpdating: types.BoolValue(*ruleResponse.ActionParameters.ServeStale.DisableStaleWhileUpdating),
- }}
+ if ruleResponse.ActionParameters.ServeStale.DisableStaleWhileUpdating != nil {
+ rule.ActionParameters[0].ServeStale = []*ActionParameterServeStaleModel{{
+ DisableStaleWhileUpdating: types.BoolValue(*ruleResponse.ActionParameters.ServeStale.DisableStaleWhileUpdating),
+ }}
+ }
}
if ruleResponse.ActionParameters.FromList != nil {
diff --git a/internal/sdkv2provider/resource_cloudflare_access_application_test.go b/internal/sdkv2provider/resource_cloudflare_access_application_test.go
index d220fe29e2..64b43713c6 100644
--- a/internal/sdkv2provider/resource_cloudflare_access_application_test.go
+++ b/internal/sdkv2provider/resource_cloudflare_access_application_test.go
@@ -255,8 +255,10 @@ func TestAccCloudflareAccessApplication_WithSCIMConfigInvalidMappingSchema(t *te
})
}
-func TestAccCloudflareAccessApplication_WithSCIMConfigHttpBasicMissingRequired(t *testing.T) {
+func TestAccCloudflareAccessApplication_WithSCIMConfigOAuthBearerToken(t *testing.T) {
rnd := generateRandomResourceName()
+ name := fmt.Sprintf("cloudflare_zero_trust_access_application.%s", rnd)
+ idpName := fmt.Sprintf("cloudflare_zero_trust_access_identity_provider.%s", rnd)
resource.Test(t, resource.TestCase{
PreCheck: func() {
@@ -266,14 +268,34 @@ func TestAccCloudflareAccessApplication_WithSCIMConfigHttpBasicMissingRequired(t
CheckDestroy: testAccCheckCloudflareAccessApplicationDestroy,
Steps: []resource.TestStep{
{
- Config: testAccCloudflareAccessApplicationSCIMConfigHttpBasicMissingRequired(rnd, accountID, domain),
- ExpectError: regexp.MustCompile(`.*all of\s*` + "`scim_config.0.authentication.0.password,scim_config.0.authentication.0.user`" + `\s*must be specified.*`),
+ Config: testAccCloudflareAccessApplicationSCIMConfigValidOAuthBearerToken(rnd, accountID, domain),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(name, consts.AccountIDSchemaKey, accountID),
+ resource.TestCheckResourceAttr(name, "name", rnd),
+ resource.TestCheckResourceAttr(name, "domain", fmt.Sprintf("%s.%s", rnd, domain)),
+ resource.TestCheckResourceAttr(name, "type", "self_hosted"),
+ resource.TestCheckResourceAttr(name, "session_duration", "24h"),
+ resource.TestCheckResourceAttr(name, "scim_config.#", "1"),
+ resource.TestCheckResourceAttr(name, "scim_config.0.enabled", "true"),
+ resource.TestCheckResourceAttr(name, "scim_config.0.remote_uri", "scim.com"),
+ resource.TestCheckResourceAttrPair(name, "scim_config.0.idp_uid", idpName, "id"),
+ resource.TestCheckResourceAttr(name, "scim_config.0.deactivate_on_delete", "true"),
+ resource.TestCheckResourceAttr(name, "scim_config.0.authentication.0.scheme", "oauthbearertoken"),
+ resource.TestCheckResourceAttrSet(name, "scim_config.0.authentication.0.token"),
+ resource.TestCheckResourceAttr(name, "scim_config.0.mappings.0.schema", "urn:ietf:params:scim:schemas:core:2.0:User"),
+ resource.TestCheckResourceAttr(name, "scim_config.0.mappings.0.enabled", "true"),
+ resource.TestCheckResourceAttr(name, "scim_config.0.mappings.0.filter", "title pr or userType eq \"Intern\""),
+ resource.TestCheckResourceAttr(name, "scim_config.0.mappings.0.transform_jsonata", "$merge([$, {'userName': $substringBefore($.userName, '@') & '+test@' & $substringAfter($.userName, '@')}])"),
+ resource.TestCheckResourceAttr(name, "scim_config.0.mappings.0.operations.0.create", "true"),
+ resource.TestCheckResourceAttr(name, "scim_config.0.mappings.0.operations.0.update", "true"),
+ resource.TestCheckResourceAttr(name, "scim_config.0.mappings.0.operations.0.delete", "true"),
+ ),
},
},
})
}
-func TestAccCloudflareAccessApplication_WithSCIMConfigOAuthBearerToken(t *testing.T) {
+func TestAccCloudflareAccessApplication_WithSCIMConfigMultiAuth(t *testing.T) {
rnd := generateRandomResourceName()
name := fmt.Sprintf("cloudflare_zero_trust_access_application.%s", rnd)
idpName := fmt.Sprintf("cloudflare_zero_trust_access_identity_provider.%s", rnd)
@@ -286,7 +308,7 @@ func TestAccCloudflareAccessApplication_WithSCIMConfigOAuthBearerToken(t *testin
CheckDestroy: testAccCheckCloudflareAccessApplicationDestroy,
Steps: []resource.TestStep{
{
- Config: testAccCloudflareAccessApplicationSCIMConfigValidOAuthBearerToken(rnd, accountID, domain),
+ Config: testAccCloudflareAccessApplicationSCIMConfigValidMulti(rnd, accountID, domain),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(name, consts.AccountIDSchemaKey, accountID),
resource.TestCheckResourceAttr(name, "name", rnd),
@@ -300,6 +322,9 @@ func TestAccCloudflareAccessApplication_WithSCIMConfigOAuthBearerToken(t *testin
resource.TestCheckResourceAttr(name, "scim_config.0.deactivate_on_delete", "true"),
resource.TestCheckResourceAttr(name, "scim_config.0.authentication.0.scheme", "oauthbearertoken"),
resource.TestCheckResourceAttrSet(name, "scim_config.0.authentication.0.token"),
+ resource.TestCheckResourceAttr(name, "scim_config.0.authentication.1.scheme", "access_service_token"),
+ resource.TestCheckResourceAttr(name, "scim_config.0.authentication.1.client_id", "1234"),
+ resource.TestCheckResourceAttrSet(name, "scim_config.0.authentication.1.client_secret"),
resource.TestCheckResourceAttr(name, "scim_config.0.mappings.0.schema", "urn:ietf:params:scim:schemas:core:2.0:User"),
resource.TestCheckResourceAttr(name, "scim_config.0.mappings.0.enabled", "true"),
resource.TestCheckResourceAttr(name, "scim_config.0.mappings.0.filter", "title pr or userType eq \"Intern\""),
@@ -358,42 +383,6 @@ func TestAccCloudflareAccessApplication_WithSCIMConfigOAuth2(t *testing.T) {
})
}
-func TestAccCloudflareAccessApplication_WithSCIMConfigOAuth2MissingRequired(t *testing.T) {
- rnd := generateRandomResourceName()
-
- resource.Test(t, resource.TestCase{
- PreCheck: func() {
- testAccPreCheck(t)
- },
- ProviderFactories: providerFactories,
- CheckDestroy: testAccCheckCloudflareAccessApplicationDestroy,
- Steps: []resource.TestStep{
- {
- Config: testAccCloudflareAccessApplicationSCIMConfigOAuth2MissingRequired(rnd, accountID, domain),
- ExpectError: regexp.MustCompile(`.*all of\s*` + "`scim_config.0.authentication.0.authorization_url,scim_config.0.authentication.0.client_id,scim_config.0.authentication.0.client_secret,scim_config.0.authentication.0.token_url`" + `\s*must be specified.*`),
- },
- },
- })
-}
-
-func TestAccCloudflareAccessApplication_WithSCIMConfigAuthenticationInvalid(t *testing.T) {
- rnd := generateRandomResourceName()
-
- resource.Test(t, resource.TestCase{
- PreCheck: func() {
- testAccPreCheck(t)
- },
- ProviderFactories: providerFactories,
- CheckDestroy: testAccCheckCloudflareAccessApplicationDestroy,
- Steps: []resource.TestStep{
- {
- Config: testAccCloudflareAccessApplicationSCIMConfigAuthenticationInvalid(rnd, accountID, domain),
- ExpectError: regexp.MustCompile(`Error: Conflicting configuration arguments`),
- },
- },
- })
-}
-
func TestAccCloudflareAccessApplication_WithCORS(t *testing.T) {
rnd := generateRandomResourceName()
name := fmt.Sprintf("cloudflare_zero_trust_access_application.%s", rnd)
@@ -1983,7 +1972,7 @@ resource "cloudflare_zero_trust_access_application" "%[1]s" {
`, rnd, accountID, domain)
}
-func testAccCloudflareAccessApplicationSCIMConfigValidOAuth2(rnd, accountID, domain string) string {
+func testAccCloudflareAccessApplicationSCIMConfigValidMulti(rnd, accountID, domain string) string {
return fmt.Sprintf(`
resource "cloudflare_zero_trust_access_identity_provider" "%[1]s" {
account_id = "%[2]s"
@@ -2015,66 +2004,13 @@ resource "cloudflare_zero_trust_access_application" "%[1]s" {
idp_uid = cloudflare_zero_trust_access_identity_provider.%[1]s.id
deactivate_on_delete = true
authentication {
- scheme = "oauth2"
- client_id = "beepboop"
- client_secret = "bop"
- authorization_url = "https://www.authorization.com"
- token_url = "https://www.token.com"
- scopes = ["read"]
- }
- 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
- }
- }
- }
-}
-`, rnd, accountID, domain)
-}
-
-func testAccCloudflareAccessApplicationSCIMConfigOAuth2MissingRequired(rnd, accountID, domain string) string {
- return fmt.Sprintf(`
-resource "cloudflare_zero_trust_access_identity_provider" "%[1]s" {
- account_id = "%[2]s"
- name = "%[1]s"
- type = "azureAD"
- config {
- client_id = "test"
- client_secret = "test"
- directory_id = "directory"
- support_groups = true
- }
- scim_config {
- enabled = true
- group_member_deprovision = true
- seat_deprovision = true
- user_deprovision = true
+ scheme = "oauthbearertoken"
+ token = "beepboop"
}
-}
-
-resource "cloudflare_zero_trust_access_application" "%[1]s" {
- account_id = "%[2]s"
- name = "%[1]s"
- type = "self_hosted"
- session_duration = "24h"
- domain = "%[1]s.%[3]s"
- scim_config {
- enabled = true
- remote_uri = "scim.com"
- idp_uid = cloudflare_zero_trust_access_identity_provider.%[1]s.id
- deactivate_on_delete = true
authentication {
- scheme = "oauth2"
- client_id = "beepboop"
- client_secret = "bop"
- authorization_url = "https://www.authorization.com"
- scopes = ["read"]
+ scheme = "access_service_token"
+ client_id = "1234"
+ client_secret = "5678"
}
mappings {
schema = "urn:ietf:params:scim:schemas:core:2.0:User"
@@ -2086,13 +2022,14 @@ resource "cloudflare_zero_trust_access_application" "%[1]s" {
update = true
delete = true
}
+ strictness = "strict"
}
}
}
`, rnd, accountID, domain)
}
-func testAccCloudflareAccessApplicationSCIMConfigAuthenticationInvalid(rnd, accountID, domain string) string {
+func testAccCloudflareAccessApplicationSCIMConfigValidOAuth2(rnd, accountID, domain string) string {
return fmt.Sprintf(`
resource "cloudflare_zero_trust_access_identity_provider" "%[1]s" {
account_id = "%[2]s"
@@ -2129,9 +2066,6 @@ resource "cloudflare_zero_trust_access_application" "%[1]s" {
client_secret = "bop"
authorization_url = "https://www.authorization.com"
token_url = "https://www.token.com"
- token = "1234"
- user = "user"
- password = "ahhh"
scopes = ["read"]
}
mappings {
@@ -2150,56 +2084,6 @@ resource "cloudflare_zero_trust_access_application" "%[1]s" {
`, rnd, accountID, domain)
}
-func testAccCloudflareAccessApplicationSCIMConfigHttpBasicMissingRequired(rnd, accountID, domain string) string {
- return fmt.Sprintf(`
-resource "cloudflare_zero_trust_access_identity_provider" "%[1]s" {
- account_id = "%[2]s"
- name = "%[1]s"
- type = "azureAD"
- config {
- client_id = "test"
- client_secret = "test"
- directory_id = "directory"
- support_groups = true
- }
- scim_config {
- enabled = true
- group_member_deprovision = true
- seat_deprovision = true
- user_deprovision = true
- }
-}
-
-resource "cloudflare_zero_trust_access_application" "%[1]s" {
- account_id = "%[2]s"
- name = "%[1]s"
- type = "self_hosted"
- session_duration = "24h"
- domain = "%[1]s.%[3]s"
- scim_config {
- enabled = true
- remote_uri = "scim.com"
- idp_uid = cloudflare_zero_trust_access_identity_provider.%[1]s.id
- deactivate_on_delete = true
- authentication {
- scheme = "httpbasic"
- user = "test"
- }
- 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
- }
- }
- }
-}`, rnd, accountID, domain)
-}
-
func testAccCloudflareAccessApplicationSCIMConfigInvalidMappingSchema(rnd, accountID, domain string) string {
return fmt.Sprintf(`
resource "cloudflare_zero_trust_access_identity_provider" "%[1]s" {
diff --git a/internal/sdkv2provider/resource_cloudflare_teams_location.go b/internal/sdkv2provider/resource_cloudflare_teams_location.go
index 8fdb131eb1..1ca7fdef4c 100644
--- a/internal/sdkv2provider/resource_cloudflare_teams_location.go
+++ b/internal/sdkv2provider/resource_cloudflare_teams_location.go
@@ -68,9 +68,6 @@ func resourceCloudflareTeamsLocationRead(ctx context.Context, d *schema.Resource
if err := d.Set("networks", flattenTeamsLocationNetworks(location.Networks)); err != nil {
return diag.FromErr(fmt.Errorf("error parsing Location networks"))
}
- if err := d.Set("policy_ids", location.PolicyIDs); err != nil {
- return diag.FromErr(fmt.Errorf("error parsing Location policy IDs"))
- }
if err := d.Set("ip", location.Ip); err != nil {
return diag.FromErr(fmt.Errorf("error parsing Location IP"))
}
diff --git a/internal/sdkv2provider/schema_cloudflare_access_application.go b/internal/sdkv2provider/schema_cloudflare_access_application.go
index e5e8967b8d..684e7ffa76 100644
--- a/internal/sdkv2provider/schema_cloudflare_access_application.go
+++ b/internal/sdkv2provider/schema_cloudflare_access_application.go
@@ -706,80 +706,65 @@ func resourceCloudflareAccessApplicationSchema() map[string]*schema.Schema {
Type: schema.TypeList,
Optional: true,
Description: "Attributes for configuring HTTP Basic, OAuth Bearer token, or OAuth 2 authentication schemes for SCIM provisioning to an application.",
- MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
// Common Attributes
"scheme": {
Type: schema.TypeString,
Required: true,
- ValidateFunc: validation.StringInSlice([]string{"httpbasic", "oauthbearertoken", "oauth2"}, false),
+ ValidateFunc: validation.StringInSlice([]string{"httpbasic", "oauthbearertoken", "oauth2", "access_service_token"}, false),
Description: "The authentication scheme to use when making SCIM requests to this application.",
},
// HTTP Basic Authentication Attributes
"user": {
- Type: schema.TypeString,
- Optional: true,
- RequiredWith: []string{"scim_config.0.authentication.0.password"},
- ConflictsWith: []string{"scim_config.0.authentication.0.token", "scim_config.0.authentication.0.client_id", "scim_config.0.authentication.0.client_secret", "scim_config.0.authentication.0.authorization_url", "scim_config.0.authentication.0.token_url", "scim_config.0.authentication.0.scopes"},
- Description: "User name used to authenticate with the remote SCIM service.",
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "User name used to authenticate with the remote SCIM service.",
},
"password": {
- Type: schema.TypeString,
- Optional: true,
- RequiredWith: []string{"scim_config.0.authentication.0.user"},
- ConflictsWith: []string{"scim_config.0.authentication.0.token", "scim_config.0.authentication.0.client_id", "scim_config.0.authentication.0.client_secret", "scim_config.0.authentication.0.authorization_url", "scim_config.0.authentication.0.token_url", "scim_config.0.authentication.0.scopes"},
+ Type: schema.TypeString,
+ Optional: true,
StateFunc: func(val interface{}) string {
return CONCEALED_STRING
},
},
// OAuth Bearer Token Authentication Attributes
"token": {
- Type: schema.TypeString,
- Optional: true,
- Description: "Token used to authenticate with the remote SCIM service.",
- ConflictsWith: []string{"scim_config.0.authentication.0.user", "scim_config.0.authentication.0.password", "scim_config.0.authentication.0.client_id", "scim_config.0.authentication.0.client_secret", "scim_config.0.authentication.0.authorization_url", "scim_config.0.authentication.0.token_url", "scim_config.0.authentication.0.scopes"},
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Token used to authenticate with the remote SCIM service.",
StateFunc: func(val interface{}) string {
return CONCEALED_STRING
},
},
// OAuth 2 Authentication Attributes
"client_id": {
- Type: schema.TypeString,
- Optional: true,
- Description: "Client ID used to authenticate when generating a token for authenticating with the remote SCIM service.",
- RequiredWith: []string{"scim_config.0.authentication.0.client_secret", "scim_config.0.authentication.0.authorization_url", "scim_config.0.authentication.0.token_url"},
- ConflictsWith: []string{"scim_config.0.authentication.0.user", "scim_config.0.authentication.0.password", "scim_config.0.authentication.0.token"},
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Client ID used to authenticate when generating a token for authenticating with the remote SCIM service.",
},
"client_secret": {
- Type: schema.TypeString,
- Optional: true,
- Description: "Secret used to authenticate when generating a token for authenticating with the remove SCIM service.",
- RequiredWith: []string{"scim_config.0.authentication.0.client_id", "scim_config.0.authentication.0.authorization_url", "scim_config.0.authentication.0.token_url"},
- ConflictsWith: []string{"scim_config.0.authentication.0.user", "scim_config.0.authentication.0.password", "scim_config.0.authentication.0.token"},
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Secret used to authenticate when generating a token for authenticating with the remove SCIM service.",
StateFunc: func(val interface{}) string {
return CONCEALED_STRING
},
},
"authorization_url": {
- Type: schema.TypeString,
- Optional: true,
- Description: "URL used to generate the auth code used during token generation.",
- RequiredWith: []string{"scim_config.0.authentication.0.client_secret", "scim_config.0.authentication.0.client_id", "scim_config.0.authentication.0.token_url"},
- ConflictsWith: []string{"scim_config.0.authentication.0.user", "scim_config.0.authentication.0.password", "scim_config.0.authentication.0.token"},
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "URL used to generate the auth code used during token generation.",
},
"token_url": {
- Type: schema.TypeString,
- Optional: true,
- Description: "URL used to generate the token used to authenticate with the remote SCIM service.",
- RequiredWith: []string{"scim_config.0.authentication.0.client_secret", "scim_config.0.authentication.0.authorization_url", "scim_config.0.authentication.0.client_id"},
- ConflictsWith: []string{"scim_config.0.authentication.0.user", "scim_config.0.authentication.0.password", "scim_config.0.authentication.0.token"},
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "URL used to generate the token used to authenticate with the remote SCIM service.",
},
"scopes": {
- Type: schema.TypeSet,
- Description: "The authorization scopes to request when generating the token used to authenticate with the remove SCIM service.",
- Optional: true,
- ConflictsWith: []string{"scim_config.0.authentication.0.user", "scim_config.0.authentication.0.password", "scim_config.0.authentication.0.token"},
+ Type: schema.TypeSet,
+ Description: "The authorization scopes to request when generating the token used to authenticate with the remove SCIM service.",
+ Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
@@ -1210,37 +1195,48 @@ func convertScimConfigMappingsSchemaToStruct(mappingData map[string]interface{})
func convertScimConfigAuthenticationSchemaToStruct(d *schema.ResourceData) *cloudflare.AccessApplicationScimAuthenticationJson {
auth := new(cloudflare.AccessApplicationScimAuthenticationJson)
-
- if _, ok := d.GetOk("scim_config.0.authentication"); ok {
- scheme := cloudflare.AccessApplicationScimAuthenticationScheme(d.Get("scim_config.0.authentication.0.scheme").(string))
+ multi := new(cloudflare.AccessApplicationMultipleScimAuthentication)
+ auth.Value = multi
+
+ keyFmt := "scim_config.0.authentication.%d"
+ i := 0
+ for _, ok := d.GetOk(fmt.Sprintf(keyFmt, i)); ok; _, ok = d.GetOk(fmt.Sprintf(keyFmt, i)) {
+ key := fmt.Sprintf(keyFmt, i)
+ scheme := cloudflare.AccessApplicationScimAuthenticationScheme(d.Get(key + ".scheme").(string))
switch scheme {
case cloudflare.AccessApplicationScimAuthenticationSchemeHttpBasic:
base := &cloudflare.AccessApplicationScimAuthenticationHttpBasic{
- User: d.Get("scim_config.0.authentication.0.user").(string),
- Password: d.Get("scim_config.0.authentication.0.password").(string),
+ User: d.Get(key + ".user").(string),
+ Password: d.Get(key + ".password").(string),
}
base.Scheme = scheme
- auth.Value = base
- break
+ *multi = append(*multi, &cloudflare.AccessApplicationScimAuthenticationSingleJSON{Value: base})
case cloudflare.AccessApplicationScimAuthenticationSchemeOauthBearerToken:
base := &cloudflare.AccessApplicationScimAuthenticationOauthBearerToken{
- Token: d.Get("scim_config.0.authentication.0.token").(string),
+ Token: d.Get(key + ".token").(string),
}
base.Scheme = scheme
- auth.Value = base
- break
+ *multi = append(*multi, &cloudflare.AccessApplicationScimAuthenticationSingleJSON{Value: base})
case cloudflare.AccessApplicationScimAuthenticationSchemeOauth2:
base := &cloudflare.AccessApplicationScimAuthenticationOauth2{
- ClientID: d.Get("scim_config.0.authentication.0.client_id").(string),
- ClientSecret: d.Get("scim_config.0.authentication.0.client_secret").(string),
- AuthorizationURL: d.Get("scim_config.0.authentication.0.authorization_url").(string),
- TokenURL: d.Get("scim_config.0.authentication.0.token_url").(string),
- Scopes: expandInterfaceToStringList(d.Get("scim_config.0.authentication.0.scopes").(*schema.Set).List()),
+ ClientID: d.Get(key + ".client_id").(string),
+ ClientSecret: d.Get(key + ".client_secret").(string),
+ AuthorizationURL: d.Get(key + ".authorization_url").(string),
+ TokenURL: d.Get(key + ".token_url").(string),
+ Scopes: expandInterfaceToStringList(d.Get(key + ".scopes").(*schema.Set).List()),
+ }
+ base.Scheme = scheme
+ *multi = append(*multi, &cloudflare.AccessApplicationScimAuthenticationSingleJSON{Value: base})
+ case cloudflare.AccessApplicationScimAuthenticationAccessServiceToken:
+ base := &cloudflare.AccessApplicationScimAuthenticationServiceToken{
+ ClientID: d.Get(key + ".client_id").(string),
+ ClientSecret: d.Get(key + ".client_secret").(string),
}
base.Scheme = scheme
- auth.Value = base
- break
+ *multi = append(*multi, &cloudflare.AccessApplicationScimAuthenticationSingleJSON{Value: base})
}
+
+ i++
}
return auth
@@ -1445,12 +1441,13 @@ func convertScimConfigStructToSchema(scimConfig *cloudflare.AccessApplicationSCI
return []interface{}{}
}
+ auth := convertScimConfigAuthenticationStructToSchema(scimConfig.Authentication)
config := map[string]interface{}{
"enabled": scimConfig.Enabled,
"remote_uri": scimConfig.RemoteURI,
"idp_uid": scimConfig.IdPUID,
"deactivate_on_delete": cloudflare.Bool(scimConfig.DeactivateOnDelete),
- "authentication": convertScimConfigAuthenticationStructToSchema(scimConfig.Authentication),
+ "authentication": auth,
"mappings": convertScimConfigMappingsStructsToSchema(scimConfig.Mappings),
}
@@ -1464,11 +1461,17 @@ func convertScimConfigAuthenticationStructToSchema(scimAuth *cloudflare.AccessAp
auth := map[string]interface{}{}
switch t := scimAuth.Value.(type) {
+ case *cloudflare.AccessApplicationMultipleScimAuthentication:
+ vals := []interface{}{}
+ for _, authn := range *t {
+ vals = append(vals, convertScimConfigSingleAuthentiationToSchema(&cloudflare.AccessApplicationScimAuthenticationJson{Value: authn.Value}))
+ }
+
+ return vals
case *cloudflare.AccessApplicationScimAuthenticationHttpBasic:
auth["scheme"] = t.Scheme
auth["user"] = t.User
auth["password"] = t.Password
-
case *cloudflare.AccessApplicationScimAuthenticationOauthBearerToken:
auth["scheme"] = t.Scheme
auth["token"] = t.Token
@@ -1479,11 +1482,43 @@ func convertScimConfigAuthenticationStructToSchema(scimAuth *cloudflare.AccessAp
auth["authorization_url"] = t.AuthorizationURL
auth["token_url"] = t.TokenURL
auth["scopes"] = t.Scopes
+ case *cloudflare.AccessApplicationScimAuthenticationServiceToken:
+ auth["scheme"] = t.Scheme
+ auth["client_id"] = t.ClientID
+ auth["client_secret"] = t.ClientSecret
}
return []interface{}{auth}
}
+func convertScimConfigSingleAuthentiationToSchema(scimAuth *cloudflare.AccessApplicationScimAuthenticationJson) interface{} {
+ auth := map[string]interface{}{}
+
+ switch t := scimAuth.Value.(type) {
+ case *cloudflare.AccessApplicationScimAuthenticationHttpBasic:
+ auth["scheme"] = t.Scheme
+ auth["user"] = t.User
+ auth["password"] = t.Password
+
+ case *cloudflare.AccessApplicationScimAuthenticationOauthBearerToken:
+ auth["scheme"] = t.Scheme
+ auth["token"] = t.Token
+ case *cloudflare.AccessApplicationScimAuthenticationOauth2:
+ auth["scheme"] = t.Scheme
+ auth["client_id"] = t.ClientID
+ auth["client_secret"] = t.ClientSecret
+ auth["authorization_url"] = t.AuthorizationURL
+ auth["token_url"] = t.TokenURL
+ auth["scopes"] = t.Scopes
+ case *cloudflare.AccessApplicationScimAuthenticationServiceToken:
+ auth["scheme"] = t.Scheme
+ auth["client_id"] = t.ClientID
+ auth["client_secret"] = t.ClientSecret
+ }
+
+ return auth
+}
+
func convertScimConfigMappingsStructsToSchema(mappingsData []*cloudflare.AccessApplicationScimMapping) []interface{} {
mappings := []interface{}{}
diff --git a/internal/sdkv2provider/schema_cloudflare_teams_location.go b/internal/sdkv2provider/schema_cloudflare_teams_location.go
index 2d24e4cdf9..08334f0abf 100644
--- a/internal/sdkv2provider/schema_cloudflare_teams_location.go
+++ b/internal/sdkv2provider/schema_cloudflare_teams_location.go
@@ -45,11 +45,6 @@ func resourceCloudflareTeamsLocationSchema() map[string]*schema.Schema {
Optional: true,
Description: "Indicator that this location needs to resolve EDNS queries.",
},
- "policy_ids": {
- Type: schema.TypeList,
- Elem: &schema.Schema{Type: schema.TypeString},
- Computed: true,
- },
"ip": {
Type: schema.TypeString,
Computed: true,