diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md
index 2e72424d20..bbd93e1eeb 100644
--- a/MIGRATION_GUIDE.md
+++ b/MIGRATION_GUIDE.md
@@ -49,7 +49,7 @@ Fields added to the resource:
New field `enabled` is required. Previously the default value during create in Snowflake was `true`. If you created a resource with Terraform, please add `enabled = true` to have the same value.
#### *(behavior change)* Force new for multiple attributes
-Force new was added for the following attributes (because no usable SQL alter statements for them):
+Force new was added for the following attributes (because there are no usable SQL alter statements for them):
- `scim_client`
- `run_as_role`
diff --git a/docs/index.md b/docs/index.md
index a2e8c0ec88..6ec1a994f7 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -230,6 +230,7 @@ The Snowflake provider will use the following order of precedence when determini
## Currently deprecated resources
- [snowflake_database_old](./docs/resources/database_old)
+- [snowflake_saml_integration](./docs/resources/saml_integration) - use [snowflake_saml2_integration](./docs/resources/saml2_integration) instead
- [snowflake_unsafe_execute](./docs/resources/unsafe_execute)
## Currently deprecated datasources
diff --git a/docs/resources/saml2_integration.md b/docs/resources/saml2_integration.md
new file mode 100644
index 0000000000..998bfab775
--- /dev/null
+++ b/docs/resources/saml2_integration.md
@@ -0,0 +1,332 @@
+---
+page_title: "snowflake_saml2_integration Resource - terraform-provider-snowflake"
+subcategory: ""
+description: |-
+
+---
+
+# snowflake_saml2_integration (Resource)
+
+
+
+## Example Usage
+
+```terraform
+# basic resource
+# each pem file contains a base64 encoded IdP signing certificate on a single line without the leading -----BEGIN CERTIFICATE----- and ending -----END CERTIFICATE----- markers.
+resource "snowflake_saml2_integration" "saml_integration" {
+ name = "saml_integration"
+ saml2_provider = "CUSTOM"
+ saml2_issuer = "test_issuer"
+ saml2_sso_url = "https://example.com"
+ saml2_x509_cert = file("cert.pem")
+}
+# resource with all fields set
+resource "snowflake_saml2_integration" "test" {
+ allowed_email_patterns = ["^(.+dev)@example.com$"]
+ allowed_user_domains = ["example.com"]
+ comment = "foo"
+ enabled = true
+ name = "saml_integration"
+ saml2_enable_sp_initiated = true
+ saml2_force_authn = true
+ saml2_issuer = "foo"
+ saml2_post_logout_redirect_url = "https://example.com"
+ saml2_provider = "CUSTOM"
+ saml2_requested_nameid_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+ saml2_sign_request = true
+ saml2_snowflake_acs_url = "example.snowflakecomputing.com/fed/login"
+ saml2_snowflake_issuer_url = "example.snowflakecomputing.com/fed/login"
+ saml2_snowflake_x509_cert = file("snowflake_cert.pem")
+ saml2_sp_initiated_login_page_label = "foo"
+ saml2_sso_url = "https://example.com"
+ saml2_x509_cert = file("cert.pem")
+}
+```
+
+
+## Schema
+
+### Required
+
+- `name` (String) Specifies the name of the SAML2 integration. This name follows the rules for Object Identifiers. The name should be unique among security integrations in your account.
+- `saml2_issuer` (String) The string containing the IdP EntityID / Issuer.
+- `saml2_provider` (String) The string describing the IdP. Valid options are: [OKTA ADFS CUSTOM].
+- `saml2_sso_url` (String) The string containing the IdP SSO URL, where the user should be redirected by Snowflake (the Service Provider) with a SAML AuthnRequest message.
+- `saml2_x509_cert` (String) The Base64 encoded IdP signing certificate on a single line without the leading -----BEGIN CERTIFICATE----- and ending -----END CERTIFICATE----- markers.
+
+### Optional
+
+- `allowed_email_patterns` (Set of String) A list of regular expressions that email addresses are matched against to authenticate with a SAML2 security integration. If this field changes value from non-empty to empty, the whole resource is recreated because of Snowflake limitations.
+- `allowed_user_domains` (Set of String) A list of email domains that can authenticate with a SAML2 security integration. If this field changes value from non-empty to empty, the whole resource is recreated because of Snowflake limitations.
+- `comment` (String) Specifies a comment for the integration.
+- `enabled` (String) Specifies whether this security integration is enabled or disabled. Available options are: `true` or `false`. When the value is not set in the configuration the provider will put `unknown` there which means to use the Snowflake default for this value.
+- `saml2_enable_sp_initiated` (String) The Boolean indicating if the Log In With button will be shown on the login page. TRUE: displays the Log in With button on the login page. FALSE: does not display the Log in With button on the login page. Available options are: `true` or `false`. When the value is not set in the configuration the provider will put `unknown` there which means to use the Snowflake default for this value.
+- `saml2_force_authn` (String) The Boolean indicating whether users, during the initial authentication flow, are forced to authenticate again to access Snowflake. When set to TRUE, Snowflake sets the ForceAuthn SAML parameter to TRUE in the outgoing request from Snowflake to the identity provider. TRUE: forces users to authenticate again to access Snowflake, even if a valid session with the identity provider exists. FALSE: does not force users to authenticate again to access Snowflake. Available options are: `true` or `false`. When the value is not set in the configuration the provider will put `unknown` there which means to use the Snowflake default for this value.
+- `saml2_post_logout_redirect_url` (String) The endpoint to which Snowflake redirects users after clicking the Log Out button in the classic Snowflake web interface. Snowflake terminates the Snowflake session upon redirecting to the specified endpoint.
+- `saml2_requested_nameid_format` (String) The SAML NameID format allows Snowflake to set an expectation of the identifying attribute of the user (i.e. SAML Subject) in the SAML assertion from the IdP to ensure a valid authentication to Snowflake. Valid options are: [urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos urn:oasis:names:tc:SAML:2.0:nameid-format:persistent urn:oasis:names:tc:SAML:2.0:nameid-format:transient]
+- `saml2_sign_request` (String) The Boolean indicating whether SAML requests are signed. TRUE: allows SAML requests to be signed. FALSE: does not allow SAML requests to be signed. Available options are: `true` or `false`. When the value is not set in the configuration the provider will put `unknown` there which means to use the Snowflake default for this value.
+- `saml2_snowflake_acs_url` (String) The string containing the Snowflake Assertion Consumer Service URL to which the IdP will send its SAML authentication response back to Snowflake. This property will be set in the SAML authentication request generated by Snowflake when initiating a SAML SSO operation with the IdP. If an incorrect value is specified, Snowflake returns an error message indicating the acceptable values to use.
+- `saml2_snowflake_issuer_url` (String) The string containing the EntityID / Issuer for the Snowflake service provider. If an incorrect value is specified, Snowflake returns an error message indicating the acceptable values to use.
+- `saml2_sp_initiated_login_page_label` (String) The string containing the label to display after the Log In With button on the login page. If this field changes value from non-empty to empty, the whole resource is recreated because of Snowflake limitations.
+
+### Read-Only
+
+- `describe_output` (List of Object) Outputs the result of `DESCRIBE SECURITY INTEGRATION` for the given integration. (see [below for nested schema](#nestedatt--describe_output))
+- `id` (String) The ID of this resource.
+- `show_output` (List of Object) Outputs the result of `SHOW SECURITY INTEGRATION` for the given integration. (see [below for nested schema](#nestedatt--show_output))
+
+
+### Nested Schema for `describe_output`
+
+Read-Only:
+
+- `allowed_email_patterns` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--allowed_email_patterns))
+- `allowed_user_domains` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--allowed_user_domains))
+- `comment` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--comment))
+- `saml2_digest_methods_used` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_digest_methods_used))
+- `saml2_enable_sp_initiated` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_enable_sp_initiated))
+- `saml2_force_authn` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_force_authn))
+- `saml2_issuer` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_issuer))
+- `saml2_post_logout_redirect_url` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_post_logout_redirect_url))
+- `saml2_provider` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_provider))
+- `saml2_requested_nameid_format` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_requested_nameid_format))
+- `saml2_sign_request` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_sign_request))
+- `saml2_signature_methods_used` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_signature_methods_used))
+- `saml2_snowflake_acs_url` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_snowflake_acs_url))
+- `saml2_snowflake_issuer_url` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_snowflake_issuer_url))
+- `saml2_snowflake_metadata` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_snowflake_metadata))
+- `saml2_snowflake_x509_cert` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_snowflake_x509_cert))
+- `saml2_sp_initiated_login_page_label` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_sp_initiated_login_page_label))
+- `saml2_sso_url` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_sso_url))
+- `saml2_x509_cert` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--saml2_x509_cert))
+
+
+### Nested Schema for `describe_output.allowed_email_patterns`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.allowed_user_domains`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.comment`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_digest_methods_used`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_enable_sp_initiated`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_force_authn`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_issuer`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_post_logout_redirect_url`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_provider`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_requested_nameid_format`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_sign_request`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_signature_methods_used`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_snowflake_acs_url`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_snowflake_issuer_url`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_snowflake_metadata`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_snowflake_x509_cert`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_sp_initiated_login_page_label`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_sso_url`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+### Nested Schema for `describe_output.saml2_x509_cert`
+
+Read-Only:
+
+- `default` (String)
+- `name` (String)
+- `type` (String)
+- `value` (String)
+
+
+
+
+### Nested Schema for `show_output`
+
+Read-Only:
+
+- `category` (String)
+- `comment` (String)
+- `created_on` (String)
+- `enabled` (Boolean)
+- `integration_type` (String)
+- `name` (String)
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+terraform import snowflake_saml2_integration.example "name"
+```
diff --git a/docs/resources/saml_integration.md b/docs/resources/saml_integration.md
index 05accb3cfe..ccc5d494f7 100644
--- a/docs/resources/saml_integration.md
+++ b/docs/resources/saml_integration.md
@@ -7,7 +7,7 @@ description: |-
# snowflake_saml_integration (Resource)
-
+~> **Deprecation** This resource is deprecated and will be removed in a future major version release. Please use [snowflake_saml2_integration](./saml2_integration) instead.
## Example Usage
diff --git a/docs/resources/scim_integration.md b/docs/resources/scim_integration.md
index 2b5f656711..43278d71f0 100644
--- a/docs/resources/scim_integration.md
+++ b/docs/resources/scim_integration.md
@@ -139,5 +139,5 @@ Read-Only:
Import is supported using the following syntax:
```shell
-terraform import snowflake_scim_integration.example name
+terraform import snowflake_scim_integration.example "name"
```
diff --git a/examples/additional/deprecated_resources.MD b/examples/additional/deprecated_resources.MD
index 979b77f0fd..c0e66e2d2d 100644
--- a/examples/additional/deprecated_resources.MD
+++ b/examples/additional/deprecated_resources.MD
@@ -1,4 +1,5 @@
## Currently deprecated resources
- [snowflake_database_old](./docs/resources/database_old)
+- [snowflake_saml_integration](./docs/resources/saml_integration) - use [snowflake_saml2_integration](./docs/resources/saml2_integration) instead
- [snowflake_unsafe_execute](./docs/resources/unsafe_execute)
diff --git a/examples/resources/snowflake_saml2_integration/import.sh b/examples/resources/snowflake_saml2_integration/import.sh
new file mode 100644
index 0000000000..bf68b01c98
--- /dev/null
+++ b/examples/resources/snowflake_saml2_integration/import.sh
@@ -0,0 +1 @@
+terraform import snowflake_saml2_integration.example "name"
diff --git a/examples/resources/snowflake_saml2_integration/resource.tf b/examples/resources/snowflake_saml2_integration/resource.tf
new file mode 100644
index 0000000000..66f435fa57
--- /dev/null
+++ b/examples/resources/snowflake_saml2_integration/resource.tf
@@ -0,0 +1,30 @@
+# basic resource
+# each pem file contains a base64 encoded IdP signing certificate on a single line without the leading -----BEGIN CERTIFICATE----- and ending -----END CERTIFICATE----- markers.
+resource "snowflake_saml2_integration" "saml_integration" {
+ name = "saml_integration"
+ saml2_provider = "CUSTOM"
+ saml2_issuer = "test_issuer"
+ saml2_sso_url = "https://example.com"
+ saml2_x509_cert = file("cert.pem")
+}
+# resource with all fields set
+resource "snowflake_saml2_integration" "test" {
+ allowed_email_patterns = ["^(.+dev)@example.com$"]
+ allowed_user_domains = ["example.com"]
+ comment = "foo"
+ enabled = true
+ name = "saml_integration"
+ saml2_enable_sp_initiated = true
+ saml2_force_authn = true
+ saml2_issuer = "foo"
+ saml2_post_logout_redirect_url = "https://example.com"
+ saml2_provider = "CUSTOM"
+ saml2_requested_nameid_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+ saml2_sign_request = true
+ saml2_snowflake_acs_url = "example.snowflakecomputing.com/fed/login"
+ saml2_snowflake_issuer_url = "example.snowflakecomputing.com/fed/login"
+ saml2_snowflake_x509_cert = file("snowflake_cert.pem")
+ saml2_sp_initiated_login_page_label = "foo"
+ saml2_sso_url = "https://example.com"
+ saml2_x509_cert = file("cert.pem")
+}
diff --git a/examples/resources/snowflake_scim_integration/import.sh b/examples/resources/snowflake_scim_integration/import.sh
index 86e162e445..365c14b973 100644
--- a/examples/resources/snowflake_scim_integration/import.sh
+++ b/examples/resources/snowflake_scim_integration/import.sh
@@ -1 +1 @@
-terraform import snowflake_scim_integration.example name
+terraform import snowflake_scim_integration.example "name"
diff --git a/pkg/acceptance/check_destroy.go b/pkg/acceptance/check_destroy.go
index f04be3e850..9e2d11420a 100644
--- a/pkg/acceptance/check_destroy.go
+++ b/pkg/acceptance/check_destroy.go
@@ -139,6 +139,9 @@ var showByIdFunctions = map[resources.Resource]showByIdFunc{
resources.RowAccessPolicy: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.RowAccessPolicies.ShowByID)
},
+ resources.Saml2SecurityIntegration: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
+ return runShowById(ctx, id, client.SecurityIntegrations.ShowByID)
+ },
resources.Schema: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Schemas.ShowByID)
},
diff --git a/pkg/acceptance/helpers/common.go b/pkg/acceptance/helpers/common.go
index 4c4aa95fb6..153b007e39 100644
--- a/pkg/acceptance/helpers/common.go
+++ b/pkg/acceptance/helpers/common.go
@@ -4,8 +4,6 @@ import (
"context"
"fmt"
"log"
- "regexp"
- "strings"
"testing"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/snowflakeroles"
@@ -62,15 +60,6 @@ func hasGranteeName(grants []sdk.Grant, role sdk.AccountObjectIdentifier) bool {
return false
}
-// MatchAllStringsInOrderNonOverlapping returns a regex matching every string in parts. Matchings are non overlapping.
-func MatchAllStringsInOrderNonOverlapping(parts []string) *regexp.Regexp {
- escapedParts := make([]string, len(parts))
- for i := range parts {
- escapedParts[i] = regexp.QuoteMeta(parts[i])
- }
- return regexp.MustCompile(strings.Join(escapedParts, "((.|\n)*)"))
-}
-
// AssertErrorContainsPartsFunc returns a function asserting error message contains each string in parts
func AssertErrorContainsPartsFunc(t *testing.T, parts []string) resource.ErrorCheckFunc {
t.Helper()
diff --git a/pkg/acceptance/helpers/common_test.go b/pkg/acceptance/helpers/common_test.go
deleted file mode 100644
index ccad8dcb51..0000000000
--- a/pkg/acceptance/helpers/common_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package helpers
-
-import (
- "testing"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestMatchAllStringsInOrderNonOverlapping(t *testing.T) {
- testCases := map[string]struct {
- parts []string
- text string
- wantMatch bool
- }{
- "empty parts and text": {
- parts: []string{},
- text: "",
- wantMatch: true,
- },
- "empty parts": {
- parts: []string{},
- text: "xyz",
- wantMatch: true,
- },
- "empty text": {
- parts: []string{"a", "b"},
- text: "",
- },
- "matching non empty": {
- parts: []string{"a", "b"},
- text: "xyaxyb",
- wantMatch: true,
- },
- "partial matching": {
- parts: []string{"a", "b"},
- text: "axyz",
- },
- "not matching": {
- parts: []string{"a", "b"},
- text: "xyz",
- },
- "wrong order": {
- parts: []string{"a", "b"},
- text: "ba",
- },
- "overlapping match": {
- parts: []string{"abb", "bba"},
- text: "abba",
- },
- }
-
- for name, tc := range testCases {
- t.Run(name, func(t *testing.T) {
- regex := MatchAllStringsInOrderNonOverlapping(tc.parts)
- require.Equal(t, tc.wantMatch, regex.Match([]byte(tc.text)))
- })
- }
-}
diff --git a/pkg/acceptance/helpers/random/certs.go b/pkg/acceptance/helpers/random/certs.go
index 80a53a83eb..c4bc17460d 100644
--- a/pkg/acceptance/helpers/random/certs.go
+++ b/pkg/acceptance/helpers/random/certs.go
@@ -4,8 +4,10 @@ import (
"bytes"
"crypto/rand"
"crypto/rsa"
+ "crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
+ "encoding/base64"
"encoding/pem"
"fmt"
"math/big"
@@ -38,8 +40,8 @@ func GenerateX509(t *testing.T) string {
return encode(t, "CERTIFICATE", caBytes)
}
-// GenerateRSA returns an RSA public key without BEGIN and END markers.
-func GenerateRSAPublicKey(t *testing.T) string {
+// GenerateRSA returns an RSA public key without BEGIN and END markers, and key's hash.
+func GenerateRSAPublicKey(t *testing.T) (string, string) {
t.Helper()
key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
@@ -47,7 +49,13 @@ func GenerateRSAPublicKey(t *testing.T) string {
pub := key.Public()
b, err := x509.MarshalPKIXPublicKey(pub.(*rsa.PublicKey))
require.NoError(t, err)
- return encode(t, "RSA PUBLIC KEY", b)
+ return encode(t, "RSA PUBLIC KEY", b), hash(t, b)
+}
+
+func hash(t *testing.T, b []byte) string {
+ t.Helper()
+ hash := sha256.Sum256(b)
+ return base64.StdEncoding.EncodeToString(hash[:])
}
func encode(t *testing.T, pemType string, b []byte) string {
diff --git a/pkg/acceptance/helpers/security_integration_client.go b/pkg/acceptance/helpers/security_integration_client.go
index 08d854fbb4..89954b4b7b 100644
--- a/pkg/acceptance/helpers/security_integration_client.go
+++ b/pkg/acceptance/helpers/security_integration_client.go
@@ -27,7 +27,7 @@ func (c *SecurityIntegrationClient) client() sdk.SecurityIntegrations {
func (c *SecurityIntegrationClient) CreateSaml2(t *testing.T, id sdk.AccountObjectIdentifier) (*sdk.SecurityIntegration, func()) {
t.Helper()
- return c.CreateSaml2WithRequest(t, sdk.NewCreateSaml2SecurityIntegrationRequest(id, false, c.ids.Alpha(), "https://example.com", "Custom", random.GenerateX509(t)))
+ return c.CreateSaml2WithRequest(t, sdk.NewCreateSaml2SecurityIntegrationRequest(id, c.ids.Alpha(), "https://example.com", "Custom", random.GenerateX509(t)))
}
func (c *SecurityIntegrationClient) CreateSaml2WithRequest(t *testing.T, request *sdk.CreateSaml2SecurityIntegrationRequest) (*sdk.SecurityIntegration, func()) {
@@ -48,6 +48,19 @@ func (c *SecurityIntegrationClient) CreateScim(t *testing.T) (*sdk.SecurityInteg
return c.CreateScimWithRequest(t, sdk.NewCreateScimSecurityIntegrationRequest(c.ids.RandomAccountObjectIdentifier(), sdk.ScimSecurityIntegrationScimClientGeneric, sdk.ScimSecurityIntegrationRunAsRoleGenericScimProvisioner))
}
+func (c *SecurityIntegrationClient) UpdateSaml2(t *testing.T, request *sdk.AlterSaml2SecurityIntegrationRequest) {
+ t.Helper()
+ ctx := context.Background()
+
+ err := c.client().AlterSaml2(ctx, request)
+ require.NoError(t, err)
+}
+
+func (c *SecurityIntegrationClient) UpdateSaml2ForceAuthn(t *testing.T, id sdk.AccountObjectIdentifier, forceAuthn bool) {
+ t.Helper()
+ c.UpdateSaml2(t, sdk.NewAlterSaml2SecurityIntegrationRequest(id).WithSet(*sdk.NewSaml2IntegrationSetRequest().WithSaml2ForceAuthn(forceAuthn)))
+}
+
func (c *SecurityIntegrationClient) CreateScimWithRequest(t *testing.T, request *sdk.CreateScimSecurityIntegrationRequest) (*sdk.SecurityIntegration, func()) {
t.Helper()
ctx := context.Background()
diff --git a/pkg/acceptance/importchecks/import_checks.go b/pkg/acceptance/importchecks/import_checks.go
index 68b3721485..fe80b244cd 100644
--- a/pkg/acceptance/importchecks/import_checks.go
+++ b/pkg/acceptance/importchecks/import_checks.go
@@ -1,6 +1,7 @@
package importchecks
import (
+ "errors"
"fmt"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
@@ -19,6 +20,22 @@ func ComposeImportStateCheck(fs ...resource.ImportStateCheckFunc) resource.Impor
}
}
+// ComposeAggregateImportStateCheck does the same as ComposeImportStateCheck, but it aggregates all the occurred errors,
+// instead of returning the first encountered one.
+func ComposeAggregateImportStateCheck(fs ...resource.ImportStateCheckFunc) resource.ImportStateCheckFunc {
+ return func(s []*terraform.InstanceState) error {
+ var result []error
+
+ for i, f := range fs {
+ if err := f(s); err != nil {
+ result = append(result, fmt.Errorf("check %d/%d error: %w", i+1, len(fs), err))
+ }
+ }
+
+ return errors.Join(result...)
+ }
+}
+
// TestCheckResourceAttrInstanceState is based on unexported testCheckResourceAttrInstanceState from teststep_providers_test.go
func TestCheckResourceAttrInstanceState(id string, attributeName, attributeValue string) resource.ImportStateCheckFunc {
return func(is []*terraform.InstanceState) error {
@@ -39,3 +56,39 @@ func TestCheckResourceAttrInstanceState(id string, attributeName, attributeValue
return fmt.Errorf("attribute %s not found in instance state", attributeName)
}
}
+
+// TestCheckResourceAttrNotInInstanceState is based on unexported testCheckResourceAttrInstanceState from teststep_providers_test.go,
+// but instead of comparing values, it only checks if the attribute is present in the InstanceState.
+func TestCheckResourceAttrNotInInstanceState(id string, attributeName string) resource.ImportStateCheckFunc {
+ return func(is []*terraform.InstanceState) error {
+ for _, v := range is {
+ if v.ID != id {
+ continue
+ }
+
+ if _, ok := v.Attributes[attributeName]; ok {
+ return fmt.Errorf("attribute %s found in instance state, but expected not to be there", attributeName)
+ }
+ }
+
+ return nil
+ }
+}
+
+// TestCheckResourceAttrInstanceStateSet is based on unexported testCheckResourceAttrInstanceState from teststep_providers_test.go,
+// but instead of comparing values, it only checks if the value is set.
+func TestCheckResourceAttrInstanceStateSet(id string, attributeName string) resource.ImportStateCheckFunc {
+ return func(is []*terraform.InstanceState) error {
+ for _, v := range is {
+ if v.ID != id {
+ continue
+ }
+
+ if _, ok := v.Attributes[attributeName]; ok {
+ return nil
+ }
+ }
+
+ return fmt.Errorf("attribute %s not found in instance state", attributeName)
+ }
+}
diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go
index c3b04d55d9..8c98fdd90a 100644
--- a/pkg/provider/provider.go
+++ b/pkg/provider/provider.go
@@ -461,6 +461,7 @@ func getResources() map[string]*schema.Resource {
"snowflake_role": resources.Role(),
"snowflake_row_access_policy": resources.RowAccessPolicy(),
"snowflake_saml_integration": resources.SAMLIntegration(),
+ "snowflake_saml2_integration": resources.SAML2Integration(),
"snowflake_schema": resources.Schema(),
"snowflake_scim_integration": resources.SCIMIntegration(),
"snowflake_secondary_database": resources.SecondaryDatabase(),
diff --git a/pkg/provider/resources/resources.go b/pkg/provider/resources/resources.go
index daef998c37..6b9fd7add1 100644
--- a/pkg/provider/resources/resources.go
+++ b/pkg/provider/resources/resources.go
@@ -29,6 +29,7 @@ const (
ResourceMonitor resource = "snowflake_resource_monitor"
Role resource = "snowflake_role"
RowAccessPolicy resource = "snowflake_row_access_policy"
+ Saml2SecurityIntegration resource = "snowflake_saml2_integration"
Schema resource = "snowflake_schema"
ScimSecurityIntegration resource = "snowflake_scim_integration"
SecondaryDatabase resource = "snowflake_secondary_database"
diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go
index 72183af3e5..060c43df5c 100644
--- a/pkg/resources/custom_diffs.go
+++ b/pkg/resources/custom_diffs.go
@@ -58,6 +58,30 @@ func ParameterValueComputedIf(key string, parameters []*sdk.Parameter, objectPar
}
}
+// ForceNewIfChangeToEmptySlice sets a ForceNew for a list field which was set to an empty value.
+func ForceNewIfChangeToEmptySlice[T any](key string) schema.CustomizeDiffFunc {
+ return customdiff.ForceNewIfChange(key, func(ctx context.Context, oldValue, newValue, meta any) bool {
+ oldList, newList := oldValue.([]T), newValue.([]T)
+ return len(oldList) > 0 && len(newList) == 0
+ })
+}
+
+// ForceNewIfChangeToEmptySet sets a ForceNew for a list field which was set to an empty value.
+func ForceNewIfChangeToEmptySet(key string) schema.CustomizeDiffFunc {
+ return customdiff.ForceNewIfChange(key, func(ctx context.Context, oldValue, newValue, meta any) bool {
+ oldList, newList := oldValue.(*schema.Set).List(), newValue.(*schema.Set).List()
+ return len(oldList) > 0 && len(newList) == 0
+ })
+}
+
+// ForceNewIfChangeToEmptyString sets a ForceNew for a string field which was set to an empty value.
+func ForceNewIfChangeToEmptyString(key string) schema.CustomizeDiffFunc {
+ return customdiff.ForceNewIfChange(key, func(ctx context.Context, oldValue, newValue, meta any) bool {
+ oldString, newString := oldValue.(string), newValue.(string)
+ return len(oldString) > 0 && len(newString) == 0
+ })
+}
+
// TODO [follow-up PR]: test
func ComputedIfAnyAttributeChanged(key string, changedAttributeKeys ...string) schema.CustomizeDiffFunc {
return customdiff.ComputedIf(key, func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) bool {
diff --git a/pkg/resources/custom_diffs_test.go b/pkg/resources/custom_diffs_test.go
index 17bcc3ae90..6e791672eb 100644
--- a/pkg/resources/custom_diffs_test.go
+++ b/pkg/resources/custom_diffs_test.go
@@ -5,7 +5,6 @@ import (
"testing"
acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance"
-
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/resources"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
@@ -31,7 +30,11 @@ func TestParameterValueComputedIf(t *testing.T) {
sdk.AccountParameterLogLevel,
func(v any) string { return v.(string) },
)
- return createProviderWithValuePropertyAndCustomDiff(t, schema.TypeString, customDiff)
+ return createProviderWithValuePropertyAndCustomDiff(t, &schema.Schema{
+ Type: schema.TypeString,
+ Computed: true,
+ Optional: true,
+ }, customDiff)
}
t.Run("config: true - state: true - level: different - value: same", func(t *testing.T) {
@@ -110,17 +113,13 @@ func TestParameterValueComputedIf(t *testing.T) {
// of getting into this situation would be in create operation for which custom diffs are skipped.
}
-func createProviderWithValuePropertyAndCustomDiff(t *testing.T, valueType schema.ValueType, customDiffFunc schema.CustomizeDiffFunc) *schema.Provider {
+func createProviderWithValuePropertyAndCustomDiff(t *testing.T, valueSchema *schema.Schema, customDiffFunc schema.CustomizeDiffFunc) *schema.Provider {
t.Helper()
return &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"test": {
Schema: map[string]*schema.Schema{
- "value": {
- Type: valueType,
- Computed: true,
- Optional: true,
- },
+ "value": valueSchema,
},
CustomizeDiff: customDiffFunc,
},
@@ -143,3 +142,202 @@ func calculateDiff(t *testing.T, providerConfig *schema.Provider, rawConfigValue
require.NoError(t, err)
return diff
}
+
+func calculateDiffFromAttributes(t *testing.T, providerConfig *schema.Provider, oldValue map[string]string, newValue map[string]any) *terraform.InstanceDiff {
+ t.Helper()
+ diff, err := providerConfig.ResourcesMap["test"].Diff(
+ context.Background(),
+ &terraform.InstanceState{
+ Attributes: oldValue,
+ },
+ &terraform.ResourceConfig{
+ Config: newValue,
+ },
+ &provider.Context{Client: acc.Client(t)},
+ )
+ require.NoError(t, err)
+ return diff
+}
+
+func TestForceNewIfChangeToEmptyString(t *testing.T) {
+ tests := []struct {
+ name string
+ stateValue map[string]string
+ rawConfigValue map[string]any
+ wantForceNew bool
+ }{
+ {
+ name: "empty to non-empty",
+ stateValue: map[string]string{},
+ rawConfigValue: map[string]any{
+ "value": "foo",
+ },
+ wantForceNew: false,
+ }, {
+ name: "empty to empty",
+ stateValue: map[string]string{},
+ rawConfigValue: map[string]any{},
+ wantForceNew: false,
+ }, {
+ name: "non-empty to empty",
+ stateValue: map[string]string{
+ "value": "foo",
+ },
+ rawConfigValue: map[string]any{},
+ wantForceNew: true,
+ }, {
+ name: "non-empty to non-empty",
+ stateValue: map[string]string{
+ "value": "bar",
+ },
+ rawConfigValue: map[string]any{
+ "value": "foo",
+ },
+ wantForceNew: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ customDiff := resources.ForceNewIfChangeToEmptyString(
+ "value",
+ )
+ provider := createProviderWithValuePropertyAndCustomDiff(t, &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ }, customDiff)
+ diff := calculateDiffFromAttributes(
+ t,
+ provider,
+ tt.stateValue,
+ tt.rawConfigValue,
+ )
+ assert.Equal(t, tt.wantForceNew, diff.RequiresNew())
+ })
+ }
+}
+
+func TestForceNewIfChangeToEmptySlice(t *testing.T) {
+ tests := []struct {
+ name string
+ stateValue map[string]string
+ rawConfigValue map[string]any
+ wantForceNew bool
+ }{
+ {
+ name: "empty to non-empty",
+ stateValue: map[string]string{},
+ rawConfigValue: map[string]any{
+ "value": []any{"foo"},
+ },
+ wantForceNew: false,
+ }, {
+ name: "empty to empty",
+ stateValue: map[string]string{},
+ rawConfigValue: map[string]any{},
+ wantForceNew: false,
+ }, {
+ name: "non-empty to empty",
+ stateValue: map[string]string{
+ "value.#": "1",
+ "value.0": "foo",
+ },
+ rawConfigValue: map[string]any{},
+ wantForceNew: true,
+ }, {
+ name: "non-empty to non-empty",
+ stateValue: map[string]string{
+ "value.#": "2",
+ "value.0": "foo",
+ "value.1": "bar",
+ },
+ rawConfigValue: map[string]any{
+ "value": []any{"foo"},
+ },
+ wantForceNew: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ customDiff := resources.ForceNewIfChangeToEmptySlice[any](
+ "value",
+ )
+ provider := createProviderWithValuePropertyAndCustomDiff(t, &schema.Schema{
+ Type: schema.TypeList,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Optional: true,
+ }, customDiff)
+ diff := calculateDiffFromAttributes(
+ t,
+ provider,
+ tt.stateValue,
+ tt.rawConfigValue,
+ )
+ assert.Equal(t, tt.wantForceNew, diff.RequiresNew())
+ })
+ }
+}
+
+func TestForceNewIfChangeToEmptySet(t *testing.T) {
+ tests := []struct {
+ name string
+ stateValue map[string]string
+ rawConfigValue map[string]any
+ wantForceNew bool
+ }{
+ {
+ name: "empty to non-empty",
+ stateValue: map[string]string{},
+ rawConfigValue: map[string]any{
+ "value": []any{"foo"},
+ },
+ wantForceNew: false,
+ }, {
+ name: "empty to empty",
+ stateValue: map[string]string{},
+ rawConfigValue: map[string]any{},
+ wantForceNew: false,
+ }, {
+ name: "non-empty to empty",
+ stateValue: map[string]string{
+ "value.#": "1",
+ "value.2577344683": "CREATE DATABASE",
+ },
+ rawConfigValue: map[string]any{},
+ wantForceNew: true,
+ }, {
+ name: "non-empty to non-empty",
+ stateValue: map[string]string{
+ "value.#": "2",
+ "value.0": "foo",
+ "value.1": "bar",
+ },
+ rawConfigValue: map[string]any{
+ "value": []any{"foo"},
+ },
+ wantForceNew: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ diff := calculateDiffFromAttributes(t,
+ createProviderWithValuePropertyAndCustomDiff(t,
+ &schema.Schema{
+ Type: schema.TypeSet,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Optional: true,
+ },
+ resources.ForceNewIfChangeToEmptySet(
+ "value",
+ ),
+ ),
+ tt.stateValue,
+ tt.rawConfigValue,
+ )
+ assert.Equal(t, tt.wantForceNew, diff.RequiresNew())
+ })
+ }
+}
diff --git a/pkg/resources/helpers_test.go b/pkg/resources/helpers_test.go
index 1b4170d41b..6447fa8e93 100644
--- a/pkg/resources/helpers_test.go
+++ b/pkg/resources/helpers_test.go
@@ -191,136 +191,6 @@ func queriedPrivilegesContainAtLeast(query func(client *sdk.Client, ctx context.
}
}
-func TestGetFirstNestedObjectByKey(t *testing.T) {
- d := schema.TestResourceDataRaw(t, map[string]*schema.Schema{
- "int_property": {
- Type: schema.TypeList,
- MaxItems: 1,
- Elem: &schema.Resource{
- Schema: map[string]*schema.Schema{
- "value": {
- Type: schema.TypeInt,
- },
- },
- },
- },
- "string_property": {
- Type: schema.TypeList,
- MaxItems: 1,
- Elem: &schema.Resource{
- Schema: map[string]*schema.Schema{
- "value": {
- Type: schema.TypeString,
- },
- },
- },
- },
- "list_property": {
- Type: schema.TypeList,
- MaxItems: 1,
- Elem: &schema.Resource{
- Schema: map[string]*schema.Schema{
- "value": {
- Type: schema.TypeList,
- Elem: &schema.Schema{
- Type: schema.TypeString,
- },
- },
- },
- },
- },
- "multiple_list_properties": {
- Type: schema.TypeList,
- MaxItems: 1,
- Elem: &schema.Resource{
- Schema: map[string]*schema.Schema{
- "value": {
- Type: schema.TypeList,
- Elem: &schema.Schema{
- Type: schema.TypeString,
- },
- },
- },
- },
- },
- "list": {
- Type: schema.TypeList,
- Elem: &schema.Schema{
- Type: schema.TypeString,
- },
- },
- "empty list": {
- Type: schema.TypeList,
- Elem: &schema.Schema{
- Type: schema.TypeString,
- },
- },
- "not_property": {
- Type: schema.TypeString,
- },
- }, map[string]any{
- "int_property": []any{
- map[string]any{
- "value": 123,
- },
- },
- "string_property": []any{
- map[string]any{
- "value": "some string",
- },
- },
- "list": []any{"one"},
- "empty_list": []any{},
- "list_property": []any{
- map[string]any{
- "value": []any{"one", "two", "three"},
- },
- },
- "multiple_list_properties": []any{
- map[string]any{
- "value": []any{"one", "two", "three"},
- },
- map[string]any{
- "value": []any{"one", "two", "three"},
- },
- },
- "not_property": "not a property",
- })
-
- intValue, err := resources.GetPropertyOfFirstNestedObjectByKey[int](d, "int_property", "value")
- assert.NoError(t, err)
- assert.Equal(t, 123, *intValue)
-
- stringValue, err := resources.GetPropertyOfFirstNestedObjectByKey[string](d, "string_property", "value")
- assert.NoError(t, err)
- assert.Equal(t, "some string", *stringValue)
-
- listValue, err := resources.GetPropertyOfFirstNestedObjectByKey[[]any](d, "list_property", "value")
- assert.NoError(t, err)
- assert.Equal(t, []any{"one", "two", "three"}, *listValue)
-
- _, err = resources.GetPropertyOfFirstNestedObjectByKey[any](d, "non_existing_property_key", "non_existing_value_key")
- assert.ErrorContains(t, err, "nested property non_existing_property_key not found")
-
- _, err = resources.GetPropertyOfFirstNestedObjectByKey[any](d, "not_property", "value")
- assert.ErrorContains(t, err, "nested property not_property is not an array or has incorrect number of values: 0, expected: 1")
-
- _, err = resources.GetPropertyOfFirstNestedObjectByKey[any](d, "empty_list", "value")
- assert.ErrorContains(t, err, "nested property empty_list not found") // Empty list is a default value, so it's treated as "not set"
-
- _, err = resources.GetPropertyOfFirstNestedObjectByKey[any](d, "multiple_list_properties", "value")
- assert.ErrorContains(t, err, "nested property multiple_list_properties is not an array or has incorrect number of values: 2, expected: 1")
-
- _, err = resources.GetPropertyOfFirstNestedObjectByKey[any](d, "list", "value")
- assert.ErrorContains(t, err, "nested property list is not of type map[string]any, got: string")
-
- _, err = resources.GetPropertyOfFirstNestedObjectByKey[any](d, "int_property", "non_existing_value_key")
- assert.ErrorContains(t, err, "nested value key non_existing_value_key couldn't be found in the nested property map int_property")
-
- _, err = resources.GetPropertyOfFirstNestedObjectByKey[int](d, "string_property", "value")
- assert.ErrorContains(t, err, "nested property string_property.value is not of type int, got: string")
-}
-
func TestListDiff(t *testing.T) {
testCases := []struct {
Name string
diff --git a/pkg/resources/saml2_integration.go b/pkg/resources/saml2_integration.go
new file mode 100644
index 0000000000..259c8847df
--- /dev/null
+++ b/pkg/resources/saml2_integration.go
@@ -0,0 +1,849 @@
+package resources
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "reflect"
+ "strconv"
+
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections"
+
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers"
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/logging"
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider"
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas"
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+var saml2IntegrationSchema = map[string]*schema.Schema{
+ "name": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Specifies the name of the SAML2 integration. This name follows the rules for Object Identifiers. The name should be unique among security integrations in your account.",
+ },
+ "enabled": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "unknown",
+ ValidateDiagFunc: StringInSlice([]string{"true", "false"}, false),
+ DiffSuppressFunc: SuppressIfAny(ignoreCaseSuppressFunc, IgnoreChangeToCurrentSnowflakeValueInShow("enabled")),
+ Description: "Specifies whether this security integration is enabled or disabled. Available options are: `true` or `false`. When the value is not set in the configuration the provider will put `unknown` there which means to use the Snowflake default for this value.",
+ },
+ "saml2_issuer": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The string containing the IdP EntityID / Issuer.",
+ },
+ "saml2_sso_url": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The string containing the IdP SSO URL, where the user should be redirected by Snowflake (the Service Provider) with a SAML AuthnRequest message.",
+ },
+ "saml2_provider": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateDiagFunc: sdkValidation(sdk.ToSaml2SecurityIntegrationSaml2ProviderOption),
+ Description: fmt.Sprintf("The string describing the IdP. Valid options are: %v.", sdk.AllSaml2SecurityIntegrationSaml2Providers),
+ },
+ "saml2_x509_cert": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The Base64 encoded IdP signing certificate on a single line without the leading -----BEGIN CERTIFICATE----- and ending -----END CERTIFICATE----- markers.",
+ },
+ "saml2_sp_initiated_login_page_label": {
+ Type: schema.TypeString,
+ Optional: true,
+ DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("saml2_sp_initiated_login_page_label"),
+ Description: "The string containing the label to display after the Log In With button on the login page. If this field changes value from non-empty to empty, the whole resource is recreated because of Snowflake limitations.",
+ },
+ "saml2_enable_sp_initiated": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "unknown",
+ ValidateDiagFunc: StringInSlice([]string{"true", "false"}, false),
+ DiffSuppressFunc: SuppressIfAny(ignoreCaseSuppressFunc, IgnoreChangeToCurrentSnowflakeValueInDescribe("saml2_enable_sp_initiated")),
+ Description: "The Boolean indicating if the Log In With button will be shown on the login page. TRUE: displays the Log in With button on the login page. FALSE: does not display the Log in With button on the login page. Available options are: `true` or `false`. When the value is not set in the configuration the provider will put `unknown` there which means to use the Snowflake default for this value.",
+ },
+ "saml2_sign_request": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "unknown",
+ ValidateDiagFunc: StringInSlice([]string{"true", "false"}, false),
+ DiffSuppressFunc: SuppressIfAny(ignoreCaseSuppressFunc, IgnoreChangeToCurrentSnowflakeValueInDescribe("saml2_sign_request")),
+ Description: "The Boolean indicating whether SAML requests are signed. TRUE: allows SAML requests to be signed. FALSE: does not allow SAML requests to be signed. Available options are: `true` or `false`. When the value is not set in the configuration the provider will put `unknown` there which means to use the Snowflake default for this value.",
+ },
+ "saml2_requested_nameid_format": {
+ Type: schema.TypeString,
+ Optional: true,
+ ValidateDiagFunc: sdkValidation(sdk.ToSaml2SecurityIntegrationSaml2RequestedNameidFormatOption),
+ DiffSuppressFunc: SuppressIfAny(ignoreCaseSuppressFunc, IgnoreChangeToCurrentSnowflakeValueInDescribe("saml2_requested_nameid_format")),
+ Description: fmt.Sprintf("The SAML NameID format allows Snowflake to set an expectation of the identifying attribute of the user (i.e. SAML Subject) in the SAML assertion from the IdP to ensure a valid authentication to Snowflake. Valid options are: %v", sdk.AllSaml2SecurityIntegrationSaml2RequestedNameidFormats),
+ },
+ "saml2_post_logout_redirect_url": {
+ Type: schema.TypeString,
+ Optional: true,
+ DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("saml2_post_logout_redirect_url"),
+ Description: "The endpoint to which Snowflake redirects users after clicking the Log Out button in the classic Snowflake web interface. Snowflake terminates the Snowflake session upon redirecting to the specified endpoint.",
+ },
+ "saml2_force_authn": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "unknown",
+ ValidateDiagFunc: StringInSlice([]string{"true", "false"}, false),
+ DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("saml2_force_authn"),
+ Description: "The Boolean indicating whether users, during the initial authentication flow, are forced to authenticate again to access Snowflake. When set to TRUE, Snowflake sets the ForceAuthn SAML parameter to TRUE in the outgoing request from Snowflake to the identity provider. TRUE: forces users to authenticate again to access Snowflake, even if a valid session with the identity provider exists. FALSE: does not force users to authenticate again to access Snowflake. Available options are: `true` or `false`. When the value is not set in the configuration the provider will put `unknown` there which means to use the Snowflake default for this value.",
+ },
+ "saml2_snowflake_issuer_url": {
+ Type: schema.TypeString,
+ Optional: true,
+ DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("saml2_snowflake_issuer_url"),
+ Description: "The string containing the EntityID / Issuer for the Snowflake service provider. If an incorrect value is specified, Snowflake returns an error message indicating the acceptable values to use.",
+ },
+ "saml2_snowflake_acs_url": {
+ Type: schema.TypeString,
+ Optional: true,
+ DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("saml2_snowflake_acs_url"),
+ Description: "The string containing the Snowflake Assertion Consumer Service URL to which the IdP will send its SAML authentication response back to Snowflake. This property will be set in the SAML authentication request generated by Snowflake when initiating a SAML SSO operation with the IdP. If an incorrect value is specified, Snowflake returns an error message indicating the acceptable values to use.",
+ },
+ "allowed_user_domains": {
+ Type: schema.TypeSet,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Optional: true,
+ Description: "A list of email domains that can authenticate with a SAML2 security integration. If this field changes value from non-empty to empty, the whole resource is recreated because of Snowflake limitations.",
+ },
+ "allowed_email_patterns": {
+ Type: schema.TypeSet,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Optional: true,
+ Description: "A list of regular expressions that email addresses are matched against to authenticate with a SAML2 security integration. If this field changes value from non-empty to empty, the whole resource is recreated because of Snowflake limitations.",
+ },
+ "comment": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Specifies a comment for the integration.",
+ },
+ ShowOutputAttributeName: {
+ Type: schema.TypeList,
+ Computed: true,
+ Description: "Outputs the result of `SHOW SECURITY INTEGRATION` for the given integration.",
+ Elem: &schema.Resource{
+ Schema: schemas.ShowSecurityIntegrationSchema,
+ },
+ },
+ DescribeOutputAttributeName: {
+ Type: schema.TypeList,
+ Computed: true,
+ Description: "Outputs the result of `DESCRIBE SECURITY INTEGRATION` for the given integration.",
+ Elem: &schema.Resource{
+ Schema: schemas.DescribeSaml2IntegrationSchema,
+ },
+ },
+}
+
+func SAML2Integration() *schema.Resource {
+ return &schema.Resource{
+ CreateContext: CreateContextSAML2Integration,
+ ReadContext: ReadContextSAML2Integration(true),
+ UpdateContext: UpdateContextSAML2Integration,
+ DeleteContext: DeleteContextSAM2LIntegration,
+
+ Schema: saml2IntegrationSchema,
+ Importer: &schema.ResourceImporter{
+ StateContext: ImportSaml2Integration,
+ },
+
+ CustomizeDiff: customdiff.All(
+ ForceNewIfChangeToEmptySet("allowed_user_domains"),
+ ForceNewIfChangeToEmptySet("allowed_email_patterns"),
+ ForceNewIfChangeToEmptyString("saml2_snowflake_issuer_url"),
+ ForceNewIfChangeToEmptyString("saml2_snowflake_acs_url"),
+ ForceNewIfChangeToEmptyString("saml2_sp_initiated_login_page_label"),
+ ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "name", "enabled", "comment"),
+ ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "saml2_issuer", "saml2_sso_url", "saml2_provider", "saml2_x509_cert",
+ "saml2_sp_initiated_login_page_label", "saml2_enable_sp_initiated", "saml2_sign_request", "saml2_requtedted_nameid_format",
+ "saml2_post_logout_redirect_url", "saml2_force_authn", "saml2_snowflake_issuer_url", "saml2_snowflake_acs_url", "allowed_user_domains",
+ "allowed_email_patterns"),
+ ),
+ }
+}
+
+func ImportSaml2Integration(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
+ logging.DebugLogger.Printf("[DEBUG] Starting saml2 integration import")
+ client := meta.(*provider.Context).Client
+ id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier)
+
+ integration, err := client.SecurityIntegrations.ShowByID(ctx, id)
+ if err != nil {
+ return nil, err
+ }
+
+ integrationProperties, err := client.SecurityIntegrations.Describe(ctx, id)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := d.Set("name", sdk.NewAccountObjectIdentifier(integration.Name).Name()); err != nil {
+ return nil, err
+ }
+ if err := d.Set("comment", integration.Comment); err != nil {
+ return nil, err
+ }
+ if err := d.Set("enabled", fmt.Sprintf("%t", integration.Enabled)); err != nil {
+ return nil, err
+ }
+
+ samlIssuer, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SAML2_ISSUER" })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find saml2 saml issuer, err = %w", err)
+ }
+ if err := d.Set("saml2_issuer", samlIssuer.Value); err != nil {
+ return nil, err
+ }
+
+ ssoUrl, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SAML2_SSO_URL" })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find saml2 sso url, err = %w", err)
+ }
+ if err := d.Set("saml2_sso_url", ssoUrl.Value); err != nil {
+ return nil, err
+ }
+
+ samlProvider, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SAML2_PROVIDER" })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find saml2 provider, err = %w", err)
+ }
+ if err := d.Set("saml2_provider", samlProvider.Value); err != nil {
+ return nil, err
+ }
+
+ x509Cert, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SAML2_X509_CERT" })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find saml2 x509 cert, err = %w", err)
+ }
+ if err := d.Set("saml2_x509_cert", x509Cert.Value); err != nil {
+ return nil, err
+ }
+
+ spInitiatedLoginPageLabel, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_SP_INITIATED_LOGIN_PAGE_LABEL"
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find saml2 sp initiated login page label, err = %w", err)
+ }
+ if err := d.Set("saml2_sp_initiated_login_page_label", spInitiatedLoginPageLabel.Value); err != nil {
+ return nil, err
+ }
+
+ enableSpInitiated, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_ENABLE_SP_INITIATED"
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find saml2 enable sp initiated, err = %w", err)
+ }
+ if err := d.Set("saml2_enable_sp_initiated", enableSpInitiated.Value); err != nil {
+ return nil, err
+ }
+
+ signRequest, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_SIGN_REQUEST"
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find saml2 sign request, err = %w", err)
+ }
+ if err := d.Set("saml2_sign_request", signRequest.Value); err != nil {
+ return nil, err
+ }
+
+ requestedNameIdFormat, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_REQUESTED_NAMEID_FORMAT"
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find saml2 requested nameid format, err = %w", err)
+ }
+ if err := d.Set("saml2_requested_nameid_format", requestedNameIdFormat.Value); err != nil {
+ return nil, err
+ }
+
+ postLogoutRedirectUrl, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_POST_LOGOUT_REDIRECT_URL"
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find saml2 post logout redirect url, err = %w", err)
+ }
+ if err := d.Set("saml2_post_logout_redirect_url", postLogoutRedirectUrl.Value); err != nil {
+ return nil, err
+ }
+
+ forceAuthn, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_FORCE_AUTHN"
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find saml2 force authn, err = %w", err)
+ }
+ if err := d.Set("saml2_force_authn", forceAuthn.Value); err != nil {
+ return nil, err
+ }
+
+ snowflakeIssuerUrl, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_SNOWFLAKE_ISSUER_URL"
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find saml2 snowflake issuer url, err = %w", err)
+ }
+ if err := d.Set("saml2_snowflake_issuer_url", snowflakeIssuerUrl.Value); err != nil {
+ return nil, err
+ }
+
+ snowflakeAcsUrl, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_SNOWFLAKE_ACS_URL"
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find saml2 snowflake acs url, err = %w", err)
+ }
+ if err := d.Set("saml2_snowflake_acs_url", snowflakeAcsUrl.Value); err != nil {
+ return nil, err
+ }
+
+ allowedUserDomains, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "ALLOWED_USER_DOMAINS"
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find allowed user domains, err = %w", err)
+ }
+ if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value)); err != nil {
+ return nil, err
+ }
+
+ allowedEmailDomains, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "ALLOWED_EMAIL_PATTERNS"
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to find allowed email patterns, err = %w", err)
+ }
+ if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value)); err != nil {
+ return nil, err
+ }
+
+ return []*schema.ResourceData{d}, nil
+}
+
+func CreateContextSAML2Integration(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
+ client := meta.(*provider.Context).Client
+
+ samlProvider, err := sdk.ToSaml2SecurityIntegrationSaml2ProviderOption(d.Get("saml2_provider").(string))
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ req := sdk.NewCreateSaml2SecurityIntegrationRequest(
+ sdk.NewAccountObjectIdentifier(d.Get("name").(string)),
+ d.Get("saml2_issuer").(string),
+ d.Get("saml2_sso_url").(string),
+ samlProvider,
+ d.Get("saml2_x509_cert").(string),
+ )
+
+ if v := d.Get("enabled").(string); v != "unknown" {
+ parsed, err := strconv.ParseBool(v)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ req.WithEnabled(parsed)
+ }
+
+ if v, ok := d.GetOk("saml2_sp_initiated_login_page_label"); ok {
+ req.WithSaml2SpInitiatedLoginPageLabel(v.(string))
+ }
+
+ if v := d.Get("saml2_enable_sp_initiated").(string); v != "unknown" {
+ parsed, err := strconv.ParseBool(v)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ req.WithSaml2EnableSpInitiated(parsed)
+ }
+
+ if v := d.Get("saml2_sign_request").(string); v != "unknown" {
+ parsed, err := strconv.ParseBool(v)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ req.WithSaml2SignRequest(parsed)
+ }
+
+ if v, ok := d.GetOk("saml2_requested_nameid_format"); ok {
+ format, err := sdk.ToSaml2SecurityIntegrationSaml2RequestedNameidFormatOption(v.(string))
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ req.WithSaml2RequestedNameidFormat(format)
+ }
+
+ if v, ok := d.GetOk("saml2_post_logout_redirect_url"); ok {
+ req.WithSaml2PostLogoutRedirectUrl(v.(string))
+ }
+
+ if v := d.Get("saml2_force_authn").(string); v != "unknown" {
+ parsed, err := strconv.ParseBool(v)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ req.WithSaml2ForceAuthn(parsed)
+ }
+
+ if v, ok := d.GetOk("saml2_snowflake_issuer_url"); ok {
+ req.WithSaml2SnowflakeIssuerUrl(v.(string))
+ }
+
+ if v, ok := d.GetOk("saml2_snowflake_acs_url"); ok {
+ req.WithSaml2SnowflakeAcsUrl(v.(string))
+ }
+
+ if v, ok := d.GetOk("allowed_user_domains"); ok {
+ stringAllowedUserDomains := expandStringList(v.(*schema.Set).List())
+ allowedUserDomains := make([]sdk.UserDomain, len(stringAllowedUserDomains))
+ for i, v := range stringAllowedUserDomains {
+ allowedUserDomains[i] = sdk.UserDomain{
+ Domain: v,
+ }
+ }
+ req.WithAllowedUserDomains(allowedUserDomains)
+ }
+
+ if v, ok := d.GetOk("allowed_email_patterns"); ok {
+ stringAllowedEmailPatterns := expandStringList(v.(*schema.Set).List())
+ allowedEmailPatterns := make([]sdk.EmailPattern, len(stringAllowedEmailPatterns))
+ for i, v := range stringAllowedEmailPatterns {
+ allowedEmailPatterns[i] = sdk.EmailPattern{
+ Pattern: v,
+ }
+ }
+ req.WithAllowedEmailPatterns(allowedEmailPatterns)
+ }
+
+ if v, ok := d.GetOk("comment"); ok {
+ req.WithComment(v.(string))
+ }
+
+ if err := client.SecurityIntegrations.CreateSaml2(ctx, req); err != nil {
+ return diag.FromErr(err)
+ }
+
+ d.SetId(d.Get("name").(string))
+
+ return ReadContextSAML2Integration(false)(ctx, d, meta)
+}
+
+func ReadContextSAML2Integration(withExternalChangesMarking bool) schema.ReadContextFunc {
+ return func(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
+ client := meta.(*provider.Context).Client
+ id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier)
+
+ integration, err := client.SecurityIntegrations.ShowByID(ctx, id)
+ if err != nil {
+ if errors.Is(err, sdk.ErrObjectNotFound) {
+ d.SetId("")
+ return diag.Diagnostics{
+ diag.Diagnostic{
+ Severity: diag.Warning,
+ Summary: "Failed to query security integration. Marking the resource as removed.",
+ Detail: fmt.Sprintf("Security integration name: %s, Err: %s", id.FullyQualifiedName(), err),
+ },
+ }
+ }
+ return diag.FromErr(err)
+ }
+
+ integrationProperties, err := client.SecurityIntegrations.Describe(ctx, id)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ if c := integration.Category; c != sdk.SecurityIntegrationCategory {
+ return diag.FromErr(fmt.Errorf("expected %v to be a %s integration, got %v", id, sdk.SecurityIntegrationCategory, c))
+ }
+
+ if err := d.Set("name", sdk.NewAccountObjectIdentifier(integration.Name).Name()); err != nil {
+ return diag.FromErr(err)
+ }
+
+ samlIssuer, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SAML2_ISSUER" })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find saml2 saml issuer, err = %w", err))
+ }
+ if err := d.Set("saml2_issuer", samlIssuer.Value); err != nil {
+ return diag.FromErr(err)
+ }
+
+ ssoUrl, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SAML2_SSO_URL" })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find saml2 sso url, err = %w", err))
+ }
+ if err := d.Set("saml2_sso_url", ssoUrl.Value); err != nil {
+ return diag.FromErr(err)
+ }
+
+ samlProvider, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SAML2_PROVIDER" })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find saml2 provider, err = %w", err))
+ }
+ if err := d.Set("saml2_provider", samlProvider.Value); err != nil {
+ return diag.FromErr(err)
+ }
+
+ x509Cert, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SAML2_X509_CERT" })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find saml2 x509 cert, err = %w", err))
+ }
+ if err := d.Set("saml2_x509_cert", x509Cert.Value); err != nil {
+ return diag.FromErr(err)
+ }
+
+ postLogoutRedirectUrl, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_POST_LOGOUT_REDIRECT_URL"
+ })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find saml2 post logout redirect url, err = %w", err))
+ }
+ if err := d.Set("saml2_post_logout_redirect_url", postLogoutRedirectUrl.Value); err != nil {
+ return diag.FromErr(err)
+ }
+
+ allowedUserDomains, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "ALLOWED_USER_DOMAINS"
+ })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find allowed user domains, err = %w", err))
+ }
+ if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value)); err != nil {
+ return diag.FromErr(err)
+ }
+
+ allowedEmailDomains, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "ALLOWED_EMAIL_PATTERNS"
+ })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find allowed email patterns, err = %w", err))
+ }
+ if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value)); err != nil {
+ return diag.FromErr(err)
+ }
+
+ if err := d.Set("comment", integration.Comment); err != nil {
+ return diag.FromErr(err)
+ }
+
+ if withExternalChangesMarking {
+ if err = handleExternalChangesToObjectInShow(d,
+ showMapping{"enabled", "enabled", integration.Enabled, integration.Enabled, nil},
+ ); err != nil {
+ return diag.FromErr(err)
+ }
+
+ enableSpInitiated, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_ENABLE_SP_INITIATED"
+ })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find saml2 enable sp initiated, err = %w", err))
+ }
+
+ signRequest, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_SIGN_REQUEST"
+ })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find saml2 sign request, err = %w", err))
+ }
+
+ requestedNameIdFormat, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_REQUESTED_NAMEID_FORMAT"
+ })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find saml2 requested nameid format, err = %w", err))
+ }
+
+ forceAuthn, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_FORCE_AUTHN"
+ })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find saml2 force authn, err = %w", err))
+ }
+
+ snowflakeIssuerUrl, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_SNOWFLAKE_ISSUER_URL"
+ })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find saml2 snowflake issuer url, err = %w", err))
+ }
+
+ snowflakeAcsUrl, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_SNOWFLAKE_ACS_URL"
+ })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find saml2 snowflake acs url, err = %w", err))
+ }
+
+ spInitiatedLoginPageLabel, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool {
+ return property.Name == "SAML2_SP_INITIATED_LOGIN_PAGE_LABEL"
+ })
+ if err != nil {
+ return diag.FromErr(fmt.Errorf("failed to find saml2 sp initiated login page label, err = %w", err))
+ }
+
+ if err = handleExternalChangesToObjectInDescribe(d,
+ describeMapping{"saml2_enable_sp_initiated", "saml2_enable_sp_initiated", enableSpInitiated.Value, enableSpInitiated.Value, nil},
+ describeMapping{"saml2_sign_request", "saml2_sign_request", signRequest.Value, signRequest.Value, nil},
+ describeMapping{"saml2_requested_nameid_format", "saml2_requested_nameid_format", requestedNameIdFormat.Value, requestedNameIdFormat.Value, nil},
+ describeMapping{"saml2_force_authn", "saml2_force_authn", forceAuthn.Value, forceAuthn.Value, nil},
+ describeMapping{"saml2_snowflake_acs_url", "saml2_snowflake_acs_url", snowflakeAcsUrl.Value, snowflakeAcsUrl.Value, nil},
+ describeMapping{"saml2_snowflake_issuer_url", "saml2_snowflake_issuer_url", snowflakeIssuerUrl.Value, snowflakeIssuerUrl.Value, nil},
+ describeMapping{"saml2_sp_initiated_login_page_label", "saml2_sp_initiated_login_page_label", spInitiatedLoginPageLabel.Value, spInitiatedLoginPageLabel.Value, nil},
+ ); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+
+ // These are all identity sets, needed for the case where:
+ // - previous config was empty (therefore Snowflake defaults had been used)
+ // - new config have the same values that are already in SF
+ if !d.GetRawConfig().IsNull() {
+ if v := d.GetRawConfig().AsValueMap()["enabled"]; !v.IsNull() {
+ if err = d.Set("enabled", v.AsString()); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ if v := d.GetRawConfig().AsValueMap()["saml2_enable_sp_initiated"]; !v.IsNull() {
+ if err = d.Set("saml2_enable_sp_initiated", v.AsString()); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ if v := d.GetRawConfig().AsValueMap()["saml2_sign_request"]; !v.IsNull() {
+ if err = d.Set("saml2_sign_request", v.AsString()); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ if v := d.GetRawConfig().AsValueMap()["saml2_requested_nameid_format"]; !v.IsNull() {
+ if err = d.Set("saml2_requested_nameid_format", v.AsString()); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ if v := d.GetRawConfig().AsValueMap()["saml2_force_authn"]; !v.IsNull() {
+ if err = d.Set("saml2_force_authn", v.AsString()); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ if v := d.GetRawConfig().AsValueMap()["saml2_snowflake_acs_url"]; !v.IsNull() {
+ if err = d.Set("saml2_snowflake_acs_url", v.AsString()); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ if v := d.GetRawConfig().AsValueMap()["saml2_snowflake_issuer_url"]; !v.IsNull() {
+ if err = d.Set("saml2_snowflake_issuer_url", v.AsString()); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ if v := d.GetRawConfig().AsValueMap()["saml2_sp_initiated_login_page_label"]; !v.IsNull() {
+ if err = d.Set("saml2_sp_initiated_login_page_label", v.AsString()); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ }
+
+ if err = d.Set(ShowOutputAttributeName, []map[string]any{schemas.SecurityIntegrationToSchema(integration)}); err != nil {
+ return diag.FromErr(err)
+ }
+
+ if err = d.Set(DescribeOutputAttributeName, []map[string]any{schemas.DescribeSaml2IntegrationToSchema(integrationProperties)}); err != nil {
+ return diag.FromErr(err)
+ }
+
+ return nil
+ }
+}
+
+func UpdateContextSAML2Integration(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
+ client := meta.(*provider.Context).Client
+ id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier)
+ set, unset := sdk.NewSaml2IntegrationSetRequest(), sdk.NewSaml2IntegrationUnsetRequest()
+
+ if d.HasChange("enabled") {
+ if v := d.Get("enabled").(string); v != "unknown" {
+ parsed, err := strconv.ParseBool(v)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ set.WithEnabled(parsed)
+ } else {
+ // TODO(SNOW-1515781): UNSET is not implemented
+ set.WithEnabled(false)
+ }
+ }
+
+ if d.HasChange("saml2_issuer") {
+ set.WithSaml2Issuer(d.Get("saml2_issuer").(string))
+ }
+
+ if d.HasChange("saml2_sso_url") {
+ set.WithSaml2SsoUrl(d.Get("saml2_sso_url").(string))
+ }
+
+ if d.HasChange("saml2_provider") {
+ valueRaw := d.Get("saml2_provider").(string)
+ value, err := sdk.ToSaml2SecurityIntegrationSaml2ProviderOption(valueRaw)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ set.WithSaml2Provider(value)
+ }
+
+ if d.HasChange("saml2_x509_cert") {
+ set.WithSaml2X509Cert(d.Get("saml2_x509_cert").(string))
+ }
+
+ if d.HasChange("saml2_sp_initiated_login_page_label") {
+ // TODO(SNOW-1515781): UNSET is not implemented and SET with empty value is invalid (conditional ForceNew on unset)
+ set.WithSaml2SpInitiatedLoginPageLabel(d.Get("saml2_sp_initiated_login_page_label").(string))
+ }
+
+ if d.HasChange("saml2_enable_sp_initiated") {
+ if v := d.Get("saml2_enable_sp_initiated").(string); v != "unknown" {
+ parsed, err := strconv.ParseBool(v)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ set.WithSaml2EnableSpInitiated(parsed)
+ } else {
+ // TODO(SNOW-1515781): UNSET is not implemented
+ set.WithSaml2EnableSpInitiated(false)
+ }
+ }
+
+ if d.HasChange("saml2_sign_request") {
+ if v := d.Get("saml2_sign_request").(string); v != "unknown" {
+ parsed, err := strconv.ParseBool(v)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ set.WithSaml2SignRequest(parsed)
+ } else {
+ // TODO(SNOW-1515781): UNSET is not implemented
+ set.WithSaml2SignRequest(false)
+ }
+ }
+
+ if d.HasChange("saml2_requested_nameid_format") {
+ if v := d.Get("saml2_requested_nameid_format").(string); len(v) > 0 {
+ value, err := sdk.ToSaml2SecurityIntegrationSaml2RequestedNameidFormatOption(v)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ set.WithSaml2RequestedNameidFormat(value)
+ } else {
+ unset.WithSaml2RequestedNameidFormat(true)
+ }
+ }
+
+ if d.HasChange("saml2_post_logout_redirect_url") {
+ if v := d.Get("saml2_post_logout_redirect_url").(string); len(v) > 0 {
+ set.WithSaml2PostLogoutRedirectUrl(v)
+ } else {
+ unset.WithSaml2PostLogoutRedirectUrl(true)
+ }
+ }
+
+ if d.HasChange("saml2_force_authn") {
+ if v := d.Get("saml2_force_authn").(string); v != "unknown" {
+ parsed, err := strconv.ParseBool(v)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ set.WithSaml2ForceAuthn(parsed)
+ } else {
+ // TODO(SNOW-1515781): UNSET is not implemented
+ set.WithSaml2ForceAuthn(false)
+ }
+ }
+
+ if d.HasChange("saml2_snowflake_issuer_url") {
+ // TODO(SNOW-1515781): UNSET is not implemented and SET with empty value is invalid (conditional ForceNew on unset)
+ set.WithSaml2SnowflakeIssuerUrl(d.Get("saml2_snowflake_issuer_url").(string))
+ }
+
+ if d.HasChange("saml2_snowflake_acs_url") {
+ // TODO(SNOW-1515781): UNSET is not implemented and SET with empty value is invalid (conditional ForceNew on unset)
+ set.WithSaml2SnowflakeAcsUrl(d.Get("saml2_snowflake_acs_url").(string))
+ }
+
+ if d.HasChange("allowed_user_domains") {
+ // TODO(SNOW-1515781): UNSET is not implemented and SET with empty list is invalid (conditional ForceNew on non-empty to empty set)
+ v := d.Get("allowed_user_domains").(*schema.Set).List()
+ userDomains := make([]sdk.UserDomain, len(v))
+ for i := range v {
+ userDomains[i] = sdk.UserDomain{
+ Domain: v[i].(string),
+ }
+ }
+ set.WithAllowedUserDomains(userDomains)
+ }
+
+ if d.HasChange("allowed_email_patterns") {
+ // TODO(SNOW-SNOW-1515781): UNSET is not implemented and SET with empty list is invalid (conditional ForceNew on non-empty to empty set)
+ v := d.Get("allowed_email_patterns").(*schema.Set).List()
+ emailPatterns := make([]sdk.EmailPattern, len(v))
+ for i := range v {
+ emailPatterns[i] = sdk.EmailPattern{
+ Pattern: v[i].(string),
+ }
+ }
+ set.WithAllowedEmailPatterns(emailPatterns)
+ }
+
+ if d.HasChange("comment") {
+ if v := d.Get("comment").(string); len(v) > 0 {
+ set.WithComment(v)
+ } else {
+ unset.WithComment(true)
+ }
+ }
+
+ if !reflect.DeepEqual(*set, sdk.Saml2IntegrationSetRequest{}) {
+ if err := client.SecurityIntegrations.AlterSaml2(ctx, sdk.NewAlterSaml2SecurityIntegrationRequest(id).WithSet(*set)); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ if !reflect.DeepEqual(*unset, sdk.Saml2IntegrationUnsetRequest{}) {
+ if err := client.SecurityIntegrations.AlterSaml2(ctx, sdk.NewAlterSaml2SecurityIntegrationRequest(id).WithUnset(*unset)); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+
+ return ReadContextSAML2Integration(false)(ctx, d, meta)
+}
+
+func DeleteContextSAM2LIntegration(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
+ id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier)
+ client := meta.(*provider.Context).Client
+
+ err := client.SecurityIntegrations.Drop(ctx, sdk.NewDropSecurityIntegrationRequest(sdk.NewAccountObjectIdentifier(id.Name())).WithIfExists(true))
+ if err != nil {
+ return diag.Diagnostics{
+ diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Error deleting integration",
+ Detail: fmt.Sprintf("id %v err = %v", id.Name(), err),
+ },
+ }
+ }
+
+ d.SetId("")
+ return nil
+}
diff --git a/pkg/resources/saml2_integration_acceptance_test.go b/pkg/resources/saml2_integration_acceptance_test.go
new file mode 100644
index 0000000000..76586044b1
--- /dev/null
+++ b/pkg/resources/saml2_integration_acceptance_test.go
@@ -0,0 +1,1048 @@
+package resources_test
+
+import (
+ "fmt"
+ "maps"
+ "regexp"
+ "strings"
+ "testing"
+
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources"
+
+ acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance"
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random"
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/importchecks"
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/planchecks"
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
+
+ tfjson "github.com/hashicorp/terraform-json"
+ "github.com/hashicorp/terraform-plugin-testing/config"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/plancheck"
+ "github.com/hashicorp/terraform-plugin-testing/tfversion"
+)
+
+func TestAcc_Saml2Integration_basic(t *testing.T) {
+ id := acc.TestClient().Ids.RandomAccountObjectIdentifier()
+ issuer, issuer2 := acc.TestClient().Ids.Alpha(), acc.TestClient().Ids.Alpha()
+ cert, cert2 := random.GenerateX509(t), random.GenerateX509(t)
+ validUrl, validUrl2 := "http://example.com", "http://example2.com"
+ acsURL := acc.TestClient().Context.ACSURL(t)
+ issuerURL := acc.TestClient().Context.IssuerURL(t)
+
+ m := func(issuer, provider, ssoUrl, x509Cert string, complete bool, unset bool) map[string]config.Variable {
+ c := map[string]config.Variable{
+ "name": config.StringVariable(id.Name()),
+ "saml2_issuer": config.StringVariable(issuer),
+ "saml2_provider": config.StringVariable(provider),
+ "saml2_sso_url": config.StringVariable(ssoUrl),
+ "saml2_x509_cert": config.StringVariable(x509Cert),
+ }
+ if complete {
+ c["enabled"] = config.BoolVariable(true)
+ c["comment"] = config.StringVariable("foo")
+ c["saml2_enable_sp_initiated"] = config.BoolVariable(true)
+ c["saml2_force_authn"] = config.BoolVariable(true)
+ c["saml2_post_logout_redirect_url"] = config.StringVariable(validUrl)
+ c["saml2_requested_nameid_format"] = config.StringVariable(string(sdk.Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified))
+ c["saml2_sign_request"] = config.BoolVariable(true)
+ // TODO(SNOW-1479617): set saml2_snowflake_x509_cert
+ c["saml2_snowflake_acs_url"] = config.StringVariable(acsURL)
+ c["saml2_snowflake_issuer_url"] = config.StringVariable(issuerURL)
+ c["saml2_sp_initiated_login_page_label"] = config.StringVariable("foo")
+ c["allowed_email_patterns"] = config.ListVariable(config.StringVariable("^(.+dev)@example.com$"))
+ c["allowed_user_domains"] = config.ListVariable(config.StringVariable("example.com"))
+ }
+ // When unsetting, we have to keep those to prevent conditional force new being triggered
+ if unset {
+ c["saml2_snowflake_acs_url"] = config.StringVariable(acsURL)
+ c["saml2_snowflake_issuer_url"] = config.StringVariable(issuerURL)
+ c["saml2_sp_initiated_login_page_label"] = config.StringVariable("foo")
+ c["allowed_email_patterns"] = config.ListVariable(config.StringVariable("^(.+dev)@example.com$"))
+ c["allowed_user_domains"] = config.ListVariable(config.StringVariable("example.com"))
+ }
+ return c
+ }
+
+ resource.Test(t, resource.TestCase{
+ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
+ PreCheck: func() { acc.TestAccPreCheck(t) },
+ TerraformVersionChecks: []tfversion.TerraformVersionCheck{
+ tfversion.RequireAbove(tfversion.Version1_5_0),
+ },
+ CheckDestroy: acc.CheckDestroy(t, resources.Saml2SecurityIntegration),
+ Steps: []resource.TestStep{
+ // create with empty optionals
+ {
+ ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Saml2Integration/basic"),
+ ConfigVariables: m(issuer, string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom), validUrl, cert, false, false),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "name", id.Name()),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "enabled", "unknown"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_issuer", issuer),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_sso_url", validUrl),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_provider", string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom)),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_x509_cert", cert),
+ resource.TestCheckNoResourceAttr("snowflake_saml2_integration.test", "saml2_sp_initiated_login_page_label"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_enable_sp_initiated", "unknown"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_sign_request", "unknown"),
+ resource.TestCheckNoResourceAttr("snowflake_saml2_integration.test", "saml2_requested_nameid_format"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_post_logout_redirect_url", ""),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_force_authn", "unknown"),
+ resource.TestCheckNoResourceAttr("snowflake_saml2_integration.test", "saml2_snowflake_issuer_url"),
+ resource.TestCheckNoResourceAttr("snowflake_saml2_integration.test", "saml2_snowflake_acs_url"),
+ resource.TestCheckNoResourceAttr("snowflake_saml2_integration.test", "allowed_user_domains"),
+ resource.TestCheckNoResourceAttr("snowflake_saml2_integration.test", "allowed_email_patterns"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "comment", ""),
+
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_issuer.0.value", issuer),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_sso_url.0.value", validUrl),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_provider.0.value", string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom)),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_x509_cert.0.value", cert),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_sp_initiated_login_page_label.0.value", ""),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_enable_sp_initiated.0.value", "false"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_x509_cert.0.value"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_sign_request.0.value", "false"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_requested_nameid_format.0.value", ""),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_post_logout_redirect_url.0.value", ""),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_force_authn.0.value", "false"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_issuer_url.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_acs_url.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_metadata.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_digest_methods_used.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_signature_methods_used.0.value"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.allowed_user_domains.0.value", "[]"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.allowed_email_patterns.0.value", "[]"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.comment.0.value", ""),
+
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.name", id.Name()),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.integration_type", "SAML2"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.category", "SECURITY"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.enabled", "false"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.comment", ""),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "show_output.0.created_on"),
+ ),
+ },
+ // import - without optionals
+ {
+ ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Saml2Integration/basic"),
+ ConfigVariables: m(issuer, string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom), validUrl, cert, false, false),
+ ResourceName: "snowflake_saml2_integration.test",
+ ImportState: true,
+ ImportStateCheck: importchecks.ComposeAggregateImportStateCheck(
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "name", id.Name()),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "enabled", "false"),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_issuer", issuer),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_sso_url", validUrl),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_provider", string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom)),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_x509_cert", cert),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_sp_initiated_login_page_label", ""),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_enable_sp_initiated", "false"),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_sign_request", "false"),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_requested_nameid_format", ""),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_post_logout_redirect_url", ""),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_force_authn", "false"),
+ importchecks.TestCheckResourceAttrInstanceStateSet(id.Name(), "saml2_snowflake_issuer_url"),
+ importchecks.TestCheckResourceAttrInstanceStateSet(id.Name(), "saml2_snowflake_acs_url"),
+ importchecks.TestCheckResourceAttrNotInInstanceState(id.Name(), "allowed_user_domains"),
+ importchecks.TestCheckResourceAttrNotInInstanceState(id.Name(), "allowed_email_patterns"),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "comment", ""),
+ ),
+ },
+ // set optionals
+ {
+ ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Saml2Integration/complete"),
+ ConfigVariables: m(issuer2, string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom), validUrl2, cert2, true, false),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "name", id.Name()),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "enabled", "true"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_issuer", issuer2),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_sso_url", validUrl2),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_provider", string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom)),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_x509_cert", cert2),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_sp_initiated_login_page_label", "foo"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_enable_sp_initiated", "true"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_sign_request", "true"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_requested_nameid_format", string(sdk.Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified)),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_post_logout_redirect_url", validUrl),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_force_authn", "true"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_snowflake_issuer_url", issuerURL),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_snowflake_acs_url", acsURL),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "allowed_user_domains.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "allowed_user_domains.0", "example.com"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "allowed_email_patterns.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "allowed_email_patterns.0", "^(.+dev)@example.com$"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "comment", "foo"),
+
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_issuer.0.value", issuer2),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_sso_url.0.value", validUrl2),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_provider.0.value", string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom)),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_x509_cert.0.value", cert2),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_sp_initiated_login_page_label.0.value", "foo"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_enable_sp_initiated.0.value", "true"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_x509_cert.0.value"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_sign_request.0.value", "true"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_requested_nameid_format.0.value", string(sdk.Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified)),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_post_logout_redirect_url.0.value", "http://example.com"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_force_authn.0.value", "true"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_issuer_url.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_acs_url.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_metadata.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_digest_methods_used.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_signature_methods_used.0.value"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.allowed_user_domains.0.value", "[example.com]"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.allowed_email_patterns.0.value", "[^(.+dev)@example.com$]"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.comment.0.value", "foo"),
+
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.name", id.Name()),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.integration_type", "SAML2"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.category", "SECURITY"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.enabled", "true"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.comment", "foo"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "show_output.0.created_on"),
+ ),
+ },
+ // import - complete
+ {
+ ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Saml2Integration/complete"),
+ ConfigVariables: m(issuer2, string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom), validUrl2, cert2, true, false),
+ ResourceName: "snowflake_saml2_integration.test",
+ ImportState: true,
+ ImportStateCheck: importchecks.ComposeAggregateImportStateCheck(
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "name", id.Name()),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "enabled", "true"),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_issuer", issuer2),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_sso_url", validUrl2),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_provider", string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom)),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_x509_cert", cert2),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_sp_initiated_login_page_label", "foo"),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_enable_sp_initiated", "true"),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_sign_request", "true"),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_requested_nameid_format", string(sdk.Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified)),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_post_logout_redirect_url", validUrl),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "saml2_force_authn", "true"),
+ importchecks.TestCheckResourceAttrInstanceStateSet(id.Name(), "saml2_snowflake_issuer_url"),
+ importchecks.TestCheckResourceAttrInstanceStateSet(id.Name(), "saml2_snowflake_acs_url"),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "allowed_user_domains.#", "1"),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "allowed_user_domains.0", "example.com"),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "allowed_email_patterns.#", "1"),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "allowed_email_patterns.0", "^(.+dev)@example.com$"),
+ importchecks.TestCheckResourceAttrInstanceState(id.Name(), "comment", "foo"),
+ ),
+ },
+ // change values externally
+ {
+ ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Saml2Integration/complete"),
+ ConfigVariables: m(issuer2, string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom), validUrl2, cert2, true, false),
+ PreConfig: func() {
+ acc.TestClient().SecurityIntegration.UpdateSaml2(t, sdk.NewAlterSaml2SecurityIntegrationRequest(id).
+ WithUnset(*sdk.NewSaml2IntegrationUnsetRequest().
+ WithSaml2RequestedNameidFormat(true).
+ WithSaml2PostLogoutRedirectUrl(true).
+ WithSaml2ForceAuthn(true).
+ WithComment(true)))
+ },
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ planchecks.ExpectDrift("snowflake_saml2_integration.test", "saml2_requested_nameid_format", sdk.String(string(sdk.Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified)), sdk.String(string(sdk.Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress))),
+ planchecks.ExpectDrift("snowflake_saml2_integration.test", "saml2_post_logout_redirect_url", sdk.String(validUrl), sdk.String("")),
+ planchecks.ExpectDrift("snowflake_saml2_integration.test", "saml2_force_authn", sdk.String("true"), sdk.String("false")),
+ planchecks.ExpectDrift("snowflake_saml2_integration.test", "comment", sdk.String("foo"), sdk.String("")),
+
+ planchecks.ExpectChange("snowflake_saml2_integration.test", "saml2_requested_nameid_format", tfjson.ActionUpdate, sdk.String(string(sdk.Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress)), sdk.String(string(sdk.Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified))),
+ planchecks.ExpectChange("snowflake_saml2_integration.test", "saml2_post_logout_redirect_url", tfjson.ActionUpdate, sdk.String(""), sdk.String(validUrl)),
+ planchecks.ExpectChange("snowflake_saml2_integration.test", "saml2_force_authn", tfjson.ActionUpdate, sdk.String("false"), sdk.String("true")),
+ planchecks.ExpectChange("snowflake_saml2_integration.test", "comment", tfjson.ActionUpdate, sdk.String(""), sdk.String("foo")),
+ },
+ },
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "name", id.Name()),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "enabled", "true"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_issuer", issuer2),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_sso_url", validUrl2),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_provider", string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom)),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_x509_cert", cert2),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_sp_initiated_login_page_label", "foo"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_enable_sp_initiated", "true"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_sign_request", "true"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_requested_nameid_format", string(sdk.Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified)),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_post_logout_redirect_url", validUrl),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_force_authn", "true"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_snowflake_issuer_url", issuerURL),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_snowflake_acs_url", acsURL),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "allowed_user_domains.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "allowed_user_domains.0", "example.com"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "allowed_email_patterns.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "allowed_email_patterns.0", "^(.+dev)@example.com$"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "comment", "foo"),
+
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_issuer.0.value", issuer2),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_sso_url.0.value", validUrl2),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_provider.0.value", string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom)),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_x509_cert.0.value", cert2),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_sp_initiated_login_page_label.0.value", "foo"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_enable_sp_initiated.0.value", "true"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_x509_cert.0.value"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_sign_request.0.value", "true"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_requested_nameid_format.0.value", string(sdk.Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified)),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_post_logout_redirect_url.0.value", "http://example.com"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_force_authn.0.value", "true"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_issuer_url.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_acs_url.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_metadata.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_digest_methods_used.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_signature_methods_used.0.value"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.allowed_user_domains.0.value", "[example.com]"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.allowed_email_patterns.0.value", "[^(.+dev)@example.com$]"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.comment.0.value", "foo"),
+
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.name", id.Name()),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.integration_type", "SAML2"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.category", "SECURITY"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.enabled", "true"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.comment", "foo"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "show_output.0.created_on"),
+ ),
+ },
+ // unset
+ {
+ ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Saml2Integration/recreates"),
+ ConfigVariables: m(issuer, string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom), validUrl, cert, false, true),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "name", id.Name()),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "enabled", "unknown"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_issuer", issuer),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_sso_url", validUrl),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_provider", string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom)),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_x509_cert", cert),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_sp_initiated_login_page_label", "foo"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_enable_sp_initiated", "unknown"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_sign_request", "unknown"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_requested_nameid_format", ""),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_post_logout_redirect_url", ""),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_force_authn", "unknown"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_snowflake_issuer_url", issuerURL),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "saml2_snowflake_acs_url", acsURL),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "allowed_user_domains.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "allowed_user_domains.0", "example.com"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "allowed_email_patterns.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "allowed_email_patterns.0", "^(.+dev)@example.com$"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "comment", ""),
+
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_issuer.0.value", issuer),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_sso_url.0.value", validUrl),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_provider.0.value", string(sdk.Saml2SecurityIntegrationSaml2ProviderCustom)),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_x509_cert.0.value", cert),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_sp_initiated_login_page_label.0.value", "foo"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_enable_sp_initiated.0.value", "false"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_x509_cert.0.value"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_sign_request.0.value", "false"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_requested_nameid_format.0.value", string(sdk.Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress)),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_post_logout_redirect_url.0.value", ""),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.saml2_force_authn.0.value", "false"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_issuer_url.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_acs_url.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_snowflake_metadata.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_digest_methods_used.0.value"),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "describe_output.0.saml2_signature_methods_used.0.value"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.allowed_user_domains.0.value", "[example.com]"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.allowed_email_patterns.0.value", "[^(.+dev)@example.com$]"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "describe_output.0.comment.0.value", ""),
+
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.#", "1"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.name", id.Name()),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.integration_type", "SAML2"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.category", "SECURITY"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.enabled", "false"),
+ resource.TestCheckResourceAttr("snowflake_saml2_integration.test", "show_output.0.comment", ""),
+ resource.TestCheckResourceAttrSet("snowflake_saml2_integration.test", "show_output.0.created_on"),
+ ),
+ },
+ },
+ })
+}
+
+func saml2ConfigWithAuthn(name, issuer, provider, ssoUrl, x509Cert string, forceAuthn bool) string {
+ return fmt.Sprintf(`
+resource "snowflake_saml2_integration" "test" {
+ name = "%s"
+ saml2_issuer = "%s"
+ saml2_provider = "%s"
+ saml2_sso_url = "%s"
+ saml2_x509_cert = <