From c471a3ca7dc8421b4b3524e797a453d00494d9a3 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Thu, 27 Jun 2024 10:36:07 +0200 Subject: [PATCH 01/18] Common changes --- pkg/acceptance/helpers/random/certs.go | 12 +- pkg/resources/common.go | 51 ++++++ pkg/sdk/security_integrations_def.go | 165 ++++++++++++++++-- ...urity_integrations_gen_integration_test.go | 4 +- v1-preparations/REMAINING_GA_OBJECTS.MD | 2 +- 5 files changed, 217 insertions(+), 17 deletions(-) diff --git a/pkg/acceptance/helpers/random/certs.go b/pkg/acceptance/helpers/random/certs.go index 80a53a83eb..770c49983c 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" @@ -39,7 +41,7 @@ func GenerateX509(t *testing.T) string { } // GenerateRSA returns an RSA public key without BEGIN and END markers. -func GenerateRSAPublicKey(t *testing.T) string { +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), fmt.Sprintf("SHA256:%s", 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/resources/common.go b/pkg/resources/common.go index 5cfbf007a4..799525341b 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -1,9 +1,19 @@ package resources import ( + "context" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "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/schema" ) @@ -52,3 +62,44 @@ func suppressCopyOptionsQuoting(_, oldValue, newValue string, _ *schema.Resource return oldWithoutQuotes == newWithoutQuotes } } + +func DeleteContextSecurityIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) 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 +} + +func RSAKeyHash(key string) (string, error) { + keyBytes := []byte(fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", key)) + + block, _ := pem.Decode(keyBytes) + if block == nil || block.Type != "PUBLIC KEY" { + return "", errors.New("Failed to decode PEM block containing public key") + } + + pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return "", fmt.Errorf("Unable to parse public key: %w", err) + } + + pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey) + if err != nil { + return "", fmt.Errorf("Unable to marshal public key: %w", err) + } + + hash := sha256.Sum256(pubKeyBytes) + return fmt.Sprintf("SHA256:%s", base64.StdEncoding.EncodeToString(hash[:])), nil +} diff --git a/pkg/sdk/security_integrations_def.go b/pkg/sdk/security_integrations_def.go index 8c87adb64c..db276281ee 100644 --- a/pkg/sdk/security_integrations_def.go +++ b/pkg/sdk/security_integrations_def.go @@ -48,6 +48,23 @@ const ( OauthSecurityIntegrationUseSecondaryRolesNone OauthSecurityIntegrationUseSecondaryRolesOption = "NONE" ) +var AllOauthSecurityIntegrationUseSecondaryRoles = []OauthSecurityIntegrationUseSecondaryRolesOption{ + OauthSecurityIntegrationUseSecondaryRolesImplicit, + OauthSecurityIntegrationUseSecondaryRolesNone, +} + +func ToOauthSecurityIntegrationUseSecondaryRolesOption(s string) (OauthSecurityIntegrationUseSecondaryRolesOption, error) { + s = strings.ToUpper(s) + switch s { + case string(OauthSecurityIntegrationUseSecondaryRolesImplicit): + return OauthSecurityIntegrationUseSecondaryRolesImplicit, nil + case string(OauthSecurityIntegrationUseSecondaryRolesNone): + return OauthSecurityIntegrationUseSecondaryRolesNone, nil + default: + return "", fmt.Errorf("invalid OauthSecurityIntegrationUseSecondaryRolesOption: %s", s) + } +} + type OauthSecurityIntegrationClientTypeOption string const ( @@ -55,6 +72,23 @@ const ( OauthSecurityIntegrationClientTypeConfidential OauthSecurityIntegrationClientTypeOption = "CONFIDENTIAL" ) +var AllOauthSecurityIntegrationClientTypes = []OauthSecurityIntegrationClientTypeOption{ + OauthSecurityIntegrationClientTypePublic, + OauthSecurityIntegrationClientTypeConfidential, +} + +func ToOauthSecurityIntegrationClientTypeOption(s string) (OauthSecurityIntegrationClientTypeOption, error) { + s = strings.ToUpper(s) + switch s { + case string(OauthSecurityIntegrationClientTypePublic): + return OauthSecurityIntegrationClientTypePublic, nil + case string(OauthSecurityIntegrationClientTypeConfidential): + return OauthSecurityIntegrationClientTypeConfidential, nil + default: + return "", fmt.Errorf("invalid OauthSecurityIntegrationClientTypeOption: %s", s) + } +} + type OauthSecurityIntegrationClientOption string const ( @@ -63,6 +97,97 @@ const ( OauthSecurityIntegrationClientTableauServer OauthSecurityIntegrationClientOption = "TABLEAU_SERVER" ) +var AllOauthSecurityIntegrationClients = []OauthSecurityIntegrationClientOption{ + OauthSecurityIntegrationClientLooker, + OauthSecurityIntegrationClientTableauDesktop, + OauthSecurityIntegrationClientTableauServer, +} + +func ToOauthSecurityIntegrationClientOption(s string) (OauthSecurityIntegrationClientOption, error) { + s = strings.ToUpper(s) + switch s { + case string(OauthSecurityIntegrationClientLooker): + return OauthSecurityIntegrationClientLooker, nil + case string(OauthSecurityIntegrationClientTableauDesktop): + return OauthSecurityIntegrationClientTableauDesktop, nil + case string(OauthSecurityIntegrationClientTableauServer): + return OauthSecurityIntegrationClientTableauServer, nil + default: + return "", fmt.Errorf("invalid OauthSecurityIntegrationClientOption: %s", s) + } +} + +type Saml2SecurityIntegrationSaml2ProviderOption string + +const ( + Saml2SecurityIntegrationSaml2ProviderOkta Saml2SecurityIntegrationSaml2ProviderOption = "OKTA" + Saml2SecurityIntegrationSaml2ProviderAdfs Saml2SecurityIntegrationSaml2ProviderOption = "ADFS" + Saml2SecurityIntegrationSaml2ProviderCustom Saml2SecurityIntegrationSaml2ProviderOption = "CUSTOM" +) + +var AllSaml2SecurityIntegrationSaml2Providers = []Saml2SecurityIntegrationSaml2ProviderOption{ + Saml2SecurityIntegrationSaml2ProviderOkta, + Saml2SecurityIntegrationSaml2ProviderAdfs, + Saml2SecurityIntegrationSaml2ProviderCustom, +} + +func ToSaml2SecurityIntegrationSaml2ProviderOption(s string) (Saml2SecurityIntegrationSaml2ProviderOption, error) { + s = strings.ToUpper(s) + switch s { + case string(Saml2SecurityIntegrationSaml2ProviderOkta): + return Saml2SecurityIntegrationSaml2ProviderOkta, nil + case string(Saml2SecurityIntegrationSaml2ProviderAdfs): + return Saml2SecurityIntegrationSaml2ProviderAdfs, nil + case string(Saml2SecurityIntegrationSaml2ProviderCustom): + return Saml2SecurityIntegrationSaml2ProviderCustom, nil + default: + return "", fmt.Errorf("invalid Saml2SecurityIntegrationSaml2ProviderOption: %s", s) + } +} + +type Saml2SecurityIntegrationSaml2RequestedNameidFormatOption string + +const ( + Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" + Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" + Saml2SecurityIntegrationSaml2RequestedNameidFormatX509SubjectName Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName" + Saml2SecurityIntegrationSaml2RequestedNameidFormatWindowsDomainQualifiedName Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName" + Saml2SecurityIntegrationSaml2RequestedNameidFormatKerberos Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos" + Saml2SecurityIntegrationSaml2RequestedNameidFormatPersistent Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" + Saml2SecurityIntegrationSaml2RequestedNameidFormatTransient Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" +) + +var AllSaml2SecurityIntegrationSaml2RequestedNameidFormats = []Saml2SecurityIntegrationSaml2RequestedNameidFormatOption{ + Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified, + Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress, + Saml2SecurityIntegrationSaml2RequestedNameidFormatX509SubjectName, + Saml2SecurityIntegrationSaml2RequestedNameidFormatWindowsDomainQualifiedName, + Saml2SecurityIntegrationSaml2RequestedNameidFormatKerberos, + Saml2SecurityIntegrationSaml2RequestedNameidFormatPersistent, + Saml2SecurityIntegrationSaml2RequestedNameidFormatTransient, +} + +func ToSaml2SecurityIntegrationSaml2RequestedNameidFormatOption(s string) (Saml2SecurityIntegrationSaml2RequestedNameidFormatOption, error) { + switch s { + case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified): + return Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified, nil + case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress): + return Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress, nil + case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatX509SubjectName): + return Saml2SecurityIntegrationSaml2RequestedNameidFormatX509SubjectName, nil + case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatWindowsDomainQualifiedName): + return Saml2SecurityIntegrationSaml2RequestedNameidFormatWindowsDomainQualifiedName, nil + case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatKerberos): + return Saml2SecurityIntegrationSaml2RequestedNameidFormatKerberos, nil + case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatPersistent): + return Saml2SecurityIntegrationSaml2RequestedNameidFormatPersistent, nil + case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatTransient): + return Saml2SecurityIntegrationSaml2RequestedNameidFormatTransient, nil + default: + return "", fmt.Errorf("invalid Saml2SecurityIntegrationSaml2RequestedNameidFormatOption: %s", s) + } +} + type ScimSecurityIntegrationScimClientOption string const ( @@ -80,11 +205,11 @@ var AllScimSecurityIntegrationScimClients = []ScimSecurityIntegrationScimClientO func ToScimSecurityIntegrationScimClientOption(s string) (ScimSecurityIntegrationScimClientOption, error) { s = strings.ToUpper(s) switch s { - case "OKTA": + case string(ScimSecurityIntegrationScimClientOkta): return ScimSecurityIntegrationScimClientOkta, nil - case "AZURE": + case string(ScimSecurityIntegrationScimClientAzure): return ScimSecurityIntegrationScimClientAzure, nil - case "GENERIC": + case string(ScimSecurityIntegrationScimClientGeneric): return ScimSecurityIntegrationScimClientGeneric, nil default: return "", fmt.Errorf("invalid ScimSecurityIntegrationScimClientOption: %s", s) @@ -108,11 +233,11 @@ var AllScimSecurityIntegrationRunAsRoles = []ScimSecurityIntegrationRunAsRoleOpt func ToScimSecurityIntegrationRunAsRoleOption(s string) (ScimSecurityIntegrationRunAsRoleOption, error) { s = strings.ToUpper(s) switch s { - case "OKTA_PROVISIONER": + case string(ScimSecurityIntegrationRunAsRoleOktaProvisioner): return ScimSecurityIntegrationRunAsRoleOktaProvisioner, nil - case "AAD_PROVISIONER": + case string(ScimSecurityIntegrationRunAsRoleAadProvisioner): return ScimSecurityIntegrationRunAsRoleAadProvisioner, nil - case "GENERIC_SCIM_PROVISIONER": + case string(ScimSecurityIntegrationRunAsRoleGenericScimProvisioner): return ScimSecurityIntegrationRunAsRoleGenericScimProvisioner, nil default: return "", fmt.Errorf("invalid ScimSecurityIntegrationRunAsRoleOption: %s", s) @@ -325,7 +450,11 @@ var saml2IntegrationSetDef = g.NewQueryStruct("Saml2IntegrationSet"). OptionalBooleanAssignment("ENABLED", g.ParameterOptions()). OptionalTextAssignment("SAML2_ISSUER", g.ParameterOptions().SingleQuotes()). OptionalTextAssignment("SAML2_SSO_URL", g.ParameterOptions().SingleQuotes()). - OptionalTextAssignment("SAML2_PROVIDER", g.ParameterOptions().SingleQuotes()). + OptionalAssignment( + "SAML2_PROVIDER", + g.KindOfT[Saml2SecurityIntegrationSaml2ProviderOption](), + g.ParameterOptions().SingleQuotes(), + ). OptionalTextAssignment("SAML2_X509_CERT", g.ParameterOptions().SingleQuotes()). ListAssignment("ALLOWED_USER_DOMAINS", "UserDomain", g.ParameterOptions().Parentheses()). ListAssignment("ALLOWED_EMAIL_PATTERNS", "EmailPattern", g.ParameterOptions().Parentheses()). @@ -333,7 +462,11 @@ var saml2IntegrationSetDef = g.NewQueryStruct("Saml2IntegrationSet"). OptionalBooleanAssignment("SAML2_ENABLE_SP_INITIATED", g.ParameterOptions()). OptionalTextAssignment("SAML2_SNOWFLAKE_X509_CERT", g.ParameterOptions().SingleQuotes()). OptionalBooleanAssignment("SAML2_SIGN_REQUEST", g.ParameterOptions()). - OptionalTextAssignment("SAML2_REQUESTED_NAMEID_FORMAT", g.ParameterOptions().SingleQuotes()). + OptionalAssignment( + "SAML2_REQUESTED_NAMEID_FORMAT", + g.KindOfT[Saml2SecurityIntegrationSaml2RequestedNameidFormatOption](), + g.ParameterOptions().SingleQuotes(), + ). OptionalTextAssignment("SAML2_POST_LOGOUT_REDIRECT_URL", g.ParameterOptions().SingleQuotes()). OptionalBooleanAssignment("SAML2_FORCE_AUTHN", g.ParameterOptions()). OptionalTextAssignment("SAML2_SNOWFLAKE_ISSUER_URL", g.ParameterOptions().SingleQuotes()). @@ -517,7 +650,7 @@ var SecurityIntegrationsDef = g.NewInterface( g.KindOfT[OauthSecurityIntegrationClientTypeOption](), g.ParameterOptions().Required().SingleQuotes(), ). - TextAssignment("OAUTH_REDIRECT_URI", g.ParameterOptions().Required().SingleQuotes()). + OptionalTextAssignment("OAUTH_REDIRECT_URI", g.ParameterOptions().SingleQuotes()). OptionalBooleanAssignment("ENABLED", g.ParameterOptions()). OptionalBooleanAssignment("OAUTH_ALLOW_NON_TLS_REDIRECT_URI", g.ParameterOptions()). OptionalBooleanAssignment("OAUTH_ENFORCE_PKCE", g.ParameterOptions()). @@ -541,10 +674,14 @@ var SecurityIntegrationsDef = g.NewInterface( createSecurityIntegrationOperation("CreateSaml2", func(qs *g.QueryStruct) *g.QueryStruct { return qs. PredefinedQueryStructField("integrationType", "string", g.StaticOptions().SQL("TYPE = SAML2")). - BooleanAssignment("ENABLED", g.ParameterOptions().Required()). + OptionalBooleanAssignment("ENABLED", g.ParameterOptions()). TextAssignment("SAML2_ISSUER", g.ParameterOptions().Required().SingleQuotes()). TextAssignment("SAML2_SSO_URL", g.ParameterOptions().Required().SingleQuotes()). - TextAssignment("SAML2_PROVIDER", g.ParameterOptions().Required().SingleQuotes()). + Assignment( + "SAML2_PROVIDER", + g.KindOfT[Saml2SecurityIntegrationSaml2ProviderOption](), + g.ParameterOptions().Required().SingleQuotes(), + ). TextAssignment("SAML2_X509_CERT", g.ParameterOptions().Required().SingleQuotes()). ListAssignment("ALLOWED_USER_DOMAINS", "UserDomain", g.ParameterOptions().Parentheses()). ListAssignment("ALLOWED_EMAIL_PATTERNS", "EmailPattern", g.ParameterOptions().Parentheses()). @@ -552,7 +689,11 @@ var SecurityIntegrationsDef = g.NewInterface( OptionalBooleanAssignment("SAML2_ENABLE_SP_INITIATED", g.ParameterOptions()). OptionalTextAssignment("SAML2_SNOWFLAKE_X509_CERT", g.ParameterOptions().SingleQuotes()). OptionalBooleanAssignment("SAML2_SIGN_REQUEST", g.ParameterOptions()). - OptionalTextAssignment("SAML2_REQUESTED_NAMEID_FORMAT", g.ParameterOptions().SingleQuotes()). + OptionalAssignment( + "SAML2_REQUESTED_NAMEID_FORMAT", + g.KindOfT[Saml2SecurityIntegrationSaml2RequestedNameidFormatOption](), + g.ParameterOptions().SingleQuotes(), + ). OptionalTextAssignment("SAML2_POST_LOGOUT_REDIRECT_URL", g.ParameterOptions().SingleQuotes()). OptionalBooleanAssignment("SAML2_FORCE_AUTHN", g.ParameterOptions()). OptionalTextAssignment("SAML2_SNOWFLAKE_ISSUER_URL", g.ParameterOptions().SingleQuotes()). diff --git a/pkg/sdk/testint/security_integrations_gen_integration_test.go b/pkg/sdk/testint/security_integrations_gen_integration_test.go index 993d6500a4..0c2c94cec5 100644 --- a/pkg/sdk/testint/security_integrations_gen_integration_test.go +++ b/pkg/sdk/testint/security_integrations_gen_integration_test.go @@ -18,7 +18,7 @@ func TestInt_SecurityIntegrations(t *testing.T) { acsURL := testClientHelper().Context.ACSURL(t) issuerURL := testClientHelper().Context.IssuerURL(t) cert := random.GenerateX509(t) - rsaKey := random.GenerateRSAPublicKey(t) + rsaKey, _ := random.GenerateRSAPublicKey(t) revertParameter := testClientHelper().Parameter.UpdateAccountParameterTemporarily(t, sdk.AccountParameterEnableIdentifierFirstLogin, "true") t.Cleanup(revertParameter) @@ -135,7 +135,7 @@ func TestInt_SecurityIntegrations(t *testing.T) { } err := client.SecurityIntegrations.CreateSaml2(ctx, saml2Req) require.NoError(t, err) - cleanupSecurityIntegration(t, id) + // cleanupSecurityIntegration(t, id) integration, err := client.SecurityIntegrations.ShowByID(ctx, id) require.NoError(t, err) diff --git a/v1-preparations/REMAINING_GA_OBJECTS.MD b/v1-preparations/REMAINING_GA_OBJECTS.MD index fe6ea6cc2d..ebdb328e3c 100644 --- a/v1-preparations/REMAINING_GA_OBJECTS.MD +++ b/v1-preparations/REMAINING_GA_OBJECTS.MD @@ -39,4 +39,4 @@ Known issues lists open issues touching the given object. Note that some of thes | PIPE | ❌ | [#2785](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2785), [#2075](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2075), [#1781](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1781), [#1707](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1707), [#1478](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1478), [#533](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/533) | | SECRET | ❌ | [#2545](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2545) | | SEQUENCE | ❌ | [#2589](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2589) | -| SESSION POLICY | ❌ | - | +| SESSION POLICY | ❌ | [#2870](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2870) | From ff5439d4e25ea10fd7e73ab4dec2a45c1ec84e20 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Thu, 27 Jun 2024 13:51:22 +0200 Subject: [PATCH 02/18] Add api auth security integrations --- ...tegration_with_authorization_code_grant.md | 67 +++++ ...ion_integration_with_client_credentials.md | 65 ++++ ...hentication_integration_with_jwt_bearer.md | 68 +++++ .../import.sh | 1 + .../resource.tf | 20 ++ .../import.sh | 1 + .../resource.tf | 19 ++ .../import.sh | 1 + .../resource.tf | 20 ++ pkg/provider/provider.go | 13 +- ...tegration_with_authorization_code_grant.go | 284 ++++++++++++++++++ ...uthorization_code_grant_acceptance_test.go | 175 +++++++++++ ...ion_integration_with_client_credentials.go | 278 +++++++++++++++++ ...with_client_credentials_acceptance_test.go | 172 +++++++++++ ...hentication_integration_with_jwt_bearer.go | 280 +++++++++++++++++ ...gration_with_jwt_bearer_acceptance_test.go | 179 +++++++++++ pkg/resources/common.go | 52 ++++ pkg/resources/custom_diffs.go | 8 + .../basic/test.tf | 6 + .../basic/variables.tf | 13 + .../complete/test.tf | 12 + .../complete/variables.tf | 31 ++ .../invalid/test.tf | 3 + .../invalid/variables.tf | 3 + .../basic/test.tf | 6 + .../basic/variables.tf | 13 + .../complete/test.tf | 10 + .../complete/variables.tf | 25 ++ .../invalid/test.tf | 3 + .../invalid/variables.tf | 3 + .../basic/test.tf | 12 + .../basic/variables.tf | 31 ++ .../complete/test.tf | 12 + .../complete/variables.tf | 31 ++ .../invalid/test.tf | 3 + .../invalid/variables.tf | 3 + pkg/sdk/security_integrations_def.go | 14 + 37 files changed, 1932 insertions(+), 5 deletions(-) create mode 100644 docs/resources/api_authentication_integration_with_authorization_code_grant.md create mode 100644 docs/resources/api_authentication_integration_with_client_credentials.md create mode 100644 docs/resources/api_authentication_integration_with_jwt_bearer.md create mode 100644 examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/import.sh create mode 100644 examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/resource.tf create mode 100644 examples/resources/snowflake_api_authentication_integration_with_client_credentials/import.sh create mode 100644 examples/resources/snowflake_api_authentication_integration_with_client_credentials/resource.tf create mode 100644 examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/import.sh create mode 100644 examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf create mode 100644 pkg/resources/api_authentication_integration_with_authorization_code_grant.go create mode 100644 pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go create mode 100644 pkg/resources/api_authentication_integration_with_client_credentials.go create mode 100644 pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go create mode 100644 pkg/resources/api_authentication_integration_with_jwt_bearer.go create mode 100644 pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/variables.tf diff --git a/docs/resources/api_authentication_integration_with_authorization_code_grant.md b/docs/resources/api_authentication_integration_with_authorization_code_grant.md new file mode 100644 index 0000000000..27ad5bb69f --- /dev/null +++ b/docs/resources/api_authentication_integration_with_authorization_code_grant.md @@ -0,0 +1,67 @@ +--- +page_title: "snowflake_api_authentication_integration_with_authorization_code_grant Resource - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +# snowflake_api_authentication_integration_with_authorization_code_grant (Resource) + + + +## Example Usage + +```terraform +# basic resource +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + enabled = true + name = "foo" + oauth_client_id = "foo" + oauth_client_secret = "foo" +} +# resource with all fields set +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + comment = "foo" + enabled = true + name = "foo" + oauth_access_token_validity = 42 + oauth_authorization_endpoint = "https://example.com" + oauth_client_auth_method = "CLIENT_SECRET_POST" + oauth_client_id = "foo" + oauth_client_secret = "foo" + oauth_refresh_token_validity = 42 + oauth_token_endpoint = "https://example.com" +} +``` + + +## Schema + +### Required + +- `enabled` (Boolean) Specifies whether this security integration is enabled or disabled. +- `name` (String) Specifies the identifier (i.e. name) for the integration. This value must be unique in your account. +- `oauth_client_id` (String) Specifies the client ID for the OAuth application in the external service. +- `oauth_client_secret` (String) Specifies the client secret for the OAuth application in the ServiceNow instance from the previous step. The connector uses this to request an access token from the ServiceNow instance. + +### Optional + +- `comment` (String) Specifies a comment for the integration. +- `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. +- `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. +- `oauth_client_auth_method` (String) Specifies the client ID for the OAuth application in the external service. +- `oauth_refresh_token_validity` (Number) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. +- `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). + +### Read-Only + +- `created_on` (String) Date and time when the integration was created. +- `id` (String) The ID of this resource. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import snowflake_api_authentication_integration_with_authorization_code_grant.example "name" +``` diff --git a/docs/resources/api_authentication_integration_with_client_credentials.md b/docs/resources/api_authentication_integration_with_client_credentials.md new file mode 100644 index 0000000000..7eab128ed5 --- /dev/null +++ b/docs/resources/api_authentication_integration_with_client_credentials.md @@ -0,0 +1,65 @@ +--- +page_title: "snowflake_api_authentication_integration_with_client_credentials Resource - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +# snowflake_api_authentication_integration_with_client_credentials (Resource) + + + +## Example Usage + +```terraform +# basic resource +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + enabled = true + name = "foo" + oauth_client_id = "foo" + oauth_client_secret = "foo" +} +# resource with all fields set +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + comment = "foo" + enabled = true + name = "foo" + oauth_access_token_validity = 42 + oauth_allowed_scopes = ["foo"] + oauth_client_auth_method = "CLIENT_SECRET_POST" + oauth_client_id = "foo" + oauth_client_secret = "foo" + oauth_token_endpoint = "https://example.com" +} +``` + + +## Schema + +### Required + +- `enabled` (Boolean) Specifies whether this security integration is enabled or disabled. +- `name` (String) Specifies the identifier (i.e. name) for the integration. This value must be unique in your account. +- `oauth_client_id` (String) Specifies the client ID for the OAuth application in the external service. +- `oauth_client_secret` (String) Specifies the client secret for the OAuth application in the ServiceNow instance from the previous step. The connector uses this to request an access token from the ServiceNow instance. + +### Optional + +- `comment` (String) Specifies a comment for the integration. +- `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. +- `oauth_allowed_scopes` (Set of String) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. +- `oauth_client_auth_method` (String) Specifies the client ID for the OAuth application in the external service. +- `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). + +### Read-Only + +- `created_on` (String) Date and time when the integration was created. +- `id` (String) The ID of this resource. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import snowflake_api_authentication_integration_with_client_credentials.example "name" +``` diff --git a/docs/resources/api_authentication_integration_with_jwt_bearer.md b/docs/resources/api_authentication_integration_with_jwt_bearer.md new file mode 100644 index 0000000000..c71685ec22 --- /dev/null +++ b/docs/resources/api_authentication_integration_with_jwt_bearer.md @@ -0,0 +1,68 @@ +--- +page_title: "snowflake_api_authentication_integration_with_jwt_bearer Resource - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +# snowflake_api_authentication_integration_with_jwt_bearer (Resource) + + + +## Example Usage + +```terraform +# basic resource +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + enabled = true + name = "foo" + oauth_client_id = "foo" + oauth_client_secret = "foo" +} +# resource with all fields set +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + comment = "foo" + enabled = true + name = "foo" + oauth_access_token_validity = 42 + oauth_authorization_endpoint = "https://example.com" + oauth_client_auth_method = "CLIENT_SECRET_POST" + oauth_client_id = "foo" + oauth_client_secret = "foo" + oauth_refresh_token_validity = 42 + oauth_token_endpoint = "https://example.com" +} +``` + + +## Schema + +### Required + +- `enabled` (Boolean) Specifies whether this security integration is enabled or disabled. +- `name` (String) Specifies the identifier (i.e. name) for the integration. This value must be unique in your account. +- `oauth_assertion_issuer` (String) +- `oauth_client_id` (String) Specifies the client ID for the OAuth application in the external service. +- `oauth_client_secret` (String) Specifies the client secret for the OAuth application in the ServiceNow instance from the previous step. The connector uses this to request an access token from the ServiceNow instance. + +### Optional + +- `comment` (String) Specifies a comment for the integration. +- `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. +- `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. +- `oauth_client_auth_method` (String) Specifies the client ID for the OAuth application in the external service. +- `oauth_refresh_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. +- `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). + +### Read-Only + +- `created_on` (String) Date and time when the integration was created. +- `id` (String) The ID of this resource. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import snowflake_api_authentication_integration_with_jwt_bearer.example "name" +``` diff --git a/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/import.sh b/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/import.sh new file mode 100644 index 0000000000..c641594f3b --- /dev/null +++ b/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/import.sh @@ -0,0 +1 @@ +terraform import snowflake_api_authentication_integration_with_authorization_code_grant.example "name" diff --git a/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/resource.tf b/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/resource.tf new file mode 100644 index 0000000000..6db7b2d569 --- /dev/null +++ b/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/resource.tf @@ -0,0 +1,20 @@ +# basic resource +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + enabled = true + name = "foo" + oauth_client_id = "foo" + oauth_client_secret = "foo" +} +# resource with all fields set +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + comment = "foo" + enabled = true + name = "foo" + oauth_access_token_validity = 42 + oauth_authorization_endpoint = "https://example.com" + oauth_client_auth_method = "CLIENT_SECRET_POST" + oauth_client_id = "foo" + oauth_client_secret = "foo" + oauth_refresh_token_validity = 42 + oauth_token_endpoint = "https://example.com" +} diff --git a/examples/resources/snowflake_api_authentication_integration_with_client_credentials/import.sh b/examples/resources/snowflake_api_authentication_integration_with_client_credentials/import.sh new file mode 100644 index 0000000000..d3454c9a27 --- /dev/null +++ b/examples/resources/snowflake_api_authentication_integration_with_client_credentials/import.sh @@ -0,0 +1 @@ +terraform import snowflake_api_authentication_integration_with_client_credentials.example "name" diff --git a/examples/resources/snowflake_api_authentication_integration_with_client_credentials/resource.tf b/examples/resources/snowflake_api_authentication_integration_with_client_credentials/resource.tf new file mode 100644 index 0000000000..3744d223b5 --- /dev/null +++ b/examples/resources/snowflake_api_authentication_integration_with_client_credentials/resource.tf @@ -0,0 +1,19 @@ +# basic resource +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + enabled = true + name = "foo" + oauth_client_id = "foo" + oauth_client_secret = "foo" +} +# resource with all fields set +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + comment = "foo" + enabled = true + name = "foo" + oauth_access_token_validity = 42 + oauth_allowed_scopes = ["foo"] + oauth_client_auth_method = "CLIENT_SECRET_POST" + oauth_client_id = "foo" + oauth_client_secret = "foo" + oauth_token_endpoint = "https://example.com" +} diff --git a/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/import.sh b/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/import.sh new file mode 100644 index 0000000000..b1cb40660a --- /dev/null +++ b/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/import.sh @@ -0,0 +1 @@ +terraform import snowflake_api_authentication_integration_with_jwt_bearer.example "name" diff --git a/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf b/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf new file mode 100644 index 0000000000..f80e334560 --- /dev/null +++ b/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf @@ -0,0 +1,20 @@ +# basic resource +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + enabled = true + name = "foo" + oauth_client_id = "foo" + oauth_client_secret = "foo" +} +# resource with all fields set +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + comment = "foo" + enabled = true + name = "foo" + oauth_access_token_validity = 42 + oauth_authorization_endpoint = "https://example.com" + oauth_client_auth_method = "CLIENT_SECRET_POST" + oauth_client_id = "foo" + oauth_client_secret = "foo" + oauth_refresh_token_validity = 42 + oauth_token_endpoint = "https://example.com" +} diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 0c4686249a..9ef822cdd4 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -421,11 +421,14 @@ func Provider() *schema.Provider { func getResources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "snowflake_account": resources.Account(), - "snowflake_account_password_policy_attachment": resources.AccountPasswordPolicyAttachment(), - "snowflake_account_parameter": resources.AccountParameter(), - "snowflake_alert": resources.Alert(), - "snowflake_api_integration": resources.APIIntegration(), + "snowflake_account": resources.Account(), + "snowflake_account_password_policy_attachment": resources.AccountPasswordPolicyAttachment(), + "snowflake_account_parameter": resources.AccountParameter(), + "snowflake_alert": resources.Alert(), + "snowflake_api_integration": resources.APIIntegration(), + "snowflake_api_authentication_integration_with_authorization_code_grant": resources.ApiAuthenticationIntegrationWithAuthorizationCodeGrant(), + "snowflake_api_authentication_integration_with_client_credentials": resources.ApiAuthenticationIntegrationWithClientCredentials(), + "snowflake_api_authentication_integration_with_jwt_bearer": resources.ApiAuthenticationIntegrationWithJwtBearer(), "snowflake_database_old": resources.DatabaseOld(), "snowflake_database": resources.Database(), "snowflake_database_role": resources.DatabaseRole(), diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go new file mode 100644 index 0000000000..60f81bba29 --- /dev/null +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go @@ -0,0 +1,284 @@ +package resources + +import ( + "context" + "errors" + "fmt" + "log" + "reflect" + "strconv" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "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" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +var apiAuthAuthorizationCodeGrantSchema = func() map[string]*schema.Schema { + uniq := map[string]*schema.Schema{ + "oauth_refresh_token_validity": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(1), + Description: "Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow.", + }, + "oauth_authorization_endpoint": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the URL for authenticating to the external service.", + }, + } + return MergeMaps(apiAuthCommonSchema, uniq) +}() + +func ApiAuthenticationIntegrationWithAuthorizationCodeGrant() *schema.Resource { + return &schema.Resource{ + CreateContext: CreateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, + ReadContext: ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, + UpdateContext: UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, + DeleteContext: DeleteContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, + CustomizeDiff: customdiff.All( + ForceNewIfChangeToEmptyString("oauth_token_endpoint"), + ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), + ForceNewIfChangeToEmptyString("oauth_client_auth_method"), + ), + Schema: apiAuthAuthorizationCodeGrantSchema, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +func CreateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + enabled := d.Get("enabled").(bool) + name := d.Get("name").(string) + oauthClientId := d.Get("oauth_client_id").(string) + oauthClientSecret := d.Get("oauth_client_secret").(string) + id := sdk.NewAccountObjectIdentifier(name) + req := sdk.NewCreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest(id, enabled, oauthClientId, oauthClientSecret) + + if v, ok := d.GetOk("comment"); ok { + req.WithComment(v.(string)) + } + + if v, ok := d.GetOk("oauth_access_token_validity"); ok { + req.WithOauthAccessTokenValidity(v.(int)) + } + + if v, ok := d.GetOk("oauth_authorization_endpoint"); ok { + req.WithOauthAuthorizationEndpoint(v.(string)) + } + + if v, ok := d.GetOk("oauth_client_auth_method"); ok { + valueRaw := v.(string) + value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(valueRaw) + if err != nil { + return diag.FromErr(err) + } + req.WithOauthClientAuthMethod(value) + } + + if v, ok := d.GetOk("oauth_refresh_token_validity"); ok { + req.WithOauthRefreshTokenValidity(v.(int)) + } + + if v, ok := d.GetOk("oauth_token_endpoint"); ok { + req.WithOauthTokenEndpoint(v.(string)) + } + + if err := client.SecurityIntegrations.CreateApiAuthenticationWithAuthorizationCodeGrantFlow(ctx, req); err != nil { + return diag.FromErr(err) + } + + d.SetId(name) + + return ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx, d, meta) +} + +func ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta interface{}) 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) + } + + 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", integration.Name); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("comment", integration.Comment); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("created_on", integration.CreatedOn.String()); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("enabled", integration.Enabled); err != nil { + return diag.FromErr(err) + } + + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return diag.FromErr(err) + } + for _, property := range properties { + name := property.Name + value := property.Value + switch name { + case "COMMENT": + if err := d.Set("comment", value); err != nil { + return diag.FromErr(err) + } + case "CREATED_ON": + if err := d.Set("created_on", value); err != nil { + return diag.FromErr(err) + } + case "ENABLED": + if err := d.Set("enabled", helpers.StringToBool(value)); err != nil { + return diag.FromErr(err) + } + case "OAUTH_ACCESS_TOKEN_VALIDITY": + valueInt, err := strconv.Atoi(value) + if err != nil { + return diag.FromErr(err) + } + if err := d.Set("oauth_access_token_validity", valueInt); err != nil { + return diag.FromErr(err) + } + case "OAUTH_AUTHORIZATION_ENDPOINT": + if err := d.Set("oauth_authorization_endpoint", value); err != nil { + return diag.FromErr(err) + } + case "OAUTH_CLIENT_AUTH_METHOD": + if err := d.Set("oauth_client_auth_method", value); err != nil { + return diag.FromErr(err) + } + case "OAUTH_CLIENT_ID": + if err := d.Set("oauth_client_id", value); err != nil { + return diag.FromErr(err) + } + case "OAUTH_CLIENT_SECRET": + if err := d.Set("oauth_client_secret", value); err != nil { + return diag.FromErr(err) + } + case "OAUTH_REFRESH_TOKEN_VALIDITY": + valueInt, err := strconv.Atoi(value) + if err != nil { + return diag.FromErr(err) + } + if err := d.Set("oauth_refresh_token_validity", valueInt); err != nil { + return diag.FromErr(err) + } + case "OAUTH_TOKEN_ENDPOINT": + if err := d.Set("oauth_token_endpoint", value); err != nil { + return diag.FromErr(err) + } + default: + log.Printf("[WARN] unexpected property %v returned from Snowflake", name) + } + } + + return nil +} + +func UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + set, unset := sdk.NewApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest(), sdk.NewApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnsetRequest() + + if d.HasChange("comment") { + set.WithComment(d.Get("comment").(string)) + } + + if d.HasChange("enabled") { + set.WithEnabled(d.Get("enabled").(bool)) + } + + if d.HasChange("oauth_access_token_validity") { + set.WithOauthAccessTokenValidity(d.Get("oauth_access_token_validity").(int)) + } + + if d.HasChange("oauth_authorization_endpoint") { + set.WithOauthAuthorizationEndpoint(d.Get("oauth_authorization_endpoint").(string)) + } + + if d.HasChange("oauth_client_auth_method") { + v := d.Get("oauth_client_auth_method").(string) + if len(v) > 0 { + value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) + if err != nil { + return diag.FromErr(err) + } + set.WithOauthClientAuthMethod(value) + } + } + + if d.HasChange("oauth_client_id") { + set.WithOauthClientId(d.Get("oauth_client_id").(string)) + } + + if d.HasChange("oauth_client_secret") { + set.WithOauthClientSecret(d.Get("oauth_client_secret").(string)) + } + + if d.HasChange("oauth_refresh_token_validity") { + set.WithOauthRefreshTokenValidity(d.Get("oauth_refresh_token_validity").(int)) + } + + if d.HasChange("oauth_token_endpoint") { + set.WithOauthTokenEndpoint(d.Get("oauth_token_endpoint").(string)) + } + + if !reflect.DeepEqual(*set, sdk.ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest{}) { + if err := client.SecurityIntegrations.AlterApiAuthenticationWithAuthorizationCodeGrantFlow(ctx, sdk.NewAlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest(id).WithSet(*set)); err != nil { + return diag.FromErr(err) + } + } + if !reflect.DeepEqual(*unset, sdk.ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnsetRequest{}) { + if err := client.SecurityIntegrations.AlterApiAuthenticationWithAuthorizationCodeGrantFlow(ctx, sdk.NewAlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest(id).WithUnset(*unset)); err != nil { + return diag.FromErr(err) + } + } + return ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx, d, meta) +} + +func DeleteContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta interface{}) 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/api_authentication_integration_with_authorization_code_grant_acceptance_test.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go new file mode 100644 index 0000000000..5280bf190c --- /dev/null +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go @@ -0,0 +1,175 @@ +package resources_test + +import ( + "testing" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func(complete bool) map[string]config.Variable { + c := map[string]config.Variable{ + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "oauth_client_id": config.StringVariable("foo"), + "oauth_client_secret": config.StringVariable("foo"), + } + if complete { + c["comment"] = config.StringVariable("foo") + c["oauth_access_token_validity"] = config.IntegerVariable(42) + c["oauth_authorization_endpoint"] = config.StringVariable("https://example.com") + c["oauth_client_auth_method"] = config.StringVariable(string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)) + c["oauth_refresh_token_validity"] = config.IntegerVariable(12345) + c["oauth_token_endpoint"] = config.StringVariable("https://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), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic"), + ConfigVariables: m(false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_authorization_code_grant.test", "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete"), + ConfigVariables: m(true), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_access_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_authorization_endpoint", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_auth_method", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity", "12345"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint", "https://example.com"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_authorization_code_grant.test", "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic"), + ConfigVariables: m(true), + ResourceName: "snowflake_api_authentication_integration_with_authorization_code_grant.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"oauth_client_secret"}, + }, + // unset + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic"), + ConfigVariables: m(false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "comment", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_access_token_validity", "0"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_authorization_endpoint", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_auth_method", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity", "0"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint", ""), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_authorization_code_grant.test", "created_on"), + ), + }, + }, + }) +} + +func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_complete(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "oauth_access_token_validity": config.IntegerVariable(42), + "oauth_authorization_endpoint": config.StringVariable("https://example.com"), + "oauth_client_auth_method": config.StringVariable(string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + "oauth_client_id": config.StringVariable("foo"), + "oauth_client_secret": config.StringVariable("foo"), + "oauth_refresh_token_validity": config.IntegerVariable(12345), + "oauth_token_endpoint": config.StringVariable("https://example.com"), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete"), + ConfigVariables: m(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_access_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_authorization_endpoint", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_auth_method", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity", "12345"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint", "https://example.com"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_authorization_code_grant.test", "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete"), + ConfigVariables: m(), + ResourceName: "snowflake_api_authentication_integration_with_authorization_code_grant.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"oauth_client_secret"}, + }, + }, + }) +} + +func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_invalidIncomplete(t *testing.T) { + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "name": config.StringVariable("foo"), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + ErrorCheck: helpers.AssertErrorContainsPartsFunc(t, []string{ + `The argument "oauth_client_secret" is required, but no definition was found.`, + `The argument "oauth_client_id" is required, but no definition was found.`, + `The argument "enabled" is required, but no definition was found.`, + }), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid"), + ConfigVariables: m(), + }, + }, + }) +} diff --git a/pkg/resources/api_authentication_integration_with_client_credentials.go b/pkg/resources/api_authentication_integration_with_client_credentials.go new file mode 100644 index 0000000000..2ba6659a0e --- /dev/null +++ b/pkg/resources/api_authentication_integration_with_client_credentials.go @@ -0,0 +1,278 @@ +package resources + +import ( + "context" + "errors" + "fmt" + "log" + "reflect" + "strconv" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "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 apiAuthClientCredentialsSchema = func() map[string]*schema.Schema { + uniq := map[string]*schema.Schema{ + "oauth_allowed_scopes": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: "Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow.", + }, + } + return MergeMaps(apiAuthCommonSchema, uniq) +}() + +func ApiAuthenticationIntegrationWithClientCredentials() *schema.Resource { + return &schema.Resource{ + CreateContext: CreateContextApiAuthenticationIntegrationWithClientCredentials, + ReadContext: ReadContextApiAuthenticationIntegrationWithClientCredentials, + UpdateContext: UpdateContextApiAuthenticationIntegrationWithClientCredentials, + DeleteContext: DeleteContextApiAuthenticationIntegrationWithClientCredentials, + Schema: apiAuthClientCredentialsSchema, + CustomizeDiff: customdiff.All( + ForceNewIfChangeToEmptyString("oauth_token_endpoint"), + ForceNewIfChangeToEmptyString("oauth_client_auth_method"), + ), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +func CreateContextApiAuthenticationIntegrationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + enabled := d.Get("enabled").(bool) + name := d.Get("name").(string) + oauthClientId := d.Get("oauth_client_id").(string) + oauthClientSecret := d.Get("oauth_client_secret").(string) + id := sdk.NewAccountObjectIdentifier(name) + req := sdk.NewCreateApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(id, enabled, oauthClientId, oauthClientSecret) + + if v, ok := d.GetOk("comment"); ok { + req.WithComment(v.(string)) + } + + if v, ok := d.GetOk("oauth_access_token_validity"); ok { + req.WithOauthAccessTokenValidity(v.(int)) + } + + if v, ok := d.GetOk("oauth_allowed_scopes"); ok { + elems := expandStringList(v.(*schema.Set).List()) + allowedScopes := make([]sdk.AllowedScope, len(elems)) + for i := range elems { + allowedScopes[i] = sdk.AllowedScope{Scope: elems[i]} + } + req.WithOauthAllowedScopes(allowedScopes) + } + + if v, ok := d.GetOk("oauth_client_auth_method"); ok { + valueRaw := v.(string) + value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(valueRaw) + if err != nil { + return diag.FromErr(err) + } + req.WithOauthClientAuthMethod(value) + } + + if v, ok := d.GetOk("oauth_token_endpoint"); ok { + req.WithOauthTokenEndpoint(v.(string)) + } + + if err := client.SecurityIntegrations.CreateApiAuthenticationWithClientCredentialsFlow(ctx, req); err != nil { + return diag.FromErr(err) + } + + d.SetId(name) + + return ReadContextApiAuthenticationIntegrationWithClientCredentials(ctx, d, meta) +} + +func ReadContextApiAuthenticationIntegrationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta interface{}) 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) + } + + 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", integration.Name); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("comment", integration.Comment); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("created_on", integration.CreatedOn.String()); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("enabled", integration.Enabled); err != nil { + return diag.FromErr(err) + } + + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return diag.FromErr(err) + } + for _, property := range properties { + name := property.Name + value := property.Value + switch name { + case "COMMENT": + if err := d.Set("comment", value); err != nil { + return diag.FromErr(err) + } + case "CREATED_ON": + if err := d.Set("created_on", value); err != nil { + return diag.FromErr(err) + } + case "ENABLED": + if err := d.Set("enabled", helpers.StringToBool(value)); err != nil { + return diag.FromErr(err) + } + case "OAUTH_ACCESS_TOKEN_VALIDITY": + valueInt, err := strconv.Atoi(value) + if err != nil { + return diag.FromErr(err) + } + if err := d.Set("oauth_access_token_validity", valueInt); err != nil { + return diag.FromErr(err) + } + case "OAUTH_ALLOWED_SCOPES": + value = strings.TrimLeft(value, "[") + value = strings.TrimRight(value, "]") + elems := strings.Split(value, ",") + if value == "" { + elems = nil + } + if err := d.Set("oauth_allowed_scopes", elems); err != nil { + return diag.FromErr(err) + } + case "OAUTH_CLIENT_AUTH_METHOD": + if err := d.Set("oauth_client_auth_method", value); err != nil { + return diag.FromErr(err) + } + case "OAUTH_CLIENT_ID": + if err := d.Set("oauth_client_id", value); err != nil { + return diag.FromErr(err) + } + case "OAUTH_CLIENT_SECRET": + if err := d.Set("oauth_client_secret", value); err != nil { + return diag.FromErr(err) + } + case "OAUTH_TOKEN_ENDPOINT": + if err := d.Set("oauth_token_endpoint", value); err != nil { + return diag.FromErr(err) + } + default: + log.Printf("[WARN] unexpected property %v returned from Snowflake", name) + } + } + + return nil +} + +func UpdateContextApiAuthenticationIntegrationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + set, unset := sdk.NewApiAuthenticationWithClientCredentialsFlowIntegrationSetRequest(), sdk.NewApiAuthenticationWithClientCredentialsFlowIntegrationUnsetRequest() + + if d.HasChange("comment") { + set.WithComment(d.Get("comment").(string)) + } + + if d.HasChange("enabled") { + set.WithEnabled(d.Get("enabled").(bool)) + } + + if d.HasChange("oauth_access_token_validity") { + set.WithOauthAccessTokenValidity(d.Get("oauth_access_token_validity").(int)) + } + + if d.HasChange("oauth_allowed_scopes") { + elems := expandStringList(d.Get("oauth_allowed_scopes").(*schema.Set).List()) + allowedScopes := make([]sdk.AllowedScope, len(elems)) + for i := range elems { + allowedScopes[i] = sdk.AllowedScope{Scope: elems[i]} + } + set.WithOauthAllowedScopes(allowedScopes) + } + + if d.HasChange("oauth_client_auth_method") { + v := d.Get("oauth_client_auth_method").(string) + if len(v) > 0 { + value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) + if err != nil { + return diag.FromErr(err) + } + set.WithOauthClientAuthMethod(value) + } + } + + if d.HasChange("oauth_client_id") { + set.WithOauthClientId(d.Get("oauth_client_id").(string)) + } + + if d.HasChange("oauth_client_secret") { + set.WithOauthClientSecret(d.Get("oauth_client_secret").(string)) + } + + if d.HasChange("oauth_token_endpoint") { + set.WithOauthTokenEndpoint(d.Get("oauth_token_endpoint").(string)) + } + + if !reflect.DeepEqual(*set, sdk.ApiAuthenticationWithClientCredentialsFlowIntegrationSetRequest{}) { + if err := client.SecurityIntegrations.AlterApiAuthenticationWithClientCredentialsFlow(ctx, sdk.NewAlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(id).WithSet(*set)); err != nil { + return diag.FromErr(err) + } + } + if !reflect.DeepEqual(*unset, sdk.ApiAuthenticationWithClientCredentialsFlowIntegrationUnsetRequest{}) { + if err := client.SecurityIntegrations.AlterApiAuthenticationWithClientCredentialsFlow(ctx, sdk.NewAlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(id).WithUnset(*unset)); err != nil { + return diag.FromErr(err) + } + } + return ReadContextApiAuthenticationIntegrationWithClientCredentials(ctx, d, meta) +} + +func DeleteContextApiAuthenticationIntegrationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta interface{}) 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/api_authentication_integration_with_client_credentials_acceptance_test.go b/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go new file mode 100644 index 0000000000..83d6f6b769 --- /dev/null +++ b/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go @@ -0,0 +1,172 @@ +package resources_test + +import ( + "testing" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_basic(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func(complete bool) map[string]config.Variable { + c := map[string]config.Variable{ + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "oauth_client_id": config.StringVariable("foo"), + "oauth_client_secret": config.StringVariable("foo"), + } + if complete { + c["comment"] = config.StringVariable("foo") + c["oauth_access_token_validity"] = config.IntegerVariable(42) + c["oauth_allowed_scopes"] = config.SetVariable(config.StringVariable("foo")) + c["oauth_client_auth_method"] = config.StringVariable(string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)) + c["oauth_token_endpoint"] = config.StringVariable("https://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), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic"), + ConfigVariables: m(false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_client_credentials.test", "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete"), + ConfigVariables: m(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_access_token_validity", "42"), + // TODO: proper test + // resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes", "config.StringVariable("foo")"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_auth_method", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint", "https://example.com"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_client_credentials.test", "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic"), + ConfigVariables: m(true), + ResourceName: "snowflake_api_authentication_integration_with_client_credentials.test", + ImportState: true, + ImportStateVerifyIgnore: []string{"oauth_client_secret"}, + }, + // unset + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic"), + ConfigVariables: m(false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "comment", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_access_token_validity", "0"), + // TODO: proper test + // resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes", "config.StringVariable("foo")"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_auth_method", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint", ""), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_client_credentials.test", "created_on"), + ), + }, + }, + }) +} + +func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_complete(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "oauth_access_token_validity": config.IntegerVariable(42), + "oauth_allowed_scopes": config.SetVariable(config.StringVariable("foo")), + "oauth_client_auth_method": config.StringVariable(string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + "oauth_client_id": config.StringVariable("foo"), + "oauth_client_secret": config.StringVariable("foo"), + "oauth_token_endpoint": config.StringVariable("https://example.com"), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete"), + ConfigVariables: m(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_access_token_validity", "42"), + // TODO: proper test + // resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes", "config.StringVariable("foo")"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_auth_method", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint", "https://example.com"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_client_credentials.test", "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete"), + ConfigVariables: m(), + ResourceName: "snowflake_api_authentication_integration_with_client_credentials.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"oauth_client_secret"}, + }, + }, + }) +} + +func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_invalidIncomplete(t *testing.T) { + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "name": config.StringVariable("foo"), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + ErrorCheck: helpers.AssertErrorContainsPartsFunc(t, []string{ + `The argument "enabled" is required, but no definition was found.`, + `The argument "oauth_client_id" is required, but no definition was found.`, + `The argument "oauth_client_secret" is required, but no definition was found.`, + }), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid"), + ConfigVariables: m(), + }, + }, + }) +} diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer.go b/pkg/resources/api_authentication_integration_with_jwt_bearer.go new file mode 100644 index 0000000000..83fb2a7798 --- /dev/null +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer.go @@ -0,0 +1,280 @@ +package resources + +import ( + "context" + "errors" + "fmt" + "log" + "reflect" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "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" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +var apiAuthJwtBearerSchema = func() map[string]*schema.Schema { + uniq := map[string]*schema.Schema{ + "oauth_refresh_token_validity": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(1), + Description: "Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server.", + }, + "oauth_authorization_endpoint": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the URL for authenticating to the external service.", + }, + "oauth_assertion_issuer": { + Type: schema.TypeString, + Required: true, + }, + } + return MergeMaps(apiAuthCommonSchema, uniq) +}() + +func ApiAuthenticationIntegrationWithJwtBearer() *schema.Resource { + return &schema.Resource{ + CreateContext: CreateContextApiAuthenticationIntegrationWithJwtBearer, + ReadContext: ReadContextApiAuthenticationIntegrationWithJwtBearer, + UpdateContext: UpdateContextApiAuthenticationIntegrationWithJwtBearer, + DeleteContext: DeleteContextApiAuthenticationIntegrationWithJwtBearer, + Schema: apiAuthJwtBearerSchema, + CustomizeDiff: customdiff.All( + ForceNewIfChangeToEmptyString("oauth_token_endpoint"), + ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), + ForceNewIfChangeToEmptyString("oauth_client_auth_method"), + ), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +func CreateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + enabled := d.Get("enabled").(bool) + name := d.Get("name").(string) + oauthClientId := d.Get("oauth_client_id").(string) + oauthClientSecret := d.Get("oauth_client_secret").(string) + oauthAssertionIssuer := d.Get("oauth_client_secret").(string) + id := sdk.NewAccountObjectIdentifier(name) + req := sdk.NewCreateApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest(id, enabled, oauthAssertionIssuer, oauthClientId, oauthClientSecret) + + if v, ok := d.GetOk("comment"); ok { + req.WithComment(v.(string)) + } + + if v, ok := d.GetOk("oauth_access_token_validity"); ok { + req.WithOauthAccessTokenValidity(v.(int)) + } + + if v, ok := d.GetOk("oauth_authorization_endpoint"); ok { + req.WithOauthAuthorizationEndpoint(v.(string)) + } + + if v, ok := d.GetOk("oauth_client_auth_method"); ok { + valueRaw := v.(string) + value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(valueRaw) + if err != nil { + return diag.FromErr(err) + } + req.WithOauthClientAuthMethod(value) + } + + if v, ok := d.GetOk("oauth_refresh_token_validity"); ok { + req.WithOauthRefreshTokenValidity(v.(int)) + } + + if v, ok := d.GetOk("oauth_token_endpoint"); ok { + req.WithOauthTokenEndpoint(v.(string)) + } + + if err := client.SecurityIntegrations.CreateApiAuthenticationWithJwtBearerFlow(ctx, req); err != nil { + return diag.FromErr(err) + } + + d.SetId(name) + + return ReadContextApiAuthenticationIntegrationWithJwtBearer(ctx, d, meta) +} + +func ReadContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta interface{}) 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) + } + + 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", integration.Name); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("comment", integration.Comment); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("created_on", integration.CreatedOn.String()); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("enabled", integration.Enabled); err != nil { + return diag.FromErr(err) + } + + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return diag.FromErr(err) + } + for _, property := range properties { + name := property.Name + value := property.Value + switch name { + case "COMMENT": + if err := d.Set("comment", value); err != nil { + return diag.FromErr(err) + } + case "CREATED_ON": + if err := d.Set("created_on", value); err != nil { + return diag.FromErr(err) + } + case "ENABLED": + if err := d.Set("enabled", helpers.StringToBool(value)); err != nil { + return diag.FromErr(err) + } + case "OAUTH_ACCESS_TOKEN_VALIDITY": + if err := d.Set("oauth_access_token_validity", "TODO"); err != nil { + return diag.FromErr(err) + } + case "OAUTH_AUTHORIZATION_ENDPOINT": + if err := d.Set("oauth_authorization_endpoint", value); err != nil { + return diag.FromErr(err) + } + case "OAUTH_CLIENT_AUTH_METHOD": + if err := d.Set("oauth_client_auth_method", value); err != nil { + return diag.FromErr(err) + } + case "OAUTH_CLIENT_ID": + if err := d.Set("oauth_client_id", value); err != nil { + return diag.FromErr(err) + } + case "OAUTH_CLIENT_SECRET": + if err := d.Set("oauth_client_secret", value); err != nil { + return diag.FromErr(err) + } + case "OAUTH_REFRESH_TOKEN_VALIDITY": + if err := d.Set("oauth_refresh_token_validity", "TODO"); err != nil { + return diag.FromErr(err) + } + case "OAUTH_TOKEN_ENDPOINT": + if err := d.Set("oauth_token_endpoint", value); err != nil { + return diag.FromErr(err) + } + default: + log.Printf("[WARN] unexpected property %v returned from Snowflake", name) + } + } + + return nil +} + +func UpdateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + set, unset := sdk.NewApiAuthenticationWithJwtBearerFlowIntegrationSetRequest(), sdk.NewApiAuthenticationWithJwtBearerFlowIntegrationUnsetRequest() + + if d.HasChange("comment") { + set.WithComment(d.Get("comment").(string)) + } + + if d.HasChange("enabled") { + set.WithEnabled(d.Get("enabled").(bool)) + } + + if d.HasChange("oauth_access_token_validity") { + set.WithOauthAccessTokenValidity(d.Get("oauth_access_token_validity").(int)) + } + + if d.HasChange("oauth_authorization_endpoint") { + set.WithOauthAuthorizationEndpoint(d.Get("oauth_authorization_endpoint").(string)) + } + + if d.HasChange("oauth_client_auth_method") { + v := d.Get("oauth_client_auth_method").(string) + if len(v) > 0 { + value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) + if err != nil { + return diag.FromErr(err) + } + set.WithOauthClientAuthMethod(value) + } + } + + if d.HasChange("oauth_client_id") { + set.WithOauthClientId(d.Get("oauth_client_id").(string)) + } + + if d.HasChange("oauth_client_secret") { + set.WithOauthClientSecret(d.Get("oauth_client_secret").(string)) + } + + if d.HasChange("oauth_refresh_token_validity") { + set.WithOauthRefreshTokenValidity(d.Get("oauth_refresh_token_validity").(int)) + } + + if d.HasChange("oauth_token_endpoint") { + set.WithOauthTokenEndpoint(d.Get("oauth_token_endpoint").(string)) + } + + if !reflect.DeepEqual(*set, sdk.ApiAuthenticationWithJwtBearerFlowIntegrationSetRequest{}) { + if err := client.SecurityIntegrations.AlterApiAuthenticationWithJwtBearerFlow(ctx, sdk.NewAlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest(id).WithSet(*set)); err != nil { + return diag.FromErr(err) + } + } + if !reflect.DeepEqual(*unset, sdk.ApiAuthenticationWithJwtBearerFlowIntegrationUnsetRequest{}) { + if err := client.SecurityIntegrations.AlterApiAuthenticationWithJwtBearerFlow(ctx, sdk.NewAlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest(id).WithUnset(*unset)); err != nil { + return diag.FromErr(err) + } + } + return ReadContextApiAuthenticationIntegrationWithJwtBearer(ctx, d, meta) +} + +func DeleteContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta interface{}) 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/api_authentication_integration_with_jwt_bearer_acceptance_test.go b/pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go new file mode 100644 index 0000000000..eb3d4a2bbf --- /dev/null +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go @@ -0,0 +1,179 @@ +package resources_test + +import ( + "testing" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestAcc_ApiAuthenticationIntegrationWithJwtBearer_basic(t *testing.T) { + // TODO [SNOW-1452191]: unskip + t.Skip("Skip because of the error: Invalid value specified for property 'OAUTH_CLIENT_SECRET'") + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func(complete bool) map[string]config.Variable { + c := map[string]config.Variable{ + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "oauth_client_id": config.StringVariable("foo"), + "oauth_client_secret": config.StringVariable("foo"), + } + if complete { + c["comment"] = config.StringVariable("foo") + c["oauth_access_token_validity"] = config.IntegerVariable(42) + c["oauth_authorization_endpoint"] = config.StringVariable("foo") + c["oauth_client_auth_method"] = config.StringVariable("foo") + c["oauth_refresh_token_validity"] = config.IntegerVariable(42) + c["oauth_token_endpoint"] = config.StringVariable("foo") + } + return c + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic"), + ConfigVariables: m(false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "name", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete"), + ConfigVariables: m(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "name", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_access_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_authorization_endpoint", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_auth_method", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_refresh_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_token_endpoint", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic"), + ConfigVariables: m(true), + ResourceName: "snowflake_api_authentication_integration_with_jwt_bearer.test", + ImportState: true, + ImportStateVerify: true, + }, + // unset + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic"), + ConfigVariables: m(false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_access_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_authorization_endpoint", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_auth_method", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_refresh_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_token_endpoint", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on"), + ), + }, + }, + }) +} + +func TestAcc_ApiAuthenticationIntegrationWithJwtBearer_complete(t *testing.T) { + // TODO [SNOW-1452191]: unskip + t.Skip("Skip because of the error: Invalid value specified for property 'OAUTH_CLIENT_SECRET'") + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "oauth_access_token_validity": config.IntegerVariable(42), + "oauth_authorization_endpoint": config.StringVariable("foo"), + "oauth_client_auth_method": config.StringVariable("foo"), + "oauth_client_id": config.StringVariable("foo"), + "oauth_client_secret": config.StringVariable("foo"), + "oauth_refresh_token_validity": config.IntegerVariable(42), + "oauth_token_endpoint": config.StringVariable("foo"), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete"), + ConfigVariables: m(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_access_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_authorization_endpoint", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_auth_method", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_refresh_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_token_endpoint", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete"), + ConfigVariables: m(), + ResourceName: "snowflake_api_authentication_integration_with_jwt_bearer.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_ApiAuthenticationIntegrationWithJwtBearer_invalidIncomplete(t *testing.T) { + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "name": config.StringVariable("foo"), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + ErrorCheck: helpers.AssertErrorContainsPartsFunc(t, []string{ + `The argument "enabled" is required, but no definition was found.`, + `The argument "oauth_client_id" is required, but no definition was found.`, + `The argument "oauth_client_secret" is required, but no definition was found.`, + }), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid"), + ConfigVariables: m(), + }, + }, + }) +} diff --git a/pkg/resources/common.go b/pkg/resources/common.go index 799525341b..ce30d70df5 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -15,6 +15,7 @@ import ( "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/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) // DiffSuppressStatement will suppress diffs between statements if they differ in only case or in @@ -103,3 +104,54 @@ func RSAKeyHash(key string) (string, error) { hash := sha256.Sum256(pubKeyBytes) return fmt.Sprintf("SHA256:%s", base64.StdEncoding.EncodeToString(hash[:])), nil } + +var apiAuthCommonSchema = map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Specifies the identifier (i.e. name) for the integration. This value must be unique in your account.", + }, + "enabled": { + Type: schema.TypeBool, + Required: true, + Description: "Specifies whether this security integration is enabled or disabled.", + }, + "oauth_token_endpoint": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly).", + }, + "oauth_client_auth_method": { + Type: schema.TypeString, + Optional: true, + // ValidateDiagFunc: sdk, + Description: "Specifies the client ID for the OAuth application in the external service.", + }, + "oauth_client_id": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the client ID for the OAuth application in the external service.", + }, + "oauth_client_secret": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the client secret for the OAuth application in the ServiceNow instance from the previous step. The connector uses this to request an access token from the ServiceNow instance.", + }, + "oauth_access_token_validity": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(1), + Description: "Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server.", + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies a comment for the integration.", + }, + "created_on": { + Type: schema.TypeString, + Computed: true, + Description: "Date and time when the integration was created.", + }, +} diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index a3f338bed1..21ff8b66b4 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -89,3 +89,11 @@ func ComputedIfAnyAttributeChanged(key string, changedAttributeKeys ...string) s return result }) } + +// 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 + }) +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/test.tf new file mode 100644 index 0000000000..daf6b9b893 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/test.tf @@ -0,0 +1,6 @@ +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + enabled = var.enabled + name = var.name + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/variables.tf new file mode 100644 index 0000000000..34a32daf3e --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/variables.tf @@ -0,0 +1,13 @@ + +variable "enabled" { + type = bool +} +variable "name" { + type = string +} +variable "oauth_client_id" { + type = string +} +variable "oauth_client_secret" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf new file mode 100644 index 0000000000..593c1f4da5 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf @@ -0,0 +1,12 @@ +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + comment = var.comment + enabled = var.enabled + name = var.name + oauth_access_token_validity = var.oauth_access_token_validity + oauth_authorization_endpoint = var.oauth_authorization_endpoint + oauth_client_auth_method = var.oauth_client_auth_method + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret + oauth_refresh_token_validity = var.oauth_refresh_token_validity + oauth_token_endpoint = var.oauth_token_endpoint +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf new file mode 100644 index 0000000000..359057c239 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf @@ -0,0 +1,31 @@ + +variable "comment" { + type = string +} +variable "enabled" { + type = bool +} +variable "name" { + type = string +} +variable "oauth_access_token_validity" { + type = number +} +variable "oauth_authorization_endpoint" { + type = string +} +variable "oauth_client_auth_method" { + type = string +} +variable "oauth_client_id" { + type = string +} +variable "oauth_client_secret" { + type = string +} +variable "oauth_refresh_token_validity" { + type = number +} +variable "oauth_token_endpoint" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/test.tf new file mode 100644 index 0000000000..13f7028ac0 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/test.tf @@ -0,0 +1,3 @@ +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + name = var.name +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/variables.tf new file mode 100644 index 0000000000..77e5cc9698 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/variables.tf @@ -0,0 +1,3 @@ +variable "name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/test.tf new file mode 100644 index 0000000000..fad468b71f --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/test.tf @@ -0,0 +1,6 @@ +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + enabled = var.enabled + name = var.name + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/variables.tf new file mode 100644 index 0000000000..34a32daf3e --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/variables.tf @@ -0,0 +1,13 @@ + +variable "enabled" { + type = bool +} +variable "name" { + type = string +} +variable "oauth_client_id" { + type = string +} +variable "oauth_client_secret" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf new file mode 100644 index 0000000000..4996a8118d --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf @@ -0,0 +1,10 @@ +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + comment = var.comment + enabled = var.enabled + name = var.name + oauth_access_token_validity = var.oauth_access_token_validity + oauth_client_auth_method = var.oauth_client_auth_method + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret + oauth_token_endpoint = var.oauth_token_endpoint +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf new file mode 100644 index 0000000000..158d73de19 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf @@ -0,0 +1,25 @@ + +variable "comment" { + type = string +} +variable "enabled" { + type = bool +} +variable "name" { + type = string +} +variable "oauth_access_token_validity" { + type = number +} +variable "oauth_client_auth_method" { + type = string +} +variable "oauth_client_id" { + type = string +} +variable "oauth_client_secret" { + type = string +} +variable "oauth_token_endpoint" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/test.tf new file mode 100644 index 0000000000..30a66f6c15 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/test.tf @@ -0,0 +1,3 @@ +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + name = var.name +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/variables.tf new file mode 100644 index 0000000000..77e5cc9698 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/variables.tf @@ -0,0 +1,3 @@ +variable "name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf new file mode 100644 index 0000000000..b3cca25614 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf @@ -0,0 +1,12 @@ +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + comment = var.comment + enabled = var.enabled + name = var.name + oauth_access_token_validity = var.oauth_access_token_validity + oauth_authorization_endpoint = var.oauth_authorization_endpoint + oauth_client_auth_method = var.oauth_client_auth_method + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret + oauth_refresh_token_validity = var.oauth_refresh_token_validity + oauth_token_endpoint = var.oauth_token_endpoint +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf new file mode 100644 index 0000000000..359057c239 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf @@ -0,0 +1,31 @@ + +variable "comment" { + type = string +} +variable "enabled" { + type = bool +} +variable "name" { + type = string +} +variable "oauth_access_token_validity" { + type = number +} +variable "oauth_authorization_endpoint" { + type = string +} +variable "oauth_client_auth_method" { + type = string +} +variable "oauth_client_id" { + type = string +} +variable "oauth_client_secret" { + type = string +} +variable "oauth_refresh_token_validity" { + type = number +} +variable "oauth_token_endpoint" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/test.tf new file mode 100644 index 0000000000..b3cca25614 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/test.tf @@ -0,0 +1,12 @@ +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + comment = var.comment + enabled = var.enabled + name = var.name + oauth_access_token_validity = var.oauth_access_token_validity + oauth_authorization_endpoint = var.oauth_authorization_endpoint + oauth_client_auth_method = var.oauth_client_auth_method + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret + oauth_refresh_token_validity = var.oauth_refresh_token_validity + oauth_token_endpoint = var.oauth_token_endpoint +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/variables.tf new file mode 100644 index 0000000000..359057c239 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/variables.tf @@ -0,0 +1,31 @@ + +variable "comment" { + type = string +} +variable "enabled" { + type = bool +} +variable "name" { + type = string +} +variable "oauth_access_token_validity" { + type = number +} +variable "oauth_authorization_endpoint" { + type = string +} +variable "oauth_client_auth_method" { + type = string +} +variable "oauth_client_id" { + type = string +} +variable "oauth_client_secret" { + type = string +} +variable "oauth_refresh_token_validity" { + type = number +} +variable "oauth_token_endpoint" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/test.tf new file mode 100644 index 0000000000..2090469c34 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/test.tf @@ -0,0 +1,3 @@ +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + name = var.name +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/variables.tf new file mode 100644 index 0000000000..77e5cc9698 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/variables.tf @@ -0,0 +1,3 @@ +variable "name" { + type = string +} diff --git a/pkg/sdk/security_integrations_def.go b/pkg/sdk/security_integrations_def.go index db276281ee..307a5dca31 100644 --- a/pkg/sdk/security_integrations_def.go +++ b/pkg/sdk/security_integrations_def.go @@ -17,6 +17,20 @@ const ( ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption = "CLIENT_SECRET_POST" ) +var AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption = []ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption{ + ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost, +} + +func ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(s string) (ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption, error) { + s = strings.ToUpper(s) + switch s { + case string(ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost): + return ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost, nil + default: + return "", fmt.Errorf("invalid ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption: %s", s) + } +} + type ExternalOauthSecurityIntegrationTypeOption string const ( From f1e366846e432491a9171357b9cb1811d959edd2 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Fri, 28 Jun 2024 16:04:48 +0200 Subject: [PATCH 03/18] authorization code grant v1 --- .../helpers/security_integration_client.go | 2 +- ...tegration_with_authorization_code_grant.go | 428 ++++++++++++++---- ...uthorization_code_grant_acceptance_test.go | 67 ++- ...ion_integration_with_client_credentials.go | 4 +- ...hentication_integration_with_jwt_bearer.go | 6 +- pkg/resources/common.go | 65 ++- pkg/resources/custom_diffs.go | 6 +- .../complete/test.tf | 2 + .../complete/variables.tf | 6 + .../complete/test.tf | 1 + .../complete/variables.tf | 3 + .../warehouse_rework_show_output_proposal.go | 18 + ...api_authentication_security_integration.go | 60 +++ pkg/sdk/security_integrations_def.go | 6 +- .../security_integrations_dto_builders_gen.go | 32 +- pkg/sdk/security_integrations_dto_gen.go | 24 +- pkg/sdk/security_integrations_gen.go | 86 ++-- pkg/sdk/security_integrations_gen_test.go | 29 +- pkg/sdk/security_integrations_impl_gen.go | 47 ++ .../security_integrations_validations_gen.go | 4 +- ...urity_integrations_gen_integration_test.go | 4 +- 21 files changed, 690 insertions(+), 210 deletions(-) create mode 100644 pkg/schemas/api_authentication_security_integration.go diff --git a/pkg/acceptance/helpers/security_integration_client.go b/pkg/acceptance/helpers/security_integration_client.go index 08d854fbb4..3c4d698120 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()) { diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go index 60f81bba29..9829ea5628 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go @@ -4,12 +4,14 @@ import ( "context" "errors" "fmt" - "log" "reflect" "strconv" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "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" @@ -21,16 +23,32 @@ import ( var apiAuthAuthorizationCodeGrantSchema = func() map[string]*schema.Schema { uniq := map[string]*schema.Schema{ "oauth_refresh_token_validity": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntAtLeast(1), - Description: "Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow.", + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(0), + Default: -1, + Description: "Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow.", + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("oauth_refresh_token_validity"), }, "oauth_authorization_endpoint": { Type: schema.TypeString, Optional: true, + Default: "unknown", Description: "Specifies the URL for authenticating to the external service.", }, + "oauth_allowed_scopes": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: "Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow.", + }, + "oauth_grant": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"unknown", "AUTHORIZATION_CODE"}, true), + Description: "Specifies the client ID for the OAuth application in the external service.", + Default: "unknown", + }, } return MergeMaps(apiAuthCommonSchema, uniq) }() @@ -38,21 +56,119 @@ var apiAuthAuthorizationCodeGrantSchema = func() map[string]*schema.Schema { func ApiAuthenticationIntegrationWithAuthorizationCodeGrant() *schema.Resource { return &schema.Resource{ CreateContext: CreateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, - ReadContext: ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, + ReadContext: ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(true), UpdateContext: UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, DeleteContext: DeleteContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, CustomizeDiff: customdiff.All( - ForceNewIfChangeToEmptyString("oauth_token_endpoint"), - ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), - ForceNewIfChangeToEmptyString("oauth_client_auth_method"), + ForceNewIfChangeToDefaultString("oauth_token_endpoint"), + ForceNewIfChangeToDefaultString("oauth_authorization_endpoint"), + ForceNewIfChangeToDefaultString("oauth_client_auth_method"), + ForceNewIfChangeToDefaultString("oauth_grant"), + ComputedIfAnyAttributeChanged(showOutputAttributeName, "enabled", "comment"), + ComputedIfAnyAttributeChanged(describeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", + "oauth_client_id", "oauth_client_auth_method", "oauth_authorization_endpoint", + "oauth_token_endpoint", "oauth_allowed_scopes", "oauth_grant"), ), Schema: apiAuthAuthorizationCodeGrantSchema, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: ImportApiAuthenticationWithAuthorizationCodeGrant, }, } } +func ImportApiAuthenticationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + logging.DebugLogger.Printf("[DEBUG] Starting api auth integration with authorization code grant 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 + } + + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return nil, err + } + + if err = d.Set("name", integration.Name); err != nil { + return nil, err + } + if err = d.Set("enabled", integration.Enabled); err != nil { + return nil, err + } + if err = d.Set("comment", integration.Comment); err != nil { + return nil, err + } + + oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" + }) + if err == nil { + value, err := strconv.Atoi(oauthAccessTokenValidity.Value) + if err != nil { + return nil, err + } + if err = d.Set("oauth_access_token_validity", value); err != nil { + return nil, err + } + } + oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" + }) + if err == nil { + value, err := strconv.Atoi(oauthRefreshTokenValidity.Value) + if err != nil { + return nil, err + } + if err = d.Set("oauth_refresh_token_validity", value); err != nil { + return nil, err + } + } + oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) + if err == nil { + if err = d.Set("oauth_client_id", oauthClientId.Value); err != nil { + return nil, err + } + } + oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_CLIENT_AUTH_METHOD" + }) + if err == nil { + if err = d.Set("oauth_client_auth_method", oauthClientAuthMethod.Value); err != nil { + return nil, err + } + } + oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_AUTHORIZATION_ENDPOINT" + }) + if err == nil { + if err = d.Set("oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value); err != nil { + return nil, err + } + } + oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) + if err == nil { + if err = d.Set("oauth_token_endpoint", oauthTokenEndpoint.Value); err != nil { + return nil, err + } + } + oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) + if err == nil { + if err = d.Set("oauth_allowed_scopes", listValueToSlice(oauthAllowedScopes.Value, true, false)); err != nil { + return nil, err + } + } + oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) + if err == nil { + if err = d.Set("oauth_grant", oauthGrant.Value); err != nil { + return nil, err + } + } + + return []*schema.ResourceData{d}, nil +} + func CreateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client enabled := d.Get("enabled").(bool) @@ -66,29 +182,42 @@ func CreateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx con req.WithComment(v.(string)) } - if v, ok := d.GetOk("oauth_access_token_validity"); ok { - req.WithOauthAccessTokenValidity(v.(int)) + if v := d.Get("oauth_access_token_validity").(int); v != -1 { + req.WithOauthAccessTokenValidity(v) } - if v, ok := d.GetOk("oauth_authorization_endpoint"); ok { - req.WithOauthAuthorizationEndpoint(v.(string)) + if v := d.Get("oauth_authorization_endpoint").(string); v != "unknown" { + req.WithOauthAuthorizationEndpoint(v) } - if v, ok := d.GetOk("oauth_client_auth_method"); ok { - valueRaw := v.(string) - value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(valueRaw) + if v := d.Get("oauth_client_auth_method").(string); v != "unknown" { + value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) if err != nil { return diag.FromErr(err) } req.WithOauthClientAuthMethod(value) } - if v, ok := d.GetOk("oauth_refresh_token_validity"); ok { - req.WithOauthRefreshTokenValidity(v.(int)) + if v := d.Get("oauth_refresh_token_validity").(int); v != -1 { + req.WithOauthRefreshTokenValidity(v) + } + + if v := d.Get("oauth_grant").(string); v != "unknown" { + if v == "AUTHORIZATION_CODE" { + req.WithOauthGrantAuthorizationCode(true) + } } - if v, ok := d.GetOk("oauth_token_endpoint"); ok { - req.WithOauthTokenEndpoint(v.(string)) + if v := d.Get("oauth_token_endpoint").(string); v != "unknown" { + req.WithOauthTokenEndpoint(v) + } + if v, ok := d.GetOk("oauth_allowed_scopes"); ok { + elems := expandStringList(v.(*schema.Set).List()) + allowedScopes := make([]sdk.AllowedScope, len(elems)) + for i := range elems { + allowedScopes[i] = sdk.AllowedScope{Scope: elems[i]} + } + req.WithOauthAllowedScopes(allowedScopes) } if err := client.SecurityIntegrations.CreateApiAuthenticationWithAuthorizationCodeGrantFlow(ctx, req); err != nil { @@ -97,110 +226,192 @@ func CreateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx con d.SetId(name) - return ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx, d, meta) + return ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(false)(ctx, d, meta) } -func ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) +func ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(withExternalChangesMarking bool) schema.ReadContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) 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), - }, + 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) + } + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return diag.FromErr(err) } - 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", integration.Name); err != nil { - return diag.FromErr(err) - } - - if err := d.Set("comment", integration.Comment); err != nil { - return diag.FromErr(err) - } - - if err := d.Set("created_on", integration.CreatedOn.String()); err != nil { - return diag.FromErr(err) - } - if err := d.Set("enabled", integration.Enabled); 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", integration.Name); err != nil { + return diag.FromErr(err) + } + if err := d.Set("comment", integration.Comment); err != nil { + return diag.FromErr(err) + } + if err := d.Set("enabled", integration.Enabled); err != nil { + return diag.FromErr(err) + } + if withExternalChangesMarking { + if err = handleExternalChangesToObjectInShow(d, + showMapping{"comment", "comment", integration.Comment, integration.Comment, nil}, + showMapping{"enabled", "enabled", integration.Enabled, integration.Enabled, nil}, + ); err != nil { + return diag.FromErr(err) + } - properties, err := client.SecurityIntegrations.Describe(ctx, id) - if err != nil { - return diag.FromErr(err) - } - for _, property := range properties { - name := property.Name - value := property.Value - switch name { - case "COMMENT": - if err := d.Set("comment", value); err != nil { + enabled, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "ENABLED" }) + if err != nil { return diag.FromErr(err) } - case "CREATED_ON": - if err := d.Set("created_on", value); err != nil { + + oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" + }) + if err != nil { return diag.FromErr(err) } - case "ENABLED": - if err := d.Set("enabled", helpers.StringToBool(value)); err != nil { + + oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" + }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_ACCESS_TOKEN_VALIDITY": - valueInt, err := strconv.Atoi(value) + + oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) if err != nil { return diag.FromErr(err) } - if err := d.Set("oauth_access_token_validity", valueInt); err != nil { + + oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_CLIENT_AUTH_METHOD" + }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_AUTHORIZATION_ENDPOINT": - if err := d.Set("oauth_authorization_endpoint", value); err != nil { + + oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_AUTHORIZATION_ENDPOINT" + }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_CLIENT_AUTH_METHOD": - if err := d.Set("oauth_client_auth_method", value); err != nil { + + oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_CLIENT_ID": - if err := d.Set("oauth_client_id", value); err != nil { + + oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_CLIENT_SECRET": - if err := d.Set("oauth_client_secret", value); err != nil { + + oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_REFRESH_TOKEN_VALIDITY": - valueInt, err := strconv.Atoi(value) + oauthAccessTokenValidityInt, err := strconv.Atoi(oauthAccessTokenValidity.Value) if err != nil { return diag.FromErr(err) } - if err := d.Set("oauth_refresh_token_validity", valueInt); err != nil { + oauthRefreshTokenValidityInt, err := strconv.Atoi(oauthRefreshTokenValidity.Value) + if err != nil { return diag.FromErr(err) } - case "OAUTH_TOKEN_ENDPOINT": - if err := d.Set("oauth_token_endpoint", value); err != nil { + if err = handleExternalChangesToObjectInDescribe(d, + describeMapping{"enabled", "enabled", enabled.Value, enabled.Value, nil}, + describeMapping{"oauth_access_token_validity", "oauth_access_token_validity", oauthAccessTokenValidityInt, oauthAccessTokenValidityInt, stringToIntNormalizer}, + describeMapping{"oauth_refresh_token_validity", "oauth_refresh_token_validity", oauthRefreshTokenValidityInt, oauthRefreshTokenValidityInt, stringToIntNormalizer}, + describeMapping{"oauth_client_id", "oauth_client_id", oauthClientId.Value, oauthClientId.Value, nil}, + describeMapping{"oauth_client_auth_method", "oauth_client_auth_method", oauthClientAuthMethod.Value, oauthClientAuthMethod.Value, nil}, + describeMapping{"oauth_authorization_endpoint", "oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value, oauthAuthorizationEndpoint.Value, nil}, + describeMapping{"oauth_token_endpoint", "oauth_token_endpoint", oauthTokenEndpoint.Value, oauthTokenEndpoint.Value, nil}, + describeMapping{"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, listValueToSlice(oauthAllowedScopes.Value, true, false), nil}, + describeMapping{"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, + ); err != nil { return diag.FromErr(err) } - default: - log.Printf("[WARN] unexpected property %v returned from Snowflake", name) } - } + if !d.GetRawConfig().IsNull() { + if v := d.GetRawConfig().AsValueMap()["enabled"]; !v.IsNull() { + if err = d.Set("enabled", v.True()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_access_token_validity"]; !v.IsNull() { + intVal, _ := v.AsBigFloat().Int64() + if err = d.Set("oauth_access_token_validity", intVal); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_refresh_token_validity"]; !v.IsNull() { + intVal, _ := v.AsBigFloat().Int64() + if err = d.Set("oauth_refresh_token_validity", intVal); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_client_id"]; !v.IsNull() { + if err = d.Set("oauth_client_id", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_client_auth_method"]; !v.IsNull() { + if err = d.Set("oauth_client_auth_method", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_authorization_endpoint"]; !v.IsNull() { + if err = d.Set("oauth_authorization_endpoint", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_token_endpoint"]; !v.IsNull() { + if err = d.Set("oauth_token_endpoint", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_allowed_scopes"]; !v.IsNull() { + if err = d.Set("oauth_allowed_scopes", ctyValToSliceString(v)); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_grant"]; !v.IsNull() { + if err = d.Set("oauth_grant", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["comment"]; !v.IsNull() { + if err = d.Set("comment", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + } - return nil + 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.ApiAuthSecurityIntegrationPropertiesToSchema(properties)}); err != nil { + return diag.FromErr(err) + } + + return nil + } } func UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -209,15 +420,32 @@ func UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx con set, unset := sdk.NewApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest(), sdk.NewApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnsetRequest() if d.HasChange("comment") { - set.WithComment(d.Get("comment").(string)) + if v, ok := d.GetOk("comment"); ok { + set.WithComment(v.(string)) + } else { + unset.WithComment(true) + } } if d.HasChange("enabled") { - set.WithEnabled(d.Get("enabled").(bool)) + if v := d.Get("comment").(string); v != "unknown" { + parsed, err := strconv.ParseBool(v) + if err != nil { + return diag.FromErr(err) + } + set.WithEnabled(parsed) + } else { + unset.WithEnabled(true) + } } if d.HasChange("oauth_access_token_validity") { - set.WithOauthAccessTokenValidity(d.Get("oauth_access_token_validity").(int)) + if v := d.Get("oauth_access_token_validity").(int); v != -1 { + set.WithOauthAccessTokenValidity(v) + } else { + // TODO: use UNSET + set.WithOauthAccessTokenValidity(0) + } } if d.HasChange("oauth_authorization_endpoint") { @@ -238,13 +466,23 @@ func UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx con if d.HasChange("oauth_client_id") { set.WithOauthClientId(d.Get("oauth_client_id").(string)) } + if d.HasChange("oauth_grant") { + if v := d.Get("oauth_grant").(string); v == "AUTHORIZATION_CODE" { + set.WithOauthGrantAuthorizationCode(true) + } + } if d.HasChange("oauth_client_secret") { set.WithOauthClientSecret(d.Get("oauth_client_secret").(string)) } if d.HasChange("oauth_refresh_token_validity") { - set.WithOauthRefreshTokenValidity(d.Get("oauth_refresh_token_validity").(int)) + if v := d.Get("oauth_refresh_token_validity").(int); v != -1 { + set.WithOauthRefreshTokenValidity(v) + } else { + // TODO: use UNSET + set.WithOauthRefreshTokenValidity(7776000) + } } if d.HasChange("oauth_token_endpoint") { @@ -261,7 +499,7 @@ func UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx con return diag.FromErr(err) } } - return ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx, d, meta) + return ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(false)(ctx, d, meta) } func DeleteContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go index 5280bf190c..c83665ef86 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go @@ -28,6 +28,8 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes c["oauth_client_auth_method"] = config.StringVariable(string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)) c["oauth_refresh_token_validity"] = config.IntegerVariable(12345) c["oauth_token_endpoint"] = config.StringVariable("https://example.com") + c["oauth_allowed_scopes"] = config.SetVariable(config.StringVariable("foo")) + c["oauth_grant"] = config.StringVariable("AUTHORIZATION_CODE") } return c } @@ -46,7 +48,28 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "name", id.Name()), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_id", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), - resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_authorization_code_grant.test", "created_on"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.integration_type", "API_AUTHENTICATION"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.comment", ""), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_access_token_validity.0.value", "0"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_refresh_token_validity.0.value", "0"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_client_id.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_client_auth_method.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_authorization_endpoint.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_token_endpoint.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_allowed_scopes.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_grant.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.parent_integration.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.auth_type.0.value", "OAUTH2"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.comment.0.value", ""), ), }, { @@ -63,8 +86,29 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity", "12345"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint", "https://example.com"), - resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_authorization_code_grant.test", "created_on"), - ), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_grant", "AUTHORIZATION_CODE"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.integration_type", "API_AUTHENTICATION"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.comment", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_access_token_validity.0.value", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_refresh_token_validity.0.value", "12345"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_client_id.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_client_auth_method.0.value", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_authorization_endpoint.0.value", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_token_endpoint.0.value", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_allowed_scopes.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_grant.0.value", "AUTHORIZATION_CODE"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.parent_integration.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.auth_type.0.value", "OAUTH2"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.comment.0.value", "foo")), }, { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic"), @@ -82,14 +126,13 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "comment", ""), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "name", id.Name()), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_access_token_validity", "0"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_authorization_endpoint", ""), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_auth_method", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_access_token_validity", "-1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_authorization_endpoint", "unknown"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_auth_method", "unknown"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_id", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity", "0"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint", ""), - resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_authorization_code_grant.test", "created_on"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity", "-1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint", "unknown"), ), }, }, @@ -110,6 +153,8 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_complete(t * "oauth_client_secret": config.StringVariable("foo"), "oauth_refresh_token_validity": config.IntegerVariable(12345), "oauth_token_endpoint": config.StringVariable("https://example.com"), + "oauth_grant": config.StringVariable("AUTHORIZATION_CODE"), + "oauth_allowed_scopes": config.SetVariable(config.StringVariable("foo")), } } resource.Test(t, resource.TestCase{ @@ -133,7 +178,9 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_complete(t * resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity", "12345"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint", "https://example.com"), - resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_authorization_code_grant.test", "created_on"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_allowed_scopes.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_allowed_scopes.0", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_allowed_scopes.0.value", "[foo]"), ), }, { diff --git a/pkg/resources/api_authentication_integration_with_client_credentials.go b/pkg/resources/api_authentication_integration_with_client_credentials.go index 2ba6659a0e..7cae18deda 100644 --- a/pkg/resources/api_authentication_integration_with_client_credentials.go +++ b/pkg/resources/api_authentication_integration_with_client_credentials.go @@ -38,8 +38,8 @@ func ApiAuthenticationIntegrationWithClientCredentials() *schema.Resource { DeleteContext: DeleteContextApiAuthenticationIntegrationWithClientCredentials, Schema: apiAuthClientCredentialsSchema, CustomizeDiff: customdiff.All( - ForceNewIfChangeToEmptyString("oauth_token_endpoint"), - ForceNewIfChangeToEmptyString("oauth_client_auth_method"), + ForceNewIfChangeToDefaultString("oauth_token_endpoint"), + ForceNewIfChangeToDefaultString("oauth_client_auth_method"), ), Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer.go b/pkg/resources/api_authentication_integration_with_jwt_bearer.go index 83fb2a7798..1039987174 100644 --- a/pkg/resources/api_authentication_integration_with_jwt_bearer.go +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer.go @@ -46,9 +46,9 @@ func ApiAuthenticationIntegrationWithJwtBearer() *schema.Resource { DeleteContext: DeleteContextApiAuthenticationIntegrationWithJwtBearer, Schema: apiAuthJwtBearerSchema, CustomizeDiff: customdiff.All( - ForceNewIfChangeToEmptyString("oauth_token_endpoint"), - ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), - ForceNewIfChangeToEmptyString("oauth_client_auth_method"), + ForceNewIfChangeToDefaultString("oauth_token_endpoint"), + ForceNewIfChangeToDefaultString("oauth_authorization_endpoint"), + ForceNewIfChangeToDefaultString("oauth_client_auth_method"), ), Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, diff --git a/pkg/resources/common.go b/pkg/resources/common.go index a63ac29624..ae173d6126 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -12,7 +12,9 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "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/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -120,13 +122,15 @@ var apiAuthCommonSchema = map[string]*schema.Schema{ "oauth_token_endpoint": { Type: schema.TypeString, Optional: true, + Default: "unknown", Description: "Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly).", }, "oauth_client_auth_method": { - Type: schema.TypeString, - Optional: true, - // ValidateDiagFunc: sdk, - Description: "Specifies the client ID for the OAuth application in the external service.", + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: sdkValidation(sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption), + Default: "unknown", + Description: fmt.Sprintf("Specifies the client ID for the OAuth application in the external service. Valid options are: %v", sdk.AsStringList(sdk.AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption)), }, "oauth_client_id": { Type: schema.TypeString, @@ -139,19 +143,58 @@ var apiAuthCommonSchema = map[string]*schema.Schema{ Description: "Specifies the client secret for the OAuth application in the ServiceNow instance from the previous step. The connector uses this to request an access token from the ServiceNow instance.", }, "oauth_access_token_validity": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntAtLeast(1), - Description: "Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server.", + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(0), + Default: -1, + Description: "Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server.", + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("oauth_access_token_validity"), }, "comment": { Type: schema.TypeString, Optional: true, Description: "Specifies a comment for the integration.", }, - "created_on": { - Type: schema.TypeString, + showOutputAttributeName: { + Type: schema.TypeList, + Computed: true, + Description: "Outputs the result of `SHOW SECURITY INTEGRATIONS` for the given security integration.", + Elem: &schema.Resource{ + Schema: schemas.ShowSecurityIntegrationSchema, + }, + }, + describeOutputAttributeName: { + Type: schema.TypeList, Computed: true, - Description: "Date and time when the integration was created.", + Description: "Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security integration.", + Elem: &schema.Resource{ + Schema: schemas.DescribeApiAuthSecurityIntegrationSchema, + }, }, } + +func listValueToSlice(value string, trimBrackets bool, trimQuotes bool) []string { + if trimBrackets { + value = strings.TrimLeft(value, "[") + value = strings.TrimRight(value, "]") + } + if value == "" { + return nil + } + elems := strings.Split(value, ",") + for i := range elems { + if trimQuotes { + elems[i] = strings.Trim(elems[i], " '") + } + } + return elems +} + +func ctyValToSliceString(val cty.Value) []string { + valueElems := val.AsValueSlice() + elems := make([]string, len(valueElems)) + for i, v := range valueElems { + elems[i] = v.AsString() + } + return elems +} diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index 56cf0b5409..65e3cd2d48 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -73,10 +73,10 @@ func ComputedIfAnyAttributeChanged(key string, changedAttributeKeys ...string) s }) } -// ForceNewIfChangeToEmptyString sets a ForceNew for a string field which was set to an empty value. -func ForceNewIfChangeToEmptyString(key string) schema.CustomizeDiffFunc { +// ForceNewIfChangeToDefaultString sets a ForceNew for a string field which was set to an empty value. +func ForceNewIfChangeToDefaultString(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 + return len(oldString) > 0 && newString == "unknown" }) } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf index 593c1f4da5..99720dc203 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf @@ -9,4 +9,6 @@ resource "snowflake_api_authentication_integration_with_authorization_code_grant oauth_client_secret = var.oauth_client_secret oauth_refresh_token_validity = var.oauth_refresh_token_validity oauth_token_endpoint = var.oauth_token_endpoint + oauth_grant = var.oauth_grant + oauth_allowed_scopes = var.oauth_allowed_scopes } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf index 359057c239..fdeaf92bff 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf @@ -29,3 +29,9 @@ variable "oauth_refresh_token_validity" { variable "oauth_token_endpoint" { type = string } +variable "oauth_grant" { + type = string +} +variable "oauth_allowed_scopes" { + type = set(string) +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf index 4996a8118d..4ad086889b 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf @@ -7,4 +7,5 @@ resource "snowflake_api_authentication_integration_with_client_credentials" "tes oauth_client_id = var.oauth_client_id oauth_client_secret = var.oauth_client_secret oauth_token_endpoint = var.oauth_token_endpoint + oauth_allowed_scopes = var.oauth_allowed_scopes } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf index 158d73de19..33a0304aff 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf @@ -23,3 +23,6 @@ variable "oauth_client_secret" { variable "oauth_token_endpoint" { type = string } +variable "oauth_allowed_scopes" { + type = set(string) +} diff --git a/pkg/resources/warehouse_rework_show_output_proposal.go b/pkg/resources/warehouse_rework_show_output_proposal.go index 654fec57e7..6447debebf 100644 --- a/pkg/resources/warehouse_rework_show_output_proposal.go +++ b/pkg/resources/warehouse_rework_show_output_proposal.go @@ -1,6 +1,8 @@ package resources import ( + "strconv" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -78,3 +80,19 @@ type describeMapping struct { valueToSet any normalizeFunc func(any) any } + +func stringToIntNormalizer(x any) any { + xInt, err := strconv.Atoi(x.(string)) + if err != nil { + xInt = 0 + } + return xInt +} + +func stringToSliceNormalizer(x any) any { + xInt, err := strconv.Atoi(x.(string)) + if err != nil { + xInt = 0 + } + return xInt +} diff --git a/pkg/schemas/api_authentication_security_integration.go b/pkg/schemas/api_authentication_security_integration.go new file mode 100644 index 0000000000..450f7eae79 --- /dev/null +++ b/pkg/schemas/api_authentication_security_integration.go @@ -0,0 +1,60 @@ +package schemas + +import ( + "log" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// DescribeApiAuthSecurityIntegrationSchema represents output of DESCRIBE query for the single SecurityIntegration. +var DescribeApiAuthSecurityIntegrationSchema = map[string]*schema.Schema{ + "enabled": DescribePropertyListSchema, + "oauth_access_token_validity": DescribePropertyListSchema, + "oauth_refresh_token_validity": DescribePropertyListSchema, + "oauth_client_id": DescribePropertyListSchema, + "oauth_client_auth_method": DescribePropertyListSchema, + "oauth_authorization_endpoint": DescribePropertyListSchema, + "oauth_token_endpoint": DescribePropertyListSchema, + "oauth_allowed_scopes": DescribePropertyListSchema, + "oauth_grant": DescribePropertyListSchema, + "parent_integration": DescribePropertyListSchema, + "auth_type": DescribePropertyListSchema, + "comment": DescribePropertyListSchema, +} + +var _ = DescribeApiAuthSecurityIntegrationSchema + +func ApiAuthSecurityIntegrationPropertiesToSchema(securityIntegrationProperties []sdk.SecurityIntegrationProperty) map[string]any { + securityIntegrationSchema := make(map[string]any) + for _, securityIntegrationProperty := range securityIntegrationProperties { + switch securityIntegrationProperty.Name { + case "ENABLED", + "OAUTH_ACCESS_TOKEN_VALIDITY", + "OAUTH_REFRESH_TOKEN_VALIDITY", + "OAUTH_CLIENT_ID", + "OAUTH_CLIENT_AUTH_METHOD", + "OAUTH_AUTHORIZATION_ENDPOINT", + "OAUTH_TOKEN_ENDPOINT", + "OAUTH_ALLOWED_SCOPES", + "OAUTH_GRANT", + "PARENT_INTEGRATION", + "AUTH_TYPE", + "COMMENT": + securityIntegrationSchema[strings.ToLower(securityIntegrationProperty.Name)] = []map[string]any{ + { + "name": securityIntegrationProperty.Name, + "type": securityIntegrationProperty.Type, + "value": securityIntegrationProperty.Value, + "default": securityIntegrationProperty.Default, + }, + } + default: + log.Printf("unknown field from DESCRIBE: %v", securityIntegrationProperty.Name) + } + } + return securityIntegrationSchema +} + +var _ = ApiAuthSecurityIntegrationPropertiesToSchema diff --git a/pkg/sdk/security_integrations_def.go b/pkg/sdk/security_integrations_def.go index 307a5dca31..cd349efb88 100644 --- a/pkg/sdk/security_integrations_def.go +++ b/pkg/sdk/security_integrations_def.go @@ -339,9 +339,10 @@ var apiAuthCodeGrantFlowIntegrationSetDef = g.NewQueryStruct("ApiAuthenticationW OptionalSQL("OAUTH_GRANT = AUTHORIZATION_CODE"). OptionalNumberAssignment("OAUTH_ACCESS_TOKEN_VALIDITY", g.ParameterOptions()). OptionalNumberAssignment("OAUTH_REFRESH_TOKEN_VALIDITY", g.ParameterOptions()). + ListAssignment("OAUTH_ALLOWED_SCOPES", "AllowedScope", g.ParameterOptions().Parentheses()). OptionalComment(). WithValidation(g.AtLeastOneValueSet, "Enabled", "OauthAuthorizationEndpoint", "OauthTokenEndpoint", "OauthClientAuthMethod", "OauthClientId", "OauthClientSecret", "OauthGrantAuthorizationCode", - "OauthAccessTokenValidity", "OauthRefreshTokenValidity", "Comment") + "OauthAccessTokenValidity", "OauthRefreshTokenValidity", "OauthAllowedScopes", "Comment") var apiAuthCodeGrantFlowIntegrationUnsetDef = g.NewQueryStruct("ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset"). OptionalSQL("ENABLED"). @@ -558,7 +559,8 @@ var SecurityIntegrationsDef = g.NewInterface( TextAssignment("OAUTH_CLIENT_SECRET", g.ParameterOptions().Required().SingleQuotes()). OptionalSQL("OAUTH_GRANT = AUTHORIZATION_CODE"). OptionalNumberAssignment("OAUTH_ACCESS_TOKEN_VALIDITY", g.ParameterOptions()). - OptionalNumberAssignment("OAUTH_REFRESH_TOKEN_VALIDITY", g.ParameterOptions()) + OptionalNumberAssignment("OAUTH_REFRESH_TOKEN_VALIDITY", g.ParameterOptions()). + ListAssignment("OAUTH_ALLOWED_SCOPES", "AllowedScope", g.ParameterOptions().Parentheses()) }), ). CustomOperation( diff --git a/pkg/sdk/security_integrations_dto_builders_gen.go b/pkg/sdk/security_integrations_dto_builders_gen.go index 2f27153cbb..253375dd32 100644 --- a/pkg/sdk/security_integrations_dto_builders_gen.go +++ b/pkg/sdk/security_integrations_dto_builders_gen.go @@ -117,6 +117,11 @@ func (s *CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegratio return s } +func (s *CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest) WithOauthAllowedScopes(OauthAllowedScopes []AllowedScope) *CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest { + s.OauthAllowedScopes = OauthAllowedScopes + return s +} + func (s *CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest) WithComment(Comment string) *CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest { s.Comment = &Comment return s @@ -343,12 +348,10 @@ func (s *CreateOauthForPartnerApplicationsSecurityIntegrationRequest) WithCommen func NewCreateOauthForCustomClientsSecurityIntegrationRequest( name AccountObjectIdentifier, OauthClientType OauthSecurityIntegrationClientTypeOption, - OauthRedirectUri string, ) *CreateOauthForCustomClientsSecurityIntegrationRequest { s := CreateOauthForCustomClientsSecurityIntegrationRequest{} s.name = name s.OauthClientType = OauthClientType - s.OauthRedirectUri = OauthRedirectUri return &s } @@ -362,6 +365,11 @@ func (s *CreateOauthForCustomClientsSecurityIntegrationRequest) WithIfNotExists( return s } +func (s *CreateOauthForCustomClientsSecurityIntegrationRequest) WithOauthRedirectUri(OauthRedirectUri string) *CreateOauthForCustomClientsSecurityIntegrationRequest { + s.OauthRedirectUri = &OauthRedirectUri + return s +} + func (s *CreateOauthForCustomClientsSecurityIntegrationRequest) WithEnabled(Enabled bool) *CreateOauthForCustomClientsSecurityIntegrationRequest { s.Enabled = &Enabled return s @@ -433,15 +441,13 @@ func (s *PreAuthorizedRolesListRequest) WithPreAuthorizedRolesList(PreAuthorized func NewCreateSaml2SecurityIntegrationRequest( name AccountObjectIdentifier, - Enabled bool, Saml2Issuer string, Saml2SsoUrl string, - Saml2Provider string, + Saml2Provider Saml2SecurityIntegrationSaml2ProviderOption, Saml2X509Cert string, ) *CreateSaml2SecurityIntegrationRequest { s := CreateSaml2SecurityIntegrationRequest{} s.name = name - s.Enabled = Enabled s.Saml2Issuer = Saml2Issuer s.Saml2SsoUrl = Saml2SsoUrl s.Saml2Provider = Saml2Provider @@ -459,6 +465,11 @@ func (s *CreateSaml2SecurityIntegrationRequest) WithIfNotExists(IfNotExists bool return s } +func (s *CreateSaml2SecurityIntegrationRequest) WithEnabled(Enabled bool) *CreateSaml2SecurityIntegrationRequest { + s.Enabled = &Enabled + return s +} + func (s *CreateSaml2SecurityIntegrationRequest) WithAllowedUserDomains(AllowedUserDomains []UserDomain) *CreateSaml2SecurityIntegrationRequest { s.AllowedUserDomains = AllowedUserDomains return s @@ -489,7 +500,7 @@ func (s *CreateSaml2SecurityIntegrationRequest) WithSaml2SignRequest(Saml2SignRe return s } -func (s *CreateSaml2SecurityIntegrationRequest) WithSaml2RequestedNameidFormat(Saml2RequestedNameidFormat string) *CreateSaml2SecurityIntegrationRequest { +func (s *CreateSaml2SecurityIntegrationRequest) WithSaml2RequestedNameidFormat(Saml2RequestedNameidFormat Saml2SecurityIntegrationSaml2RequestedNameidFormatOption) *CreateSaml2SecurityIntegrationRequest { s.Saml2RequestedNameidFormat = &Saml2RequestedNameidFormat return s } @@ -744,6 +755,11 @@ func (s *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest) W return s } +func (s *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest) WithOauthAllowedScopes(OauthAllowedScopes []AllowedScope) *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest { + s.OauthAllowedScopes = OauthAllowedScopes + return s +} + func (s *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest) WithComment(Comment string) *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest { s.Comment = &Comment return s @@ -1259,7 +1275,7 @@ func (s *Saml2IntegrationSetRequest) WithSaml2SsoUrl(Saml2SsoUrl string) *Saml2I return s } -func (s *Saml2IntegrationSetRequest) WithSaml2Provider(Saml2Provider string) *Saml2IntegrationSetRequest { +func (s *Saml2IntegrationSetRequest) WithSaml2Provider(Saml2Provider Saml2SecurityIntegrationSaml2ProviderOption) *Saml2IntegrationSetRequest { s.Saml2Provider = &Saml2Provider return s } @@ -1299,7 +1315,7 @@ func (s *Saml2IntegrationSetRequest) WithSaml2SignRequest(Saml2SignRequest bool) return s } -func (s *Saml2IntegrationSetRequest) WithSaml2RequestedNameidFormat(Saml2RequestedNameidFormat string) *Saml2IntegrationSetRequest { +func (s *Saml2IntegrationSetRequest) WithSaml2RequestedNameidFormat(Saml2RequestedNameidFormat Saml2SecurityIntegrationSaml2RequestedNameidFormatOption) *Saml2IntegrationSetRequest { s.Saml2RequestedNameidFormat = &Saml2RequestedNameidFormat return s } diff --git a/pkg/sdk/security_integrations_dto_gen.go b/pkg/sdk/security_integrations_dto_gen.go index d788094ce5..cbb16cbe9a 100644 --- a/pkg/sdk/security_integrations_dto_gen.go +++ b/pkg/sdk/security_integrations_dto_gen.go @@ -53,6 +53,7 @@ type CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationReq OauthGrantAuthorizationCode *bool OauthAccessTokenValidity *int OauthRefreshTokenValidity *int + OauthAllowedScopes []AllowedScope Comment *string } @@ -120,16 +121,12 @@ type CreateOauthForPartnerApplicationsSecurityIntegrationRequest struct { Comment *string } -func (r *CreateOauthForPartnerApplicationsSecurityIntegrationRequest) GetName() AccountObjectIdentifier { - return r.name -} - type CreateOauthForCustomClientsSecurityIntegrationRequest struct { OrReplace *bool IfNotExists *bool name AccountObjectIdentifier // required OauthClientType OauthSecurityIntegrationClientTypeOption // required - OauthRedirectUri string // required + OauthRedirectUri *string Enabled *bool OauthAllowNonTlsRedirectUri *bool OauthEnforcePkce *bool @@ -156,18 +153,18 @@ type CreateSaml2SecurityIntegrationRequest struct { OrReplace *bool IfNotExists *bool name AccountObjectIdentifier // required - Enabled bool // required - Saml2Issuer string // required - Saml2SsoUrl string // required - Saml2Provider string // required - Saml2X509Cert string // required + Enabled *bool + Saml2Issuer string // required + Saml2SsoUrl string // required + Saml2Provider Saml2SecurityIntegrationSaml2ProviderOption // required + Saml2X509Cert string // required AllowedUserDomains []UserDomain AllowedEmailPatterns []EmailPattern Saml2SpInitiatedLoginPageLabel *string Saml2EnableSpInitiated *bool Saml2SnowflakeX509Cert *string Saml2SignRequest *bool - Saml2RequestedNameidFormat *string + Saml2RequestedNameidFormat *Saml2SecurityIntegrationSaml2RequestedNameidFormatOption Saml2PostLogoutRedirectUrl *string Saml2ForceAuthn *bool Saml2SnowflakeIssuerUrl *string @@ -241,6 +238,7 @@ type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest struct OauthGrantAuthorizationCode *bool OauthAccessTokenValidity *int OauthRefreshTokenValidity *int + OauthAllowedScopes []AllowedScope Comment *string } @@ -378,7 +376,7 @@ type Saml2IntegrationSetRequest struct { Enabled *bool Saml2Issuer *string Saml2SsoUrl *string - Saml2Provider *string + Saml2Provider *Saml2SecurityIntegrationSaml2ProviderOption Saml2X509Cert *string AllowedUserDomains []UserDomain AllowedEmailPatterns []EmailPattern @@ -386,7 +384,7 @@ type Saml2IntegrationSetRequest struct { Saml2EnableSpInitiated *bool Saml2SnowflakeX509Cert *string Saml2SignRequest *bool - Saml2RequestedNameidFormat *string + Saml2RequestedNameidFormat *Saml2SecurityIntegrationSaml2RequestedNameidFormatOption Saml2PostLogoutRedirectUrl *string Saml2ForceAuthn *bool Saml2SnowflakeIssuerUrl *string diff --git a/pkg/sdk/security_integrations_gen.go b/pkg/sdk/security_integrations_gen.go index 3d64607a73..ec59560e63 100644 --- a/pkg/sdk/security_integrations_gen.go +++ b/pkg/sdk/security_integrations_gen.go @@ -74,6 +74,7 @@ type CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOpt OauthGrantAuthorizationCode *bool `ddl:"keyword" sql:"OAUTH_GRANT = AUTHORIZATION_CODE"` OauthAccessTokenValidity *int `ddl:"parameter" sql:"OAUTH_ACCESS_TOKEN_VALIDITY"` OauthRefreshTokenValidity *int `ddl:"parameter" sql:"OAUTH_REFRESH_TOKEN_VALIDITY"` + OauthAllowedScopes []AllowedScope `ddl:"parameter,parentheses" sql:"OAUTH_ALLOWED_SCOPES"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } @@ -180,7 +181,7 @@ type CreateOauthForCustomClientsSecurityIntegrationOptions struct { integrationType string `ddl:"static" sql:"TYPE = OAUTH"` oauthClient string `ddl:"static" sql:"OAUTH_CLIENT = CUSTOM"` OauthClientType OauthSecurityIntegrationClientTypeOption `ddl:"parameter,single_quotes" sql:"OAUTH_CLIENT_TYPE"` - OauthRedirectUri string `ddl:"parameter,single_quotes" sql:"OAUTH_REDIRECT_URI"` + OauthRedirectUri *string `ddl:"parameter,single_quotes" sql:"OAUTH_REDIRECT_URI"` Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthAllowNonTlsRedirectUri *bool `ddl:"parameter" sql:"OAUTH_ALLOW_NON_TLS_REDIRECT_URI"` OauthEnforcePkce *bool `ddl:"parameter" sql:"OAUTH_ENFORCE_PKCE"` @@ -197,29 +198,29 @@ type CreateOauthForCustomClientsSecurityIntegrationOptions struct { // CreateSaml2SecurityIntegrationOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-saml2. type CreateSaml2SecurityIntegrationOptions struct { - create bool `ddl:"static" sql:"CREATE"` - OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` - securityIntegration bool `ddl:"static" sql:"SECURITY INTEGRATION"` - IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` - name AccountObjectIdentifier `ddl:"identifier"` - integrationType string `ddl:"static" sql:"TYPE = SAML2"` - Enabled bool `ddl:"parameter" sql:"ENABLED"` - Saml2Issuer string `ddl:"parameter,single_quotes" sql:"SAML2_ISSUER"` - Saml2SsoUrl string `ddl:"parameter,single_quotes" sql:"SAML2_SSO_URL"` - Saml2Provider string `ddl:"parameter,single_quotes" sql:"SAML2_PROVIDER"` - Saml2X509Cert string `ddl:"parameter,single_quotes" sql:"SAML2_X509_CERT"` - AllowedUserDomains []UserDomain `ddl:"parameter,parentheses" sql:"ALLOWED_USER_DOMAINS"` - AllowedEmailPatterns []EmailPattern `ddl:"parameter,parentheses" sql:"ALLOWED_EMAIL_PATTERNS"` - Saml2SpInitiatedLoginPageLabel *string `ddl:"parameter,single_quotes" sql:"SAML2_SP_INITIATED_LOGIN_PAGE_LABEL"` - Saml2EnableSpInitiated *bool `ddl:"parameter" sql:"SAML2_ENABLE_SP_INITIATED"` - Saml2SnowflakeX509Cert *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_X509_CERT"` - Saml2SignRequest *bool `ddl:"parameter" sql:"SAML2_SIGN_REQUEST"` - Saml2RequestedNameidFormat *string `ddl:"parameter,single_quotes" sql:"SAML2_REQUESTED_NAMEID_FORMAT"` - Saml2PostLogoutRedirectUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_POST_LOGOUT_REDIRECT_URL"` - Saml2ForceAuthn *bool `ddl:"parameter" sql:"SAML2_FORCE_AUTHN"` - Saml2SnowflakeIssuerUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ISSUER_URL"` - Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + securityIntegration bool `ddl:"static" sql:"SECURITY INTEGRATION"` + IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` + name AccountObjectIdentifier `ddl:"identifier"` + integrationType string `ddl:"static" sql:"TYPE = SAML2"` + Enabled *bool `ddl:"parameter" sql:"ENABLED"` + Saml2Issuer string `ddl:"parameter,single_quotes" sql:"SAML2_ISSUER"` + Saml2SsoUrl string `ddl:"parameter,single_quotes" sql:"SAML2_SSO_URL"` + Saml2Provider Saml2SecurityIntegrationSaml2ProviderOption `ddl:"parameter,single_quotes" sql:"SAML2_PROVIDER"` + Saml2X509Cert string `ddl:"parameter,single_quotes" sql:"SAML2_X509_CERT"` + AllowedUserDomains []UserDomain `ddl:"parameter,parentheses" sql:"ALLOWED_USER_DOMAINS"` + AllowedEmailPatterns []EmailPattern `ddl:"parameter,parentheses" sql:"ALLOWED_EMAIL_PATTERNS"` + Saml2SpInitiatedLoginPageLabel *string `ddl:"parameter,single_quotes" sql:"SAML2_SP_INITIATED_LOGIN_PAGE_LABEL"` + Saml2EnableSpInitiated *bool `ddl:"parameter" sql:"SAML2_ENABLE_SP_INITIATED"` + Saml2SnowflakeX509Cert *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_X509_CERT"` + Saml2SignRequest *bool `ddl:"parameter" sql:"SAML2_SIGN_REQUEST"` + Saml2RequestedNameidFormat *Saml2SecurityIntegrationSaml2RequestedNameidFormatOption `ddl:"parameter,single_quotes" sql:"SAML2_REQUESTED_NAMEID_FORMAT"` + Saml2PostLogoutRedirectUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_POST_LOGOUT_REDIRECT_URL"` + Saml2ForceAuthn *bool `ddl:"parameter" sql:"SAML2_FORCE_AUTHN"` + Saml2SnowflakeIssuerUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ISSUER_URL"` + Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } type UserDomain struct { @@ -298,6 +299,7 @@ type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet struct { OauthGrantAuthorizationCode *bool `ddl:"keyword" sql:"OAUTH_GRANT = AUTHORIZATION_CODE"` OauthAccessTokenValidity *int `ddl:"parameter" sql:"OAUTH_ACCESS_TOKEN_VALIDITY"` OauthRefreshTokenValidity *int `ddl:"parameter" sql:"OAUTH_REFRESH_TOKEN_VALIDITY"` + OauthAllowedScopes []AllowedScope `ddl:"parameter,parentheses" sql:"OAUTH_ALLOWED_SCOPES"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } @@ -447,23 +449,23 @@ type AlterSaml2SecurityIntegrationOptions struct { } type Saml2IntegrationSet struct { - Enabled *bool `ddl:"parameter" sql:"ENABLED"` - Saml2Issuer *string `ddl:"parameter,single_quotes" sql:"SAML2_ISSUER"` - Saml2SsoUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SSO_URL"` - Saml2Provider *string `ddl:"parameter,single_quotes" sql:"SAML2_PROVIDER"` - Saml2X509Cert *string `ddl:"parameter,single_quotes" sql:"SAML2_X509_CERT"` - AllowedUserDomains []UserDomain `ddl:"parameter,parentheses" sql:"ALLOWED_USER_DOMAINS"` - AllowedEmailPatterns []EmailPattern `ddl:"parameter,parentheses" sql:"ALLOWED_EMAIL_PATTERNS"` - Saml2SpInitiatedLoginPageLabel *string `ddl:"parameter,single_quotes" sql:"SAML2_SP_INITIATED_LOGIN_PAGE_LABEL"` - Saml2EnableSpInitiated *bool `ddl:"parameter" sql:"SAML2_ENABLE_SP_INITIATED"` - Saml2SnowflakeX509Cert *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_X509_CERT"` - Saml2SignRequest *bool `ddl:"parameter" sql:"SAML2_SIGN_REQUEST"` - Saml2RequestedNameidFormat *string `ddl:"parameter,single_quotes" sql:"SAML2_REQUESTED_NAMEID_FORMAT"` - Saml2PostLogoutRedirectUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_POST_LOGOUT_REDIRECT_URL"` - Saml2ForceAuthn *bool `ddl:"parameter" sql:"SAML2_FORCE_AUTHN"` - Saml2SnowflakeIssuerUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ISSUER_URL"` - Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + Enabled *bool `ddl:"parameter" sql:"ENABLED"` + Saml2Issuer *string `ddl:"parameter,single_quotes" sql:"SAML2_ISSUER"` + Saml2SsoUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SSO_URL"` + Saml2Provider *Saml2SecurityIntegrationSaml2ProviderOption `ddl:"parameter,single_quotes" sql:"SAML2_PROVIDER"` + Saml2X509Cert *string `ddl:"parameter,single_quotes" sql:"SAML2_X509_CERT"` + AllowedUserDomains []UserDomain `ddl:"parameter,parentheses" sql:"ALLOWED_USER_DOMAINS"` + AllowedEmailPatterns []EmailPattern `ddl:"parameter,parentheses" sql:"ALLOWED_EMAIL_PATTERNS"` + Saml2SpInitiatedLoginPageLabel *string `ddl:"parameter,single_quotes" sql:"SAML2_SP_INITIATED_LOGIN_PAGE_LABEL"` + Saml2EnableSpInitiated *bool `ddl:"parameter" sql:"SAML2_ENABLE_SP_INITIATED"` + Saml2SnowflakeX509Cert *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_X509_CERT"` + Saml2SignRequest *bool `ddl:"parameter" sql:"SAML2_SIGN_REQUEST"` + Saml2RequestedNameidFormat *Saml2SecurityIntegrationSaml2RequestedNameidFormatOption `ddl:"parameter,single_quotes" sql:"SAML2_REQUESTED_NAMEID_FORMAT"` + Saml2PostLogoutRedirectUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_POST_LOGOUT_REDIRECT_URL"` + Saml2ForceAuthn *bool `ddl:"parameter" sql:"SAML2_FORCE_AUTHN"` + Saml2SnowflakeIssuerUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ISSUER_URL"` + Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } type Saml2IntegrationUnset struct { @@ -484,14 +486,12 @@ type AlterScimSecurityIntegrationOptions struct { Set *ScimIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ScimIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ScimIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` NetworkPolicy *AccountObjectIdentifier `ddl:"identifier,equals" sql:"NETWORK_POLICY"` SyncPassword *bool `ddl:"parameter" sql:"SYNC_PASSWORD"` Comment *StringAllowEmpty `ddl:"parameter" sql:"COMMENT"` } - type ScimIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` NetworkPolicy *bool `ddl:"keyword" sql:"NETWORK_POLICY"` diff --git a/pkg/sdk/security_integrations_gen_test.go b/pkg/sdk/security_integrations_gen_test.go index 940c9956fe..a9729ff651 100644 --- a/pkg/sdk/security_integrations_gen_test.go +++ b/pkg/sdk/security_integrations_gen_test.go @@ -232,9 +232,8 @@ func TestSecurityIntegrations_CreateOauthForCustomClients(t *testing.T) { // Minimal valid CreateOauthForCustomClientsSecurityIntegrationOptions defaultOpts := func() *CreateOauthForCustomClientsSecurityIntegrationOptions { return &CreateOauthForCustomClientsSecurityIntegrationOptions{ - name: id, - OauthClientType: OauthSecurityIntegrationClientTypePublic, - OauthRedirectUri: "uri", + name: id, + OauthClientType: OauthSecurityIntegrationClientTypePublic, } } @@ -253,7 +252,7 @@ func TestSecurityIntegrations_CreateOauthForCustomClients(t *testing.T) { t.Run("basic", func(t *testing.T) { opts := defaultOpts() opts.OrReplace = Bool(true) - assertOptsValidAndSQLEquals(t, opts, "CREATE OR REPLACE SECURITY INTEGRATION %s TYPE = OAUTH OAUTH_CLIENT = CUSTOM OAUTH_CLIENT_TYPE = 'PUBLIC' OAUTH_REDIRECT_URI = 'uri'", id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, "CREATE OR REPLACE SECURITY INTEGRATION %s TYPE = OAUTH OAUTH_CLIENT = CUSTOM OAUTH_CLIENT_TYPE = 'PUBLIC'", id.FullyQualifiedName()) }) t.Run("all options", func(t *testing.T) { @@ -261,7 +260,7 @@ func TestSecurityIntegrations_CreateOauthForCustomClients(t *testing.T) { roleID, role2ID, npID := randomAccountObjectIdentifier(), randomAccountObjectIdentifier(), randomAccountObjectIdentifier() opts.IfNotExists = Bool(true) opts.OauthClientType = OauthSecurityIntegrationClientTypePublic - opts.OauthRedirectUri = "uri" + opts.OauthRedirectUri = Pointer("uri") opts.Enabled = Pointer(true) opts.OauthAllowNonTlsRedirectUri = Pointer(true) opts.OauthEnforcePkce = Pointer(true) @@ -342,7 +341,6 @@ func TestSecurityIntegrations_CreateSaml2(t *testing.T) { defaultOpts := func() *CreateSaml2SecurityIntegrationOptions { return &CreateSaml2SecurityIntegrationOptions{ name: id, - Enabled: true, Saml2Issuer: "issuer", Saml2SsoUrl: "url", Saml2Provider: "provider", @@ -365,19 +363,20 @@ func TestSecurityIntegrations_CreateSaml2(t *testing.T) { t.Run("basic", func(t *testing.T) { opts := defaultOpts() opts.OrReplace = Bool(true) - assertOptsValidAndSQLEquals(t, opts, "CREATE OR REPLACE SECURITY INTEGRATION %s TYPE = SAML2 ENABLED = true SAML2_ISSUER = 'issuer' SAML2_SSO_URL = 'url' SAML2_PROVIDER = 'provider' SAML2_X509_CERT = 'cert'", id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, "CREATE OR REPLACE SECURITY INTEGRATION %s TYPE = SAML2 SAML2_ISSUER = 'issuer' SAML2_SSO_URL = 'url' SAML2_PROVIDER = 'provider' SAML2_X509_CERT = 'cert'", id.FullyQualifiedName()) }) t.Run("all options", func(t *testing.T) { opts := defaultOpts() opts.IfNotExists = Bool(true) + opts.Enabled = Bool(true) opts.AllowedEmailPatterns = []EmailPattern{{Pattern: "pattern"}} opts.AllowedUserDomains = []UserDomain{{Domain: "domain"}} opts.Comment = Pointer("a") opts.Saml2EnableSpInitiated = Pointer(true) opts.Saml2ForceAuthn = Pointer(true) opts.Saml2PostLogoutRedirectUrl = Pointer("redirect") - opts.Saml2RequestedNameidFormat = Pointer("format") + opts.Saml2RequestedNameidFormat = Pointer(Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress) opts.Saml2SignRequest = Pointer(true) opts.Saml2SnowflakeAcsUrl = Pointer("acs") opts.Saml2SnowflakeIssuerUrl = Pointer("issuer") @@ -386,8 +385,8 @@ func TestSecurityIntegrations_CreateSaml2(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, "CREATE SECURITY INTEGRATION IF NOT EXISTS %s TYPE = SAML2 ENABLED = true SAML2_ISSUER = 'issuer' SAML2_SSO_URL = 'url' SAML2_PROVIDER = 'provider' SAML2_X509_CERT = 'cert'"+ " ALLOWED_USER_DOMAINS = ('domain') ALLOWED_EMAIL_PATTERNS = ('pattern') SAML2_SP_INITIATED_LOGIN_PAGE_LABEL = 'label' SAML2_ENABLE_SP_INITIATED = true SAML2_SNOWFLAKE_X509_CERT = 'cert' SAML2_SIGN_REQUEST = true"+ - " SAML2_REQUESTED_NAMEID_FORMAT = 'format' SAML2_POST_LOGOUT_REDIRECT_URL = 'redirect' SAML2_FORCE_AUTHN = true SAML2_SNOWFLAKE_ISSUER_URL = 'issuer' SAML2_SNOWFLAKE_ACS_URL = 'acs'"+ - " COMMENT = 'a'", id.FullyQualifiedName()) + " SAML2_REQUESTED_NAMEID_FORMAT = '%s' SAML2_POST_LOGOUT_REDIRECT_URL = 'redirect' SAML2_FORCE_AUTHN = true SAML2_SNOWFLAKE_ISSUER_URL = 'issuer' SAML2_SNOWFLAKE_ACS_URL = 'acs'"+ + " COMMENT = 'a'", id.FullyQualifiedName(), Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress) }) } @@ -1177,7 +1176,7 @@ func TestSecurityIntegrations_AlterSaml2(t *testing.T) { Enabled: Pointer(true), Saml2Issuer: Pointer("issuer"), Saml2SsoUrl: Pointer("url"), - Saml2Provider: Pointer("provider"), + Saml2Provider: Pointer(Saml2SecurityIntegrationSaml2ProviderCustom), Saml2X509Cert: Pointer("cert"), AllowedUserDomains: []UserDomain{{Domain: "domain"}}, AllowedEmailPatterns: []EmailPattern{{Pattern: "pattern"}}, @@ -1185,17 +1184,17 @@ func TestSecurityIntegrations_AlterSaml2(t *testing.T) { Saml2EnableSpInitiated: Pointer(true), Saml2SnowflakeX509Cert: Pointer("cert"), Saml2SignRequest: Pointer(true), - Saml2RequestedNameidFormat: Pointer("format"), + Saml2RequestedNameidFormat: Pointer(Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress), Saml2PostLogoutRedirectUrl: Pointer("redirect"), Saml2ForceAuthn: Pointer(true), Saml2SnowflakeIssuerUrl: Pointer("issuer"), Saml2SnowflakeAcsUrl: Pointer("acs"), Comment: Pointer("a"), } - assertOptsValidAndSQLEquals(t, opts, "ALTER SECURITY INTEGRATION %s SET ENABLED = true, SAML2_ISSUER = 'issuer', SAML2_SSO_URL = 'url', SAML2_PROVIDER = 'provider', SAML2_X509_CERT = 'cert',"+ + assertOptsValidAndSQLEquals(t, opts, "ALTER SECURITY INTEGRATION %s SET ENABLED = true, SAML2_ISSUER = 'issuer', SAML2_SSO_URL = 'url', SAML2_PROVIDER = '%s', SAML2_X509_CERT = 'cert',"+ " ALLOWED_USER_DOMAINS = ('domain'), ALLOWED_EMAIL_PATTERNS = ('pattern'), SAML2_SP_INITIATED_LOGIN_PAGE_LABEL = 'label', SAML2_ENABLE_SP_INITIATED = true, SAML2_SNOWFLAKE_X509_CERT = 'cert', SAML2_SIGN_REQUEST = true,"+ - " SAML2_REQUESTED_NAMEID_FORMAT = 'format', SAML2_POST_LOGOUT_REDIRECT_URL = 'redirect', SAML2_FORCE_AUTHN = true, SAML2_SNOWFLAKE_ISSUER_URL = 'issuer', SAML2_SNOWFLAKE_ACS_URL = 'acs',"+ - " COMMENT = 'a'", id.FullyQualifiedName()) + " SAML2_REQUESTED_NAMEID_FORMAT = '%s', SAML2_POST_LOGOUT_REDIRECT_URL = 'redirect', SAML2_FORCE_AUTHN = true, SAML2_SNOWFLAKE_ISSUER_URL = 'issuer', SAML2_SNOWFLAKE_ACS_URL = 'acs',"+ + " COMMENT = 'a'", id.FullyQualifiedName(), Saml2SecurityIntegrationSaml2ProviderCustom, Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress) }) t.Run("all options - unset", func(t *testing.T) { diff --git a/pkg/sdk/security_integrations_impl_gen.go b/pkg/sdk/security_integrations_impl_gen.go index c2244cb30d..7522b9ca5a 100644 --- a/pkg/sdk/security_integrations_impl_gen.go +++ b/pkg/sdk/security_integrations_impl_gen.go @@ -161,6 +161,7 @@ func (r *CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegratio OauthGrantAuthorizationCode: r.OauthGrantAuthorizationCode, OauthAccessTokenValidity: r.OauthAccessTokenValidity, OauthRefreshTokenValidity: r.OauthRefreshTokenValidity, + OauthAllowedScopes: r.OauthAllowedScopes, Comment: r.Comment, } return opts @@ -206,21 +207,25 @@ func (r *CreateExternalOauthSecurityIntegrationRequest) toOpts() *CreateExternal ExternalOauthScopeMappingAttribute: r.ExternalOauthScopeMappingAttribute, Comment: r.Comment, } + if r.ExternalOauthBlockedRolesList != nil { opts.ExternalOauthBlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.ExternalOauthBlockedRolesList.BlockedRolesList, } } + if r.ExternalOauthAllowedRolesList != nil { opts.ExternalOauthAllowedRolesList = &AllowedRolesList{ AllowedRolesList: r.ExternalOauthAllowedRolesList.AllowedRolesList, } } + if r.ExternalOauthAudienceList != nil { opts.ExternalOauthAudienceList = &AudienceList{ AudienceList: r.ExternalOauthAudienceList.AudienceList, } } + return opts } @@ -238,11 +243,13 @@ func (r *CreateOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() * Comment: r.Comment, } + if r.BlockedRolesList != nil { opts.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.BlockedRolesList.BlockedRolesList, } } + return opts } @@ -265,16 +272,19 @@ func (r *CreateOauthForCustomClientsSecurityIntegrationRequest) toOpts() *Create OauthClientRsaPublicKey2: r.OauthClientRsaPublicKey2, Comment: r.Comment, } + if r.PreAuthorizedRolesList != nil { opts.PreAuthorizedRolesList = &PreAuthorizedRolesList{ PreAuthorizedRolesList: r.PreAuthorizedRolesList.PreAuthorizedRolesList, } } + if r.BlockedRolesList != nil { opts.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.BlockedRolesList.BlockedRolesList, } } + return opts } @@ -326,6 +336,7 @@ func (r *AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationReque SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ApiAuthenticationWithClientCredentialsFlowIntegrationSet{ Enabled: r.Set.Enabled, @@ -340,12 +351,14 @@ func (r *AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationReque Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &ApiAuthenticationWithClientCredentialsFlowIntegrationUnset{ Enabled: r.Unset.Enabled, Comment: r.Unset.Comment, } } + return opts } @@ -356,6 +369,7 @@ func (r *AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegration SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet{ Enabled: r.Set.Enabled, @@ -367,15 +381,18 @@ func (r *AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegration OauthGrantAuthorizationCode: r.Set.OauthGrantAuthorizationCode, OauthAccessTokenValidity: r.Set.OauthAccessTokenValidity, OauthRefreshTokenValidity: r.Set.OauthRefreshTokenValidity, + OauthAllowedScopes: r.Set.OauthAllowedScopes, Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset{ Enabled: r.Unset.Enabled, Comment: r.Unset.Comment, } } + return opts } @@ -386,6 +403,7 @@ func (r *AlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest) toOp SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ApiAuthenticationWithJwtBearerFlowIntegrationSet{ Enabled: r.Set.Enabled, @@ -400,12 +418,14 @@ func (r *AlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest) toOp Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &ApiAuthenticationWithJwtBearerFlowIntegrationUnset{ Enabled: r.Unset.Enabled, Comment: r.Unset.Comment, } } + return opts } @@ -416,7 +436,9 @@ func (r *AlterExternalOauthSecurityIntegrationRequest) toOpts() *AlterExternalOa SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { + opts.Set = &ExternalOauthIntegrationSet{ Enabled: r.Set.Enabled, ExternalOauthType: r.Set.ExternalOauthType, @@ -432,28 +454,34 @@ func (r *AlterExternalOauthSecurityIntegrationRequest) toOpts() *AlterExternalOa ExternalOauthScopeDelimiter: r.Set.ExternalOauthScopeDelimiter, Comment: r.Set.Comment, } + if r.Set.ExternalOauthBlockedRolesList != nil { opts.Set.ExternalOauthBlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.Set.ExternalOauthBlockedRolesList.BlockedRolesList, } } + if r.Set.ExternalOauthAllowedRolesList != nil { opts.Set.ExternalOauthAllowedRolesList = &AllowedRolesList{ AllowedRolesList: r.Set.ExternalOauthAllowedRolesList.AllowedRolesList, } } + if r.Set.ExternalOauthAudienceList != nil { opts.Set.ExternalOauthAudienceList = &AudienceList{ AudienceList: r.Set.ExternalOauthAudienceList.AudienceList, } } + } + if r.Unset != nil { opts.Unset = &ExternalOauthIntegrationUnset{ Enabled: r.Unset.Enabled, ExternalOauthAudienceList: r.Unset.ExternalOauthAudienceList, } } + return opts } @@ -464,7 +492,9 @@ func (r *AlterOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() *A SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { + opts.Set = &OauthForPartnerApplicationsIntegrationSet{ Enabled: r.Set.Enabled, OauthIssueRefreshTokens: r.Set.OauthIssueRefreshTokens, @@ -474,18 +504,22 @@ func (r *AlterOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() *A Comment: r.Set.Comment, } + if r.Set.BlockedRolesList != nil { opts.Set.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.Set.BlockedRolesList.BlockedRolesList, } } + } + if r.Unset != nil { opts.Unset = &OauthForPartnerApplicationsIntegrationUnset{ Enabled: r.Unset.Enabled, OauthUseSecondaryRoles: r.Unset.OauthUseSecondaryRoles, } } + return opts } @@ -496,7 +530,9 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { + opts.Set = &OauthForCustomClientsIntegrationSet{ Enabled: r.Set.Enabled, OauthRedirectUri: r.Set.OauthRedirectUri, @@ -511,17 +547,21 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa OauthClientRsaPublicKey2: r.Set.OauthClientRsaPublicKey2, Comment: r.Set.Comment, } + if r.Set.PreAuthorizedRolesList != nil { opts.Set.PreAuthorizedRolesList = &PreAuthorizedRolesList{ PreAuthorizedRolesList: r.Set.PreAuthorizedRolesList.PreAuthorizedRolesList, } } + if r.Set.BlockedRolesList != nil { opts.Set.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.Set.BlockedRolesList.BlockedRolesList, } } + } + if r.Unset != nil { opts.Unset = &OauthForCustomClientsIntegrationUnset{ Enabled: r.Unset.Enabled, @@ -531,6 +571,7 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa OauthUseSecondaryRoles: r.Unset.OauthUseSecondaryRoles, } } + return opts } @@ -543,6 +584,7 @@ func (r *AlterSaml2SecurityIntegrationRequest) toOpts() *AlterSaml2SecurityInteg RefreshSaml2SnowflakePrivateKey: r.RefreshSaml2SnowflakePrivateKey, } + if r.Set != nil { opts.Set = &Saml2IntegrationSet{ Enabled: r.Set.Enabled, @@ -564,6 +606,7 @@ func (r *AlterSaml2SecurityIntegrationRequest) toOpts() *AlterSaml2SecurityInteg Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &Saml2IntegrationUnset{ Saml2ForceAuthn: r.Unset.Saml2ForceAuthn, @@ -572,6 +615,7 @@ func (r *AlterSaml2SecurityIntegrationRequest) toOpts() *AlterSaml2SecurityInteg Comment: r.Unset.Comment, } } + return opts } @@ -582,6 +626,7 @@ func (r *AlterScimSecurityIntegrationRequest) toOpts() *AlterScimSecurityIntegra SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ScimIntegrationSet{ Enabled: r.Set.Enabled, @@ -590,6 +635,7 @@ func (r *AlterScimSecurityIntegrationRequest) toOpts() *AlterScimSecurityIntegra Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &ScimIntegrationUnset{ Enabled: r.Unset.Enabled, @@ -597,6 +643,7 @@ func (r *AlterScimSecurityIntegrationRequest) toOpts() *AlterScimSecurityIntegra SyncPassword: r.Unset.SyncPassword, } } + return opts } diff --git a/pkg/sdk/security_integrations_validations_gen.go b/pkg/sdk/security_integrations_validations_gen.go index 2e6129b157..7c9b21606d 100644 --- a/pkg/sdk/security_integrations_validations_gen.go +++ b/pkg/sdk/security_integrations_validations_gen.go @@ -182,8 +182,8 @@ func (opts *AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrat errs = append(errs, errExactlyOneOf("AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOptions", "Set", "Unset", "SetTags", "UnsetTags")) } if valueSet(opts.Set) { - if !anyValueSet(opts.Set.Enabled, opts.Set.OauthAuthorizationEndpoint, opts.Set.OauthTokenEndpoint, opts.Set.OauthClientAuthMethod, opts.Set.OauthClientId, opts.Set.OauthClientSecret, opts.Set.OauthGrantAuthorizationCode, opts.Set.OauthAccessTokenValidity, opts.Set.OauthRefreshTokenValidity, opts.Set.Comment) { - errs = append(errs, errAtLeastOneOf("AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOptions.Set", "Enabled", "OauthAuthorizationEndpoint", "OauthTokenEndpoint", "OauthClientAuthMethod", "OauthClientId", "OauthClientSecret", "OauthGrantAuthorizationCode", "OauthAccessTokenValidity", "OauthRefreshTokenValidity", "Comment")) + if !anyValueSet(opts.Set.Enabled, opts.Set.OauthAuthorizationEndpoint, opts.Set.OauthTokenEndpoint, opts.Set.OauthClientAuthMethod, opts.Set.OauthClientId, opts.Set.OauthClientSecret, opts.Set.OauthGrantAuthorizationCode, opts.Set.OauthAccessTokenValidity, opts.Set.OauthRefreshTokenValidity, opts.Set.OauthAllowedScopes, opts.Set.Comment) { + errs = append(errs, errAtLeastOneOf("AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOptions.Set", "Enabled", "OauthAuthorizationEndpoint", "OauthTokenEndpoint", "OauthClientAuthMethod", "OauthClientId", "OauthClientSecret", "OauthGrantAuthorizationCode", "OauthAccessTokenValidity", "OauthRefreshTokenValidity", "OauthAllowedScopes", "Comment")) } } if valueSet(opts.Unset) { diff --git a/pkg/sdk/testint/security_integrations_gen_integration_test.go b/pkg/sdk/testint/security_integrations_gen_integration_test.go index 0c2c94cec5..71ebefba1c 100644 --- a/pkg/sdk/testint/security_integrations_gen_integration_test.go +++ b/pkg/sdk/testint/security_integrations_gen_integration_test.go @@ -96,7 +96,7 @@ func TestInt_SecurityIntegrations(t *testing.T) { createOauthCustom := func(t *testing.T, with func(*sdk.CreateOauthForCustomClientsSecurityIntegrationRequest)) (*sdk.SecurityIntegration, sdk.AccountObjectIdentifier) { t.Helper() id := testClientHelper().Ids.RandomAccountObjectIdentifier() - req := sdk.NewCreateOauthForCustomClientsSecurityIntegrationRequest(id, sdk.OauthSecurityIntegrationClientTypePublic, "https://example.com") + req := sdk.NewCreateOauthForCustomClientsSecurityIntegrationRequest(id, sdk.OauthSecurityIntegrationClientTypePublic) if with != nil { with(req) } @@ -129,7 +129,7 @@ func TestInt_SecurityIntegrations(t *testing.T) { t.Helper() id := testClientHelper().Ids.RandomAccountObjectIdentifier() issuer := testClientHelper().Ids.Alpha() - saml2Req := sdk.NewCreateSaml2SecurityIntegrationRequest(id, false, issuer, "https://example.com", "Custom", cert) + saml2Req := sdk.NewCreateSaml2SecurityIntegrationRequest(id, issuer, "https://example.com", "Custom", cert) if with != nil { with(saml2Req) } From e70b12381fdb06da7bc3ad8291d0611ae7d0485f Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Mon, 1 Jul 2024 08:56:12 +0200 Subject: [PATCH 04/18] Add remaining api auth flows --- ...tegration_with_authorization_code_grant.go | 19 +- ...uthorization_code_grant_acceptance_test.go | 4 +- ...ion_integration_with_client_credentials.go | 414 +++++++++++++----- ...with_client_credentials_acceptance_test.go | 105 +++-- ...hentication_integration_with_jwt_bearer.go | 397 +++++++++++++---- pkg/resources/common.go | 8 + .../complete/test.tf | 20 +- .../complete/variables.tf | 6 + v1-preparations/CHANGES_BEFORE_V1.md | 16 +- 9 files changed, 742 insertions(+), 247 deletions(-) diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go index 9829ea5628..01d0701de4 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go @@ -22,14 +22,6 @@ import ( var apiAuthAuthorizationCodeGrantSchema = func() map[string]*schema.Schema { uniq := map[string]*schema.Schema{ - "oauth_refresh_token_validity": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntAtLeast(0), - Default: -1, - Description: "Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow.", - DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("oauth_refresh_token_validity"), - }, "oauth_authorization_endpoint": { Type: schema.TypeString, Optional: true, @@ -46,7 +38,7 @@ var apiAuthAuthorizationCodeGrantSchema = func() map[string]*schema.Schema { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringInSlice([]string{"unknown", "AUTHORIZATION_CODE"}, true), - Description: "Specifies the client ID for the OAuth application in the external service.", + Description: "Specifies the type of OAuth flow.", Default: "unknown", }, } @@ -489,6 +481,15 @@ func UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx con set.WithOauthTokenEndpoint(d.Get("oauth_token_endpoint").(string)) } + if d.HasChange("oauth_allowed_scopes") { + elems := expandStringList(d.Get("oauth_allowed_scopes").(*schema.Set).List()) + allowedScopes := make([]sdk.AllowedScope, len(elems)) + for i := range elems { + allowedScopes[i] = sdk.AllowedScope{Scope: elems[i]} + } + set.WithOauthAllowedScopes(allowedScopes) + } + if !reflect.DeepEqual(*set, sdk.ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest{}) { if err := client.SecurityIntegrations.AlterApiAuthenticationWithAuthorizationCodeGrantFlow(ctx, sdk.NewAlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest(id).WithSet(*set)); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go index c83665ef86..41b9de7b2b 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go @@ -87,6 +87,8 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity", "12345"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint", "https://example.com"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_grant", "AUTHORIZATION_CODE"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_allowed_scopes.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_allowed_scopes.0", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.#", "1"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.name", id.Name()), @@ -104,7 +106,7 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_client_auth_method.0.value", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_authorization_endpoint.0.value", "https://example.com"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_token_endpoint.0.value", "https://example.com"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_allowed_scopes.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_allowed_scopes.0.value", "[foo]"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_grant.0.value", "AUTHORIZATION_CODE"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.parent_integration.0.value", ""), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.auth_type.0.value", "OAUTH2"), diff --git a/pkg/resources/api_authentication_integration_with_client_credentials.go b/pkg/resources/api_authentication_integration_with_client_credentials.go index 7cae18deda..84f986da1f 100644 --- a/pkg/resources/api_authentication_integration_with_client_credentials.go +++ b/pkg/resources/api_authentication_integration_with_client_credentials.go @@ -4,18 +4,20 @@ import ( "context" "errors" "fmt" - "log" "reflect" "strconv" - "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "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" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) var apiAuthClientCredentialsSchema = func() map[string]*schema.Schema { @@ -26,6 +28,13 @@ var apiAuthClientCredentialsSchema = func() map[string]*schema.Schema { Optional: true, Description: "Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow.", }, + "oauth_grant": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"unknown", "CLIENT_CREDENTIALS"}, true), + Description: "Specifies the type of OAuth flow.", + Default: "unknown", + }, } return MergeMaps(apiAuthCommonSchema, uniq) }() @@ -33,20 +42,109 @@ var apiAuthClientCredentialsSchema = func() map[string]*schema.Schema { func ApiAuthenticationIntegrationWithClientCredentials() *schema.Resource { return &schema.Resource{ CreateContext: CreateContextApiAuthenticationIntegrationWithClientCredentials, - ReadContext: ReadContextApiAuthenticationIntegrationWithClientCredentials, + ReadContext: ReadContextApiAuthenticationIntegrationWithClientCredentials(true), UpdateContext: UpdateContextApiAuthenticationIntegrationWithClientCredentials, DeleteContext: DeleteContextApiAuthenticationIntegrationWithClientCredentials, Schema: apiAuthClientCredentialsSchema, CustomizeDiff: customdiff.All( ForceNewIfChangeToDefaultString("oauth_token_endpoint"), ForceNewIfChangeToDefaultString("oauth_client_auth_method"), + ForceNewIfChangeToDefaultString("oauth_grant"), + ComputedIfAnyAttributeChanged(showOutputAttributeName, "enabled", "comment"), + ComputedIfAnyAttributeChanged(describeOutputAttributeName, "enabled", "comment", "oauth_refresh_token_validity", "oauth_refresh_token_validity", + "oauth_client_id", "oauth_client_auth_method", "oauth_token_endpoint", "oauth_allowed_scopes", "oauth_grant"), ), Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: ImportApiAuthenticationWithClientCredentials, }, } } +func ImportApiAuthenticationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + logging.DebugLogger.Printf("[DEBUG] Starting api auth integration with client credentials 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 + } + + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return nil, err + } + + if err = d.Set("name", integration.Name); err != nil { + return nil, err + } + if err = d.Set("enabled", integration.Enabled); err != nil { + return nil, err + } + if err = d.Set("comment", integration.Comment); err != nil { + return nil, err + } + + oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" + }) + if err == nil { + value, err := strconv.Atoi(oauthAccessTokenValidity.Value) + if err != nil { + return nil, err + } + if err = d.Set("oauth_access_token_validity", value); err != nil { + return nil, err + } + } + oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" + }) + if err == nil { + value, err := strconv.Atoi(oauthRefreshTokenValidity.Value) + if err != nil { + return nil, err + } + if err = d.Set("oauth_refresh_token_validity", value); err != nil { + return nil, err + } + } + oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) + if err == nil { + if err = d.Set("oauth_client_id", oauthClientId.Value); err != nil { + return nil, err + } + } + oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_CLIENT_AUTH_METHOD" + }) + if err == nil { + if err = d.Set("oauth_client_auth_method", oauthClientAuthMethod.Value); err != nil { + return nil, err + } + } + oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) + if err == nil { + if err = d.Set("oauth_token_endpoint", oauthTokenEndpoint.Value); err != nil { + return nil, err + } + } + oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) + if err == nil { + if err = d.Set("oauth_allowed_scopes", listValueToSlice(oauthAllowedScopes.Value, true, false)); err != nil { + return nil, err + } + } + oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) + if err == nil { + if err = d.Set("oauth_grant", oauthGrant.Value); err != nil { + return nil, err + } + } + + return []*schema.ResourceData{d}, nil +} + func CreateContextApiAuthenticationIntegrationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client enabled := d.Get("enabled").(bool) @@ -60,10 +158,31 @@ func CreateContextApiAuthenticationIntegrationWithClientCredentials(ctx context. req.WithComment(v.(string)) } - if v, ok := d.GetOk("oauth_access_token_validity"); ok { - req.WithOauthAccessTokenValidity(v.(int)) + if v := d.Get("oauth_access_token_validity").(int); v != -1 { + req.WithOauthAccessTokenValidity(v) + } + + if v := d.Get("oauth_client_auth_method").(string); v != "unknown" { + value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) + if err != nil { + return diag.FromErr(err) + } + req.WithOauthClientAuthMethod(value) + } + + if v := d.Get("oauth_refresh_token_validity").(int); v != -1 { + req.WithOauthRefreshTokenValidity(v) } + if v := d.Get("oauth_grant").(string); v != "unknown" { + if v == "CLIENT_CREDENTIALS" { + req.WithOauthGrantClientCredentials(true) + } + } + + if v := d.Get("oauth_token_endpoint").(string); v != "unknown" { + req.WithOauthTokenEndpoint(v) + } if v, ok := d.GetOk("oauth_allowed_scopes"); ok { elems := expandStringList(v.(*schema.Set).List()) allowedScopes := make([]sdk.AllowedScope, len(elems)) @@ -73,127 +192,184 @@ func CreateContextApiAuthenticationIntegrationWithClientCredentials(ctx context. req.WithOauthAllowedScopes(allowedScopes) } - if v, ok := d.GetOk("oauth_client_auth_method"); ok { - valueRaw := v.(string) - value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(valueRaw) - if err != nil { - return diag.FromErr(err) - } - req.WithOauthClientAuthMethod(value) - } - - if v, ok := d.GetOk("oauth_token_endpoint"); ok { - req.WithOauthTokenEndpoint(v.(string)) - } - if err := client.SecurityIntegrations.CreateApiAuthenticationWithClientCredentialsFlow(ctx, req); err != nil { return diag.FromErr(err) } d.SetId(name) - return ReadContextApiAuthenticationIntegrationWithClientCredentials(ctx, d, meta) + return ReadContextApiAuthenticationIntegrationWithClientCredentials(false)(ctx, d, meta) } -func ReadContextApiAuthenticationIntegrationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta interface{}) 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), - }, +func ReadContextApiAuthenticationIntegrationWithClientCredentials(withExternalChangesMarking bool) schema.ReadContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) 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) + } + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return diag.FromErr(err) } - 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", integration.Name); err != nil { - return diag.FromErr(err) - } - - if err := d.Set("comment", integration.Comment); err != nil { - return diag.FromErr(err) - } - - if err := d.Set("created_on", integration.CreatedOn.String()); err != nil { - return diag.FromErr(err) - } - if err := d.Set("enabled", integration.Enabled); 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", integration.Name); err != nil { + return diag.FromErr(err) + } + if err := d.Set("comment", integration.Comment); err != nil { + return diag.FromErr(err) + } + if err := d.Set("enabled", integration.Enabled); err != nil { + return diag.FromErr(err) + } + if withExternalChangesMarking { + if err = handleExternalChangesToObjectInShow(d, + showMapping{"comment", "comment", integration.Comment, integration.Comment, nil}, + showMapping{"enabled", "enabled", integration.Enabled, integration.Enabled, nil}, + ); err != nil { + return diag.FromErr(err) + } - properties, err := client.SecurityIntegrations.Describe(ctx, id) - if err != nil { - return diag.FromErr(err) - } - for _, property := range properties { - name := property.Name - value := property.Value - switch name { - case "COMMENT": - if err := d.Set("comment", value); err != nil { + enabled, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "ENABLED" }) + if err != nil { return diag.FromErr(err) } - case "CREATED_ON": - if err := d.Set("created_on", value); err != nil { + + oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" + }) + if err != nil { return diag.FromErr(err) } - case "ENABLED": - if err := d.Set("enabled", helpers.StringToBool(value)); err != nil { + + oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" + }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_ACCESS_TOKEN_VALIDITY": - valueInt, err := strconv.Atoi(value) + + oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) if err != nil { return diag.FromErr(err) } - if err := d.Set("oauth_access_token_validity", valueInt); err != nil { + + oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_CLIENT_AUTH_METHOD" + }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_ALLOWED_SCOPES": - value = strings.TrimLeft(value, "[") - value = strings.TrimRight(value, "]") - elems := strings.Split(value, ",") - if value == "" { - elems = nil + + oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) + if err != nil { + return diag.FromErr(err) } - if err := d.Set("oauth_allowed_scopes", elems); err != nil { + + oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_CLIENT_AUTH_METHOD": - if err := d.Set("oauth_client_auth_method", value); err != nil { + + oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_CLIENT_ID": - if err := d.Set("oauth_client_id", value); err != nil { + oauthAccessTokenValidityInt, err := strconv.Atoi(oauthAccessTokenValidity.Value) + if err != nil { return diag.FromErr(err) } - case "OAUTH_CLIENT_SECRET": - if err := d.Set("oauth_client_secret", value); err != nil { + oauthRefreshTokenValidityInt, err := strconv.Atoi(oauthRefreshTokenValidity.Value) + if err != nil { return diag.FromErr(err) } - case "OAUTH_TOKEN_ENDPOINT": - if err := d.Set("oauth_token_endpoint", value); err != nil { + if err = handleExternalChangesToObjectInDescribe(d, + describeMapping{"enabled", "enabled", enabled.Value, enabled.Value, nil}, + describeMapping{"oauth_access_token_validity", "oauth_access_token_validity", oauthAccessTokenValidityInt, oauthAccessTokenValidityInt, stringToIntNormalizer}, + describeMapping{"oauth_refresh_token_validity", "oauth_refresh_token_validity", oauthRefreshTokenValidityInt, oauthRefreshTokenValidityInt, stringToIntNormalizer}, + describeMapping{"oauth_client_id", "oauth_client_id", oauthClientId.Value, oauthClientId.Value, nil}, + describeMapping{"oauth_client_auth_method", "oauth_client_auth_method", oauthClientAuthMethod.Value, oauthClientAuthMethod.Value, nil}, + describeMapping{"oauth_token_endpoint", "oauth_token_endpoint", oauthTokenEndpoint.Value, oauthTokenEndpoint.Value, nil}, + describeMapping{"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, listValueToSlice(oauthAllowedScopes.Value, true, false), nil}, + describeMapping{"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, + ); err != nil { return diag.FromErr(err) } - default: - log.Printf("[WARN] unexpected property %v returned from Snowflake", name) } - } + if !d.GetRawConfig().IsNull() { + if v := d.GetRawConfig().AsValueMap()["enabled"]; !v.IsNull() { + if err = d.Set("enabled", v.True()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_access_token_validity"]; !v.IsNull() { + intVal, _ := v.AsBigFloat().Int64() + if err = d.Set("oauth_access_token_validity", intVal); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_refresh_token_validity"]; !v.IsNull() { + intVal, _ := v.AsBigFloat().Int64() + if err = d.Set("oauth_refresh_token_validity", intVal); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_client_id"]; !v.IsNull() { + if err = d.Set("oauth_client_id", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_client_auth_method"]; !v.IsNull() { + if err = d.Set("oauth_client_auth_method", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_token_endpoint"]; !v.IsNull() { + if err = d.Set("oauth_token_endpoint", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_allowed_scopes"]; !v.IsNull() { + if err = d.Set("oauth_allowed_scopes", ctyValToSliceString(v)); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_grant"]; !v.IsNull() { + if err = d.Set("oauth_grant", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["comment"]; !v.IsNull() { + if err = d.Set("comment", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + } - return nil + 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.ApiAuthSecurityIntegrationPropertiesToSchema(properties)}); err != nil { + return diag.FromErr(err) + } + + return nil + } } func UpdateContextApiAuthenticationIntegrationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -202,24 +378,32 @@ func UpdateContextApiAuthenticationIntegrationWithClientCredentials(ctx context. set, unset := sdk.NewApiAuthenticationWithClientCredentialsFlowIntegrationSetRequest(), sdk.NewApiAuthenticationWithClientCredentialsFlowIntegrationUnsetRequest() if d.HasChange("comment") { - set.WithComment(d.Get("comment").(string)) + if v, ok := d.GetOk("comment"); ok { + set.WithComment(v.(string)) + } else { + unset.WithComment(true) + } } if d.HasChange("enabled") { - set.WithEnabled(d.Get("enabled").(bool)) + if v := d.Get("comment").(string); v != "unknown" { + parsed, err := strconv.ParseBool(v) + if err != nil { + return diag.FromErr(err) + } + set.WithEnabled(parsed) + } else { + unset.WithEnabled(true) + } } if d.HasChange("oauth_access_token_validity") { - set.WithOauthAccessTokenValidity(d.Get("oauth_access_token_validity").(int)) - } - - if d.HasChange("oauth_allowed_scopes") { - elems := expandStringList(d.Get("oauth_allowed_scopes").(*schema.Set).List()) - allowedScopes := make([]sdk.AllowedScope, len(elems)) - for i := range elems { - allowedScopes[i] = sdk.AllowedScope{Scope: elems[i]} + if v := d.Get("oauth_access_token_validity").(int); v != -1 { + set.WithOauthAccessTokenValidity(v) + } else { + // TODO: use UNSET + set.WithOauthAccessTokenValidity(0) } - set.WithOauthAllowedScopes(allowedScopes) } if d.HasChange("oauth_client_auth_method") { @@ -236,15 +420,38 @@ func UpdateContextApiAuthenticationIntegrationWithClientCredentials(ctx context. if d.HasChange("oauth_client_id") { set.WithOauthClientId(d.Get("oauth_client_id").(string)) } + if d.HasChange("oauth_grant") { + if v := d.Get("oauth_grant").(string); v == "CLIENT_CREDENTIALS" { + set.WithOauthGrantClientCredentials(true) + } + } if d.HasChange("oauth_client_secret") { set.WithOauthClientSecret(d.Get("oauth_client_secret").(string)) } + if d.HasChange("oauth_refresh_token_validity") { + if v := d.Get("oauth_refresh_token_validity").(int); v != -1 { + set.WithOauthRefreshTokenValidity(v) + } else { + // TODO: use UNSET + set.WithOauthRefreshTokenValidity(7776000) + } + } + if d.HasChange("oauth_token_endpoint") { set.WithOauthTokenEndpoint(d.Get("oauth_token_endpoint").(string)) } + if d.HasChange("oauth_allowed_scopes") { + elems := expandStringList(d.Get("oauth_allowed_scopes").(*schema.Set).List()) + allowedScopes := make([]sdk.AllowedScope, len(elems)) + for i := range elems { + allowedScopes[i] = sdk.AllowedScope{Scope: elems[i]} + } + set.WithOauthAllowedScopes(allowedScopes) + } + if !reflect.DeepEqual(*set, sdk.ApiAuthenticationWithClientCredentialsFlowIntegrationSetRequest{}) { if err := client.SecurityIntegrations.AlterApiAuthenticationWithClientCredentialsFlow(ctx, sdk.NewAlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(id).WithSet(*set)); err != nil { return diag.FromErr(err) @@ -255,7 +462,8 @@ func UpdateContextApiAuthenticationIntegrationWithClientCredentials(ctx context. return diag.FromErr(err) } } - return ReadContextApiAuthenticationIntegrationWithClientCredentials(ctx, d, meta) + + return ReadContextApiAuthenticationIntegrationWithClientCredentials(false)(ctx, d, meta) } func DeleteContextApiAuthenticationIntegrationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { diff --git a/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go b/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go index 83d6f6b769..98e94d07ae 100644 --- a/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go +++ b/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go @@ -24,9 +24,11 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_basic(t *testing. if complete { c["comment"] = config.StringVariable("foo") c["oauth_access_token_validity"] = config.IntegerVariable(42) - c["oauth_allowed_scopes"] = config.SetVariable(config.StringVariable("foo")) c["oauth_client_auth_method"] = config.StringVariable(string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)) + c["oauth_refresh_token_validity"] = config.IntegerVariable(12345) c["oauth_token_endpoint"] = config.StringVariable("https://example.com") + c["oauth_allowed_scopes"] = config.SetVariable(config.StringVariable("foo")) + c["oauth_grant"] = config.StringVariable("CLIENT_CREDENTIALS") } return c } @@ -40,54 +42,94 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_basic(t *testing. { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic"), ConfigVariables: m(false), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "name", id.Name()), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_id", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), - resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_client_credentials.test", "created_on"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.integration_type", "API_AUTHENTICATION"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.comment", ""), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_access_token_validity.0.value", "0"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_refresh_token_validity.0.value", "0"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_client_id.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_client_auth_method.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_token_endpoint.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_allowed_scopes.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_grant.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.parent_integration.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.auth_type.0.value", "OAUTH2"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.comment.0.value", ""), ), }, { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete"), ConfigVariables: m(true), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "comment", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "name", id.Name()), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_access_token_validity", "42"), - // TODO: proper test - // resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes", "config.StringVariable("foo")"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_auth_method", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_id", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_refresh_token_validity", "12345"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint", "https://example.com"), - resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_client_credentials.test", "created_on"), - ), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_grant", "CLIENT_CREDENTIALS"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes.0", "foo"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.integration_type", "API_AUTHENTICATION"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.comment", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_access_token_validity.0.value", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_refresh_token_validity.0.value", "12345"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_client_id.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_client_auth_method.0.value", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_token_endpoint.0.value", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_allowed_scopes.0.value", "[foo]"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_grant.0.value", "CLIENT_CREDENTIALS"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.parent_integration.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.auth_type.0.value", "OAUTH2"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.comment.0.value", "foo")), }, { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic"), ConfigVariables: m(true), ResourceName: "snowflake_api_authentication_integration_with_client_credentials.test", ImportState: true, + ImportStateVerify: true, ImportStateVerifyIgnore: []string{"oauth_client_secret"}, }, // unset { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic"), ConfigVariables: m(false), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "comment", ""), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "name", id.Name()), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_access_token_validity", "0"), - // TODO: proper test - // resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes", "config.StringVariable("foo")"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_auth_method", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_access_token_validity", "-1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_auth_method", "unknown"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_id", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint", ""), - resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_client_credentials.test", "created_on"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_refresh_token_validity", "-1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint", "unknown"), ), }, }, @@ -98,15 +140,17 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_complete(t *testi id := acc.TestClient().Ids.RandomAccountObjectIdentifier() m := func() map[string]config.Variable { return map[string]config.Variable{ - "comment": config.StringVariable("foo"), - "enabled": config.BoolVariable(true), - "name": config.StringVariable(id.Name()), - "oauth_access_token_validity": config.IntegerVariable(42), - "oauth_allowed_scopes": config.SetVariable(config.StringVariable("foo")), - "oauth_client_auth_method": config.StringVariable(string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), - "oauth_client_id": config.StringVariable("foo"), - "oauth_client_secret": config.StringVariable("foo"), - "oauth_token_endpoint": config.StringVariable("https://example.com"), + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "oauth_access_token_validity": config.IntegerVariable(42), + "oauth_client_auth_method": config.StringVariable(string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + "oauth_client_id": config.StringVariable("foo"), + "oauth_client_secret": config.StringVariable("foo"), + "oauth_refresh_token_validity": config.IntegerVariable(12345), + "oauth_token_endpoint": config.StringVariable("https://example.com"), + "oauth_grant": config.StringVariable("CLIENT_CREDENTIALS"), + "oauth_allowed_scopes": config.SetVariable(config.StringVariable("foo")), } } resource.Test(t, resource.TestCase{ @@ -119,18 +163,19 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_complete(t *testi { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete"), ConfigVariables: m(), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "comment", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "name", id.Name()), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_access_token_validity", "42"), - // TODO: proper test - // resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes", "config.StringVariable("foo")"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_auth_method", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_id", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_refresh_token_validity", "12345"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint", "https://example.com"), - resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_client_credentials.test", "created_on"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes.0", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_allowed_scopes.0.value", "[foo]"), ), }, { @@ -158,9 +203,9 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_invalidIncomplete tfversion.RequireAbove(tfversion.Version1_5_0), }, ErrorCheck: helpers.AssertErrorContainsPartsFunc(t, []string{ - `The argument "enabled" is required, but no definition was found.`, - `The argument "oauth_client_id" is required, but no definition was found.`, `The argument "oauth_client_secret" is required, but no definition was found.`, + `The argument "oauth_client_id" is required, but no definition was found.`, + `The argument "enabled" is required, but no definition was found.`, }), Steps: []resource.TestStep{ { diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer.go b/pkg/resources/api_authentication_integration_with_jwt_bearer.go index 1039987174..155dc19f3d 100644 --- a/pkg/resources/api_authentication_integration_with_jwt_bearer.go +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer.go @@ -4,11 +4,14 @@ import ( "context" "errors" "fmt" - "log" "reflect" + "strconv" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "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" @@ -19,12 +22,6 @@ import ( var apiAuthJwtBearerSchema = func() map[string]*schema.Schema { uniq := map[string]*schema.Schema{ - "oauth_refresh_token_validity": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntAtLeast(1), - Description: "Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server.", - }, "oauth_authorization_endpoint": { Type: schema.TypeString, Optional: true, @@ -34,6 +31,13 @@ var apiAuthJwtBearerSchema = func() map[string]*schema.Schema { Type: schema.TypeString, Required: true, }, + "oauth_grant": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"unknown", "JWT_BEARER"}, true), + Description: "Specifies the type of OAuth flow.", + Default: "unknown", + }, } return MergeMaps(apiAuthCommonSchema, uniq) }() @@ -41,7 +45,7 @@ var apiAuthJwtBearerSchema = func() map[string]*schema.Schema { func ApiAuthenticationIntegrationWithJwtBearer() *schema.Resource { return &schema.Resource{ CreateContext: CreateContextApiAuthenticationIntegrationWithJwtBearer, - ReadContext: ReadContextApiAuthenticationIntegrationWithJwtBearer, + ReadContext: ReadContextApiAuthenticationIntegrationWithJwtBearer(true), UpdateContext: UpdateContextApiAuthenticationIntegrationWithJwtBearer, DeleteContext: DeleteContextApiAuthenticationIntegrationWithJwtBearer, Schema: apiAuthJwtBearerSchema, @@ -49,13 +53,103 @@ func ApiAuthenticationIntegrationWithJwtBearer() *schema.Resource { ForceNewIfChangeToDefaultString("oauth_token_endpoint"), ForceNewIfChangeToDefaultString("oauth_authorization_endpoint"), ForceNewIfChangeToDefaultString("oauth_client_auth_method"), + ForceNewIfChangeToDefaultString("oauth_grant"), + ComputedIfAnyAttributeChanged(showOutputAttributeName, "enabled", "comment"), + ComputedIfAnyAttributeChanged(describeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", + "oauth_client_id", "oauth_client_auth_method", "oauth_authorization_endpoint", + "oauth_token_endpoint", "oauth_grant", "oauth_assertion_issuer"), ), Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: ImportApiAuthenticationWithJwtBearer, }, } } +func ImportApiAuthenticationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + logging.DebugLogger.Printf("[DEBUG] Starting api auth integration with jwt bearer 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 + } + + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return nil, err + } + + if err = d.Set("name", integration.Name); err != nil { + return nil, err + } + if err = d.Set("enabled", integration.Enabled); err != nil { + return nil, err + } + if err = d.Set("comment", integration.Comment); err != nil { + return nil, err + } + + oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" + }) + if err == nil { + value, err := strconv.Atoi(oauthAccessTokenValidity.Value) + if err != nil { + return nil, err + } + if err = d.Set("oauth_access_token_validity", value); err != nil { + return nil, err + } + } + oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" + }) + if err == nil { + value, err := strconv.Atoi(oauthRefreshTokenValidity.Value) + if err != nil { + return nil, err + } + if err = d.Set("oauth_refresh_token_validity", value); err != nil { + return nil, err + } + } + oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) + if err == nil { + if err = d.Set("oauth_client_id", oauthClientId.Value); err != nil { + return nil, err + } + } + oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_CLIENT_AUTH_METHOD" + }) + if err == nil { + if err = d.Set("oauth_client_auth_method", oauthClientAuthMethod.Value); err != nil { + return nil, err + } + } + oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) + if err == nil { + if err = d.Set("oauth_token_endpoint", oauthTokenEndpoint.Value); err != nil { + return nil, err + } + } + oauthAssertionIssuer, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ASSERTION_ISSUER" }) + if err == nil { + if err = d.Set("oauth_assertion_issuer", oauthAssertionIssuer); err != nil { + return nil, err + } + } + oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) + if err == nil { + if err = d.Set("oauth_grant", oauthGrant.Value); err != nil { + return nil, err + } + } + + return []*schema.ResourceData{d}, nil +} + func CreateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client enabled := d.Get("enabled").(bool) @@ -70,29 +164,34 @@ func CreateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, req.WithComment(v.(string)) } - if v, ok := d.GetOk("oauth_access_token_validity"); ok { - req.WithOauthAccessTokenValidity(v.(int)) + if v := d.Get("oauth_access_token_validity").(int); v != -1 { + req.WithOauthAccessTokenValidity(v) } - if v, ok := d.GetOk("oauth_authorization_endpoint"); ok { - req.WithOauthAuthorizationEndpoint(v.(string)) + if v := d.Get("oauth_authorization_endpoint").(string); v != "unknown" { + req.WithOauthAuthorizationEndpoint(v) } - if v, ok := d.GetOk("oauth_client_auth_method"); ok { - valueRaw := v.(string) - value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(valueRaw) + if v := d.Get("oauth_client_auth_method").(string); v != "unknown" { + value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) if err != nil { return diag.FromErr(err) } req.WithOauthClientAuthMethod(value) } - if v, ok := d.GetOk("oauth_refresh_token_validity"); ok { - req.WithOauthRefreshTokenValidity(v.(int)) + if v := d.Get("oauth_refresh_token_validity").(int); v != -1 { + req.WithOauthRefreshTokenValidity(v) + } + + if v := d.Get("oauth_grant").(string); v != "unknown" { + if v == "JWT_BEARER" { + req.WithOauthGrantJwtBearer(true) + } } - if v, ok := d.GetOk("oauth_token_endpoint"); ok { - req.WithOauthTokenEndpoint(v.(string)) + if v := d.Get("oauth_token_endpoint").(string); v != "unknown" { + req.WithOauthTokenEndpoint(v) } if err := client.SecurityIntegrations.CreateApiAuthenticationWithJwtBearerFlow(ctx, req); err != nil { @@ -101,102 +200,192 @@ func CreateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d.SetId(name) - return ReadContextApiAuthenticationIntegrationWithJwtBearer(ctx, d, meta) + return ReadContextApiAuthenticationIntegrationWithJwtBearer(false)(ctx, d, meta) } -func ReadContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) +func ReadContextApiAuthenticationIntegrationWithJwtBearer(withExternalChangesMarking bool) schema.ReadContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) 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), - }, + 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) + } + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return diag.FromErr(err) } - 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", integration.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("comment", integration.Comment); 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", integration.Name); err != nil { + return diag.FromErr(err) + } + if err := d.Set("comment", integration.Comment); err != nil { + return diag.FromErr(err) + } + if err := d.Set("enabled", integration.Enabled); err != nil { + return diag.FromErr(err) + } + if withExternalChangesMarking { + if err = handleExternalChangesToObjectInShow(d, + showMapping{"comment", "comment", integration.Comment, integration.Comment, nil}, + showMapping{"enabled", "enabled", integration.Enabled, integration.Enabled, nil}, + ); err != nil { + return diag.FromErr(err) + } - if err := d.Set("created_on", integration.CreatedOn.String()); err != nil { - return diag.FromErr(err) - } + enabled, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "ENABLED" }) + if err != nil { + return diag.FromErr(err) + } - if err := d.Set("enabled", integration.Enabled); err != nil { - return diag.FromErr(err) - } + oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" + }) + if err != nil { + return diag.FromErr(err) + } - properties, err := client.SecurityIntegrations.Describe(ctx, id) - if err != nil { - return diag.FromErr(err) - } - for _, property := range properties { - name := property.Name - value := property.Value - switch name { - case "COMMENT": - if err := d.Set("comment", value); err != nil { + oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" + }) + if err != nil { return diag.FromErr(err) } - case "CREATED_ON": - if err := d.Set("created_on", value); err != nil { + + oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) + if err != nil { return diag.FromErr(err) } - case "ENABLED": - if err := d.Set("enabled", helpers.StringToBool(value)); err != nil { + + oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_CLIENT_AUTH_METHOD" + }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_ACCESS_TOKEN_VALIDITY": - if err := d.Set("oauth_access_token_validity", "TODO"); err != nil { + + oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_AUTHORIZATION_ENDPOINT" + }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_AUTHORIZATION_ENDPOINT": - if err := d.Set("oauth_authorization_endpoint", value); err != nil { + + oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_CLIENT_AUTH_METHOD": - if err := d.Set("oauth_client_auth_method", value); err != nil { + + oauthAssertionIssuer, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ASSERTION_ISSUER" }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_CLIENT_ID": - if err := d.Set("oauth_client_id", value); err != nil { + + oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) + if err != nil { return diag.FromErr(err) } - case "OAUTH_CLIENT_SECRET": - if err := d.Set("oauth_client_secret", value); err != nil { + oauthAccessTokenValidityInt, err := strconv.Atoi(oauthAccessTokenValidity.Value) + if err != nil { return diag.FromErr(err) } - case "OAUTH_REFRESH_TOKEN_VALIDITY": - if err := d.Set("oauth_refresh_token_validity", "TODO"); err != nil { + oauthRefreshTokenValidityInt, err := strconv.Atoi(oauthRefreshTokenValidity.Value) + if err != nil { return diag.FromErr(err) } - case "OAUTH_TOKEN_ENDPOINT": - if err := d.Set("oauth_token_endpoint", value); err != nil { + if err = handleExternalChangesToObjectInDescribe(d, + describeMapping{"enabled", "enabled", enabled.Value, enabled.Value, nil}, + describeMapping{"oauth_access_token_validity", "oauth_access_token_validity", oauthAccessTokenValidityInt, oauthAccessTokenValidityInt, stringToIntNormalizer}, + describeMapping{"oauth_refresh_token_validity", "oauth_refresh_token_validity", oauthRefreshTokenValidityInt, oauthRefreshTokenValidityInt, stringToIntNormalizer}, + describeMapping{"oauth_client_id", "oauth_client_id", oauthClientId.Value, oauthClientId.Value, nil}, + describeMapping{"oauth_client_auth_method", "oauth_client_auth_method", oauthClientAuthMethod.Value, oauthClientAuthMethod.Value, nil}, + describeMapping{"oauth_authorization_endpoint", "oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value, oauthAuthorizationEndpoint.Value, nil}, + describeMapping{"oauth_token_endpoint", "oauth_token_endpoint", oauthTokenEndpoint.Value, oauthTokenEndpoint.Value, nil}, + describeMapping{"oauth_assertion_issuer", "oauth_assertion_issuer", oauthAssertionIssuer.Value, oauthAssertionIssuer.Value, nil}, + describeMapping{"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, + ); err != nil { return diag.FromErr(err) } - default: - log.Printf("[WARN] unexpected property %v returned from Snowflake", name) } - } + if !d.GetRawConfig().IsNull() { + if v := d.GetRawConfig().AsValueMap()["enabled"]; !v.IsNull() { + if err = d.Set("enabled", v.True()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_access_token_validity"]; !v.IsNull() { + intVal, _ := v.AsBigFloat().Int64() + if err = d.Set("oauth_access_token_validity", intVal); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_refresh_token_validity"]; !v.IsNull() { + intVal, _ := v.AsBigFloat().Int64() + if err = d.Set("oauth_refresh_token_validity", intVal); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_client_id"]; !v.IsNull() { + if err = d.Set("oauth_client_id", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_client_auth_method"]; !v.IsNull() { + if err = d.Set("oauth_client_auth_method", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_authorization_endpoint"]; !v.IsNull() { + if err = d.Set("oauth_authorization_endpoint", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_token_endpoint"]; !v.IsNull() { + if err = d.Set("oauth_token_endpoint", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_assertion_issuer"]; !v.IsNull() { + if err = d.Set("oauth_assertion_issuer", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_grant"]; !v.IsNull() { + if err = d.Set("oauth_grant", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + if v := d.GetRawConfig().AsValueMap()["comment"]; !v.IsNull() { + if err = d.Set("comment", v.AsString()); err != nil { + return diag.FromErr(err) + } + } + } - return nil + 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.ApiAuthSecurityIntegrationPropertiesToSchema(properties)}); err != nil { + return diag.FromErr(err) + } + + return nil + } } func UpdateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -205,15 +394,32 @@ func UpdateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, set, unset := sdk.NewApiAuthenticationWithJwtBearerFlowIntegrationSetRequest(), sdk.NewApiAuthenticationWithJwtBearerFlowIntegrationUnsetRequest() if d.HasChange("comment") { - set.WithComment(d.Get("comment").(string)) + if v, ok := d.GetOk("comment"); ok { + set.WithComment(v.(string)) + } else { + unset.WithComment(true) + } } if d.HasChange("enabled") { - set.WithEnabled(d.Get("enabled").(bool)) + if v := d.Get("comment").(string); v != "unknown" { + parsed, err := strconv.ParseBool(v) + if err != nil { + return diag.FromErr(err) + } + set.WithEnabled(parsed) + } else { + unset.WithEnabled(true) + } } if d.HasChange("oauth_access_token_validity") { - set.WithOauthAccessTokenValidity(d.Get("oauth_access_token_validity").(int)) + if v := d.Get("oauth_access_token_validity").(int); v != -1 { + set.WithOauthAccessTokenValidity(v) + } else { + // TODO: use UNSET + set.WithOauthAccessTokenValidity(0) + } } if d.HasChange("oauth_authorization_endpoint") { @@ -234,19 +440,28 @@ func UpdateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, if d.HasChange("oauth_client_id") { set.WithOauthClientId(d.Get("oauth_client_id").(string)) } + if d.HasChange("oauth_grant") { + if v := d.Get("oauth_grant").(string); v == "JWT_BEARER" { + set.WithOauthGrantJwtBearer(true) + } + } if d.HasChange("oauth_client_secret") { set.WithOauthClientSecret(d.Get("oauth_client_secret").(string)) } if d.HasChange("oauth_refresh_token_validity") { - set.WithOauthRefreshTokenValidity(d.Get("oauth_refresh_token_validity").(int)) + if v := d.Get("oauth_refresh_token_validity").(int); v != -1 { + set.WithOauthRefreshTokenValidity(v) + } else { + // TODO: use UNSET + set.WithOauthRefreshTokenValidity(7776000) + } } if d.HasChange("oauth_token_endpoint") { set.WithOauthTokenEndpoint(d.Get("oauth_token_endpoint").(string)) } - if !reflect.DeepEqual(*set, sdk.ApiAuthenticationWithJwtBearerFlowIntegrationSetRequest{}) { if err := client.SecurityIntegrations.AlterApiAuthenticationWithJwtBearerFlow(ctx, sdk.NewAlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest(id).WithSet(*set)); err != nil { return diag.FromErr(err) @@ -257,7 +472,7 @@ func UpdateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, return diag.FromErr(err) } } - return ReadContextApiAuthenticationIntegrationWithJwtBearer(ctx, d, meta) + return ReadContextApiAuthenticationIntegrationWithJwtBearer(false)(ctx, d, meta) } func DeleteContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { diff --git a/pkg/resources/common.go b/pkg/resources/common.go index ae173d6126..4397479176 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -150,6 +150,14 @@ var apiAuthCommonSchema = map[string]*schema.Schema{ Description: "Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server.", DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("oauth_access_token_validity"), }, + "oauth_refresh_token_validity": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(1), + Default: -1, + Description: "Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server.", + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("oauth_refresh_token_validity"), + }, "comment": { Type: schema.TypeString, Optional: true, diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf index 4ad086889b..7bcdc528ca 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf @@ -1,11 +1,13 @@ resource "snowflake_api_authentication_integration_with_client_credentials" "test" { - comment = var.comment - enabled = var.enabled - name = var.name - oauth_access_token_validity = var.oauth_access_token_validity - oauth_client_auth_method = var.oauth_client_auth_method - oauth_client_id = var.oauth_client_id - oauth_client_secret = var.oauth_client_secret - oauth_token_endpoint = var.oauth_token_endpoint - oauth_allowed_scopes = var.oauth_allowed_scopes + comment = var.comment + enabled = var.enabled + name = var.name + oauth_access_token_validity = var.oauth_access_token_validity + oauth_refresh_token_validity = var.oauth_refresh_token_validity + oauth_client_auth_method = var.oauth_client_auth_method + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret + oauth_token_endpoint = var.oauth_token_endpoint + oauth_allowed_scopes = var.oauth_allowed_scopes + oauth_grant = var.oauth_grant } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf index 33a0304aff..dc1cd1053b 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf @@ -11,6 +11,9 @@ variable "name" { variable "oauth_access_token_validity" { type = number } +variable "oauth_refresh_token_validity" { + type = number +} variable "oauth_client_auth_method" { type = string } @@ -26,3 +29,6 @@ variable "oauth_token_endpoint" { variable "oauth_allowed_scopes" { type = set(string) } +variable "oauth_grant" { + type = string +} diff --git a/v1-preparations/CHANGES_BEFORE_V1.md b/v1-preparations/CHANGES_BEFORE_V1.md index 2f8f505102..c30564dbc4 100644 --- a/v1-preparations/CHANGES_BEFORE_V1.md +++ b/v1-preparations/CHANGES_BEFORE_V1.md @@ -5,17 +5,25 @@ Each provider version lists changes made in resources and datasources definition ## Default values For any resource that went through the rework as part of the [resource preparation for V1](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/ROADMAP.md#preparing-essential-ga-objects-for-the-provider-v1), -the behaviour for default values may change from the previous one. +the behaviour for default values may change from the previous one. -In the past, the provider copied defaults from Snowflake, creating a tight coupling between them. +In the past, the provider copied defaults from Snowflake, creating a tight coupling between them. However, this approach posed a challenge as the defaults on the Snowflake side could change and vary between accounts based on their configurations. Now, whenever the value is not specified in the configuration, we let the Snowflake fill out the default value for a given field -(if there is one). Using such defaults may lead to non-idempotent cases where the same configuration may -create a resource with slightly different configuration in Snowflake (depending on the Snowflake Edition and Version, +(if there is one). Using such defaults may lead to non-idempotent cases where the same configuration may +create a resource with slightly different configuration in Snowflake (depending on the Snowflake Edition and Version, current account configuration, and most-likely other factors). That is why we recommend setting optional fields where you want to ensure that the specified value has been set on the Snowflake side. +## v0.91.0 ➞ v0.92.0 +### new resources for API Authentication security integrations +New resources are introduced for each type of oauth flow: +- `snowflake_api_authentication_integration_with_authorization_code_grant` +- `snowflake_api_authentication_integration_with_client_credentials` +- `snowflake_api_authentication_integration_with_jwt_bearer` +Check https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-api-auth for more details. + ## v0.91.0 ➞ v0.92.0 ### snowflake_scim_integration resource changes From a3048688bdc0fd693fe42b9d2250cc0fac5f1661 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Mon, 1 Jul 2024 11:25:42 +0200 Subject: [PATCH 05/18] Small fixes --- ...tegration_with_authorization_code_grant.md | 172 +++++++++++++++++- ...ion_integration_with_client_credentials.md | 170 ++++++++++++++++- ...hentication_integration_with_jwt_bearer.md | 169 ++++++++++++++++- .../resource.tf | 10 +- ...gration_with_jwt_bearer_acceptance_test.go | 2 + pkg/resources/common.go | 2 +- pkg/resources/custom_diffs.go | 4 +- .../complete/variables.tf | 1 - .../basic/variables.tf | 1 - .../complete/variables.tf | 1 - .../basic/test.tf | 14 +- .../basic/variables.tf | 4 +- .../complete/test.tf | 1 + .../complete/variables.tf | 4 +- .../warehouse_rework_show_output_proposal.go | 32 ++-- pkg/sdk/security_integrations_gen_test.go | 2 +- ...urity_integrations_gen_integration_test.go | 2 +- v1-preparations/CHANGES_BEFORE_V1.md | 2 +- 18 files changed, 543 insertions(+), 50 deletions(-) diff --git a/docs/resources/api_authentication_integration_with_authorization_code_grant.md b/docs/resources/api_authentication_integration_with_authorization_code_grant.md index 27ad5bb69f..3232655ae0 100644 --- a/docs/resources/api_authentication_integration_with_authorization_code_grant.md +++ b/docs/resources/api_authentication_integration_with_authorization_code_grant.md @@ -48,15 +48,181 @@ resource "snowflake_api_authentication_integration_with_authorization_code_grant - `comment` (String) Specifies a comment for the integration. - `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. +- `oauth_allowed_scopes` (Set of String) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. - `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. -- `oauth_client_auth_method` (String) Specifies the client ID for the OAuth application in the external service. -- `oauth_refresh_token_validity` (Number) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. +- `oauth_client_auth_method` (String) Specifies the client ID for the OAuth application in the external service. Valid options are: [CLIENT_SECRET_POST] +- `oauth_grant` (String) Specifies the type of OAuth flow. +- `oauth_refresh_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). ### Read-Only -- `created_on` (String) Date and time when the integration was created. +- `describe_output` (List of Object) Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security 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 INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--show_output)) + + +### Nested Schema for `describe_output` + +Read-Only: + +- `auth_type` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--auth_type)) +- `comment` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--comment)) +- `enabled` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--enabled)) +- `oauth_access_token_validity` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_access_token_validity)) +- `oauth_allowed_scopes` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_allowed_scopes)) +- `oauth_authorization_endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_authorization_endpoint)) +- `oauth_client_auth_method` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_client_auth_method)) +- `oauth_client_id` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_client_id)) +- `oauth_grant` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_grant)) +- `oauth_refresh_token_validity` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_refresh_token_validity)) +- `oauth_token_endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_token_endpoint)) +- `parent_integration` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--parent_integration)) + + +### Nested Schema for `describe_output.auth_type` + +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.enabled` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_access_token_validity` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_allowed_scopes` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_authorization_endpoint` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_client_auth_method` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_client_id` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_grant` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_refresh_token_validity` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_token_endpoint` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.parent_integration` + +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 diff --git a/docs/resources/api_authentication_integration_with_client_credentials.md b/docs/resources/api_authentication_integration_with_client_credentials.md index 7eab128ed5..239a553dc6 100644 --- a/docs/resources/api_authentication_integration_with_client_credentials.md +++ b/docs/resources/api_authentication_integration_with_client_credentials.md @@ -48,13 +48,179 @@ resource "snowflake_api_authentication_integration_with_client_credentials" "tes - `comment` (String) Specifies a comment for the integration. - `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_allowed_scopes` (Set of String) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. -- `oauth_client_auth_method` (String) Specifies the client ID for the OAuth application in the external service. +- `oauth_client_auth_method` (String) Specifies the client ID for the OAuth application in the external service. Valid options are: [CLIENT_SECRET_POST] +- `oauth_grant` (String) Specifies the type of OAuth flow. +- `oauth_refresh_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). ### Read-Only -- `created_on` (String) Date and time when the integration was created. +- `describe_output` (List of Object) Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security 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 INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--show_output)) + + +### Nested Schema for `describe_output` + +Read-Only: + +- `auth_type` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--auth_type)) +- `comment` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--comment)) +- `enabled` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--enabled)) +- `oauth_access_token_validity` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_access_token_validity)) +- `oauth_allowed_scopes` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_allowed_scopes)) +- `oauth_authorization_endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_authorization_endpoint)) +- `oauth_client_auth_method` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_client_auth_method)) +- `oauth_client_id` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_client_id)) +- `oauth_grant` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_grant)) +- `oauth_refresh_token_validity` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_refresh_token_validity)) +- `oauth_token_endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_token_endpoint)) +- `parent_integration` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--parent_integration)) + + +### Nested Schema for `describe_output.auth_type` + +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.enabled` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_access_token_validity` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_allowed_scopes` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_authorization_endpoint` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_client_auth_method` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_client_id` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_grant` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_refresh_token_validity` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_token_endpoint` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.parent_integration` + +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 diff --git a/docs/resources/api_authentication_integration_with_jwt_bearer.md b/docs/resources/api_authentication_integration_with_jwt_bearer.md index c71685ec22..e84b3ccd2f 100644 --- a/docs/resources/api_authentication_integration_with_jwt_bearer.md +++ b/docs/resources/api_authentication_integration_with_jwt_bearer.md @@ -50,14 +50,179 @@ resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { - `comment` (String) Specifies a comment for the integration. - `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. -- `oauth_client_auth_method` (String) Specifies the client ID for the OAuth application in the external service. +- `oauth_client_auth_method` (String) Specifies the client ID for the OAuth application in the external service. Valid options are: [CLIENT_SECRET_POST] +- `oauth_grant` (String) Specifies the type of OAuth flow. - `oauth_refresh_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). ### Read-Only -- `created_on` (String) Date and time when the integration was created. +- `describe_output` (List of Object) Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security 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 INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--show_output)) + + +### Nested Schema for `describe_output` + +Read-Only: + +- `auth_type` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--auth_type)) +- `comment` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--comment)) +- `enabled` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--enabled)) +- `oauth_access_token_validity` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_access_token_validity)) +- `oauth_allowed_scopes` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_allowed_scopes)) +- `oauth_authorization_endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_authorization_endpoint)) +- `oauth_client_auth_method` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_client_auth_method)) +- `oauth_client_id` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_client_id)) +- `oauth_grant` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_grant)) +- `oauth_refresh_token_validity` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_refresh_token_validity)) +- `oauth_token_endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_token_endpoint)) +- `parent_integration` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--parent_integration)) + + +### Nested Schema for `describe_output.auth_type` + +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.enabled` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_access_token_validity` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_allowed_scopes` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_authorization_endpoint` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_client_auth_method` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_client_id` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_grant` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_refresh_token_validity` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_token_endpoint` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.parent_integration` + +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 diff --git a/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf b/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf index f80e334560..0751baabb9 100644 --- a/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf +++ b/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf @@ -1,9 +1,10 @@ # basic resource resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { - enabled = true - name = "foo" - oauth_client_id = "foo" - oauth_client_secret = "foo" + enabled = true + name = "foo" + oauth_client_id = "foo" + oauth_client_secret = "foo" + oauth_assertion_issuer = "foo" } # resource with all fields set resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { @@ -17,4 +18,5 @@ resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { oauth_client_secret = "foo" oauth_refresh_token_validity = 42 oauth_token_endpoint = "https://example.com" + oauth_assertion_issuer = "foo" } diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go b/pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go index eb3d4a2bbf..a047be0ab2 100644 --- a/pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go @@ -166,6 +166,8 @@ func TestAcc_ApiAuthenticationIntegrationWithJwtBearer_invalidIncomplete(t *test }, ErrorCheck: helpers.AssertErrorContainsPartsFunc(t, []string{ `The argument "enabled" is required, but no definition was found.`, + // this one is trimmed because of inconsistent \n behavior in error message + `The argument "oauth_assertion_issuer" is required, but no definition`, `The argument "oauth_client_id" is required, but no definition was found.`, `The argument "oauth_client_secret" is required, but no definition was found.`, }), diff --git a/pkg/resources/common.go b/pkg/resources/common.go index 4397479176..2d1cef5554 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -130,7 +130,7 @@ var apiAuthCommonSchema = map[string]*schema.Schema{ Optional: true, ValidateDiagFunc: sdkValidation(sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption), Default: "unknown", - Description: fmt.Sprintf("Specifies the client ID for the OAuth application in the external service. Valid options are: %v", sdk.AsStringList(sdk.AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption)), + Description: fmt.Sprintf("Specifies that POST is used as the authentication method to the external service. Valid options are: %v", sdk.AsStringList(sdk.AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption)), }, "oauth_client_id": { Type: schema.TypeString, diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index 65e3cd2d48..e5e7a8b504 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -73,10 +73,10 @@ func ComputedIfAnyAttributeChanged(key string, changedAttributeKeys ...string) s }) } -// ForceNewIfChangeToDefaultString sets a ForceNew for a string field which was set to an empty value. +// ForceNewIfChangeToDefaultString sets a ForceNew for a string field which was changed to an unknown value. func ForceNewIfChangeToDefaultString(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 && newString == "unknown" + return oldString != "unknown" && newString == "unknown" }) } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf index fdeaf92bff..38b4993f9e 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf @@ -1,4 +1,3 @@ - variable "comment" { type = string } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/variables.tf index 34a32daf3e..f0aca04e61 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/variables.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/variables.tf @@ -1,4 +1,3 @@ - variable "enabled" { type = bool } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf index dc1cd1053b..53e4acbe3e 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf @@ -1,4 +1,3 @@ - variable "comment" { type = string } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf index b3cca25614..734576561b 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf @@ -1,12 +1,6 @@ resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { - comment = var.comment - enabled = var.enabled - name = var.name - oauth_access_token_validity = var.oauth_access_token_validity - oauth_authorization_endpoint = var.oauth_authorization_endpoint - oauth_client_auth_method = var.oauth_client_auth_method - oauth_client_id = var.oauth_client_id - oauth_client_secret = var.oauth_client_secret - oauth_refresh_token_validity = var.oauth_refresh_token_validity - oauth_token_endpoint = var.oauth_token_endpoint + name = var.name + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret + oauth_assertion_issuer = var.oauth_assertion_issuer } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf index 359057c239..10920ad938 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf @@ -1,4 +1,3 @@ - variable "comment" { type = string } @@ -29,3 +28,6 @@ variable "oauth_refresh_token_validity" { variable "oauth_token_endpoint" { type = string } +variable "oauth_assertion_issuer" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/test.tf index b3cca25614..f514dd7e23 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/test.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/test.tf @@ -9,4 +9,5 @@ resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { oauth_client_secret = var.oauth_client_secret oauth_refresh_token_validity = var.oauth_refresh_token_validity oauth_token_endpoint = var.oauth_token_endpoint + oauth_assertion_issuer = var.oauth_assertion_issuer } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/variables.tf index 359057c239..10920ad938 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/variables.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/variables.tf @@ -1,4 +1,3 @@ - variable "comment" { type = string } @@ -29,3 +28,6 @@ variable "oauth_refresh_token_validity" { variable "oauth_token_endpoint" { type = string } +variable "oauth_assertion_issuer" { + type = string +} diff --git a/pkg/resources/warehouse_rework_show_output_proposal.go b/pkg/resources/warehouse_rework_show_output_proposal.go index 6447debebf..31d4ad21f5 100644 --- a/pkg/resources/warehouse_rework_show_output_proposal.go +++ b/pkg/resources/warehouse_rework_show_output_proposal.go @@ -20,7 +20,11 @@ func handleExternalChangesToObjectInShow(d *schema.ResourceData, mappings ...sho for _, mapping := range mappings { valueToCompareFrom := result[mapping.nameInShow] if mapping.normalizeFunc != nil { - valueToCompareFrom = mapping.normalizeFunc(valueToCompareFrom) + var err error + valueToCompareFrom, err = mapping.normalizeFunc(valueToCompareFrom) + if err != nil { + return err + } } if valueToCompareFrom != mapping.valueToCompare { if err := d.Set(mapping.nameInConfig, mapping.valueToSet); err != nil { @@ -38,7 +42,7 @@ type showMapping struct { nameInConfig string valueToCompare any valueToSet any - normalizeFunc func(any) any + normalizeFunc func(any) (any, error) } // handleExternalChangesToObjectInDescribe assumes that show output is kept in describeOutputAttributeName attribute @@ -60,7 +64,11 @@ func handleExternalChangesToObjectInDescribe(d *schema.ResourceData, mappings .. valueToCompareFrom := valueToCompareFromList[0].(map[string]any)["value"] if mapping.normalizeFunc != nil { - valueToCompareFrom = mapping.normalizeFunc(valueToCompareFrom) + var err error + valueToCompareFrom, err = mapping.normalizeFunc(valueToCompareFrom) + if err != nil { + return err + } } if valueToCompareFrom != mapping.valueToCompare { if err := d.Set(mapping.nameInConfig, mapping.valueToSet); err != nil { @@ -78,21 +86,9 @@ type describeMapping struct { nameInConfig string valueToCompare any valueToSet any - normalizeFunc func(any) any + normalizeFunc func(any) (any, error) } -func stringToIntNormalizer(x any) any { - xInt, err := strconv.Atoi(x.(string)) - if err != nil { - xInt = 0 - } - return xInt -} - -func stringToSliceNormalizer(x any) any { - xInt, err := strconv.Atoi(x.(string)) - if err != nil { - xInt = 0 - } - return xInt +func stringToIntNormalizer(x any) (any, error) { + return strconv.Atoi(x.(string)) } diff --git a/pkg/sdk/security_integrations_gen_test.go b/pkg/sdk/security_integrations_gen_test.go index a9729ff651..e58384d489 100644 --- a/pkg/sdk/security_integrations_gen_test.go +++ b/pkg/sdk/security_integrations_gen_test.go @@ -569,7 +569,7 @@ func TestSecurityIntegrations_AlterApiAuthenticationWithAuthorizationCodeFlow(t opts := defaultOpts() opts.Set = &ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet{} assertOptsInvalidJoinedErrors(t, opts, errAtLeastOneOf("AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOptions.Set", "Enabled", "OauthAuthorizationEndpoint", "OauthTokenEndpoint", - "OauthClientAuthMethod", "OauthClientId", "OauthClientSecret", "OauthGrantAuthorizationCode", "OauthAccessTokenValidity", "OauthRefreshTokenValidity", "Comment")) + "OauthClientAuthMethod", "OauthClientId", "OauthClientSecret", "OauthGrantAuthorizationCode", "OauthAccessTokenValidity", "OauthRefreshTokenValidity", "OauthAllowedScopes", "Comment")) }) t.Run("validation: at least one of the fields [opts.Unset.*] should be set", func(t *testing.T) { diff --git a/pkg/sdk/testint/security_integrations_gen_integration_test.go b/pkg/sdk/testint/security_integrations_gen_integration_test.go index 71ebefba1c..ba06ea567b 100644 --- a/pkg/sdk/testint/security_integrations_gen_integration_test.go +++ b/pkg/sdk/testint/security_integrations_gen_integration_test.go @@ -135,7 +135,7 @@ func TestInt_SecurityIntegrations(t *testing.T) { } err := client.SecurityIntegrations.CreateSaml2(ctx, saml2Req) require.NoError(t, err) - // cleanupSecurityIntegration(t, id) + cleanupSecurityIntegration(t, id) integration, err := client.SecurityIntegrations.ShowByID(ctx, id) require.NoError(t, err) diff --git a/v1-preparations/CHANGES_BEFORE_V1.md b/v1-preparations/CHANGES_BEFORE_V1.md index c30564dbc4..6d76f55c9e 100644 --- a/v1-preparations/CHANGES_BEFORE_V1.md +++ b/v1-preparations/CHANGES_BEFORE_V1.md @@ -16,7 +16,7 @@ create a resource with slightly different configuration in Snowflake (depending current account configuration, and most-likely other factors). That is why we recommend setting optional fields where you want to ensure that the specified value has been set on the Snowflake side. -## v0.91.0 ➞ v0.92.0 +## v0.92.0 ➞ v0.93.0 ### new resources for API Authentication security integrations New resources are introduced for each type of oauth flow: - `snowflake_api_authentication_integration_with_authorization_code_grant` From 6c99578812aa49ca64e65e55ca2c065fb1a56deb Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Mon, 1 Jul 2024 11:27:38 +0200 Subject: [PATCH 06/18] Revert "Common changes" This reverts commit c471a3ca7dc8421b4b3524e797a453d00494d9a3. --- pkg/acceptance/helpers/random/certs.go | 12 +- pkg/resources/common.go | 49 ------ pkg/sdk/security_integrations_def.go | 165 ++---------------- ...urity_integrations_gen_integration_test.go | 2 +- v1-preparations/REMAINING_GA_OBJECTS.MD | 2 +- 5 files changed, 16 insertions(+), 214 deletions(-) diff --git a/pkg/acceptance/helpers/random/certs.go b/pkg/acceptance/helpers/random/certs.go index 770c49983c..80a53a83eb 100644 --- a/pkg/acceptance/helpers/random/certs.go +++ b/pkg/acceptance/helpers/random/certs.go @@ -4,10 +4,8 @@ import ( "bytes" "crypto/rand" "crypto/rsa" - "crypto/sha256" "crypto/x509" "crypto/x509/pkix" - "encoding/base64" "encoding/pem" "fmt" "math/big" @@ -41,7 +39,7 @@ func GenerateX509(t *testing.T) string { } // GenerateRSA returns an RSA public key without BEGIN and END markers. -func GenerateRSAPublicKey(t *testing.T) (string, string) { +func GenerateRSAPublicKey(t *testing.T) string { t.Helper() key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err) @@ -49,13 +47,7 @@ func GenerateRSAPublicKey(t *testing.T) (string, string) { pub := key.Public() b, err := x509.MarshalPKIXPublicKey(pub.(*rsa.PublicKey)) require.NoError(t, err) - return encode(t, "RSA PUBLIC KEY", b), fmt.Sprintf("SHA256:%s", hash(t, b)) -} - -func hash(t *testing.T, b []byte) string { - t.Helper() - hash := sha256.Sum256(b) - return base64.StdEncoding.EncodeToString(hash[:]) + return encode(t, "RSA PUBLIC KEY", b) } func encode(t *testing.T, pemType string, b []byte) string { diff --git a/pkg/resources/common.go b/pkg/resources/common.go index 2d1cef5554..6046b09d6e 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -1,21 +1,13 @@ package resources import ( - "context" - "crypto/sha256" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "errors" "fmt" "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" - "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/go-cty/cty" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) @@ -66,47 +58,6 @@ func suppressQuoting(_, oldValue, newValue string, _ *schema.ResourceData) bool } } -func DeleteContextSecurityIntegration(ctx context.Context, d *schema.ResourceData, meta interface{}) 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 -} - -func RSAKeyHash(key string) (string, error) { - keyBytes := []byte(fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", key)) - - block, _ := pem.Decode(keyBytes) - if block == nil || block.Type != "PUBLIC KEY" { - return "", errors.New("Failed to decode PEM block containing public key") - } - - pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return "", fmt.Errorf("Unable to parse public key: %w", err) - } - - pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey) - if err != nil { - return "", fmt.Errorf("Unable to marshal public key: %w", err) - } - - hash := sha256.Sum256(pubKeyBytes) - return fmt.Sprintf("SHA256:%s", base64.StdEncoding.EncodeToString(hash[:])), nil -} - var apiAuthCommonSchema = map[string]*schema.Schema{ "name": { Type: schema.TypeString, diff --git a/pkg/sdk/security_integrations_def.go b/pkg/sdk/security_integrations_def.go index cd349efb88..da6c92481b 100644 --- a/pkg/sdk/security_integrations_def.go +++ b/pkg/sdk/security_integrations_def.go @@ -62,23 +62,6 @@ const ( OauthSecurityIntegrationUseSecondaryRolesNone OauthSecurityIntegrationUseSecondaryRolesOption = "NONE" ) -var AllOauthSecurityIntegrationUseSecondaryRoles = []OauthSecurityIntegrationUseSecondaryRolesOption{ - OauthSecurityIntegrationUseSecondaryRolesImplicit, - OauthSecurityIntegrationUseSecondaryRolesNone, -} - -func ToOauthSecurityIntegrationUseSecondaryRolesOption(s string) (OauthSecurityIntegrationUseSecondaryRolesOption, error) { - s = strings.ToUpper(s) - switch s { - case string(OauthSecurityIntegrationUseSecondaryRolesImplicit): - return OauthSecurityIntegrationUseSecondaryRolesImplicit, nil - case string(OauthSecurityIntegrationUseSecondaryRolesNone): - return OauthSecurityIntegrationUseSecondaryRolesNone, nil - default: - return "", fmt.Errorf("invalid OauthSecurityIntegrationUseSecondaryRolesOption: %s", s) - } -} - type OauthSecurityIntegrationClientTypeOption string const ( @@ -86,23 +69,6 @@ const ( OauthSecurityIntegrationClientTypeConfidential OauthSecurityIntegrationClientTypeOption = "CONFIDENTIAL" ) -var AllOauthSecurityIntegrationClientTypes = []OauthSecurityIntegrationClientTypeOption{ - OauthSecurityIntegrationClientTypePublic, - OauthSecurityIntegrationClientTypeConfidential, -} - -func ToOauthSecurityIntegrationClientTypeOption(s string) (OauthSecurityIntegrationClientTypeOption, error) { - s = strings.ToUpper(s) - switch s { - case string(OauthSecurityIntegrationClientTypePublic): - return OauthSecurityIntegrationClientTypePublic, nil - case string(OauthSecurityIntegrationClientTypeConfidential): - return OauthSecurityIntegrationClientTypeConfidential, nil - default: - return "", fmt.Errorf("invalid OauthSecurityIntegrationClientTypeOption: %s", s) - } -} - type OauthSecurityIntegrationClientOption string const ( @@ -111,97 +77,6 @@ const ( OauthSecurityIntegrationClientTableauServer OauthSecurityIntegrationClientOption = "TABLEAU_SERVER" ) -var AllOauthSecurityIntegrationClients = []OauthSecurityIntegrationClientOption{ - OauthSecurityIntegrationClientLooker, - OauthSecurityIntegrationClientTableauDesktop, - OauthSecurityIntegrationClientTableauServer, -} - -func ToOauthSecurityIntegrationClientOption(s string) (OauthSecurityIntegrationClientOption, error) { - s = strings.ToUpper(s) - switch s { - case string(OauthSecurityIntegrationClientLooker): - return OauthSecurityIntegrationClientLooker, nil - case string(OauthSecurityIntegrationClientTableauDesktop): - return OauthSecurityIntegrationClientTableauDesktop, nil - case string(OauthSecurityIntegrationClientTableauServer): - return OauthSecurityIntegrationClientTableauServer, nil - default: - return "", fmt.Errorf("invalid OauthSecurityIntegrationClientOption: %s", s) - } -} - -type Saml2SecurityIntegrationSaml2ProviderOption string - -const ( - Saml2SecurityIntegrationSaml2ProviderOkta Saml2SecurityIntegrationSaml2ProviderOption = "OKTA" - Saml2SecurityIntegrationSaml2ProviderAdfs Saml2SecurityIntegrationSaml2ProviderOption = "ADFS" - Saml2SecurityIntegrationSaml2ProviderCustom Saml2SecurityIntegrationSaml2ProviderOption = "CUSTOM" -) - -var AllSaml2SecurityIntegrationSaml2Providers = []Saml2SecurityIntegrationSaml2ProviderOption{ - Saml2SecurityIntegrationSaml2ProviderOkta, - Saml2SecurityIntegrationSaml2ProviderAdfs, - Saml2SecurityIntegrationSaml2ProviderCustom, -} - -func ToSaml2SecurityIntegrationSaml2ProviderOption(s string) (Saml2SecurityIntegrationSaml2ProviderOption, error) { - s = strings.ToUpper(s) - switch s { - case string(Saml2SecurityIntegrationSaml2ProviderOkta): - return Saml2SecurityIntegrationSaml2ProviderOkta, nil - case string(Saml2SecurityIntegrationSaml2ProviderAdfs): - return Saml2SecurityIntegrationSaml2ProviderAdfs, nil - case string(Saml2SecurityIntegrationSaml2ProviderCustom): - return Saml2SecurityIntegrationSaml2ProviderCustom, nil - default: - return "", fmt.Errorf("invalid Saml2SecurityIntegrationSaml2ProviderOption: %s", s) - } -} - -type Saml2SecurityIntegrationSaml2RequestedNameidFormatOption string - -const ( - Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" - Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" - Saml2SecurityIntegrationSaml2RequestedNameidFormatX509SubjectName Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName" - Saml2SecurityIntegrationSaml2RequestedNameidFormatWindowsDomainQualifiedName Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName" - Saml2SecurityIntegrationSaml2RequestedNameidFormatKerberos Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos" - Saml2SecurityIntegrationSaml2RequestedNameidFormatPersistent Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" - Saml2SecurityIntegrationSaml2RequestedNameidFormatTransient Saml2SecurityIntegrationSaml2RequestedNameidFormatOption = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" -) - -var AllSaml2SecurityIntegrationSaml2RequestedNameidFormats = []Saml2SecurityIntegrationSaml2RequestedNameidFormatOption{ - Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified, - Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress, - Saml2SecurityIntegrationSaml2RequestedNameidFormatX509SubjectName, - Saml2SecurityIntegrationSaml2RequestedNameidFormatWindowsDomainQualifiedName, - Saml2SecurityIntegrationSaml2RequestedNameidFormatKerberos, - Saml2SecurityIntegrationSaml2RequestedNameidFormatPersistent, - Saml2SecurityIntegrationSaml2RequestedNameidFormatTransient, -} - -func ToSaml2SecurityIntegrationSaml2RequestedNameidFormatOption(s string) (Saml2SecurityIntegrationSaml2RequestedNameidFormatOption, error) { - switch s { - case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified): - return Saml2SecurityIntegrationSaml2RequestedNameidFormatUnspecified, nil - case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress): - return Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress, nil - case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatX509SubjectName): - return Saml2SecurityIntegrationSaml2RequestedNameidFormatX509SubjectName, nil - case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatWindowsDomainQualifiedName): - return Saml2SecurityIntegrationSaml2RequestedNameidFormatWindowsDomainQualifiedName, nil - case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatKerberos): - return Saml2SecurityIntegrationSaml2RequestedNameidFormatKerberos, nil - case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatPersistent): - return Saml2SecurityIntegrationSaml2RequestedNameidFormatPersistent, nil - case string(Saml2SecurityIntegrationSaml2RequestedNameidFormatTransient): - return Saml2SecurityIntegrationSaml2RequestedNameidFormatTransient, nil - default: - return "", fmt.Errorf("invalid Saml2SecurityIntegrationSaml2RequestedNameidFormatOption: %s", s) - } -} - type ScimSecurityIntegrationScimClientOption string const ( @@ -219,11 +94,11 @@ var AllScimSecurityIntegrationScimClients = []ScimSecurityIntegrationScimClientO func ToScimSecurityIntegrationScimClientOption(s string) (ScimSecurityIntegrationScimClientOption, error) { s = strings.ToUpper(s) switch s { - case string(ScimSecurityIntegrationScimClientOkta): + case "OKTA": return ScimSecurityIntegrationScimClientOkta, nil - case string(ScimSecurityIntegrationScimClientAzure): + case "AZURE": return ScimSecurityIntegrationScimClientAzure, nil - case string(ScimSecurityIntegrationScimClientGeneric): + case "GENERIC": return ScimSecurityIntegrationScimClientGeneric, nil default: return "", fmt.Errorf("invalid ScimSecurityIntegrationScimClientOption: %s", s) @@ -247,11 +122,11 @@ var AllScimSecurityIntegrationRunAsRoles = []ScimSecurityIntegrationRunAsRoleOpt func ToScimSecurityIntegrationRunAsRoleOption(s string) (ScimSecurityIntegrationRunAsRoleOption, error) { s = strings.ToUpper(s) switch s { - case string(ScimSecurityIntegrationRunAsRoleOktaProvisioner): + case "OKTA_PROVISIONER": return ScimSecurityIntegrationRunAsRoleOktaProvisioner, nil - case string(ScimSecurityIntegrationRunAsRoleAadProvisioner): + case "AAD_PROVISIONER": return ScimSecurityIntegrationRunAsRoleAadProvisioner, nil - case string(ScimSecurityIntegrationRunAsRoleGenericScimProvisioner): + case "GENERIC_SCIM_PROVISIONER": return ScimSecurityIntegrationRunAsRoleGenericScimProvisioner, nil default: return "", fmt.Errorf("invalid ScimSecurityIntegrationRunAsRoleOption: %s", s) @@ -465,11 +340,7 @@ var saml2IntegrationSetDef = g.NewQueryStruct("Saml2IntegrationSet"). OptionalBooleanAssignment("ENABLED", g.ParameterOptions()). OptionalTextAssignment("SAML2_ISSUER", g.ParameterOptions().SingleQuotes()). OptionalTextAssignment("SAML2_SSO_URL", g.ParameterOptions().SingleQuotes()). - OptionalAssignment( - "SAML2_PROVIDER", - g.KindOfT[Saml2SecurityIntegrationSaml2ProviderOption](), - g.ParameterOptions().SingleQuotes(), - ). + OptionalTextAssignment("SAML2_PROVIDER", g.ParameterOptions().SingleQuotes()). OptionalTextAssignment("SAML2_X509_CERT", g.ParameterOptions().SingleQuotes()). ListAssignment("ALLOWED_USER_DOMAINS", "UserDomain", g.ParameterOptions().Parentheses()). ListAssignment("ALLOWED_EMAIL_PATTERNS", "EmailPattern", g.ParameterOptions().Parentheses()). @@ -477,11 +348,7 @@ var saml2IntegrationSetDef = g.NewQueryStruct("Saml2IntegrationSet"). OptionalBooleanAssignment("SAML2_ENABLE_SP_INITIATED", g.ParameterOptions()). OptionalTextAssignment("SAML2_SNOWFLAKE_X509_CERT", g.ParameterOptions().SingleQuotes()). OptionalBooleanAssignment("SAML2_SIGN_REQUEST", g.ParameterOptions()). - OptionalAssignment( - "SAML2_REQUESTED_NAMEID_FORMAT", - g.KindOfT[Saml2SecurityIntegrationSaml2RequestedNameidFormatOption](), - g.ParameterOptions().SingleQuotes(), - ). + OptionalTextAssignment("SAML2_REQUESTED_NAMEID_FORMAT", g.ParameterOptions().SingleQuotes()). OptionalTextAssignment("SAML2_POST_LOGOUT_REDIRECT_URL", g.ParameterOptions().SingleQuotes()). OptionalBooleanAssignment("SAML2_FORCE_AUTHN", g.ParameterOptions()). OptionalTextAssignment("SAML2_SNOWFLAKE_ISSUER_URL", g.ParameterOptions().SingleQuotes()). @@ -666,7 +533,7 @@ var SecurityIntegrationsDef = g.NewInterface( g.KindOfT[OauthSecurityIntegrationClientTypeOption](), g.ParameterOptions().Required().SingleQuotes(), ). - OptionalTextAssignment("OAUTH_REDIRECT_URI", g.ParameterOptions().SingleQuotes()). + TextAssignment("OAUTH_REDIRECT_URI", g.ParameterOptions().Required().SingleQuotes()). OptionalBooleanAssignment("ENABLED", g.ParameterOptions()). OptionalBooleanAssignment("OAUTH_ALLOW_NON_TLS_REDIRECT_URI", g.ParameterOptions()). OptionalBooleanAssignment("OAUTH_ENFORCE_PKCE", g.ParameterOptions()). @@ -690,14 +557,10 @@ var SecurityIntegrationsDef = g.NewInterface( createSecurityIntegrationOperation("CreateSaml2", func(qs *g.QueryStruct) *g.QueryStruct { return qs. PredefinedQueryStructField("integrationType", "string", g.StaticOptions().SQL("TYPE = SAML2")). - OptionalBooleanAssignment("ENABLED", g.ParameterOptions()). + BooleanAssignment("ENABLED", g.ParameterOptions().Required()). TextAssignment("SAML2_ISSUER", g.ParameterOptions().Required().SingleQuotes()). TextAssignment("SAML2_SSO_URL", g.ParameterOptions().Required().SingleQuotes()). - Assignment( - "SAML2_PROVIDER", - g.KindOfT[Saml2SecurityIntegrationSaml2ProviderOption](), - g.ParameterOptions().Required().SingleQuotes(), - ). + TextAssignment("SAML2_PROVIDER", g.ParameterOptions().Required().SingleQuotes()). TextAssignment("SAML2_X509_CERT", g.ParameterOptions().Required().SingleQuotes()). ListAssignment("ALLOWED_USER_DOMAINS", "UserDomain", g.ParameterOptions().Parentheses()). ListAssignment("ALLOWED_EMAIL_PATTERNS", "EmailPattern", g.ParameterOptions().Parentheses()). @@ -705,11 +568,7 @@ var SecurityIntegrationsDef = g.NewInterface( OptionalBooleanAssignment("SAML2_ENABLE_SP_INITIATED", g.ParameterOptions()). OptionalTextAssignment("SAML2_SNOWFLAKE_X509_CERT", g.ParameterOptions().SingleQuotes()). OptionalBooleanAssignment("SAML2_SIGN_REQUEST", g.ParameterOptions()). - OptionalAssignment( - "SAML2_REQUESTED_NAMEID_FORMAT", - g.KindOfT[Saml2SecurityIntegrationSaml2RequestedNameidFormatOption](), - g.ParameterOptions().SingleQuotes(), - ). + OptionalTextAssignment("SAML2_REQUESTED_NAMEID_FORMAT", g.ParameterOptions().SingleQuotes()). OptionalTextAssignment("SAML2_POST_LOGOUT_REDIRECT_URL", g.ParameterOptions().SingleQuotes()). OptionalBooleanAssignment("SAML2_FORCE_AUTHN", g.ParameterOptions()). OptionalTextAssignment("SAML2_SNOWFLAKE_ISSUER_URL", g.ParameterOptions().SingleQuotes()). diff --git a/pkg/sdk/testint/security_integrations_gen_integration_test.go b/pkg/sdk/testint/security_integrations_gen_integration_test.go index ba06ea567b..997daa6529 100644 --- a/pkg/sdk/testint/security_integrations_gen_integration_test.go +++ b/pkg/sdk/testint/security_integrations_gen_integration_test.go @@ -18,7 +18,7 @@ func TestInt_SecurityIntegrations(t *testing.T) { acsURL := testClientHelper().Context.ACSURL(t) issuerURL := testClientHelper().Context.IssuerURL(t) cert := random.GenerateX509(t) - rsaKey, _ := random.GenerateRSAPublicKey(t) + rsaKey := random.GenerateRSAPublicKey(t) revertParameter := testClientHelper().Parameter.UpdateAccountParameterTemporarily(t, sdk.AccountParameterEnableIdentifierFirstLogin, "true") t.Cleanup(revertParameter) diff --git a/v1-preparations/REMAINING_GA_OBJECTS.MD b/v1-preparations/REMAINING_GA_OBJECTS.MD index ebdb328e3c..fe6ea6cc2d 100644 --- a/v1-preparations/REMAINING_GA_OBJECTS.MD +++ b/v1-preparations/REMAINING_GA_OBJECTS.MD @@ -39,4 +39,4 @@ Known issues lists open issues touching the given object. Note that some of thes | PIPE | ❌ | [#2785](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2785), [#2075](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2075), [#1781](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1781), [#1707](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1707), [#1478](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1478), [#533](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/533) | | SECRET | ❌ | [#2545](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2545) | | SEQUENCE | ❌ | [#2589](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2589) | -| SESSION POLICY | ❌ | [#2870](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2870) | +| SESSION POLICY | ❌ | - | From 433c4d7e326f600ad4d3e32eb81df741c0e65d2b Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Mon, 1 Jul 2024 11:46:26 +0200 Subject: [PATCH 07/18] fix after restore --- .../security_integrations_dto_builders_gen.go | 22 ++-- pkg/sdk/security_integrations_dto_gen.go | 30 ++--- pkg/sdk/security_integrations_gen.go | 118 ++++++------------ pkg/sdk/security_integrations_gen_test.go | 28 ++--- 4 files changed, 72 insertions(+), 126 deletions(-) diff --git a/pkg/sdk/security_integrations_dto_builders_gen.go b/pkg/sdk/security_integrations_dto_builders_gen.go index 253375dd32..455dfb4f7e 100644 --- a/pkg/sdk/security_integrations_dto_builders_gen.go +++ b/pkg/sdk/security_integrations_dto_builders_gen.go @@ -348,10 +348,12 @@ func (s *CreateOauthForPartnerApplicationsSecurityIntegrationRequest) WithCommen func NewCreateOauthForCustomClientsSecurityIntegrationRequest( name AccountObjectIdentifier, OauthClientType OauthSecurityIntegrationClientTypeOption, + OauthRedirectUri string, ) *CreateOauthForCustomClientsSecurityIntegrationRequest { s := CreateOauthForCustomClientsSecurityIntegrationRequest{} s.name = name s.OauthClientType = OauthClientType + s.OauthRedirectUri = OauthRedirectUri return &s } @@ -365,11 +367,6 @@ func (s *CreateOauthForCustomClientsSecurityIntegrationRequest) WithIfNotExists( return s } -func (s *CreateOauthForCustomClientsSecurityIntegrationRequest) WithOauthRedirectUri(OauthRedirectUri string) *CreateOauthForCustomClientsSecurityIntegrationRequest { - s.OauthRedirectUri = &OauthRedirectUri - return s -} - func (s *CreateOauthForCustomClientsSecurityIntegrationRequest) WithEnabled(Enabled bool) *CreateOauthForCustomClientsSecurityIntegrationRequest { s.Enabled = &Enabled return s @@ -441,13 +438,15 @@ func (s *PreAuthorizedRolesListRequest) WithPreAuthorizedRolesList(PreAuthorized func NewCreateSaml2SecurityIntegrationRequest( name AccountObjectIdentifier, + Enabled bool, Saml2Issuer string, Saml2SsoUrl string, - Saml2Provider Saml2SecurityIntegrationSaml2ProviderOption, + Saml2Provider string, Saml2X509Cert string, ) *CreateSaml2SecurityIntegrationRequest { s := CreateSaml2SecurityIntegrationRequest{} s.name = name + s.Enabled = Enabled s.Saml2Issuer = Saml2Issuer s.Saml2SsoUrl = Saml2SsoUrl s.Saml2Provider = Saml2Provider @@ -465,11 +464,6 @@ func (s *CreateSaml2SecurityIntegrationRequest) WithIfNotExists(IfNotExists bool return s } -func (s *CreateSaml2SecurityIntegrationRequest) WithEnabled(Enabled bool) *CreateSaml2SecurityIntegrationRequest { - s.Enabled = &Enabled - return s -} - func (s *CreateSaml2SecurityIntegrationRequest) WithAllowedUserDomains(AllowedUserDomains []UserDomain) *CreateSaml2SecurityIntegrationRequest { s.AllowedUserDomains = AllowedUserDomains return s @@ -500,7 +494,7 @@ func (s *CreateSaml2SecurityIntegrationRequest) WithSaml2SignRequest(Saml2SignRe return s } -func (s *CreateSaml2SecurityIntegrationRequest) WithSaml2RequestedNameidFormat(Saml2RequestedNameidFormat Saml2SecurityIntegrationSaml2RequestedNameidFormatOption) *CreateSaml2SecurityIntegrationRequest { +func (s *CreateSaml2SecurityIntegrationRequest) WithSaml2RequestedNameidFormat(Saml2RequestedNameidFormat string) *CreateSaml2SecurityIntegrationRequest { s.Saml2RequestedNameidFormat = &Saml2RequestedNameidFormat return s } @@ -1275,7 +1269,7 @@ func (s *Saml2IntegrationSetRequest) WithSaml2SsoUrl(Saml2SsoUrl string) *Saml2I return s } -func (s *Saml2IntegrationSetRequest) WithSaml2Provider(Saml2Provider Saml2SecurityIntegrationSaml2ProviderOption) *Saml2IntegrationSetRequest { +func (s *Saml2IntegrationSetRequest) WithSaml2Provider(Saml2Provider string) *Saml2IntegrationSetRequest { s.Saml2Provider = &Saml2Provider return s } @@ -1315,7 +1309,7 @@ func (s *Saml2IntegrationSetRequest) WithSaml2SignRequest(Saml2SignRequest bool) return s } -func (s *Saml2IntegrationSetRequest) WithSaml2RequestedNameidFormat(Saml2RequestedNameidFormat Saml2SecurityIntegrationSaml2RequestedNameidFormatOption) *Saml2IntegrationSetRequest { +func (s *Saml2IntegrationSetRequest) WithSaml2RequestedNameidFormat(Saml2RequestedNameidFormat string) *Saml2IntegrationSetRequest { s.Saml2RequestedNameidFormat = &Saml2RequestedNameidFormat return s } diff --git a/pkg/sdk/security_integrations_dto_gen.go b/pkg/sdk/security_integrations_dto_gen.go index cbb16cbe9a..6684943f75 100644 --- a/pkg/sdk/security_integrations_dto_gen.go +++ b/pkg/sdk/security_integrations_dto_gen.go @@ -126,7 +126,7 @@ type CreateOauthForCustomClientsSecurityIntegrationRequest struct { IfNotExists *bool name AccountObjectIdentifier // required OauthClientType OauthSecurityIntegrationClientTypeOption // required - OauthRedirectUri *string + OauthRedirectUri string // required Enabled *bool OauthAllowNonTlsRedirectUri *bool OauthEnforcePkce *bool @@ -141,10 +141,6 @@ type CreateOauthForCustomClientsSecurityIntegrationRequest struct { Comment *string } -func (r *CreateOauthForCustomClientsSecurityIntegrationRequest) GetName() AccountObjectIdentifier { - return r.name -} - type PreAuthorizedRolesListRequest struct { PreAuthorizedRolesList []AccountObjectIdentifier } @@ -153,18 +149,18 @@ type CreateSaml2SecurityIntegrationRequest struct { OrReplace *bool IfNotExists *bool name AccountObjectIdentifier // required - Enabled *bool - Saml2Issuer string // required - Saml2SsoUrl string // required - Saml2Provider Saml2SecurityIntegrationSaml2ProviderOption // required - Saml2X509Cert string // required + Enabled bool // required + Saml2Issuer string // required + Saml2SsoUrl string // required + Saml2Provider string // required + Saml2X509Cert string // required AllowedUserDomains []UserDomain AllowedEmailPatterns []EmailPattern Saml2SpInitiatedLoginPageLabel *string Saml2EnableSpInitiated *bool Saml2SnowflakeX509Cert *string Saml2SignRequest *bool - Saml2RequestedNameidFormat *Saml2SecurityIntegrationSaml2RequestedNameidFormatOption + Saml2RequestedNameidFormat *string Saml2PostLogoutRedirectUrl *string Saml2ForceAuthn *bool Saml2SnowflakeIssuerUrl *string @@ -172,10 +168,6 @@ type CreateSaml2SecurityIntegrationRequest struct { Comment *string } -func (r *CreateSaml2SecurityIntegrationRequest) GetName() AccountObjectIdentifier { - return r.name -} - type CreateScimSecurityIntegrationRequest struct { OrReplace *bool IfNotExists *bool @@ -188,10 +180,6 @@ type CreateScimSecurityIntegrationRequest struct { Comment *string } -func (r *CreateScimSecurityIntegrationRequest) GetName() AccountObjectIdentifier { - return r.name -} - type AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest struct { IfExists *bool name AccountObjectIdentifier // required @@ -376,7 +364,7 @@ type Saml2IntegrationSetRequest struct { Enabled *bool Saml2Issuer *string Saml2SsoUrl *string - Saml2Provider *Saml2SecurityIntegrationSaml2ProviderOption + Saml2Provider *string Saml2X509Cert *string AllowedUserDomains []UserDomain AllowedEmailPatterns []EmailPattern @@ -384,7 +372,7 @@ type Saml2IntegrationSetRequest struct { Saml2EnableSpInitiated *bool Saml2SnowflakeX509Cert *string Saml2SignRequest *bool - Saml2RequestedNameidFormat *Saml2SecurityIntegrationSaml2RequestedNameidFormatOption + Saml2RequestedNameidFormat *string Saml2PostLogoutRedirectUrl *string Saml2ForceAuthn *bool Saml2SnowflakeIssuerUrl *string diff --git a/pkg/sdk/security_integrations_gen.go b/pkg/sdk/security_integrations_gen.go index ec59560e63..f5ca9d1285 100644 --- a/pkg/sdk/security_integrations_gen.go +++ b/pkg/sdk/security_integrations_gen.go @@ -51,7 +51,6 @@ type CreateApiAuthenticationWithClientCredentialsFlowSecurityIntegrationOptions OauthAllowedScopes []AllowedScope `ddl:"parameter,parentheses" sql:"OAUTH_ALLOWED_SCOPES"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type AllowedScope struct { Scope string `ddl:"keyword,single_quotes"` } @@ -124,27 +123,21 @@ type CreateExternalOauthSecurityIntegrationOptions struct { ExternalOauthScopeMappingAttribute *string `ddl:"parameter,single_quotes" sql:"EXTERNAL_OAUTH_SCOPE_MAPPING_ATTRIBUTE"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type AllowedRolesList struct { AllowedRolesList []AccountObjectIdentifier `ddl:"list,must_parentheses"` } - type BlockedRolesList struct { BlockedRolesList []AccountObjectIdentifier `ddl:"list,must_parentheses"` } - type JwsKeysUrl struct { JwsKeyUrl string `ddl:"keyword,single_quotes"` } - type AudienceList struct { AudienceList []AudienceListItem `ddl:"list,must_parentheses"` } - type AudienceListItem struct { Item string `ddl:"keyword,single_quotes"` } - type TokenUserMappingClaim struct { Claim string `ddl:"keyword,single_quotes"` } @@ -166,7 +159,6 @@ type CreateOauthForPartnerApplicationsSecurityIntegrationOptions struct { BlockedRolesList *BlockedRolesList `ddl:"parameter,parentheses" sql:"BLOCKED_ROLES_LIST"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type PreAuthorizedRolesList struct { PreAuthorizedRolesList []AccountObjectIdentifier `ddl:"list,must_parentheses"` } @@ -181,7 +173,7 @@ type CreateOauthForCustomClientsSecurityIntegrationOptions struct { integrationType string `ddl:"static" sql:"TYPE = OAUTH"` oauthClient string `ddl:"static" sql:"OAUTH_CLIENT = CUSTOM"` OauthClientType OauthSecurityIntegrationClientTypeOption `ddl:"parameter,single_quotes" sql:"OAUTH_CLIENT_TYPE"` - OauthRedirectUri *string `ddl:"parameter,single_quotes" sql:"OAUTH_REDIRECT_URI"` + OauthRedirectUri string `ddl:"parameter,single_quotes" sql:"OAUTH_REDIRECT_URI"` Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthAllowNonTlsRedirectUri *bool `ddl:"parameter" sql:"OAUTH_ALLOW_NON_TLS_REDIRECT_URI"` OauthEnforcePkce *bool `ddl:"parameter" sql:"OAUTH_ENFORCE_PKCE"` @@ -198,35 +190,33 @@ type CreateOauthForCustomClientsSecurityIntegrationOptions struct { // CreateSaml2SecurityIntegrationOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-saml2. type CreateSaml2SecurityIntegrationOptions struct { - create bool `ddl:"static" sql:"CREATE"` - OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` - securityIntegration bool `ddl:"static" sql:"SECURITY INTEGRATION"` - IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` - name AccountObjectIdentifier `ddl:"identifier"` - integrationType string `ddl:"static" sql:"TYPE = SAML2"` - Enabled *bool `ddl:"parameter" sql:"ENABLED"` - Saml2Issuer string `ddl:"parameter,single_quotes" sql:"SAML2_ISSUER"` - Saml2SsoUrl string `ddl:"parameter,single_quotes" sql:"SAML2_SSO_URL"` - Saml2Provider Saml2SecurityIntegrationSaml2ProviderOption `ddl:"parameter,single_quotes" sql:"SAML2_PROVIDER"` - Saml2X509Cert string `ddl:"parameter,single_quotes" sql:"SAML2_X509_CERT"` - AllowedUserDomains []UserDomain `ddl:"parameter,parentheses" sql:"ALLOWED_USER_DOMAINS"` - AllowedEmailPatterns []EmailPattern `ddl:"parameter,parentheses" sql:"ALLOWED_EMAIL_PATTERNS"` - Saml2SpInitiatedLoginPageLabel *string `ddl:"parameter,single_quotes" sql:"SAML2_SP_INITIATED_LOGIN_PAGE_LABEL"` - Saml2EnableSpInitiated *bool `ddl:"parameter" sql:"SAML2_ENABLE_SP_INITIATED"` - Saml2SnowflakeX509Cert *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_X509_CERT"` - Saml2SignRequest *bool `ddl:"parameter" sql:"SAML2_SIGN_REQUEST"` - Saml2RequestedNameidFormat *Saml2SecurityIntegrationSaml2RequestedNameidFormatOption `ddl:"parameter,single_quotes" sql:"SAML2_REQUESTED_NAMEID_FORMAT"` - Saml2PostLogoutRedirectUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_POST_LOGOUT_REDIRECT_URL"` - Saml2ForceAuthn *bool `ddl:"parameter" sql:"SAML2_FORCE_AUTHN"` - Saml2SnowflakeIssuerUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ISSUER_URL"` - Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + securityIntegration bool `ddl:"static" sql:"SECURITY INTEGRATION"` + IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` + name AccountObjectIdentifier `ddl:"identifier"` + integrationType string `ddl:"static" sql:"TYPE = SAML2"` + Enabled bool `ddl:"parameter" sql:"ENABLED"` + Saml2Issuer string `ddl:"parameter,single_quotes" sql:"SAML2_ISSUER"` + Saml2SsoUrl string `ddl:"parameter,single_quotes" sql:"SAML2_SSO_URL"` + Saml2Provider string `ddl:"parameter,single_quotes" sql:"SAML2_PROVIDER"` + Saml2X509Cert string `ddl:"parameter,single_quotes" sql:"SAML2_X509_CERT"` + AllowedUserDomains []UserDomain `ddl:"parameter,parentheses" sql:"ALLOWED_USER_DOMAINS"` + AllowedEmailPatterns []EmailPattern `ddl:"parameter,parentheses" sql:"ALLOWED_EMAIL_PATTERNS"` + Saml2SpInitiatedLoginPageLabel *string `ddl:"parameter,single_quotes" sql:"SAML2_SP_INITIATED_LOGIN_PAGE_LABEL"` + Saml2EnableSpInitiated *bool `ddl:"parameter" sql:"SAML2_ENABLE_SP_INITIATED"` + Saml2SnowflakeX509Cert *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_X509_CERT"` + Saml2SignRequest *bool `ddl:"parameter" sql:"SAML2_SIGN_REQUEST"` + Saml2RequestedNameidFormat *string `ddl:"parameter,single_quotes" sql:"SAML2_REQUESTED_NAMEID_FORMAT"` + Saml2PostLogoutRedirectUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_POST_LOGOUT_REDIRECT_URL"` + Saml2ForceAuthn *bool `ddl:"parameter" sql:"SAML2_FORCE_AUTHN"` + Saml2SnowflakeIssuerUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ISSUER_URL"` + Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type UserDomain struct { Domain string `ddl:"keyword,single_quotes"` } - type EmailPattern struct { Pattern string `ddl:"keyword,single_quotes"` } @@ -258,7 +248,6 @@ type AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationOptions s Set *ApiAuthenticationWithClientCredentialsFlowIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ApiAuthenticationWithClientCredentialsFlowIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ApiAuthenticationWithClientCredentialsFlowIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthTokenEndpoint *string `ddl:"parameter,single_quotes" sql:"OAUTH_TOKEN_ENDPOINT"` @@ -271,7 +260,6 @@ type ApiAuthenticationWithClientCredentialsFlowIntegrationSet struct { OauthAllowedScopes []AllowedScope `ddl:"parameter,parentheses" sql:"OAUTH_ALLOWED_SCOPES"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type ApiAuthenticationWithClientCredentialsFlowIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` Comment *bool `ddl:"keyword" sql:"COMMENT"` @@ -288,7 +276,6 @@ type AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOpti Set *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthAuthorizationEndpoint *string `ddl:"parameter,single_quotes" sql:"OAUTH_AUTHORIZATION_ENDPOINT"` @@ -302,7 +289,6 @@ type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet struct { OauthAllowedScopes []AllowedScope `ddl:"parameter,parentheses" sql:"OAUTH_ALLOWED_SCOPES"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` Comment *bool `ddl:"keyword" sql:"COMMENT"` @@ -319,7 +305,6 @@ type AlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationOptions struct { Set *ApiAuthenticationWithJwtBearerFlowIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ApiAuthenticationWithJwtBearerFlowIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ApiAuthenticationWithJwtBearerFlowIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthAuthorizationEndpoint *string `ddl:"parameter,single_quotes" sql:"OAUTH_AUTHORIZATION_ENDPOINT"` @@ -332,7 +317,6 @@ type ApiAuthenticationWithJwtBearerFlowIntegrationSet struct { OauthRefreshTokenValidity *int `ddl:"parameter" sql:"OAUTH_REFRESH_TOKEN_VALIDITY"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type ApiAuthenticationWithJwtBearerFlowIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` Comment *bool `ddl:"keyword" sql:"COMMENT"` @@ -349,7 +333,6 @@ type AlterExternalOauthSecurityIntegrationOptions struct { Set *ExternalOauthIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ExternalOauthIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ExternalOauthIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` ExternalOauthType *ExternalOauthSecurityIntegrationTypeOption `ddl:"parameter" sql:"EXTERNAL_OAUTH_TYPE"` @@ -366,7 +349,6 @@ type ExternalOauthIntegrationSet struct { ExternalOauthScopeDelimiter *string `ddl:"parameter,single_quotes" sql:"EXTERNAL_OAUTH_SCOPE_DELIMITER"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type ExternalOauthIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` ExternalOauthAudienceList *bool `ddl:"keyword" sql:"EXTERNAL_OAUTH_AUDIENCE_LIST"` @@ -383,7 +365,6 @@ type AlterOauthForPartnerApplicationsSecurityIntegrationOptions struct { Set *OauthForPartnerApplicationsIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *OauthForPartnerApplicationsIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type OauthForPartnerApplicationsIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthIssueRefreshTokens *bool `ddl:"parameter" sql:"OAUTH_ISSUE_REFRESH_TOKENS"` @@ -393,7 +374,6 @@ type OauthForPartnerApplicationsIntegrationSet struct { BlockedRolesList *BlockedRolesList `ddl:"parameter,parentheses" sql:"BLOCKED_ROLES_LIST"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type OauthForPartnerApplicationsIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` OauthUseSecondaryRoles *bool `ddl:"keyword" sql:"OAUTH_USE_SECONDARY_ROLES"` @@ -410,7 +390,6 @@ type AlterOauthForCustomClientsSecurityIntegrationOptions struct { Set *OauthForCustomClientsIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *OauthForCustomClientsIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type OauthForCustomClientsIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthRedirectUri *string `ddl:"parameter,single_quotes" sql:"OAUTH_REDIRECT_URI"` @@ -426,7 +405,6 @@ type OauthForCustomClientsIntegrationSet struct { OauthClientRsaPublicKey2 *string `ddl:"parameter,single_quotes" sql:"OAUTH_CLIENT_RSA_PUBLIC_KEY_2"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type OauthForCustomClientsIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` NetworkPolicy *bool `ddl:"keyword" sql:"NETWORK_POLICY"` @@ -447,27 +425,25 @@ type AlterSaml2SecurityIntegrationOptions struct { Unset *Saml2IntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` RefreshSaml2SnowflakePrivateKey *bool `ddl:"keyword" sql:"REFRESH SAML2_SNOWFLAKE_PRIVATE_KEY"` } - type Saml2IntegrationSet struct { - Enabled *bool `ddl:"parameter" sql:"ENABLED"` - Saml2Issuer *string `ddl:"parameter,single_quotes" sql:"SAML2_ISSUER"` - Saml2SsoUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SSO_URL"` - Saml2Provider *Saml2SecurityIntegrationSaml2ProviderOption `ddl:"parameter,single_quotes" sql:"SAML2_PROVIDER"` - Saml2X509Cert *string `ddl:"parameter,single_quotes" sql:"SAML2_X509_CERT"` - AllowedUserDomains []UserDomain `ddl:"parameter,parentheses" sql:"ALLOWED_USER_DOMAINS"` - AllowedEmailPatterns []EmailPattern `ddl:"parameter,parentheses" sql:"ALLOWED_EMAIL_PATTERNS"` - Saml2SpInitiatedLoginPageLabel *string `ddl:"parameter,single_quotes" sql:"SAML2_SP_INITIATED_LOGIN_PAGE_LABEL"` - Saml2EnableSpInitiated *bool `ddl:"parameter" sql:"SAML2_ENABLE_SP_INITIATED"` - Saml2SnowflakeX509Cert *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_X509_CERT"` - Saml2SignRequest *bool `ddl:"parameter" sql:"SAML2_SIGN_REQUEST"` - Saml2RequestedNameidFormat *Saml2SecurityIntegrationSaml2RequestedNameidFormatOption `ddl:"parameter,single_quotes" sql:"SAML2_REQUESTED_NAMEID_FORMAT"` - Saml2PostLogoutRedirectUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_POST_LOGOUT_REDIRECT_URL"` - Saml2ForceAuthn *bool `ddl:"parameter" sql:"SAML2_FORCE_AUTHN"` - Saml2SnowflakeIssuerUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ISSUER_URL"` - Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + Enabled *bool `ddl:"parameter" sql:"ENABLED"` + Saml2Issuer *string `ddl:"parameter,single_quotes" sql:"SAML2_ISSUER"` + Saml2SsoUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SSO_URL"` + Saml2Provider *string `ddl:"parameter,single_quotes" sql:"SAML2_PROVIDER"` + Saml2X509Cert *string `ddl:"parameter,single_quotes" sql:"SAML2_X509_CERT"` + AllowedUserDomains []UserDomain `ddl:"parameter,parentheses" sql:"ALLOWED_USER_DOMAINS"` + AllowedEmailPatterns []EmailPattern `ddl:"parameter,parentheses" sql:"ALLOWED_EMAIL_PATTERNS"` + Saml2SpInitiatedLoginPageLabel *string `ddl:"parameter,single_quotes" sql:"SAML2_SP_INITIATED_LOGIN_PAGE_LABEL"` + Saml2EnableSpInitiated *bool `ddl:"parameter" sql:"SAML2_ENABLE_SP_INITIATED"` + Saml2SnowflakeX509Cert *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_X509_CERT"` + Saml2SignRequest *bool `ddl:"parameter" sql:"SAML2_SIGN_REQUEST"` + Saml2RequestedNameidFormat *string `ddl:"parameter,single_quotes" sql:"SAML2_REQUESTED_NAMEID_FORMAT"` + Saml2PostLogoutRedirectUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_POST_LOGOUT_REDIRECT_URL"` + Saml2ForceAuthn *bool `ddl:"parameter" sql:"SAML2_FORCE_AUTHN"` + Saml2SnowflakeIssuerUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ISSUER_URL"` + Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type Saml2IntegrationUnset struct { Saml2ForceAuthn *bool `ddl:"keyword" sql:"SAML2_FORCE_AUTHN"` Saml2RequestedNameidFormat *bool `ddl:"keyword" sql:"SAML2_REQUESTED_NAMEID_FORMAT"` @@ -512,14 +488,12 @@ type DescribeSecurityIntegrationOptions struct { securityIntegration bool `ddl:"static" sql:"SECURITY INTEGRATION"` name AccountObjectIdentifier `ddl:"identifier"` } - type securityIntegrationDescRow struct { Property string `db:"property"` PropertyType string `db:"property_type"` PropertyValue string `db:"property_value"` PropertyDefault string `db:"property_default"` } - type SecurityIntegrationProperty struct { Name string Type string @@ -527,21 +501,12 @@ type SecurityIntegrationProperty struct { Default string } -func (s SecurityIntegrationProperty) GetName() string { - return s.Name -} - -func (s SecurityIntegrationProperty) GetDefault() string { - return s.Default -} - // ShowSecurityIntegrationOptions is based on https://docs.snowflake.com/en/sql-reference/sql/show-integrations. type ShowSecurityIntegrationOptions struct { show bool `ddl:"static" sql:"SHOW"` securityIntegrations bool `ddl:"static" sql:"SECURITY INTEGRATIONS"` Like *Like `ddl:"keyword" sql:"LIKE"` } - type securityIntegrationShowRow struct { Name string `db:"name"` Type string `db:"type"` @@ -550,7 +515,6 @@ type securityIntegrationShowRow struct { Comment sql.NullString `db:"comment"` CreatedOn time.Time `db:"created_on"` } - type SecurityIntegration struct { Name string IntegrationType string diff --git a/pkg/sdk/security_integrations_gen_test.go b/pkg/sdk/security_integrations_gen_test.go index e58384d489..d541a217b1 100644 --- a/pkg/sdk/security_integrations_gen_test.go +++ b/pkg/sdk/security_integrations_gen_test.go @@ -232,8 +232,9 @@ func TestSecurityIntegrations_CreateOauthForCustomClients(t *testing.T) { // Minimal valid CreateOauthForCustomClientsSecurityIntegrationOptions defaultOpts := func() *CreateOauthForCustomClientsSecurityIntegrationOptions { return &CreateOauthForCustomClientsSecurityIntegrationOptions{ - name: id, - OauthClientType: OauthSecurityIntegrationClientTypePublic, + name: id, + OauthClientType: OauthSecurityIntegrationClientTypePublic, + OauthRedirectUri: "uri", } } @@ -252,7 +253,7 @@ func TestSecurityIntegrations_CreateOauthForCustomClients(t *testing.T) { t.Run("basic", func(t *testing.T) { opts := defaultOpts() opts.OrReplace = Bool(true) - assertOptsValidAndSQLEquals(t, opts, "CREATE OR REPLACE SECURITY INTEGRATION %s TYPE = OAUTH OAUTH_CLIENT = CUSTOM OAUTH_CLIENT_TYPE = 'PUBLIC'", id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, "CREATE OR REPLACE SECURITY INTEGRATION %s TYPE = OAUTH OAUTH_CLIENT = CUSTOM OAUTH_CLIENT_TYPE = 'PUBLIC' OAUTH_REDIRECT_URI = 'uri'", id.FullyQualifiedName()) }) t.Run("all options", func(t *testing.T) { @@ -260,7 +261,6 @@ func TestSecurityIntegrations_CreateOauthForCustomClients(t *testing.T) { roleID, role2ID, npID := randomAccountObjectIdentifier(), randomAccountObjectIdentifier(), randomAccountObjectIdentifier() opts.IfNotExists = Bool(true) opts.OauthClientType = OauthSecurityIntegrationClientTypePublic - opts.OauthRedirectUri = Pointer("uri") opts.Enabled = Pointer(true) opts.OauthAllowNonTlsRedirectUri = Pointer(true) opts.OauthEnforcePkce = Pointer(true) @@ -345,6 +345,7 @@ func TestSecurityIntegrations_CreateSaml2(t *testing.T) { Saml2SsoUrl: "url", Saml2Provider: "provider", Saml2X509Cert: "cert", + Enabled: true, } } @@ -363,20 +364,19 @@ func TestSecurityIntegrations_CreateSaml2(t *testing.T) { t.Run("basic", func(t *testing.T) { opts := defaultOpts() opts.OrReplace = Bool(true) - assertOptsValidAndSQLEquals(t, opts, "CREATE OR REPLACE SECURITY INTEGRATION %s TYPE = SAML2 SAML2_ISSUER = 'issuer' SAML2_SSO_URL = 'url' SAML2_PROVIDER = 'provider' SAML2_X509_CERT = 'cert'", id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, "CREATE OR REPLACE SECURITY INTEGRATION %s TYPE = SAML2 ENABLED = true SAML2_ISSUER = 'issuer' SAML2_SSO_URL = 'url' SAML2_PROVIDER = 'provider' SAML2_X509_CERT = 'cert'", id.FullyQualifiedName()) }) t.Run("all options", func(t *testing.T) { opts := defaultOpts() opts.IfNotExists = Bool(true) - opts.Enabled = Bool(true) opts.AllowedEmailPatterns = []EmailPattern{{Pattern: "pattern"}} opts.AllowedUserDomains = []UserDomain{{Domain: "domain"}} opts.Comment = Pointer("a") opts.Saml2EnableSpInitiated = Pointer(true) opts.Saml2ForceAuthn = Pointer(true) opts.Saml2PostLogoutRedirectUrl = Pointer("redirect") - opts.Saml2RequestedNameidFormat = Pointer(Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress) + opts.Saml2RequestedNameidFormat = Pointer("format") opts.Saml2SignRequest = Pointer(true) opts.Saml2SnowflakeAcsUrl = Pointer("acs") opts.Saml2SnowflakeIssuerUrl = Pointer("issuer") @@ -385,8 +385,8 @@ func TestSecurityIntegrations_CreateSaml2(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, "CREATE SECURITY INTEGRATION IF NOT EXISTS %s TYPE = SAML2 ENABLED = true SAML2_ISSUER = 'issuer' SAML2_SSO_URL = 'url' SAML2_PROVIDER = 'provider' SAML2_X509_CERT = 'cert'"+ " ALLOWED_USER_DOMAINS = ('domain') ALLOWED_EMAIL_PATTERNS = ('pattern') SAML2_SP_INITIATED_LOGIN_PAGE_LABEL = 'label' SAML2_ENABLE_SP_INITIATED = true SAML2_SNOWFLAKE_X509_CERT = 'cert' SAML2_SIGN_REQUEST = true"+ - " SAML2_REQUESTED_NAMEID_FORMAT = '%s' SAML2_POST_LOGOUT_REDIRECT_URL = 'redirect' SAML2_FORCE_AUTHN = true SAML2_SNOWFLAKE_ISSUER_URL = 'issuer' SAML2_SNOWFLAKE_ACS_URL = 'acs'"+ - " COMMENT = 'a'", id.FullyQualifiedName(), Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress) + " SAML2_REQUESTED_NAMEID_FORMAT = 'format' SAML2_POST_LOGOUT_REDIRECT_URL = 'redirect' SAML2_FORCE_AUTHN = true SAML2_SNOWFLAKE_ISSUER_URL = 'issuer' SAML2_SNOWFLAKE_ACS_URL = 'acs'"+ + " COMMENT = 'a'", id.FullyQualifiedName()) }) } @@ -1176,7 +1176,7 @@ func TestSecurityIntegrations_AlterSaml2(t *testing.T) { Enabled: Pointer(true), Saml2Issuer: Pointer("issuer"), Saml2SsoUrl: Pointer("url"), - Saml2Provider: Pointer(Saml2SecurityIntegrationSaml2ProviderCustom), + Saml2Provider: Pointer("provider"), Saml2X509Cert: Pointer("cert"), AllowedUserDomains: []UserDomain{{Domain: "domain"}}, AllowedEmailPatterns: []EmailPattern{{Pattern: "pattern"}}, @@ -1184,17 +1184,17 @@ func TestSecurityIntegrations_AlterSaml2(t *testing.T) { Saml2EnableSpInitiated: Pointer(true), Saml2SnowflakeX509Cert: Pointer("cert"), Saml2SignRequest: Pointer(true), - Saml2RequestedNameidFormat: Pointer(Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress), + Saml2RequestedNameidFormat: Pointer("format"), Saml2PostLogoutRedirectUrl: Pointer("redirect"), Saml2ForceAuthn: Pointer(true), Saml2SnowflakeIssuerUrl: Pointer("issuer"), Saml2SnowflakeAcsUrl: Pointer("acs"), Comment: Pointer("a"), } - assertOptsValidAndSQLEquals(t, opts, "ALTER SECURITY INTEGRATION %s SET ENABLED = true, SAML2_ISSUER = 'issuer', SAML2_SSO_URL = 'url', SAML2_PROVIDER = '%s', SAML2_X509_CERT = 'cert',"+ + assertOptsValidAndSQLEquals(t, opts, "ALTER SECURITY INTEGRATION %s SET ENABLED = true, SAML2_ISSUER = 'issuer', SAML2_SSO_URL = 'url', SAML2_PROVIDER = 'provider', SAML2_X509_CERT = 'cert',"+ " ALLOWED_USER_DOMAINS = ('domain'), ALLOWED_EMAIL_PATTERNS = ('pattern'), SAML2_SP_INITIATED_LOGIN_PAGE_LABEL = 'label', SAML2_ENABLE_SP_INITIATED = true, SAML2_SNOWFLAKE_X509_CERT = 'cert', SAML2_SIGN_REQUEST = true,"+ - " SAML2_REQUESTED_NAMEID_FORMAT = '%s', SAML2_POST_LOGOUT_REDIRECT_URL = 'redirect', SAML2_FORCE_AUTHN = true, SAML2_SNOWFLAKE_ISSUER_URL = 'issuer', SAML2_SNOWFLAKE_ACS_URL = 'acs',"+ - " COMMENT = 'a'", id.FullyQualifiedName(), Saml2SecurityIntegrationSaml2ProviderCustom, Saml2SecurityIntegrationSaml2RequestedNameidFormatEmailAddress) + " SAML2_REQUESTED_NAMEID_FORMAT = 'format', SAML2_POST_LOGOUT_REDIRECT_URL = 'redirect', SAML2_FORCE_AUTHN = true, SAML2_SNOWFLAKE_ISSUER_URL = 'issuer', SAML2_SNOWFLAKE_ACS_URL = 'acs',"+ + " COMMENT = 'a'", id.FullyQualifiedName()) }) t.Run("all options - unset", func(t *testing.T) { From a46e58d07148ff2274513ae73a3added3d44a59f Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Mon, 1 Jul 2024 11:56:27 +0200 Subject: [PATCH 08/18] fix tests --- ..._integration_with_authorization_code_grant.md | 2 +- ...cation_integration_with_client_credentials.md | 2 +- ...authentication_integration_with_jwt_bearer.md | 12 +++++++----- .../helpers/security_integration_client.go | 2 +- pkg/sdk/security_integrations_dto_gen.go | 16 ++++++++++++++++ pkg/sdk/security_integrations_gen.go | 8 ++++++++ pkg/sdk/security_integrations_gen_test.go | 6 ++++-- pkg/sdk/security_integrations_impl_gen.go | 6 ------ ...security_integrations_gen_integration_test.go | 4 ++-- 9 files changed, 40 insertions(+), 18 deletions(-) diff --git a/docs/resources/api_authentication_integration_with_authorization_code_grant.md b/docs/resources/api_authentication_integration_with_authorization_code_grant.md index 3232655ae0..ac40baadca 100644 --- a/docs/resources/api_authentication_integration_with_authorization_code_grant.md +++ b/docs/resources/api_authentication_integration_with_authorization_code_grant.md @@ -50,7 +50,7 @@ resource "snowflake_api_authentication_integration_with_authorization_code_grant - `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_allowed_scopes` (Set of String) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. - `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. -- `oauth_client_auth_method` (String) Specifies the client ID for the OAuth application in the external service. Valid options are: [CLIENT_SECRET_POST] +- `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. Valid options are: [CLIENT_SECRET_POST] - `oauth_grant` (String) Specifies the type of OAuth flow. - `oauth_refresh_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). diff --git a/docs/resources/api_authentication_integration_with_client_credentials.md b/docs/resources/api_authentication_integration_with_client_credentials.md index 239a553dc6..2ecda2920d 100644 --- a/docs/resources/api_authentication_integration_with_client_credentials.md +++ b/docs/resources/api_authentication_integration_with_client_credentials.md @@ -48,7 +48,7 @@ resource "snowflake_api_authentication_integration_with_client_credentials" "tes - `comment` (String) Specifies a comment for the integration. - `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_allowed_scopes` (Set of String) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. -- `oauth_client_auth_method` (String) Specifies the client ID for the OAuth application in the external service. Valid options are: [CLIENT_SECRET_POST] +- `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. Valid options are: [CLIENT_SECRET_POST] - `oauth_grant` (String) Specifies the type of OAuth flow. - `oauth_refresh_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). diff --git a/docs/resources/api_authentication_integration_with_jwt_bearer.md b/docs/resources/api_authentication_integration_with_jwt_bearer.md index e84b3ccd2f..7064a2e672 100644 --- a/docs/resources/api_authentication_integration_with_jwt_bearer.md +++ b/docs/resources/api_authentication_integration_with_jwt_bearer.md @@ -14,10 +14,11 @@ description: |- ```terraform # basic resource resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { - enabled = true - name = "foo" - oauth_client_id = "foo" - oauth_client_secret = "foo" + enabled = true + name = "foo" + oauth_client_id = "foo" + oauth_client_secret = "foo" + oauth_assertion_issuer = "foo" } # resource with all fields set resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { @@ -31,6 +32,7 @@ resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { oauth_client_secret = "foo" oauth_refresh_token_validity = 42 oauth_token_endpoint = "https://example.com" + oauth_assertion_issuer = "foo" } ``` @@ -50,7 +52,7 @@ resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { - `comment` (String) Specifies a comment for the integration. - `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. -- `oauth_client_auth_method` (String) Specifies the client ID for the OAuth application in the external service. Valid options are: [CLIENT_SECRET_POST] +- `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. Valid options are: [CLIENT_SECRET_POST] - `oauth_grant` (String) Specifies the type of OAuth flow. - `oauth_refresh_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). diff --git a/pkg/acceptance/helpers/security_integration_client.go b/pkg/acceptance/helpers/security_integration_client.go index 3c4d698120..08d854fbb4 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, c.ids.Alpha(), "https://example.com", "Custom", random.GenerateX509(t))) + return c.CreateSaml2WithRequest(t, sdk.NewCreateSaml2SecurityIntegrationRequest(id, false, c.ids.Alpha(), "https://example.com", "Custom", random.GenerateX509(t))) } func (c *SecurityIntegrationClient) CreateSaml2WithRequest(t *testing.T, request *sdk.CreateSaml2SecurityIntegrationRequest) (*sdk.SecurityIntegration, func()) { diff --git a/pkg/sdk/security_integrations_dto_gen.go b/pkg/sdk/security_integrations_dto_gen.go index 6684943f75..bdbf31b41c 100644 --- a/pkg/sdk/security_integrations_dto_gen.go +++ b/pkg/sdk/security_integrations_dto_gen.go @@ -121,6 +121,10 @@ type CreateOauthForPartnerApplicationsSecurityIntegrationRequest struct { Comment *string } +func (r *CreateOauthForPartnerApplicationsSecurityIntegrationRequest) GetName() AccountObjectIdentifier { + return r.name +} + type CreateOauthForCustomClientsSecurityIntegrationRequest struct { OrReplace *bool IfNotExists *bool @@ -141,6 +145,10 @@ type CreateOauthForCustomClientsSecurityIntegrationRequest struct { Comment *string } +func (r *CreateOauthForCustomClientsSecurityIntegrationRequest) GetName() AccountObjectIdentifier { + return r.name +} + type PreAuthorizedRolesListRequest struct { PreAuthorizedRolesList []AccountObjectIdentifier } @@ -168,6 +176,10 @@ type CreateSaml2SecurityIntegrationRequest struct { Comment *string } +func (r *CreateSaml2SecurityIntegrationRequest) GetName() AccountObjectIdentifier { + return r.name +} + type CreateScimSecurityIntegrationRequest struct { OrReplace *bool IfNotExists *bool @@ -180,6 +192,10 @@ type CreateScimSecurityIntegrationRequest struct { Comment *string } +func (r *CreateScimSecurityIntegrationRequest) GetName() AccountObjectIdentifier { + return r.name +} + type AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest struct { IfExists *bool name AccountObjectIdentifier // required diff --git a/pkg/sdk/security_integrations_gen.go b/pkg/sdk/security_integrations_gen.go index f5ca9d1285..fb3f15bb92 100644 --- a/pkg/sdk/security_integrations_gen.go +++ b/pkg/sdk/security_integrations_gen.go @@ -501,6 +501,14 @@ type SecurityIntegrationProperty struct { Default string } +func (s SecurityIntegrationProperty) GetName() string { + return s.Name +} + +func (s SecurityIntegrationProperty) GetDefault() string { + return s.Default +} + // ShowSecurityIntegrationOptions is based on https://docs.snowflake.com/en/sql-reference/sql/show-integrations. type ShowSecurityIntegrationOptions struct { show bool `ddl:"static" sql:"SHOW"` diff --git a/pkg/sdk/security_integrations_gen_test.go b/pkg/sdk/security_integrations_gen_test.go index d541a217b1..495ddcce3a 100644 --- a/pkg/sdk/security_integrations_gen_test.go +++ b/pkg/sdk/security_integrations_gen_test.go @@ -95,10 +95,11 @@ func TestSecurityIntegrations_CreateApiAuthenticationWithAuthorizationCodeGrantF opts.OauthGrantAuthorizationCode = Pointer(true) opts.OauthAccessTokenValidity = Pointer(42) opts.OauthRefreshTokenValidity = Pointer(42) + opts.OauthAllowedScopes = []AllowedScope{{Scope: "bar"}} opts.Comment = Pointer("foo") assertOptsValidAndSQLEquals(t, opts, "CREATE SECURITY INTEGRATION IF NOT EXISTS %s TYPE = API_AUTHENTICATION AUTH_TYPE = OAUTH2 ENABLED = true OAUTH_AUTHORIZATION_ENDPOINT = 'foo'"+ " OAUTH_TOKEN_ENDPOINT = 'foo' OAUTH_CLIENT_AUTH_METHOD = CLIENT_SECRET_POST OAUTH_CLIENT_ID = 'foo' OAUTH_CLIENT_SECRET = 'bar' OAUTH_GRANT = AUTHORIZATION_CODE"+ - " OAUTH_ACCESS_TOKEN_VALIDITY = 42 OAUTH_REFRESH_TOKEN_VALIDITY = 42 COMMENT = 'foo'", id.FullyQualifiedName()) + " OAUTH_ACCESS_TOKEN_VALIDITY = 42 OAUTH_REFRESH_TOKEN_VALIDITY = 42 OAUTH_ALLOWED_SCOPES = ('bar') COMMENT = 'foo'", id.FullyQualifiedName()) }) } @@ -597,11 +598,12 @@ func TestSecurityIntegrations_AlterApiAuthenticationWithAuthorizationCodeFlow(t OauthGrantAuthorizationCode: Pointer(true), OauthAccessTokenValidity: Pointer(42), OauthRefreshTokenValidity: Pointer(42), + OauthAllowedScopes: []AllowedScope{{Scope: "bar"}}, Comment: Pointer("foo"), } assertOptsValidAndSQLEquals(t, opts, "ALTER SECURITY INTEGRATION %s SET ENABLED = true, OAUTH_TOKEN_ENDPOINT = 'foo', OAUTH_CLIENT_AUTH_METHOD = CLIENT_SECRET_POST,"+ " OAUTH_CLIENT_ID = 'foo', OAUTH_CLIENT_SECRET = 'foo', OAUTH_GRANT = AUTHORIZATION_CODE, OAUTH_ACCESS_TOKEN_VALIDITY = 42,"+ - " OAUTH_REFRESH_TOKEN_VALIDITY = 42, COMMENT = 'foo'", id.FullyQualifiedName()) + " OAUTH_REFRESH_TOKEN_VALIDITY = 42, OAUTH_ALLOWED_SCOPES = ('bar'), COMMENT = 'foo'", id.FullyQualifiedName()) }) t.Run("all options - unset", func(t *testing.T) { diff --git a/pkg/sdk/security_integrations_impl_gen.go b/pkg/sdk/security_integrations_impl_gen.go index 7522b9ca5a..f77857865f 100644 --- a/pkg/sdk/security_integrations_impl_gen.go +++ b/pkg/sdk/security_integrations_impl_gen.go @@ -438,7 +438,6 @@ func (r *AlterExternalOauthSecurityIntegrationRequest) toOpts() *AlterExternalOa } if r.Set != nil { - opts.Set = &ExternalOauthIntegrationSet{ Enabled: r.Set.Enabled, ExternalOauthType: r.Set.ExternalOauthType, @@ -472,7 +471,6 @@ func (r *AlterExternalOauthSecurityIntegrationRequest) toOpts() *AlterExternalOa AudienceList: r.Set.ExternalOauthAudienceList.AudienceList, } } - } if r.Unset != nil { @@ -494,7 +492,6 @@ func (r *AlterOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() *A } if r.Set != nil { - opts.Set = &OauthForPartnerApplicationsIntegrationSet{ Enabled: r.Set.Enabled, OauthIssueRefreshTokens: r.Set.OauthIssueRefreshTokens, @@ -510,7 +507,6 @@ func (r *AlterOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() *A BlockedRolesList: r.Set.BlockedRolesList.BlockedRolesList, } } - } if r.Unset != nil { @@ -532,7 +528,6 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa } if r.Set != nil { - opts.Set = &OauthForCustomClientsIntegrationSet{ Enabled: r.Set.Enabled, OauthRedirectUri: r.Set.OauthRedirectUri, @@ -559,7 +554,6 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa BlockedRolesList: r.Set.BlockedRolesList.BlockedRolesList, } } - } if r.Unset != nil { diff --git a/pkg/sdk/testint/security_integrations_gen_integration_test.go b/pkg/sdk/testint/security_integrations_gen_integration_test.go index 997daa6529..993d6500a4 100644 --- a/pkg/sdk/testint/security_integrations_gen_integration_test.go +++ b/pkg/sdk/testint/security_integrations_gen_integration_test.go @@ -96,7 +96,7 @@ func TestInt_SecurityIntegrations(t *testing.T) { createOauthCustom := func(t *testing.T, with func(*sdk.CreateOauthForCustomClientsSecurityIntegrationRequest)) (*sdk.SecurityIntegration, sdk.AccountObjectIdentifier) { t.Helper() id := testClientHelper().Ids.RandomAccountObjectIdentifier() - req := sdk.NewCreateOauthForCustomClientsSecurityIntegrationRequest(id, sdk.OauthSecurityIntegrationClientTypePublic) + req := sdk.NewCreateOauthForCustomClientsSecurityIntegrationRequest(id, sdk.OauthSecurityIntegrationClientTypePublic, "https://example.com") if with != nil { with(req) } @@ -129,7 +129,7 @@ func TestInt_SecurityIntegrations(t *testing.T) { t.Helper() id := testClientHelper().Ids.RandomAccountObjectIdentifier() issuer := testClientHelper().Ids.Alpha() - saml2Req := sdk.NewCreateSaml2SecurityIntegrationRequest(id, issuer, "https://example.com", "Custom", cert) + saml2Req := sdk.NewCreateSaml2SecurityIntegrationRequest(id, false, issuer, "https://example.com", "Custom", cert) if with != nil { with(saml2Req) } From f0ff5f74768ce12f8e6c71cc44773dc63fbc487c Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Tue, 2 Jul 2024 14:22:41 +0200 Subject: [PATCH 09/18] Address review comments --- .../resource.tf | 15 +- .../resource.tf | 16 +- .../resource.tf | 18 +- pkg/provider/provider.go | 116 +++--- .../api_authentication_integration_common.go | 376 ++++++++++++++++++ ...tegration_with_authorization_code_grant.go | 344 +++------------- ...uthorization_code_grant_acceptance_test.go | 48 ++- ...ion_integration_with_client_credentials.go | 328 +++------------ ...with_client_credentials_acceptance_test.go | 46 ++- ...hentication_integration_with_jwt_bearer.go | 343 +++------------- ...gration_with_jwt_bearer_acceptance_test.go | 32 ++ pkg/resources/common.go | 81 +--- pkg/resources/custom_diffs.go | 6 +- .../basic/test.tf | 1 + .../basic/variables.tf | 18 - ...api_authentication_security_integration.go | 9 +- pkg/sdk/security_integrations_def.go | 7 +- v1-preparations/ESSENTIAL_GA_OBJECTS.MD | 4 +- 18 files changed, 747 insertions(+), 1061 deletions(-) create mode 100644 pkg/resources/api_authentication_integration_common.go diff --git a/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/resource.tf b/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/resource.tf index 6db7b2d569..c6f0047f70 100644 --- a/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/resource.tf +++ b/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/resource.tf @@ -1,20 +1,21 @@ # basic resource resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { enabled = true - name = "foo" - oauth_client_id = "foo" - oauth_client_secret = "foo" + name = "test" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" } # resource with all fields set resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { - comment = "foo" + comment = "comment" enabled = true - name = "foo" + name = "test" oauth_access_token_validity = 42 + oauth_allowed_scopes = ["useraccount"] oauth_authorization_endpoint = "https://example.com" oauth_client_auth_method = "CLIENT_SECRET_POST" - oauth_client_id = "foo" - oauth_client_secret = "foo" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" oauth_refresh_token_validity = 42 oauth_token_endpoint = "https://example.com" } diff --git a/examples/resources/snowflake_api_authentication_integration_with_client_credentials/resource.tf b/examples/resources/snowflake_api_authentication_integration_with_client_credentials/resource.tf index 3744d223b5..6e246b8a68 100644 --- a/examples/resources/snowflake_api_authentication_integration_with_client_credentials/resource.tf +++ b/examples/resources/snowflake_api_authentication_integration_with_client_credentials/resource.tf @@ -1,19 +1,19 @@ # basic resource resource "snowflake_api_authentication_integration_with_client_credentials" "test" { enabled = true - name = "foo" - oauth_client_id = "foo" - oauth_client_secret = "foo" + name = "test" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" } # resource with all fields set resource "snowflake_api_authentication_integration_with_client_credentials" "test" { - comment = "foo" + comment = "comment" enabled = true - name = "foo" + name = "test" oauth_access_token_validity = 42 - oauth_allowed_scopes = ["foo"] + oauth_allowed_scopes = ["useraccount"] oauth_client_auth_method = "CLIENT_SECRET_POST" - oauth_client_id = "foo" - oauth_client_secret = "foo" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" oauth_token_endpoint = "https://example.com" } diff --git a/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf b/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf index 0751baabb9..c46ab6c5f4 100644 --- a/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf +++ b/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf @@ -1,22 +1,22 @@ # basic resource resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { enabled = true - name = "foo" - oauth_client_id = "foo" - oauth_client_secret = "foo" - oauth_assertion_issuer = "foo" + name = "test" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" + oauth_assertion_issuer = "issuer" } # resource with all fields set resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { - comment = "foo" + comment = "comment" enabled = true - name = "foo" + name = "test" oauth_access_token_validity = 42 oauth_authorization_endpoint = "https://example.com" oauth_client_auth_method = "CLIENT_SECRET_POST" - oauth_client_id = "foo" - oauth_client_secret = "foo" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" oauth_refresh_token_validity = 42 oauth_token_endpoint = "https://example.com" - oauth_assertion_issuer = "foo" + oauth_assertion_issuer = "issuer" } diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 9e41ddaa49..36bcd15abb 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -425,67 +425,67 @@ func getResources() map[string]*schema.Resource { "snowflake_account_password_policy_attachment": resources.AccountPasswordPolicyAttachment(), "snowflake_account_parameter": resources.AccountParameter(), "snowflake_alert": resources.Alert(), - "snowflake_api_integration": resources.APIIntegration(), "snowflake_api_authentication_integration_with_authorization_code_grant": resources.ApiAuthenticationIntegrationWithAuthorizationCodeGrant(), "snowflake_api_authentication_integration_with_client_credentials": resources.ApiAuthenticationIntegrationWithClientCredentials(), "snowflake_api_authentication_integration_with_jwt_bearer": resources.ApiAuthenticationIntegrationWithJwtBearer(), - "snowflake_database_old": resources.DatabaseOld(), - "snowflake_database": resources.Database(), - "snowflake_database_role": resources.DatabaseRole(), - "snowflake_dynamic_table": resources.DynamicTable(), - "snowflake_email_notification_integration": resources.EmailNotificationIntegration(), - "snowflake_external_function": resources.ExternalFunction(), - "snowflake_external_oauth_integration": resources.ExternalOauthIntegration(), - "snowflake_external_table": resources.ExternalTable(), - "snowflake_failover_group": resources.FailoverGroup(), - "snowflake_file_format": resources.FileFormat(), - "snowflake_function": resources.Function(), - "snowflake_grant_account_role": resources.GrantAccountRole(), - "snowflake_grant_application_role": resources.GrantApplicationRole(), - "snowflake_grant_database_role": resources.GrantDatabaseRole(), - "snowflake_grant_ownership": resources.GrantOwnership(), - "snowflake_grant_privileges_to_account_role": resources.GrantPrivilegesToAccountRole(), - "snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(), - "snowflake_grant_privileges_to_share": resources.GrantPrivilegesToShare(), - "snowflake_managed_account": resources.ManagedAccount(), - "snowflake_masking_policy": resources.MaskingPolicy(), - "snowflake_materialized_view": resources.MaterializedView(), - "snowflake_network_policy": resources.NetworkPolicy(), - "snowflake_network_policy_attachment": resources.NetworkPolicyAttachment(), - "snowflake_network_rule": resources.NetworkRule(), - "snowflake_notification_integration": resources.NotificationIntegration(), - "snowflake_oauth_integration": resources.OAuthIntegration(), - "snowflake_object_parameter": resources.ObjectParameter(), - "snowflake_password_policy": resources.PasswordPolicy(), - "snowflake_pipe": resources.Pipe(), - "snowflake_procedure": resources.Procedure(), - "snowflake_resource_monitor": resources.ResourceMonitor(), - "snowflake_role": resources.Role(), - "snowflake_row_access_policy": resources.RowAccessPolicy(), - "snowflake_saml_integration": resources.SAMLIntegration(), - "snowflake_schema": resources.Schema(), - "snowflake_scim_integration": resources.SCIMIntegration(), - "snowflake_secondary_database": resources.SecondaryDatabase(), - "snowflake_sequence": resources.Sequence(), - "snowflake_session_parameter": resources.SessionParameter(), - "snowflake_share": resources.Share(), - "snowflake_shared_database": resources.SharedDatabase(), - "snowflake_stage": resources.Stage(), - "snowflake_storage_integration": resources.StorageIntegration(), - "snowflake_stream": resources.Stream(), - "snowflake_table": resources.Table(), - "snowflake_table_column_masking_policy_application": resources.TableColumnMaskingPolicyApplication(), - "snowflake_table_constraint": resources.TableConstraint(), - "snowflake_tag": resources.Tag(), - "snowflake_tag_association": resources.TagAssociation(), - "snowflake_tag_masking_policy_association": resources.TagMaskingPolicyAssociation(), - "snowflake_task": resources.Task(), - "snowflake_unsafe_execute": resources.UnsafeExecute(), - "snowflake_user": resources.User(), - "snowflake_user_password_policy_attachment": resources.UserPasswordPolicyAttachment(), - "snowflake_user_public_keys": resources.UserPublicKeys(), - "snowflake_view": resources.View(), - "snowflake_warehouse": resources.Warehouse(), + "snowflake_api_integration": resources.APIIntegration(), + "snowflake_database_old": resources.DatabaseOld(), + "snowflake_database": resources.Database(), + "snowflake_database_role": resources.DatabaseRole(), + "snowflake_dynamic_table": resources.DynamicTable(), + "snowflake_email_notification_integration": resources.EmailNotificationIntegration(), + "snowflake_external_function": resources.ExternalFunction(), + "snowflake_external_oauth_integration": resources.ExternalOauthIntegration(), + "snowflake_external_table": resources.ExternalTable(), + "snowflake_failover_group": resources.FailoverGroup(), + "snowflake_file_format": resources.FileFormat(), + "snowflake_function": resources.Function(), + "snowflake_grant_account_role": resources.GrantAccountRole(), + "snowflake_grant_application_role": resources.GrantApplicationRole(), + "snowflake_grant_database_role": resources.GrantDatabaseRole(), + "snowflake_grant_ownership": resources.GrantOwnership(), + "snowflake_grant_privileges_to_account_role": resources.GrantPrivilegesToAccountRole(), + "snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(), + "snowflake_grant_privileges_to_share": resources.GrantPrivilegesToShare(), + "snowflake_managed_account": resources.ManagedAccount(), + "snowflake_masking_policy": resources.MaskingPolicy(), + "snowflake_materialized_view": resources.MaterializedView(), + "snowflake_network_policy": resources.NetworkPolicy(), + "snowflake_network_policy_attachment": resources.NetworkPolicyAttachment(), + "snowflake_network_rule": resources.NetworkRule(), + "snowflake_notification_integration": resources.NotificationIntegration(), + "snowflake_oauth_integration": resources.OAuthIntegration(), + "snowflake_object_parameter": resources.ObjectParameter(), + "snowflake_password_policy": resources.PasswordPolicy(), + "snowflake_pipe": resources.Pipe(), + "snowflake_procedure": resources.Procedure(), + "snowflake_resource_monitor": resources.ResourceMonitor(), + "snowflake_role": resources.Role(), + "snowflake_row_access_policy": resources.RowAccessPolicy(), + "snowflake_saml_integration": resources.SAMLIntegration(), + "snowflake_schema": resources.Schema(), + "snowflake_scim_integration": resources.SCIMIntegration(), + "snowflake_secondary_database": resources.SecondaryDatabase(), + "snowflake_sequence": resources.Sequence(), + "snowflake_session_parameter": resources.SessionParameter(), + "snowflake_share": resources.Share(), + "snowflake_shared_database": resources.SharedDatabase(), + "snowflake_stage": resources.Stage(), + "snowflake_storage_integration": resources.StorageIntegration(), + "snowflake_stream": resources.Stream(), + "snowflake_table": resources.Table(), + "snowflake_table_column_masking_policy_application": resources.TableColumnMaskingPolicyApplication(), + "snowflake_table_constraint": resources.TableConstraint(), + "snowflake_tag": resources.Tag(), + "snowflake_tag_association": resources.TagAssociation(), + "snowflake_tag_masking_policy_association": resources.TagMaskingPolicyAssociation(), + "snowflake_task": resources.Task(), + "snowflake_unsafe_execute": resources.UnsafeExecute(), + "snowflake_user": resources.User(), + "snowflake_user_password_policy_attachment": resources.UserPasswordPolicyAttachment(), + "snowflake_user_public_keys": resources.UserPublicKeys(), + "snowflake_view": resources.View(), + "snowflake_warehouse": resources.Warehouse(), } } diff --git a/pkg/resources/api_authentication_integration_common.go b/pkg/resources/api_authentication_integration_common.go new file mode 100644 index 0000000000..e51666c832 --- /dev/null +++ b/pkg/resources/api_authentication_integration_common.go @@ -0,0 +1,376 @@ +package resources + +import ( + "context" + "fmt" + "strconv" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "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/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +var apiAuthCommonSchema = map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Specifies the identifier (i.e. name) for the integration. This value must be unique in your account.", + }, + "enabled": { + Type: schema.TypeBool, + Required: true, + Description: "Specifies whether this security integration is enabled or disabled.", + }, + "oauth_client_id": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the client ID for the OAuth application in the external service.", + }, + "oauth_client_secret": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the client secret for the OAuth application in the ServiceNow instance from the previous step. The connector uses this to request an access token from the ServiceNow instance.", + }, + "oauth_token_endpoint": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly).", + }, + "oauth_client_auth_method": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: sdkValidation(sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption), + Description: fmt.Sprintf("Specifies that POST is used as the authentication method to the external service. Valid options are: %v", sdk.AsStringList(sdk.AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption)), + }, + "oauth_access_token_validity": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(0), + Default: -1, + Description: "Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server.", + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("oauth_access_token_validity"), + }, + "oauth_refresh_token_validity": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(1), + Description: "Specifies the value to determine the validity of the refresh token obtained from the OAuth server.", + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("oauth_refresh_token_validity"), + }, + "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 INTEGRATIONS` for the given security integration.", + Elem: &schema.Resource{ + Schema: schemas.ShowSecurityIntegrationSchema, + }, + }, + describeOutputAttributeName: { + Type: schema.TypeList, + Computed: true, + Description: "Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security integration.", + Elem: &schema.Resource{ + Schema: schemas.DescribeApiAuthSecurityIntegrationSchema, + }, + }, +} + +type commonApiAuthSet struct { + enabled *bool + oauthClientId *string + oauthClientSecret *string + comment *string + oauthAccessTokenValidity *int + oauthClientAuthMethod *sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption + oauthTokenEndpoint *string + oauthRefreshTokenValidity *int +} + +type commonApiAuthUnset struct { + comment *bool +} + +func handleApiAuthUpdate(d *schema.ResourceData) (commonApiAuthSet, commonApiAuthUnset, error) { + set, unset := commonApiAuthSet{}, commonApiAuthUnset{} + + if d.HasChange("enabled") { + // required field + set.enabled = sdk.Pointer(d.Get("enabled").(bool)) + } + + if d.HasChange("oauth_client_id") { + // required field + set.oauthClientId = sdk.Pointer(d.Get("oauth_client_id").(string)) + } + + if d.HasChange("oauth_client_secret") { + // required field + set.oauthClientSecret = sdk.Pointer(d.Get("oauth_client_secret").(string)) + } + + if d.HasChange("comment") { + if v, ok := d.GetOk("comment"); ok { + set.comment = sdk.Pointer(v.(string)) + } else { + unset.comment = sdk.Pointer(true) + } + } + + if d.HasChange("oauth_access_token_validity") { + if v := d.Get("oauth_access_token_validity").(int); v != -1 { + set.oauthAccessTokenValidity = sdk.Pointer(v) + } else { + // TODO(SNOW-1515781): use UNSET + set.oauthAccessTokenValidity = sdk.Pointer(0) + } + } + + if d.HasChange("oauth_client_auth_method") { + v := d.Get("oauth_client_auth_method").(string) + if len(v) > 0 { + value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) + if err != nil { + return commonApiAuthSet{}, commonApiAuthUnset{}, err + } + set.oauthClientAuthMethod = sdk.Pointer(value) + } + // else: force new + } + + if d.HasChange("oauth_refresh_token_validity") { + if v, ok := d.GetOk("oauth_refresh_token_validity"); ok { + set.oauthRefreshTokenValidity = sdk.Pointer(v.(int)) + } else { + // TODO(SNOW-1515781): use UNSET + set.oauthRefreshTokenValidity = sdk.Pointer(7776000) + } + } + + if d.HasChange("oauth_token_endpoint") { + if v, ok := d.GetOk("oauth_token_endpoint"); ok { + set.oauthTokenEndpoint = sdk.Pointer(v.(string)) + } + // else: force new + } + return set, unset, nil +} + +type commonApiAuthCreate struct { + name string + enabled bool + oauthClientId string + oauthClientSecret string + comment *string + oauthAccessTokenValidity *int + oauthClientAuthMethod *sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption + oauthTokenEndpoint *string + oauthRefreshTokenValidity *int +} + +func handleApiAuthCreate(d *schema.ResourceData) (commonApiAuthCreate, error) { + create := commonApiAuthCreate{ + enabled: d.Get("enabled").(bool), + name: d.Get("name").(string), + oauthClientId: d.Get("oauth_client_id").(string), + oauthClientSecret: d.Get("oauth_client_secret").(string), + } + if v, ok := d.GetOk("comment"); ok { + create.comment = sdk.Pointer(v.(string)) + } + + if v := d.Get("oauth_access_token_validity").(int); v != -1 { + create.oauthAccessTokenValidity = sdk.Pointer(v) + } + if v, ok := d.GetOk("oauth_refresh_token_validity"); ok { + create.oauthRefreshTokenValidity = sdk.Pointer(v.(int)) + } + if v, ok := d.GetOk("oauth_token_endpoint"); ok { + create.oauthTokenEndpoint = sdk.Pointer(v.(string)) + } + if v, ok := d.GetOk("oauth_client_auth_method"); ok { + value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v.(string)) + if err != nil { + return commonApiAuthCreate{}, err + } + create.oauthClientAuthMethod = sdk.Pointer(value) + } + + return create, nil +} + +func handleApiAuthImport(ctx context.Context, d *schema.ResourceData, integration *sdk.SecurityIntegration, + properties []sdk.SecurityIntegrationProperty, +) error { + if err := d.Set("name", integration.Name); err != nil { + return err + } + if err := d.Set("enabled", integration.Enabled); err != nil { + return err + } + if err := d.Set("comment", integration.Comment); err != nil { + return err + } + + oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" + }) + if err == nil { + value, err := strconv.Atoi(oauthAccessTokenValidity.Value) + if err != nil { + return err + } + if err = d.Set("oauth_access_token_validity", value); err != nil { + return err + } + } + oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" + }) + if err == nil { + value, err := strconv.Atoi(oauthRefreshTokenValidity.Value) + if err != nil { + return err + } + if err = d.Set("oauth_refresh_token_validity", value); err != nil { + return err + } + } + oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) + if err == nil { + if err = d.Set("oauth_client_id", oauthClientId.Value); err != nil { + return err + } + } + oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_CLIENT_AUTH_METHOD" + }) + if err == nil { + if err = d.Set("oauth_client_auth_method", oauthClientAuthMethod.Value); err != nil { + return err + } + } + oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) + if err == nil { + if err = d.Set("oauth_token_endpoint", oauthTokenEndpoint.Value); err != nil { + return err + } + } + + return nil +} + +func handleApiAuthRead(d *schema.ResourceData, + integration *sdk.SecurityIntegration, + properties []sdk.SecurityIntegrationProperty, + withExternalChangesMarking bool, + extraFieldsDescribeMappings []describeMapping, +) error { + if err := d.Set("name", integration.Name); err != nil { + return err + } + if err := d.Set("comment", integration.Comment); err != nil { + return err + } + if err := d.Set("enabled", integration.Enabled); err != nil { + return err + } + if withExternalChangesMarking { + oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" + }) + if err != nil { + return err + } + + oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" + }) + if err != nil { + return err + } + + oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) + if err != nil { + return err + } + + oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_CLIENT_AUTH_METHOD" + }) + if err != nil { + return err + } + + oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) + if err != nil { + return err + } + + oauthAccessTokenValidityInt, err := strconv.Atoi(oauthAccessTokenValidity.Value) + if err != nil { + return err + } + oauthRefreshTokenValidityInt, err := strconv.Atoi(oauthRefreshTokenValidity.Value) + if err != nil { + return err + } + + if err = handleExternalChangesToObjectInDescribe(d, + append(extraFieldsDescribeMappings, + describeMapping{"oauth_access_token_validity", "oauth_access_token_validity", oauthAccessTokenValidityInt, oauthAccessTokenValidityInt, stringToIntNormalizer}, + describeMapping{"oauth_refresh_token_validity", "oauth_refresh_token_validity", oauthRefreshTokenValidityInt, oauthRefreshTokenValidityInt, stringToIntNormalizer}, + describeMapping{"oauth_client_id", "oauth_client_id", oauthClientId.Value, oauthClientId.Value, nil}, + describeMapping{"oauth_client_auth_method", "oauth_client_auth_method", oauthClientAuthMethod.Value, oauthClientAuthMethod.Value, nil}, + describeMapping{"oauth_token_endpoint", "oauth_token_endpoint", oauthTokenEndpoint.Value, oauthTokenEndpoint.Value, nil}, + )..., + ); err != nil { + return err + } + } + if !d.GetRawConfig().IsNull() { + if v := d.GetRawConfig().AsValueMap()["oauth_access_token_validity"]; !v.IsNull() { + intVal, _ := v.AsBigFloat().Int64() + if err := d.Set("oauth_access_token_validity", intVal); err != nil { + return err + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_refresh_token_validity"]; !v.IsNull() { + intVal, _ := v.AsBigFloat().Int64() + if err := d.Set("oauth_refresh_token_validity", intVal); err != nil { + return err + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_client_id"]; !v.IsNull() { + if err := d.Set("oauth_client_id", v.AsString()); err != nil { + return err + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_client_auth_method"]; !v.IsNull() { + if err := d.Set("oauth_client_auth_method", v.AsString()); err != nil { + return err + } + } + if v := d.GetRawConfig().AsValueMap()["oauth_token_endpoint"]; !v.IsNull() { + if err := d.Set("oauth_token_endpoint", v.AsString()); err != nil { + return err + } + } + } + + if err := d.Set(showOutputAttributeName, []map[string]any{schemas.SecurityIntegrationToSchema(integration)}); err != nil { + return err + } + + if err := d.Set(describeOutputAttributeName, []map[string]any{schemas.ApiAuthSecurityIntegrationPropertiesToSchema(properties)}); err != nil { + return err + } + return nil +} diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go index 01d0701de4..283dc4fba6 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go @@ -5,13 +5,11 @@ import ( "errors" "fmt" "reflect" - "strconv" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" "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" @@ -21,11 +19,10 @@ import ( ) var apiAuthAuthorizationCodeGrantSchema = func() map[string]*schema.Schema { - uniq := map[string]*schema.Schema{ + apiAuthAuthorizationCodeGrant := map[string]*schema.Schema{ "oauth_authorization_endpoint": { Type: schema.TypeString, Optional: true, - Default: "unknown", Description: "Specifies the URL for authenticating to the external service.", }, "oauth_allowed_scopes": { @@ -37,12 +34,11 @@ var apiAuthAuthorizationCodeGrantSchema = func() map[string]*schema.Schema { "oauth_grant": { Type: schema.TypeString, Optional: true, - ValidateFunc: validation.StringInSlice([]string{"unknown", "AUTHORIZATION_CODE"}, true), + ValidateFunc: validation.StringInSlice([]string{sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode}, true), Description: "Specifies the type of OAuth flow.", - Default: "unknown", }, } - return MergeMaps(apiAuthCommonSchema, uniq) + return MergeMaps(apiAuthCommonSchema, apiAuthAuthorizationCodeGrant) }() func ApiAuthenticationIntegrationWithAuthorizationCodeGrant() *schema.Resource { @@ -52,10 +48,10 @@ func ApiAuthenticationIntegrationWithAuthorizationCodeGrant() *schema.Resource { UpdateContext: UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, DeleteContext: DeleteContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, CustomizeDiff: customdiff.All( - ForceNewIfChangeToDefaultString("oauth_token_endpoint"), - ForceNewIfChangeToDefaultString("oauth_authorization_endpoint"), - ForceNewIfChangeToDefaultString("oauth_client_auth_method"), - ForceNewIfChangeToDefaultString("oauth_grant"), + ForceNewIfChangeToEmptyString("oauth_token_endpoint"), + ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), + ForceNewIfChangeToEmptyString("oauth_client_auth_method"), + ForceNewIfChangeToEmptyString("oauth_grant"), ComputedIfAnyAttributeChanged(showOutputAttributeName, "enabled", "comment"), ComputedIfAnyAttributeChanged(describeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", "oauth_client_id", "oauth_client_auth_method", "oauth_authorization_endpoint", @@ -83,54 +79,9 @@ func ImportApiAuthenticationWithAuthorizationCodeGrant(ctx context.Context, d *s return nil, err } - if err = d.Set("name", integration.Name); err != nil { + if err := handleApiAuthImport(ctx, d, integration, properties); err != nil { return nil, err } - if err = d.Set("enabled", integration.Enabled); err != nil { - return nil, err - } - if err = d.Set("comment", integration.Comment); err != nil { - return nil, err - } - - oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" - }) - if err == nil { - value, err := strconv.Atoi(oauthAccessTokenValidity.Value) - if err != nil { - return nil, err - } - if err = d.Set("oauth_access_token_validity", value); err != nil { - return nil, err - } - } - oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" - }) - if err == nil { - value, err := strconv.Atoi(oauthRefreshTokenValidity.Value) - if err != nil { - return nil, err - } - if err = d.Set("oauth_refresh_token_validity", value); err != nil { - return nil, err - } - } - oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) - if err == nil { - if err = d.Set("oauth_client_id", oauthClientId.Value); err != nil { - return nil, err - } - } - oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_CLIENT_AUTH_METHOD" - }) - if err == nil { - if err = d.Set("oauth_client_auth_method", oauthClientAuthMethod.Value); err != nil { - return nil, err - } - } oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_AUTHORIZATION_ENDPOINT" }) @@ -139,12 +90,6 @@ func ImportApiAuthenticationWithAuthorizationCodeGrant(ctx context.Context, d *s return nil, err } } - oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) - if err == nil { - if err = d.Set("oauth_token_endpoint", oauthTokenEndpoint.Value); err != nil { - return nil, err - } - } oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) if err == nil { if err = d.Set("oauth_allowed_scopes", listValueToSlice(oauthAllowedScopes.Value, true, false)); err != nil { @@ -163,46 +108,28 @@ func ImportApiAuthenticationWithAuthorizationCodeGrant(ctx context.Context, d *s func CreateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client - enabled := d.Get("enabled").(bool) - name := d.Get("name").(string) - oauthClientId := d.Get("oauth_client_id").(string) - oauthClientSecret := d.Get("oauth_client_secret").(string) - id := sdk.NewAccountObjectIdentifier(name) - req := sdk.NewCreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest(id, enabled, oauthClientId, oauthClientSecret) - - if v, ok := d.GetOk("comment"); ok { - req.WithComment(v.(string)) - } - - if v := d.Get("oauth_access_token_validity").(int); v != -1 { - req.WithOauthAccessTokenValidity(v) - } - - if v := d.Get("oauth_authorization_endpoint").(string); v != "unknown" { - req.WithOauthAuthorizationEndpoint(v) - } - - if v := d.Get("oauth_client_auth_method").(string); v != "unknown" { - value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) - if err != nil { - return diag.FromErr(err) - } - req.WithOauthClientAuthMethod(value) + commonCreate, err := handleApiAuthCreate(d) + if err != nil { + return diag.FromErr(err) } + id := sdk.NewAccountObjectIdentifier(commonCreate.name) + req := sdk.NewCreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest(id, commonCreate.enabled, commonCreate.oauthClientId, commonCreate.oauthClientSecret) + req.Comment = commonCreate.comment + req.OauthAccessTokenValidity = commonCreate.oauthAccessTokenValidity + req.OauthRefreshTokenValidity = commonCreate.oauthRefreshTokenValidity + req.OauthTokenEndpoint = commonCreate.oauthTokenEndpoint + req.OauthClientAuthMethod = commonCreate.oauthClientAuthMethod - if v := d.Get("oauth_refresh_token_validity").(int); v != -1 { - req.WithOauthRefreshTokenValidity(v) + if v, ok := d.GetOk("oauth_authorization_endpoint"); ok { + req.WithOauthAuthorizationEndpoint(v.(string)) } - if v := d.Get("oauth_grant").(string); v != "unknown" { - if v == "AUTHORIZATION_CODE" { + if v, ok := d.GetOk("oauth_grant"); ok { + if v.(string) == sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode { req.WithOauthGrantAuthorizationCode(true) } } - if v := d.Get("oauth_token_endpoint").(string); v != "unknown" { - req.WithOauthTokenEndpoint(v) - } if v, ok := d.GetOk("oauth_allowed_scopes"); ok { elems := expandStringList(v.(*schema.Set).List()) allowedScopes := make([]sdk.AllowedScope, len(elems)) @@ -216,8 +143,7 @@ func CreateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx con return diag.FromErr(err) } - d.SetId(name) - + d.SetId(helpers.EncodeSnowflakeID(id)) return ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(false)(ctx, d, meta) } @@ -248,137 +174,37 @@ func ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(withExter 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", integration.Name); err != nil { + oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_AUTHORIZATION_ENDPOINT" + }) + if err != nil { return diag.FromErr(err) } - if err := d.Set("comment", integration.Comment); err != nil { + + oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) + if err != nil { return diag.FromErr(err) } - if err := d.Set("enabled", integration.Enabled); err != nil { + + oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) + if err != nil { return diag.FromErr(err) } - if withExternalChangesMarking { - if err = handleExternalChangesToObjectInShow(d, - showMapping{"comment", "comment", integration.Comment, integration.Comment, nil}, - showMapping{"enabled", "enabled", integration.Enabled, integration.Enabled, nil}, - ); err != nil { - return diag.FromErr(err) - } - - enabled, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "ENABLED" }) - if err != nil { - return diag.FromErr(err) - } - - oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" - }) - if err != nil { - return diag.FromErr(err) - } - - oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" - }) - if err != nil { - return diag.FromErr(err) - } - - oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) - if err != nil { - return diag.FromErr(err) - } - - oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_CLIENT_AUTH_METHOD" - }) - if err != nil { - return diag.FromErr(err) - } - - oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_AUTHORIZATION_ENDPOINT" - }) - if err != nil { - return diag.FromErr(err) - } - - oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) - if err != nil { - return diag.FromErr(err) - } - - oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) - if err != nil { - return diag.FromErr(err) - } - - oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) - if err != nil { - return diag.FromErr(err) - } - oauthAccessTokenValidityInt, err := strconv.Atoi(oauthAccessTokenValidity.Value) - if err != nil { - return diag.FromErr(err) - } - oauthRefreshTokenValidityInt, err := strconv.Atoi(oauthRefreshTokenValidity.Value) - if err != nil { - return diag.FromErr(err) - } - if err = handleExternalChangesToObjectInDescribe(d, - describeMapping{"enabled", "enabled", enabled.Value, enabled.Value, nil}, - describeMapping{"oauth_access_token_validity", "oauth_access_token_validity", oauthAccessTokenValidityInt, oauthAccessTokenValidityInt, stringToIntNormalizer}, - describeMapping{"oauth_refresh_token_validity", "oauth_refresh_token_validity", oauthRefreshTokenValidityInt, oauthRefreshTokenValidityInt, stringToIntNormalizer}, - describeMapping{"oauth_client_id", "oauth_client_id", oauthClientId.Value, oauthClientId.Value, nil}, - describeMapping{"oauth_client_auth_method", "oauth_client_auth_method", oauthClientAuthMethod.Value, oauthClientAuthMethod.Value, nil}, - describeMapping{"oauth_authorization_endpoint", "oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value, oauthAuthorizationEndpoint.Value, nil}, - describeMapping{"oauth_token_endpoint", "oauth_token_endpoint", oauthTokenEndpoint.Value, oauthTokenEndpoint.Value, nil}, - describeMapping{"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, listValueToSlice(oauthAllowedScopes.Value, true, false), nil}, - describeMapping{"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, - ); err != nil { - return diag.FromErr(err) - } + if err := handleApiAuthRead(d, integration, properties, withExternalChangesMarking, []describeMapping{ + {"oauth_authorization_endpoint", "oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value, oauthAuthorizationEndpoint.Value, nil}, + {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, listValueToSlice(oauthAllowedScopes.Value, true, false), nil}, + {"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, + }); err != nil { + return diag.FromErr(err) } if !d.GetRawConfig().IsNull() { - if v := d.GetRawConfig().AsValueMap()["enabled"]; !v.IsNull() { - if err = d.Set("enabled", v.True()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_access_token_validity"]; !v.IsNull() { - intVal, _ := v.AsBigFloat().Int64() - if err = d.Set("oauth_access_token_validity", intVal); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_refresh_token_validity"]; !v.IsNull() { - intVal, _ := v.AsBigFloat().Int64() - if err = d.Set("oauth_refresh_token_validity", intVal); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_client_id"]; !v.IsNull() { - if err = d.Set("oauth_client_id", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_client_auth_method"]; !v.IsNull() { - if err = d.Set("oauth_client_auth_method", v.AsString()); err != nil { - return diag.FromErr(err) - } - } if v := d.GetRawConfig().AsValueMap()["oauth_authorization_endpoint"]; !v.IsNull() { if err = d.Set("oauth_authorization_endpoint", v.AsString()); err != nil { return diag.FromErr(err) } } - if v := d.GetRawConfig().AsValueMap()["oauth_token_endpoint"]; !v.IsNull() { - if err = d.Set("oauth_token_endpoint", v.AsString()); err != nil { - return diag.FromErr(err) - } - } if v := d.GetRawConfig().AsValueMap()["oauth_allowed_scopes"]; !v.IsNull() { - if err = d.Set("oauth_allowed_scopes", ctyValToSliceString(v)); err != nil { + if err = d.Set("oauth_allowed_scopes", ctyValToSliceString(v.AsValueSlice())); err != nil { return diag.FromErr(err) } } @@ -387,19 +213,6 @@ func ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(withExter return diag.FromErr(err) } } - if v := d.GetRawConfig().AsValueMap()["comment"]; !v.IsNull() { - if err = d.Set("comment", 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.ApiAuthSecurityIntegrationPropertiesToSchema(properties)}); err != nil { - return diag.FromErr(err) } return nil @@ -409,76 +222,31 @@ func ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(withExter func UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) - set, unset := sdk.NewApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest(), sdk.NewApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnsetRequest() - - if d.HasChange("comment") { - if v, ok := d.GetOk("comment"); ok { - set.WithComment(v.(string)) - } else { - unset.WithComment(true) - } + commonSet, commonUnset, err := handleApiAuthUpdate(d) + if err != nil { + return diag.FromErr(err) } - - if d.HasChange("enabled") { - if v := d.Get("comment").(string); v != "unknown" { - parsed, err := strconv.ParseBool(v) - if err != nil { - return diag.FromErr(err) - } - set.WithEnabled(parsed) - } else { - unset.WithEnabled(true) - } + set := &sdk.ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest{ + Enabled: commonSet.enabled, + OauthTokenEndpoint: commonSet.oauthTokenEndpoint, + OauthClientAuthMethod: commonSet.oauthClientAuthMethod, + OauthClientId: commonSet.oauthClientId, + OauthClientSecret: commonSet.oauthClientSecret, + OauthAccessTokenValidity: commonSet.oauthAccessTokenValidity, + OauthRefreshTokenValidity: commonSet.oauthRefreshTokenValidity, + Comment: commonSet.comment, } - - if d.HasChange("oauth_access_token_validity") { - if v := d.Get("oauth_access_token_validity").(int); v != -1 { - set.WithOauthAccessTokenValidity(v) - } else { - // TODO: use UNSET - set.WithOauthAccessTokenValidity(0) - } + unset := &sdk.ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnsetRequest{ + Comment: commonUnset.comment, } - if d.HasChange("oauth_authorization_endpoint") { set.WithOauthAuthorizationEndpoint(d.Get("oauth_authorization_endpoint").(string)) } - - if d.HasChange("oauth_client_auth_method") { - v := d.Get("oauth_client_auth_method").(string) - if len(v) > 0 { - value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) - if err != nil { - return diag.FromErr(err) - } - set.WithOauthClientAuthMethod(value) - } - } - - if d.HasChange("oauth_client_id") { - set.WithOauthClientId(d.Get("oauth_client_id").(string)) - } if d.HasChange("oauth_grant") { - if v := d.Get("oauth_grant").(string); v == "AUTHORIZATION_CODE" { + if v := d.Get("oauth_grant").(string); v == sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode { set.WithOauthGrantAuthorizationCode(true) } - } - - if d.HasChange("oauth_client_secret") { - set.WithOauthClientSecret(d.Get("oauth_client_secret").(string)) - } - - if d.HasChange("oauth_refresh_token_validity") { - if v := d.Get("oauth_refresh_token_validity").(int); v != -1 { - set.WithOauthRefreshTokenValidity(v) - } else { - // TODO: use UNSET - set.WithOauthRefreshTokenValidity(7776000) - } - } - - if d.HasChange("oauth_token_endpoint") { - set.WithOauthTokenEndpoint(d.Get("oauth_token_endpoint").(string)) + // else: force new } if d.HasChange("oauth_allowed_scopes") { diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go index 41b9de7b2b..b8e6166208 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go @@ -5,6 +5,7 @@ import ( acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/importchecks" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-testing/config" @@ -29,7 +30,7 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes c["oauth_refresh_token_validity"] = config.IntegerVariable(12345) c["oauth_token_endpoint"] = config.StringVariable("https://example.com") c["oauth_allowed_scopes"] = config.SetVariable(config.StringVariable("foo")) - c["oauth_grant"] = config.StringVariable("AUTHORIZATION_CODE") + c["oauth_grant"] = config.StringVariable(sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode) } return c } @@ -72,6 +73,37 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.comment.0.value", ""), ), }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic"), + ConfigVariables: m(false), + ResourceName: "snowflake_api_authentication_integration_with_authorization_code_grant.test", + ImportState: true, + ImportStateCheck: importchecks.ComposeImportStateCheck( + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "oauth_client_id", "foo"), + + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.integration_type", "API_AUTHENTICATION"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.category", "SECURITY"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.comment", ""), + + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.enabled.0.value", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_access_token_validity.0.value", "0"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_refresh_token_validity.0.value", "0"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_id.0.value", "foo"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_auth_method.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_token_endpoint.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_allowed_scopes.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_grant.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.parent_integration.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.auth_type.0.value", "OAUTH2"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.comment.0.value", ""), + ), + }, { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete"), ConfigVariables: m(true), @@ -86,7 +118,7 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity", "12345"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint", "https://example.com"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_grant", "AUTHORIZATION_CODE"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_grant", sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_allowed_scopes.#", "1"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_allowed_scopes.0", "foo"), @@ -107,7 +139,7 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_authorization_endpoint.0.value", "https://example.com"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_token_endpoint.0.value", "https://example.com"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_allowed_scopes.0.value", "[foo]"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_grant.0.value", "AUTHORIZATION_CODE"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_grant.0.value", sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.parent_integration.0.value", ""), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.auth_type.0.value", "OAUTH2"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.comment.0.value", "foo")), @@ -129,12 +161,12 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "name", id.Name()), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_access_token_validity", "-1"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_authorization_endpoint", "unknown"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_auth_method", "unknown"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_authorization_endpoint"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_auth_method"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_id", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity", "-1"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint", "unknown"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint"), ), }, }, @@ -155,7 +187,7 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_complete(t * "oauth_client_secret": config.StringVariable("foo"), "oauth_refresh_token_validity": config.IntegerVariable(12345), "oauth_token_endpoint": config.StringVariable("https://example.com"), - "oauth_grant": config.StringVariable("AUTHORIZATION_CODE"), + "oauth_grant": config.StringVariable(sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode), "oauth_allowed_scopes": config.SetVariable(config.StringVariable("foo")), } } diff --git a/pkg/resources/api_authentication_integration_with_client_credentials.go b/pkg/resources/api_authentication_integration_with_client_credentials.go index 84f986da1f..bd49f918da 100644 --- a/pkg/resources/api_authentication_integration_with_client_credentials.go +++ b/pkg/resources/api_authentication_integration_with_client_credentials.go @@ -5,13 +5,11 @@ import ( "errors" "fmt" "reflect" - "strconv" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" "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" @@ -21,7 +19,7 @@ import ( ) var apiAuthClientCredentialsSchema = func() map[string]*schema.Schema { - uniq := map[string]*schema.Schema{ + apiAuthClientCredentials := map[string]*schema.Schema{ "oauth_allowed_scopes": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, @@ -31,12 +29,11 @@ var apiAuthClientCredentialsSchema = func() map[string]*schema.Schema { "oauth_grant": { Type: schema.TypeString, Optional: true, - ValidateFunc: validation.StringInSlice([]string{"unknown", "CLIENT_CREDENTIALS"}, true), + ValidateFunc: validation.StringInSlice([]string{sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials}, true), Description: "Specifies the type of OAuth flow.", - Default: "unknown", }, } - return MergeMaps(apiAuthCommonSchema, uniq) + return MergeMaps(apiAuthCommonSchema, apiAuthClientCredentials) }() func ApiAuthenticationIntegrationWithClientCredentials() *schema.Resource { @@ -47,11 +44,11 @@ func ApiAuthenticationIntegrationWithClientCredentials() *schema.Resource { DeleteContext: DeleteContextApiAuthenticationIntegrationWithClientCredentials, Schema: apiAuthClientCredentialsSchema, CustomizeDiff: customdiff.All( - ForceNewIfChangeToDefaultString("oauth_token_endpoint"), - ForceNewIfChangeToDefaultString("oauth_client_auth_method"), - ForceNewIfChangeToDefaultString("oauth_grant"), + ForceNewIfChangeToEmptyString("oauth_token_endpoint"), + ForceNewIfChangeToEmptyString("oauth_client_auth_method"), + ForceNewIfChangeToEmptyString("oauth_grant"), ComputedIfAnyAttributeChanged(showOutputAttributeName, "enabled", "comment"), - ComputedIfAnyAttributeChanged(describeOutputAttributeName, "enabled", "comment", "oauth_refresh_token_validity", "oauth_refresh_token_validity", + ComputedIfAnyAttributeChanged(describeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", "oauth_client_id", "oauth_client_auth_method", "oauth_token_endpoint", "oauth_allowed_scopes", "oauth_grant"), ), Importer: &schema.ResourceImporter{ @@ -74,61 +71,9 @@ func ImportApiAuthenticationWithClientCredentials(ctx context.Context, d *schema if err != nil { return nil, err } - - if err = d.Set("name", integration.Name); err != nil { - return nil, err - } - if err = d.Set("enabled", integration.Enabled); err != nil { - return nil, err - } - if err = d.Set("comment", integration.Comment); err != nil { + if err := handleApiAuthImport(ctx, d, integration, properties); err != nil { return nil, err } - - oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" - }) - if err == nil { - value, err := strconv.Atoi(oauthAccessTokenValidity.Value) - if err != nil { - return nil, err - } - if err = d.Set("oauth_access_token_validity", value); err != nil { - return nil, err - } - } - oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" - }) - if err == nil { - value, err := strconv.Atoi(oauthRefreshTokenValidity.Value) - if err != nil { - return nil, err - } - if err = d.Set("oauth_refresh_token_validity", value); err != nil { - return nil, err - } - } - oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) - if err == nil { - if err = d.Set("oauth_client_id", oauthClientId.Value); err != nil { - return nil, err - } - } - oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_CLIENT_AUTH_METHOD" - }) - if err == nil { - if err = d.Set("oauth_client_auth_method", oauthClientAuthMethod.Value); err != nil { - return nil, err - } - } - oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) - if err == nil { - if err = d.Set("oauth_token_endpoint", oauthTokenEndpoint.Value); err != nil { - return nil, err - } - } oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) if err == nil { if err = d.Set("oauth_allowed_scopes", listValueToSlice(oauthAllowedScopes.Value, true, false)); err != nil { @@ -147,42 +92,24 @@ func ImportApiAuthenticationWithClientCredentials(ctx context.Context, d *schema func CreateContextApiAuthenticationIntegrationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client - enabled := d.Get("enabled").(bool) - name := d.Get("name").(string) - oauthClientId := d.Get("oauth_client_id").(string) - oauthClientSecret := d.Get("oauth_client_secret").(string) - id := sdk.NewAccountObjectIdentifier(name) - req := sdk.NewCreateApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(id, enabled, oauthClientId, oauthClientSecret) - - if v, ok := d.GetOk("comment"); ok { - req.WithComment(v.(string)) - } - - if v := d.Get("oauth_access_token_validity").(int); v != -1 { - req.WithOauthAccessTokenValidity(v) - } - - if v := d.Get("oauth_client_auth_method").(string); v != "unknown" { - value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) - if err != nil { - return diag.FromErr(err) - } - req.WithOauthClientAuthMethod(value) - } - - if v := d.Get("oauth_refresh_token_validity").(int); v != -1 { - req.WithOauthRefreshTokenValidity(v) + commonCreate, err := handleApiAuthCreate(d) + if err != nil { + return diag.FromErr(err) } + id := sdk.NewAccountObjectIdentifier(commonCreate.name) + req := sdk.NewCreateApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(id, commonCreate.enabled, commonCreate.oauthClientId, commonCreate.oauthClientSecret) + req.Comment = commonCreate.comment + req.OauthAccessTokenValidity = commonCreate.oauthAccessTokenValidity + req.OauthRefreshTokenValidity = commonCreate.oauthRefreshTokenValidity + req.OauthTokenEndpoint = commonCreate.oauthTokenEndpoint + req.OauthClientAuthMethod = commonCreate.oauthClientAuthMethod - if v := d.Get("oauth_grant").(string); v != "unknown" { - if v == "CLIENT_CREDENTIALS" { + if v, ok := d.GetOk("oauth_grant"); ok { + if v.(string) == sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials { req.WithOauthGrantClientCredentials(true) } } - if v := d.Get("oauth_token_endpoint").(string); v != "unknown" { - req.WithOauthTokenEndpoint(v) - } if v, ok := d.GetOk("oauth_allowed_scopes"); ok { elems := expandStringList(v.(*schema.Set).List()) allowedScopes := make([]sdk.AllowedScope, len(elems)) @@ -196,8 +123,7 @@ func CreateContextApiAuthenticationIntegrationWithClientCredentials(ctx context. return diag.FromErr(err) } - d.SetId(name) - + d.SetId(helpers.EncodeSnowflakeID(id)) return ReadContextApiAuthenticationIntegrationWithClientCredentials(false)(ctx, d, meta) } @@ -227,147 +153,34 @@ func ReadContextApiAuthenticationIntegrationWithClientCredentials(withExternalCh 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", integration.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("comment", integration.Comment); err != nil { + oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) + if err != nil { return diag.FromErr(err) } - if err := d.Set("enabled", integration.Enabled); err != nil { + + oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) + if err != nil { return diag.FromErr(err) } - if withExternalChangesMarking { - if err = handleExternalChangesToObjectInShow(d, - showMapping{"comment", "comment", integration.Comment, integration.Comment, nil}, - showMapping{"enabled", "enabled", integration.Enabled, integration.Enabled, nil}, - ); err != nil { - return diag.FromErr(err) - } - - enabled, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "ENABLED" }) - if err != nil { - return diag.FromErr(err) - } - - oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" - }) - if err != nil { - return diag.FromErr(err) - } - - oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" - }) - if err != nil { - return diag.FromErr(err) - } - - oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) - if err != nil { - return diag.FromErr(err) - } - oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_CLIENT_AUTH_METHOD" - }) - if err != nil { - return diag.FromErr(err) - } - - oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) - if err != nil { - return diag.FromErr(err) - } - - oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) - if err != nil { - return diag.FromErr(err) - } - - oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) - if err != nil { - return diag.FromErr(err) - } - oauthAccessTokenValidityInt, err := strconv.Atoi(oauthAccessTokenValidity.Value) - if err != nil { - return diag.FromErr(err) - } - oauthRefreshTokenValidityInt, err := strconv.Atoi(oauthRefreshTokenValidity.Value) - if err != nil { - return diag.FromErr(err) - } - if err = handleExternalChangesToObjectInDescribe(d, - describeMapping{"enabled", "enabled", enabled.Value, enabled.Value, nil}, - describeMapping{"oauth_access_token_validity", "oauth_access_token_validity", oauthAccessTokenValidityInt, oauthAccessTokenValidityInt, stringToIntNormalizer}, - describeMapping{"oauth_refresh_token_validity", "oauth_refresh_token_validity", oauthRefreshTokenValidityInt, oauthRefreshTokenValidityInt, stringToIntNormalizer}, - describeMapping{"oauth_client_id", "oauth_client_id", oauthClientId.Value, oauthClientId.Value, nil}, - describeMapping{"oauth_client_auth_method", "oauth_client_auth_method", oauthClientAuthMethod.Value, oauthClientAuthMethod.Value, nil}, - describeMapping{"oauth_token_endpoint", "oauth_token_endpoint", oauthTokenEndpoint.Value, oauthTokenEndpoint.Value, nil}, - describeMapping{"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, listValueToSlice(oauthAllowedScopes.Value, true, false), nil}, - describeMapping{"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, - ); err != nil { - return diag.FromErr(err) - } + if err := handleApiAuthRead(d, integration, properties, withExternalChangesMarking, []describeMapping{ + {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, listValueToSlice(oauthAllowedScopes.Value, true, false), nil}, + {"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, + }); err != nil { + return diag.FromErr(err) } if !d.GetRawConfig().IsNull() { - if v := d.GetRawConfig().AsValueMap()["enabled"]; !v.IsNull() { - if err = d.Set("enabled", v.True()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_access_token_validity"]; !v.IsNull() { - intVal, _ := v.AsBigFloat().Int64() - if err = d.Set("oauth_access_token_validity", intVal); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_refresh_token_validity"]; !v.IsNull() { - intVal, _ := v.AsBigFloat().Int64() - if err = d.Set("oauth_refresh_token_validity", intVal); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_client_id"]; !v.IsNull() { - if err = d.Set("oauth_client_id", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_client_auth_method"]; !v.IsNull() { - if err = d.Set("oauth_client_auth_method", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_token_endpoint"]; !v.IsNull() { - if err = d.Set("oauth_token_endpoint", v.AsString()); err != nil { - return diag.FromErr(err) - } - } if v := d.GetRawConfig().AsValueMap()["oauth_allowed_scopes"]; !v.IsNull() { - if err = d.Set("oauth_allowed_scopes", ctyValToSliceString(v)); err != nil { + if err := d.Set("oauth_allowed_scopes", ctyValToSliceString(v.AsValueSlice())); err != nil { return diag.FromErr(err) } } if v := d.GetRawConfig().AsValueMap()["oauth_grant"]; !v.IsNull() { - if err = d.Set("oauth_grant", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["comment"]; !v.IsNull() { - if err = d.Set("comment", v.AsString()); err != nil { + if err := d.Set("oauth_grant", 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.ApiAuthSecurityIntegrationPropertiesToSchema(properties)}); err != nil { - return diag.FromErr(err) - } - return nil } } @@ -375,72 +188,29 @@ func ReadContextApiAuthenticationIntegrationWithClientCredentials(withExternalCh func UpdateContextApiAuthenticationIntegrationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) - set, unset := sdk.NewApiAuthenticationWithClientCredentialsFlowIntegrationSetRequest(), sdk.NewApiAuthenticationWithClientCredentialsFlowIntegrationUnsetRequest() - - if d.HasChange("comment") { - if v, ok := d.GetOk("comment"); ok { - set.WithComment(v.(string)) - } else { - unset.WithComment(true) - } - } - - if d.HasChange("enabled") { - if v := d.Get("comment").(string); v != "unknown" { - parsed, err := strconv.ParseBool(v) - if err != nil { - return diag.FromErr(err) - } - set.WithEnabled(parsed) - } else { - unset.WithEnabled(true) - } - } - if d.HasChange("oauth_access_token_validity") { - if v := d.Get("oauth_access_token_validity").(int); v != -1 { - set.WithOauthAccessTokenValidity(v) - } else { - // TODO: use UNSET - set.WithOauthAccessTokenValidity(0) - } + commonSet, commonUnset, err := handleApiAuthUpdate(d) + if err != nil { + return diag.FromErr(err) } - - if d.HasChange("oauth_client_auth_method") { - v := d.Get("oauth_client_auth_method").(string) - if len(v) > 0 { - value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) - if err != nil { - return diag.FromErr(err) - } - set.WithOauthClientAuthMethod(value) - } + set := &sdk.ApiAuthenticationWithClientCredentialsFlowIntegrationSetRequest{ + Enabled: commonSet.enabled, + OauthTokenEndpoint: commonSet.oauthTokenEndpoint, + OauthClientAuthMethod: commonSet.oauthClientAuthMethod, + OauthClientId: commonSet.oauthClientId, + OauthClientSecret: commonSet.oauthClientSecret, + OauthAccessTokenValidity: commonSet.oauthAccessTokenValidity, + OauthRefreshTokenValidity: commonSet.oauthRefreshTokenValidity, + Comment: commonSet.comment, } - - if d.HasChange("oauth_client_id") { - set.WithOauthClientId(d.Get("oauth_client_id").(string)) + unset := &sdk.ApiAuthenticationWithClientCredentialsFlowIntegrationUnsetRequest{ + Comment: commonUnset.comment, } if d.HasChange("oauth_grant") { - if v := d.Get("oauth_grant").(string); v == "CLIENT_CREDENTIALS" { + if v := d.Get("oauth_grant").(string); v == sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials { set.WithOauthGrantClientCredentials(true) } - } - - if d.HasChange("oauth_client_secret") { - set.WithOauthClientSecret(d.Get("oauth_client_secret").(string)) - } - - if d.HasChange("oauth_refresh_token_validity") { - if v := d.Get("oauth_refresh_token_validity").(int); v != -1 { - set.WithOauthRefreshTokenValidity(v) - } else { - // TODO: use UNSET - set.WithOauthRefreshTokenValidity(7776000) - } - } - - if d.HasChange("oauth_token_endpoint") { - set.WithOauthTokenEndpoint(d.Get("oauth_token_endpoint").(string)) + // else: force new } if d.HasChange("oauth_allowed_scopes") { diff --git a/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go b/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go index 98e94d07ae..06dce53d48 100644 --- a/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go +++ b/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go @@ -5,6 +5,7 @@ import ( acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/importchecks" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-testing/config" @@ -28,7 +29,7 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_basic(t *testing. c["oauth_refresh_token_validity"] = config.IntegerVariable(12345) c["oauth_token_endpoint"] = config.StringVariable("https://example.com") c["oauth_allowed_scopes"] = config.SetVariable(config.StringVariable("foo")) - c["oauth_grant"] = config.StringVariable("CLIENT_CREDENTIALS") + c["oauth_grant"] = config.StringVariable(sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials) } return c } @@ -70,6 +71,37 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_basic(t *testing. resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.comment.0.value", ""), ), }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic"), + ConfigVariables: m(false), + ResourceName: "snowflake_api_authentication_integration_with_client_credentials.test", + ImportState: true, + ImportStateCheck: importchecks.ComposeImportStateCheck( + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "oauth_client_id", "foo"), + + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.integration_type", "API_AUTHENTICATION"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.category", "SECURITY"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.comment", ""), + + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.enabled.0.value", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_access_token_validity.0.value", "0"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_refresh_token_validity.0.value", "0"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_id.0.value", "foo"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_auth_method.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_token_endpoint.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_allowed_scopes.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_grant.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.parent_integration.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.auth_type.0.value", "OAUTH2"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.comment.0.value", ""), + ), + }, { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete"), ConfigVariables: m(true), @@ -83,7 +115,7 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_basic(t *testing. resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_refresh_token_validity", "12345"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint", "https://example.com"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_grant", "CLIENT_CREDENTIALS"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_grant", sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes.#", "1"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes.0", "foo"), @@ -103,7 +135,7 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_basic(t *testing. resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_client_auth_method.0.value", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_token_endpoint.0.value", "https://example.com"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_allowed_scopes.0.value", "[foo]"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_grant.0.value", "CLIENT_CREDENTIALS"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_grant.0.value", sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.parent_integration.0.value", ""), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.auth_type.0.value", "OAUTH2"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.comment.0.value", "foo")), @@ -125,11 +157,11 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_basic(t *testing. resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "enabled", "true"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "name", id.Name()), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_access_token_validity", "-1"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_auth_method", "unknown"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_auth_method"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_id", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_refresh_token_validity", "-1"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint", "unknown"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_refresh_token_validity"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint"), ), }, }, @@ -149,7 +181,7 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_complete(t *testi "oauth_client_secret": config.StringVariable("foo"), "oauth_refresh_token_validity": config.IntegerVariable(12345), "oauth_token_endpoint": config.StringVariable("https://example.com"), - "oauth_grant": config.StringVariable("CLIENT_CREDENTIALS"), + "oauth_grant": config.StringVariable(sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials), "oauth_allowed_scopes": config.SetVariable(config.StringVariable("foo")), } } diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer.go b/pkg/resources/api_authentication_integration_with_jwt_bearer.go index 155dc19f3d..f5468ca0d6 100644 --- a/pkg/resources/api_authentication_integration_with_jwt_bearer.go +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer.go @@ -5,13 +5,11 @@ import ( "errors" "fmt" "reflect" - "strconv" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" "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" @@ -21,7 +19,7 @@ import ( ) var apiAuthJwtBearerSchema = func() map[string]*schema.Schema { - uniq := map[string]*schema.Schema{ + apiAuthJwtBearer := map[string]*schema.Schema{ "oauth_authorization_endpoint": { Type: schema.TypeString, Optional: true, @@ -34,12 +32,11 @@ var apiAuthJwtBearerSchema = func() map[string]*schema.Schema { "oauth_grant": { Type: schema.TypeString, Optional: true, - ValidateFunc: validation.StringInSlice([]string{"unknown", "JWT_BEARER"}, true), + ValidateFunc: validation.StringInSlice([]string{sdk.ApiAuthenticationSecurityIntegrationOauthGrantJwtBearer}, true), Description: "Specifies the type of OAuth flow.", - Default: "unknown", }, } - return MergeMaps(apiAuthCommonSchema, uniq) + return MergeMaps(apiAuthCommonSchema, apiAuthJwtBearer) }() func ApiAuthenticationIntegrationWithJwtBearer() *schema.Resource { @@ -50,10 +47,10 @@ func ApiAuthenticationIntegrationWithJwtBearer() *schema.Resource { DeleteContext: DeleteContextApiAuthenticationIntegrationWithJwtBearer, Schema: apiAuthJwtBearerSchema, CustomizeDiff: customdiff.All( - ForceNewIfChangeToDefaultString("oauth_token_endpoint"), - ForceNewIfChangeToDefaultString("oauth_authorization_endpoint"), - ForceNewIfChangeToDefaultString("oauth_client_auth_method"), - ForceNewIfChangeToDefaultString("oauth_grant"), + ForceNewIfChangeToEmptyString("oauth_token_endpoint"), + ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), + ForceNewIfChangeToEmptyString("oauth_client_auth_method"), + ForceNewIfChangeToEmptyString("oauth_grant"), ComputedIfAnyAttributeChanged(showOutputAttributeName, "enabled", "comment"), ComputedIfAnyAttributeChanged(describeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", "oauth_client_id", "oauth_client_auth_method", "oauth_authorization_endpoint", @@ -79,64 +76,20 @@ func ImportApiAuthenticationWithJwtBearer(ctx context.Context, d *schema.Resourc if err != nil { return nil, err } - - if err = d.Set("name", integration.Name); err != nil { - return nil, err - } - if err = d.Set("enabled", integration.Enabled); err != nil { - return nil, err - } - if err = d.Set("comment", integration.Comment); err != nil { + if err := handleApiAuthImport(ctx, d, integration, properties); err != nil { return nil, err } - - oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" + oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_AUTHORIZATION_ENDPOINT" }) if err == nil { - value, err := strconv.Atoi(oauthAccessTokenValidity.Value) - if err != nil { - return nil, err - } - if err = d.Set("oauth_access_token_validity", value); err != nil { - return nil, err - } - } - oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" - }) - if err == nil { - value, err := strconv.Atoi(oauthRefreshTokenValidity.Value) - if err != nil { - return nil, err - } - if err = d.Set("oauth_refresh_token_validity", value); err != nil { - return nil, err - } - } - oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) - if err == nil { - if err = d.Set("oauth_client_id", oauthClientId.Value); err != nil { - return nil, err - } - } - oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_CLIENT_AUTH_METHOD" - }) - if err == nil { - if err = d.Set("oauth_client_auth_method", oauthClientAuthMethod.Value); err != nil { - return nil, err - } - } - oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) - if err == nil { - if err = d.Set("oauth_token_endpoint", oauthTokenEndpoint.Value); err != nil { + if err = d.Set("oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value); err != nil { return nil, err } } oauthAssertionIssuer, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ASSERTION_ISSUER" }) if err == nil { - if err = d.Set("oauth_assertion_issuer", oauthAssertionIssuer); err != nil { + if err = d.Set("oauth_assertion_issuer", oauthAssertionIssuer.Value); err != nil { return nil, err } } @@ -152,54 +105,33 @@ func ImportApiAuthenticationWithJwtBearer(ctx context.Context, d *schema.Resourc func CreateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client - enabled := d.Get("enabled").(bool) - name := d.Get("name").(string) - oauthClientId := d.Get("oauth_client_id").(string) - oauthClientSecret := d.Get("oauth_client_secret").(string) - oauthAssertionIssuer := d.Get("oauth_client_secret").(string) - id := sdk.NewAccountObjectIdentifier(name) - req := sdk.NewCreateApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest(id, enabled, oauthAssertionIssuer, oauthClientId, oauthClientSecret) - - if v, ok := d.GetOk("comment"); ok { - req.WithComment(v.(string)) - } - - if v := d.Get("oauth_access_token_validity").(int); v != -1 { - req.WithOauthAccessTokenValidity(v) - } - - if v := d.Get("oauth_authorization_endpoint").(string); v != "unknown" { - req.WithOauthAuthorizationEndpoint(v) - } - - if v := d.Get("oauth_client_auth_method").(string); v != "unknown" { - value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) - if err != nil { - return diag.FromErr(err) - } - req.WithOauthClientAuthMethod(value) + commonCreate, err := handleApiAuthCreate(d) + if err != nil { + return diag.FromErr(err) } + id := sdk.NewAccountObjectIdentifier(commonCreate.name) + req := sdk.NewCreateApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest(id, commonCreate.enabled, d.Get("oauth_assertion_issuer").(string), commonCreate.oauthClientId, commonCreate.oauthClientSecret) + req.Comment = commonCreate.comment + req.OauthAccessTokenValidity = commonCreate.oauthAccessTokenValidity + req.OauthRefreshTokenValidity = commonCreate.oauthRefreshTokenValidity + req.OauthTokenEndpoint = commonCreate.oauthTokenEndpoint + req.OauthClientAuthMethod = commonCreate.oauthClientAuthMethod - if v := d.Get("oauth_refresh_token_validity").(int); v != -1 { - req.WithOauthRefreshTokenValidity(v) + if v, ok := d.GetOk("oauth_authorization_endpoint"); ok { + req.WithOauthAuthorizationEndpoint(v.(string)) } - if v := d.Get("oauth_grant").(string); v != "unknown" { - if v == "JWT_BEARER" { + if v, ok := d.GetOk("oauth_grant"); ok { + if v.(string) == sdk.ApiAuthenticationSecurityIntegrationOauthGrantJwtBearer { req.WithOauthGrantJwtBearer(true) } } - if v := d.Get("oauth_token_endpoint").(string); v != "unknown" { - req.WithOauthTokenEndpoint(v) - } - if err := client.SecurityIntegrations.CreateApiAuthenticationWithJwtBearerFlow(ctx, req); err != nil { return diag.FromErr(err) } - d.SetId(name) - + d.SetId(helpers.EncodeSnowflakeID(id)) return ReadContextApiAuthenticationIntegrationWithJwtBearer(false)(ctx, d, meta) } @@ -230,135 +162,34 @@ func ReadContextApiAuthenticationIntegrationWithJwtBearer(withExternalChangesMar 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", integration.Name); err != nil { + oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_AUTHORIZATION_ENDPOINT" + }) + if err != nil { return diag.FromErr(err) } - if err := d.Set("comment", integration.Comment); err != nil { + + oauthAssertionIssuer, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ASSERTION_ISSUER" }) + if err != nil { return diag.FromErr(err) } - if err := d.Set("enabled", integration.Enabled); err != nil { + oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) + if err != nil { return diag.FromErr(err) } - if withExternalChangesMarking { - if err = handleExternalChangesToObjectInShow(d, - showMapping{"comment", "comment", integration.Comment, integration.Comment, nil}, - showMapping{"enabled", "enabled", integration.Enabled, integration.Enabled, nil}, - ); err != nil { - return diag.FromErr(err) - } - - enabled, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "ENABLED" }) - if err != nil { - return diag.FromErr(err) - } - - oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" - }) - if err != nil { - return diag.FromErr(err) - } - - oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" - }) - if err != nil { - return diag.FromErr(err) - } - - oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) - if err != nil { - return diag.FromErr(err) - } - - oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_CLIENT_AUTH_METHOD" - }) - if err != nil { - return diag.FromErr(err) - } - - oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "OAUTH_AUTHORIZATION_ENDPOINT" - }) - if err != nil { - return diag.FromErr(err) - } - - oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) - if err != nil { - return diag.FromErr(err) - } - - oauthAssertionIssuer, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ASSERTION_ISSUER" }) - if err != nil { - return diag.FromErr(err) - } - - oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) - if err != nil { - return diag.FromErr(err) - } - oauthAccessTokenValidityInt, err := strconv.Atoi(oauthAccessTokenValidity.Value) - if err != nil { - return diag.FromErr(err) - } - oauthRefreshTokenValidityInt, err := strconv.Atoi(oauthRefreshTokenValidity.Value) - if err != nil { - return diag.FromErr(err) - } - if err = handleExternalChangesToObjectInDescribe(d, - describeMapping{"enabled", "enabled", enabled.Value, enabled.Value, nil}, - describeMapping{"oauth_access_token_validity", "oauth_access_token_validity", oauthAccessTokenValidityInt, oauthAccessTokenValidityInt, stringToIntNormalizer}, - describeMapping{"oauth_refresh_token_validity", "oauth_refresh_token_validity", oauthRefreshTokenValidityInt, oauthRefreshTokenValidityInt, stringToIntNormalizer}, - describeMapping{"oauth_client_id", "oauth_client_id", oauthClientId.Value, oauthClientId.Value, nil}, - describeMapping{"oauth_client_auth_method", "oauth_client_auth_method", oauthClientAuthMethod.Value, oauthClientAuthMethod.Value, nil}, - describeMapping{"oauth_authorization_endpoint", "oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value, oauthAuthorizationEndpoint.Value, nil}, - describeMapping{"oauth_token_endpoint", "oauth_token_endpoint", oauthTokenEndpoint.Value, oauthTokenEndpoint.Value, nil}, - describeMapping{"oauth_assertion_issuer", "oauth_assertion_issuer", oauthAssertionIssuer.Value, oauthAssertionIssuer.Value, nil}, - describeMapping{"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, - ); err != nil { - return diag.FromErr(err) - } + if err := handleApiAuthRead(d, integration, properties, withExternalChangesMarking, []describeMapping{ + {"oauth_authorization_endpoint", "oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value, oauthAuthorizationEndpoint.Value, nil}, + {"oauth_assertion_issuer", "oauth_assertion_issuer", oauthAssertionIssuer.Value, oauthAssertionIssuer.Value, nil}, + {"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, + }); err != nil { + return diag.FromErr(err) } if !d.GetRawConfig().IsNull() { - if v := d.GetRawConfig().AsValueMap()["enabled"]; !v.IsNull() { - if err = d.Set("enabled", v.True()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_access_token_validity"]; !v.IsNull() { - intVal, _ := v.AsBigFloat().Int64() - if err = d.Set("oauth_access_token_validity", intVal); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_refresh_token_validity"]; !v.IsNull() { - intVal, _ := v.AsBigFloat().Int64() - if err = d.Set("oauth_refresh_token_validity", intVal); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_client_id"]; !v.IsNull() { - if err = d.Set("oauth_client_id", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_client_auth_method"]; !v.IsNull() { - if err = d.Set("oauth_client_auth_method", v.AsString()); err != nil { - return diag.FromErr(err) - } - } if v := d.GetRawConfig().AsValueMap()["oauth_authorization_endpoint"]; !v.IsNull() { if err = d.Set("oauth_authorization_endpoint", v.AsString()); err != nil { return diag.FromErr(err) } } - if v := d.GetRawConfig().AsValueMap()["oauth_token_endpoint"]; !v.IsNull() { - if err = d.Set("oauth_token_endpoint", v.AsString()); err != nil { - return diag.FromErr(err) - } - } if v := d.GetRawConfig().AsValueMap()["oauth_assertion_issuer"]; !v.IsNull() { if err = d.Set("oauth_assertion_issuer", v.AsString()); err != nil { return diag.FromErr(err) @@ -369,19 +200,6 @@ func ReadContextApiAuthenticationIntegrationWithJwtBearer(withExternalChangesMar return diag.FromErr(err) } } - if v := d.GetRawConfig().AsValueMap()["comment"]; !v.IsNull() { - if err = d.Set("comment", 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.ApiAuthSecurityIntegrationPropertiesToSchema(properties)}); err != nil { - return diag.FromErr(err) } return nil @@ -391,76 +209,31 @@ func ReadContextApiAuthenticationIntegrationWithJwtBearer(withExternalChangesMar func UpdateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) - set, unset := sdk.NewApiAuthenticationWithJwtBearerFlowIntegrationSetRequest(), sdk.NewApiAuthenticationWithJwtBearerFlowIntegrationUnsetRequest() - - if d.HasChange("comment") { - if v, ok := d.GetOk("comment"); ok { - set.WithComment(v.(string)) - } else { - unset.WithComment(true) - } + commonSet, commonUnset, err := handleApiAuthUpdate(d) + if err != nil { + return diag.FromErr(err) } - - if d.HasChange("enabled") { - if v := d.Get("comment").(string); v != "unknown" { - parsed, err := strconv.ParseBool(v) - if err != nil { - return diag.FromErr(err) - } - set.WithEnabled(parsed) - } else { - unset.WithEnabled(true) - } + set := &sdk.ApiAuthenticationWithJwtBearerFlowIntegrationSetRequest{ + Enabled: commonSet.enabled, + OauthTokenEndpoint: commonSet.oauthTokenEndpoint, + OauthClientAuthMethod: commonSet.oauthClientAuthMethod, + OauthClientId: commonSet.oauthClientId, + OauthClientSecret: commonSet.oauthClientSecret, + OauthAccessTokenValidity: commonSet.oauthAccessTokenValidity, + OauthRefreshTokenValidity: commonSet.oauthRefreshTokenValidity, + Comment: commonSet.comment, } - - if d.HasChange("oauth_access_token_validity") { - if v := d.Get("oauth_access_token_validity").(int); v != -1 { - set.WithOauthAccessTokenValidity(v) - } else { - // TODO: use UNSET - set.WithOauthAccessTokenValidity(0) - } + unset := &sdk.ApiAuthenticationWithJwtBearerFlowIntegrationUnsetRequest{ + Comment: commonUnset.comment, } - if d.HasChange("oauth_authorization_endpoint") { set.WithOauthAuthorizationEndpoint(d.Get("oauth_authorization_endpoint").(string)) } - - if d.HasChange("oauth_client_auth_method") { - v := d.Get("oauth_client_auth_method").(string) - if len(v) > 0 { - value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) - if err != nil { - return diag.FromErr(err) - } - set.WithOauthClientAuthMethod(value) - } - } - - if d.HasChange("oauth_client_id") { - set.WithOauthClientId(d.Get("oauth_client_id").(string)) - } if d.HasChange("oauth_grant") { - if v := d.Get("oauth_grant").(string); v == "JWT_BEARER" { + if v := d.Get("oauth_grant").(string); v == sdk.ApiAuthenticationSecurityIntegrationOauthGrantJwtBearer { set.WithOauthGrantJwtBearer(true) } - } - - if d.HasChange("oauth_client_secret") { - set.WithOauthClientSecret(d.Get("oauth_client_secret").(string)) - } - - if d.HasChange("oauth_refresh_token_validity") { - if v := d.Get("oauth_refresh_token_validity").(int); v != -1 { - set.WithOauthRefreshTokenValidity(v) - } else { - // TODO: use UNSET - set.WithOauthRefreshTokenValidity(7776000) - } - } - - if d.HasChange("oauth_token_endpoint") { - set.WithOauthTokenEndpoint(d.Get("oauth_token_endpoint").(string)) + // else: force new } if !reflect.DeepEqual(*set, sdk.ApiAuthenticationWithJwtBearerFlowIntegrationSetRequest{}) { if err := client.SecurityIntegrations.AlterApiAuthenticationWithJwtBearerFlow(ctx, sdk.NewAlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest(id).WithSet(*set)); err != nil { diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go b/pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go index a047be0ab2..9f4d24d8ff 100644 --- a/pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go @@ -5,6 +5,7 @@ import ( acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/importchecks" "github.com/hashicorp/terraform-plugin-testing/config" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -50,6 +51,37 @@ func TestAcc_ApiAuthenticationIntegrationWithJwtBearer_basic(t *testing.T) { resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on"), ), }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic"), + ConfigVariables: m(false), + ResourceName: "snowflake_api_authentication_integration_with_jwt_bearer.test", + ImportState: true, + ImportStateCheck: importchecks.ComposeImportStateCheck( + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "oauth_client_id", "foo"), + + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.integration_type", "API_AUTHENTICATION"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.category", "SECURITY"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.comment", ""), + + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.enabled.0.value", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_access_token_validity.0.value", "0"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_refresh_token_validity.0.value", "0"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_id.0.value", "foo"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_auth_method.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_token_endpoint.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_allowed_scopes.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_grant.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.parent_integration.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.auth_type.0.value", "OAUTH2"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.comment.0.value", ""), + ), + }, { ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete"), ConfigVariables: m(true), diff --git a/pkg/resources/common.go b/pkg/resources/common.go index 6046b09d6e..204ca93e1a 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -1,15 +1,11 @@ package resources import ( - "fmt" "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) // DiffSuppressStatement will suppress diffs between statements if they differ in only case or in @@ -58,80 +54,6 @@ func suppressQuoting(_, oldValue, newValue string, _ *schema.ResourceData) bool } } -var apiAuthCommonSchema = map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "Specifies the identifier (i.e. name) for the integration. This value must be unique in your account.", - }, - "enabled": { - Type: schema.TypeBool, - Required: true, - Description: "Specifies whether this security integration is enabled or disabled.", - }, - "oauth_token_endpoint": { - Type: schema.TypeString, - Optional: true, - Default: "unknown", - Description: "Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly).", - }, - "oauth_client_auth_method": { - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: sdkValidation(sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption), - Default: "unknown", - Description: fmt.Sprintf("Specifies that POST is used as the authentication method to the external service. Valid options are: %v", sdk.AsStringList(sdk.AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption)), - }, - "oauth_client_id": { - Type: schema.TypeString, - Required: true, - Description: "Specifies the client ID for the OAuth application in the external service.", - }, - "oauth_client_secret": { - Type: schema.TypeString, - Required: true, - Description: "Specifies the client secret for the OAuth application in the ServiceNow instance from the previous step. The connector uses this to request an access token from the ServiceNow instance.", - }, - "oauth_access_token_validity": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntAtLeast(0), - Default: -1, - Description: "Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server.", - DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("oauth_access_token_validity"), - }, - "oauth_refresh_token_validity": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntAtLeast(1), - Default: -1, - Description: "Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server.", - DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInShow("oauth_refresh_token_validity"), - }, - "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 INTEGRATIONS` for the given security integration.", - Elem: &schema.Resource{ - Schema: schemas.ShowSecurityIntegrationSchema, - }, - }, - describeOutputAttributeName: { - Type: schema.TypeList, - Computed: true, - Description: "Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security integration.", - Elem: &schema.Resource{ - Schema: schemas.DescribeApiAuthSecurityIntegrationSchema, - }, - }, -} - func listValueToSlice(value string, trimBrackets bool, trimQuotes bool) []string { if trimBrackets { value = strings.TrimLeft(value, "[") @@ -149,8 +71,7 @@ func listValueToSlice(value string, trimBrackets bool, trimQuotes bool) []string return elems } -func ctyValToSliceString(val cty.Value) []string { - valueElems := val.AsValueSlice() +func ctyValToSliceString(valueElems []cty.Value) []string { elems := make([]string, len(valueElems)) for i, v := range valueElems { elems[i] = v.AsString() diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index e5e7a8b504..56cf0b5409 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -73,10 +73,10 @@ func ComputedIfAnyAttributeChanged(key string, changedAttributeKeys ...string) s }) } -// ForceNewIfChangeToDefaultString sets a ForceNew for a string field which was changed to an unknown value. -func ForceNewIfChangeToDefaultString(key string) schema.CustomizeDiffFunc { +// 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 oldString != "unknown" && newString == "unknown" + return len(oldString) > 0 && len(newString) == 0 }) } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf index 734576561b..25b0e145af 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf @@ -1,5 +1,6 @@ resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { name = var.name + enabled = var.enabled oauth_client_id = var.oauth_client_id oauth_client_secret = var.oauth_client_secret oauth_assertion_issuer = var.oauth_assertion_issuer diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf index 10920ad938..879a48ea65 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf @@ -1,33 +1,15 @@ -variable "comment" { - type = string -} variable "enabled" { type = bool } variable "name" { type = string } -variable "oauth_access_token_validity" { - type = number -} -variable "oauth_authorization_endpoint" { - type = string -} -variable "oauth_client_auth_method" { - type = string -} variable "oauth_client_id" { type = string } variable "oauth_client_secret" { type = string } -variable "oauth_refresh_token_validity" { - type = number -} -variable "oauth_token_endpoint" { - type = string -} variable "oauth_assertion_issuer" { type = string } diff --git a/pkg/schemas/api_authentication_security_integration.go b/pkg/schemas/api_authentication_security_integration.go index 450f7eae79..e1a85f3807 100644 --- a/pkg/schemas/api_authentication_security_integration.go +++ b/pkg/schemas/api_authentication_security_integration.go @@ -42,14 +42,7 @@ func ApiAuthSecurityIntegrationPropertiesToSchema(securityIntegrationProperties "PARENT_INTEGRATION", "AUTH_TYPE", "COMMENT": - securityIntegrationSchema[strings.ToLower(securityIntegrationProperty.Name)] = []map[string]any{ - { - "name": securityIntegrationProperty.Name, - "type": securityIntegrationProperty.Type, - "value": securityIntegrationProperty.Value, - "default": securityIntegrationProperty.Default, - }, - } + securityIntegrationSchema[strings.ToLower(securityIntegrationProperty.Name)] = []map[string]any{SecurityIntegrationPropertyToSchema(&securityIntegrationProperty)} default: log.Printf("unknown field from DESCRIBE: %v", securityIntegrationProperty.Name) } diff --git a/pkg/sdk/security_integrations_def.go b/pkg/sdk/security_integrations_def.go index da6c92481b..db9d832cf4 100644 --- a/pkg/sdk/security_integrations_def.go +++ b/pkg/sdk/security_integrations_def.go @@ -9,7 +9,12 @@ import ( //go:generate go run ./poc/main.go -const SecurityIntegrationCategory = "SECURITY" +const ( + SecurityIntegrationCategory = "SECURITY" + ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode = "AUTHORIZATION_CODE" + ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials = "CLIENT_CREDENTIALS" + ApiAuthenticationSecurityIntegrationOauthGrantJwtBearer = "JWT_BEARER" +) type ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption string diff --git a/v1-preparations/ESSENTIAL_GA_OBJECTS.MD b/v1-preparations/ESSENTIAL_GA_OBJECTS.MD index d175228f57..08876ae856 100644 --- a/v1-preparations/ESSENTIAL_GA_OBJECTS.MD +++ b/v1-preparations/ESSENTIAL_GA_OBJECTS.MD @@ -19,7 +19,7 @@ newer provider versions. We will address these while working on the given object | RESOURCE MONITOR | ❌ | [#1990](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1990), [#1832](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1832), [#1821](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1821), [#1754](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1754), [#1716](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1716), [#1714](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1714), [#1624](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1624), [#1500](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1500), [#1175](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1175) | | ROLE | ❌ | - | | SECURITY INTEGRATION | 👨‍💻 | [#2719](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2719), [#2568](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2568), [#2177](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2177), [#1851](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1851), [#1773](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1773), [#1741](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1741), [#1637](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1637), [#1503](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1503), [#1498](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1498), [#1421](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1421), [#1224](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1224) | -| USER | ❌ | [#2817](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2817), [#2662](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2662), [#1572](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1572), [#1535](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1535), [#1155](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1155) | +| USER | ❌ | [#2902](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2902), [#2817](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2817), [#2662](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2662), [#1572](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1572), [#1535](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1535), [#1155](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1155) | | WAREHOUSE | 👨‍💻 | [#1844](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1844), [#1104](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1104) | | FUNCTION | ❌ | [2859](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2859), [#2735](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2735), [#2426](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2426), [#1479](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1479), [#1393](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1393), [#1208](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1208), [#1079](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1079) | | MASKING POLICY | ❌ | [#2236](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2236), [#2035](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2035), [#1799](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1799), [#1764](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1764), [#1656](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1656), [#1444](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1444), [#1422](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1422), [#1097](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1097) | @@ -33,4 +33,4 @@ newer provider versions. We will address these while working on the given object | TAG | ❌ | [#2598](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2598), [#1910](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1910), [#1909](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1909), [#1862](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1862), [#1806](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1806), [#1657](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1657), [#1496](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1496), [#1443](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1443), [#1394](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1394), [#1372](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1372), [#1074](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1074) | | TASK | ❌ | [#1419](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1419), [#1250](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1250), [#1194](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1194), [#1088](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1088) | | VIEW | ❌ | [#2430](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2430), [#2085](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2085), [#2055](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2055), [#2031](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2031), [#1526](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1526), [#1253](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1253), [#1049](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1049) | -| snowflake_unsafe_execute | ❌ | - | \ No newline at end of file +| snowflake_unsafe_execute | ❌ | - | From aa93eaf8c9daa0a25fab0ee70364b92eb63b8986 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Tue, 2 Jul 2024 14:45:17 +0200 Subject: [PATCH 10/18] Minor fixes --- ...tegration_with_authorization_code_grant.md | 17 ++--- ...ion_integration_with_client_credentials.md | 18 ++--- ...hentication_integration_with_jwt_bearer.md | 20 +++--- ...tegration_with_authorization_code_grant.go | 4 +- ...ion_integration_with_client_credentials.go | 4 +- pkg/resources/common.go | 10 +-- pkg/resources/common_test.go | 71 +++++++++++++++++++ ...api_authentication_security_integration.go | 1 + pkg/sdk/security_integrations_def.go | 2 +- 9 files changed, 110 insertions(+), 37 deletions(-) diff --git a/docs/resources/api_authentication_integration_with_authorization_code_grant.md b/docs/resources/api_authentication_integration_with_authorization_code_grant.md index ac40baadca..cbbbfd11e5 100644 --- a/docs/resources/api_authentication_integration_with_authorization_code_grant.md +++ b/docs/resources/api_authentication_integration_with_authorization_code_grant.md @@ -15,20 +15,21 @@ description: |- # basic resource resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { enabled = true - name = "foo" - oauth_client_id = "foo" - oauth_client_secret = "foo" + name = "test" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" } # resource with all fields set resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { - comment = "foo" + comment = "comment" enabled = true - name = "foo" + name = "test" oauth_access_token_validity = 42 + oauth_allowed_scopes = ["useraccount"] oauth_authorization_endpoint = "https://example.com" oauth_client_auth_method = "CLIENT_SECRET_POST" - oauth_client_id = "foo" - oauth_client_secret = "foo" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" oauth_refresh_token_validity = 42 oauth_token_endpoint = "https://example.com" } @@ -52,7 +53,7 @@ resource "snowflake_api_authentication_integration_with_authorization_code_grant - `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. - `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. Valid options are: [CLIENT_SECRET_POST] - `oauth_grant` (String) Specifies the type of OAuth flow. -- `oauth_refresh_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. +- `oauth_refresh_token_validity` (Number) Specifies the value to determine the validity of the refresh token obtained from the OAuth server. - `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). ### Read-Only diff --git a/docs/resources/api_authentication_integration_with_client_credentials.md b/docs/resources/api_authentication_integration_with_client_credentials.md index 2ecda2920d..d6bffd9b18 100644 --- a/docs/resources/api_authentication_integration_with_client_credentials.md +++ b/docs/resources/api_authentication_integration_with_client_credentials.md @@ -15,20 +15,20 @@ description: |- # basic resource resource "snowflake_api_authentication_integration_with_client_credentials" "test" { enabled = true - name = "foo" - oauth_client_id = "foo" - oauth_client_secret = "foo" + name = "test" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" } # resource with all fields set resource "snowflake_api_authentication_integration_with_client_credentials" "test" { - comment = "foo" + comment = "comment" enabled = true - name = "foo" + name = "test" oauth_access_token_validity = 42 - oauth_allowed_scopes = ["foo"] + oauth_allowed_scopes = ["useraccount"] oauth_client_auth_method = "CLIENT_SECRET_POST" - oauth_client_id = "foo" - oauth_client_secret = "foo" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" oauth_token_endpoint = "https://example.com" } ``` @@ -50,7 +50,7 @@ resource "snowflake_api_authentication_integration_with_client_credentials" "tes - `oauth_allowed_scopes` (Set of String) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. - `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. Valid options are: [CLIENT_SECRET_POST] - `oauth_grant` (String) Specifies the type of OAuth flow. -- `oauth_refresh_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. +- `oauth_refresh_token_validity` (Number) Specifies the value to determine the validity of the refresh token obtained from the OAuth server. - `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). ### Read-Only diff --git a/docs/resources/api_authentication_integration_with_jwt_bearer.md b/docs/resources/api_authentication_integration_with_jwt_bearer.md index 7064a2e672..28fc7276f9 100644 --- a/docs/resources/api_authentication_integration_with_jwt_bearer.md +++ b/docs/resources/api_authentication_integration_with_jwt_bearer.md @@ -15,24 +15,24 @@ description: |- # basic resource resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { enabled = true - name = "foo" - oauth_client_id = "foo" - oauth_client_secret = "foo" - oauth_assertion_issuer = "foo" + name = "test" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" + oauth_assertion_issuer = "issuer" } # resource with all fields set resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { - comment = "foo" + comment = "comment" enabled = true - name = "foo" + name = "test" oauth_access_token_validity = 42 oauth_authorization_endpoint = "https://example.com" oauth_client_auth_method = "CLIENT_SECRET_POST" - oauth_client_id = "foo" - oauth_client_secret = "foo" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" oauth_refresh_token_validity = 42 oauth_token_endpoint = "https://example.com" - oauth_assertion_issuer = "foo" + oauth_assertion_issuer = "issuer" } ``` @@ -54,7 +54,7 @@ resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { - `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. - `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. Valid options are: [CLIENT_SECRET_POST] - `oauth_grant` (String) Specifies the type of OAuth flow. -- `oauth_refresh_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. +- `oauth_refresh_token_validity` (Number) Specifies the value to determine the validity of the refresh token obtained from the OAuth server. - `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). ### Read-Only diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go index 283dc4fba6..9a299e765f 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go @@ -92,7 +92,7 @@ func ImportApiAuthenticationWithAuthorizationCodeGrant(ctx context.Context, d *s } oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) if err == nil { - if err = d.Set("oauth_allowed_scopes", listValueToSlice(oauthAllowedScopes.Value, true, false)); err != nil { + if err = d.Set("oauth_allowed_scopes", listValueToSlice(oauthAllowedScopes.Value, false)); err != nil { return nil, err } } @@ -192,7 +192,7 @@ func ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(withExter } if err := handleApiAuthRead(d, integration, properties, withExternalChangesMarking, []describeMapping{ {"oauth_authorization_endpoint", "oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value, oauthAuthorizationEndpoint.Value, nil}, - {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, listValueToSlice(oauthAllowedScopes.Value, true, false), nil}, + {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, listValueToSlice(oauthAllowedScopes.Value, false), nil}, {"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, }); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/api_authentication_integration_with_client_credentials.go b/pkg/resources/api_authentication_integration_with_client_credentials.go index bd49f918da..de01ded6db 100644 --- a/pkg/resources/api_authentication_integration_with_client_credentials.go +++ b/pkg/resources/api_authentication_integration_with_client_credentials.go @@ -76,7 +76,7 @@ func ImportApiAuthenticationWithClientCredentials(ctx context.Context, d *schema } oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) if err == nil { - if err = d.Set("oauth_allowed_scopes", listValueToSlice(oauthAllowedScopes.Value, true, false)); err != nil { + if err = d.Set("oauth_allowed_scopes", listValueToSlice(oauthAllowedScopes.Value, false)); err != nil { return nil, err } } @@ -164,7 +164,7 @@ func ReadContextApiAuthenticationIntegrationWithClientCredentials(withExternalCh } if err := handleApiAuthRead(d, integration, properties, withExternalChangesMarking, []describeMapping{ - {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, listValueToSlice(oauthAllowedScopes.Value, true, false), nil}, + {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, listValueToSlice(oauthAllowedScopes.Value, false), nil}, {"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, }); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/common.go b/pkg/resources/common.go index 204ca93e1a..6f4731bbc4 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -54,11 +54,9 @@ func suppressQuoting(_, oldValue, newValue string, _ *schema.ResourceData) bool } } -func listValueToSlice(value string, trimBrackets bool, trimQuotes bool) []string { - if trimBrackets { - value = strings.TrimLeft(value, "[") - value = strings.TrimRight(value, "]") - } +func listValueToSlice(value string, trimQuotes bool) []string { + value = strings.TrimLeft(value, "[") + value = strings.TrimRight(value, "]") if value == "" { return nil } @@ -66,6 +64,8 @@ func listValueToSlice(value string, trimBrackets bool, trimQuotes bool) []string for i := range elems { if trimQuotes { elems[i] = strings.Trim(elems[i], " '") + } else { + elems[i] = strings.Trim(elems[i], " ") } } return elems diff --git a/pkg/resources/common_test.go b/pkg/resources/common_test.go index 1212114e11..50bcaffdf4 100644 --- a/pkg/resources/common_test.go +++ b/pkg/resources/common_test.go @@ -1,6 +1,7 @@ package resources import ( + "reflect" "testing" "github.com/stretchr/testify/require" @@ -47,3 +48,73 @@ func Test_suppressIdentifierQuoting(t *testing.T) { require.False(t, result) }) } + +func Test_listValueToSlice(t *testing.T) { + tests := []struct { + name string + value string + trimQuotes bool + want []string + }{ + { + name: "empty list", + want: nil, + }, + { + name: "empty list with brackets", + value: "[]", + want: nil, + }, + { + name: "one element in list", + value: "a", + want: []string{"a"}, + }, + { + name: "one element in list, wrapped", + value: "'a'", + trimQuotes: true, + want: []string{"a"}, + }, + { + name: "one element in list with brackets", + value: "[a]", + want: []string{"a"}, + }, + { + name: "one element in list wrapped, with brackets", + value: "['a']", + trimQuotes: true, + want: []string{"a"}, + }, + { + name: "multiple elements in list", + value: "a, b", + want: []string{"a", "b"}, + }, + { + name: "multiple elements in list wrapped", + value: "'a', 'b'", + trimQuotes: true, + want: []string{"a", "b"}, + }, + { + name: "multiple elements in list with brackets", + value: "[a, b]", + want: []string{"a", "b"}, + }, + { + name: "multiple elements in list wrapped, with brackets", + value: "['a', 'b' ]", + trimQuotes: true, + want: []string{"a", "b"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := listValueToSlice(tt.value, tt.trimQuotes); !reflect.DeepEqual(got, tt.want) { + t.Errorf("listValueToSlice() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/schemas/api_authentication_security_integration.go b/pkg/schemas/api_authentication_security_integration.go index e1a85f3807..913c99574e 100644 --- a/pkg/schemas/api_authentication_security_integration.go +++ b/pkg/schemas/api_authentication_security_integration.go @@ -29,6 +29,7 @@ var _ = DescribeApiAuthSecurityIntegrationSchema func ApiAuthSecurityIntegrationPropertiesToSchema(securityIntegrationProperties []sdk.SecurityIntegrationProperty) map[string]any { securityIntegrationSchema := make(map[string]any) for _, securityIntegrationProperty := range securityIntegrationProperties { + securityIntegrationProperty := securityIntegrationProperty switch securityIntegrationProperty.Name { case "ENABLED", "OAUTH_ACCESS_TOKEN_VALIDITY", diff --git a/pkg/sdk/security_integrations_def.go b/pkg/sdk/security_integrations_def.go index db9d832cf4..20aaa7779b 100644 --- a/pkg/sdk/security_integrations_def.go +++ b/pkg/sdk/security_integrations_def.go @@ -12,7 +12,7 @@ import ( const ( SecurityIntegrationCategory = "SECURITY" ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode = "AUTHORIZATION_CODE" - ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials = "CLIENT_CREDENTIALS" + ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials = "CLIENT_CREDENTIALS" //nolint:gosec ApiAuthenticationSecurityIntegrationOauthGrantJwtBearer = "JWT_BEARER" ) From 744b4ed3a75bff2377d93539547f5d1aac71b1ad Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Thu, 4 Jul 2024 15:55:55 +0200 Subject: [PATCH 11/18] Fixes --- .../api_authentication_integration_common.go | 12 ++++++------ ...tion_integration_with_authorization_code_grant.go | 4 ++-- ...entication_integration_with_client_credentials.go | 4 ++-- ...api_authentication_integration_with_jwt_bearer.go | 4 ++-- pkg/sdk/security_integrations_gen_test.go | 1 - 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/pkg/resources/api_authentication_integration_common.go b/pkg/resources/api_authentication_integration_common.go index e51666c832..82fd7ca023 100644 --- a/pkg/resources/api_authentication_integration_common.go +++ b/pkg/resources/api_authentication_integration_common.go @@ -65,7 +65,7 @@ var apiAuthCommonSchema = map[string]*schema.Schema{ Optional: true, Description: "Specifies a comment for the integration.", }, - showOutputAttributeName: { + ShowOutputAttributeName: { Type: schema.TypeList, Computed: true, Description: "Outputs the result of `SHOW SECURITY INTEGRATIONS` for the given security integration.", @@ -73,7 +73,7 @@ var apiAuthCommonSchema = map[string]*schema.Schema{ Schema: schemas.ShowSecurityIntegrationSchema, }, }, - describeOutputAttributeName: { + DescribeOutputAttributeName: { Type: schema.TypeList, Computed: true, Description: "Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security integration.", @@ -325,8 +325,8 @@ func handleApiAuthRead(d *schema.ResourceData, if err = handleExternalChangesToObjectInDescribe(d, append(extraFieldsDescribeMappings, - describeMapping{"oauth_access_token_validity", "oauth_access_token_validity", oauthAccessTokenValidityInt, oauthAccessTokenValidityInt, stringToIntNormalizer}, - describeMapping{"oauth_refresh_token_validity", "oauth_refresh_token_validity", oauthRefreshTokenValidityInt, oauthRefreshTokenValidityInt, stringToIntNormalizer}, + describeMapping{"oauth_access_token_validity", "oauth_access_token_validity", oauthAccessTokenValidityInt, oauthAccessTokenValidityInt, nil}, + describeMapping{"oauth_refresh_token_validity", "oauth_refresh_token_validity", oauthRefreshTokenValidityInt, oauthRefreshTokenValidityInt, nil}, describeMapping{"oauth_client_id", "oauth_client_id", oauthClientId.Value, oauthClientId.Value, nil}, describeMapping{"oauth_client_auth_method", "oauth_client_auth_method", oauthClientAuthMethod.Value, oauthClientAuthMethod.Value, nil}, describeMapping{"oauth_token_endpoint", "oauth_token_endpoint", oauthTokenEndpoint.Value, oauthTokenEndpoint.Value, nil}, @@ -365,11 +365,11 @@ func handleApiAuthRead(d *schema.ResourceData, } } - if err := d.Set(showOutputAttributeName, []map[string]any{schemas.SecurityIntegrationToSchema(integration)}); err != nil { + if err := d.Set(ShowOutputAttributeName, []map[string]any{schemas.SecurityIntegrationToSchema(integration)}); err != nil { return err } - if err := d.Set(describeOutputAttributeName, []map[string]any{schemas.ApiAuthSecurityIntegrationPropertiesToSchema(properties)}); err != nil { + if err := d.Set(DescribeOutputAttributeName, []map[string]any{schemas.ApiAuthSecurityIntegrationPropertiesToSchema(properties)}); err != nil { return err } return nil diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go index 9a299e765f..e463e8974b 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go @@ -52,8 +52,8 @@ func ApiAuthenticationIntegrationWithAuthorizationCodeGrant() *schema.Resource { ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), ForceNewIfChangeToEmptyString("oauth_client_auth_method"), ForceNewIfChangeToEmptyString("oauth_grant"), - ComputedIfAnyAttributeChanged(showOutputAttributeName, "enabled", "comment"), - ComputedIfAnyAttributeChanged(describeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", + ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "enabled", "comment"), + ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", "oauth_client_id", "oauth_client_auth_method", "oauth_authorization_endpoint", "oauth_token_endpoint", "oauth_allowed_scopes", "oauth_grant"), ), diff --git a/pkg/resources/api_authentication_integration_with_client_credentials.go b/pkg/resources/api_authentication_integration_with_client_credentials.go index de01ded6db..ae9d169fc7 100644 --- a/pkg/resources/api_authentication_integration_with_client_credentials.go +++ b/pkg/resources/api_authentication_integration_with_client_credentials.go @@ -47,8 +47,8 @@ func ApiAuthenticationIntegrationWithClientCredentials() *schema.Resource { ForceNewIfChangeToEmptyString("oauth_token_endpoint"), ForceNewIfChangeToEmptyString("oauth_client_auth_method"), ForceNewIfChangeToEmptyString("oauth_grant"), - ComputedIfAnyAttributeChanged(showOutputAttributeName, "enabled", "comment"), - ComputedIfAnyAttributeChanged(describeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", + ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "enabled", "comment"), + ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", "oauth_client_id", "oauth_client_auth_method", "oauth_token_endpoint", "oauth_allowed_scopes", "oauth_grant"), ), Importer: &schema.ResourceImporter{ diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer.go b/pkg/resources/api_authentication_integration_with_jwt_bearer.go index f5468ca0d6..31e9e73fbb 100644 --- a/pkg/resources/api_authentication_integration_with_jwt_bearer.go +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer.go @@ -51,8 +51,8 @@ func ApiAuthenticationIntegrationWithJwtBearer() *schema.Resource { ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), ForceNewIfChangeToEmptyString("oauth_client_auth_method"), ForceNewIfChangeToEmptyString("oauth_grant"), - ComputedIfAnyAttributeChanged(showOutputAttributeName, "enabled", "comment"), - ComputedIfAnyAttributeChanged(describeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", + ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "enabled", "comment"), + ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", "oauth_client_id", "oauth_client_auth_method", "oauth_authorization_endpoint", "oauth_token_endpoint", "oauth_grant", "oauth_assertion_issuer"), ), diff --git a/pkg/sdk/security_integrations_gen_test.go b/pkg/sdk/security_integrations_gen_test.go index a8d86f3e44..f867465a44 100644 --- a/pkg/sdk/security_integrations_gen_test.go +++ b/pkg/sdk/security_integrations_gen_test.go @@ -346,7 +346,6 @@ func TestSecurityIntegrations_CreateSaml2(t *testing.T) { Saml2SsoUrl: "url", Saml2Provider: "provider", Saml2X509Cert: "cert", - Enabled: true, } } From 6f18db2af67b3c2bb2fdd0c694e85a99e5da6862 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Fri, 5 Jul 2024 14:42:13 +0200 Subject: [PATCH 12/18] Use new methods --- .../api_authentication_integration_common.go | 41 +++-------- ...tegration_with_authorization_code_grant.go | 31 +++----- ...ion_integration_with_client_credentials.go | 23 +++--- ...hentication_integration_with_jwt_bearer.go | 25 ++----- pkg/resources/common.go | 17 ----- pkg/resources/common_test.go | 71 ------------------- pkg/resources/saml2_integration.go | 8 +-- pkg/resources/show_and_describe_handlers.go | 4 ++ pkg/sdk/parsers.go | 24 +++---- pkg/sdk/parsers_test.go | 9 +-- 10 files changed, 62 insertions(+), 191 deletions(-) diff --git a/pkg/resources/api_authentication_integration_common.go b/pkg/resources/api_authentication_integration_common.go index 82fd7ca023..1657b555b1 100644 --- a/pkg/resources/api_authentication_integration_common.go +++ b/pkg/resources/api_authentication_integration_common.go @@ -1,7 +1,6 @@ package resources import ( - "context" "fmt" "strconv" @@ -43,7 +42,7 @@ var apiAuthCommonSchema = map[string]*schema.Schema{ Type: schema.TypeString, Optional: true, ValidateDiagFunc: sdkValidation(sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption), - Description: fmt.Sprintf("Specifies that POST is used as the authentication method to the external service. Valid options are: %v", sdk.AsStringList(sdk.AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption)), + Description: fmt.Sprintf("Specifies that POST is used as the authentication method to the external service. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.AsStringList(sdk.AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption))), }, "oauth_access_token_validity": { Type: schema.TypeInt, @@ -206,7 +205,7 @@ func handleApiAuthCreate(d *schema.ResourceData) (commonApiAuthCreate, error) { return create, nil } -func handleApiAuthImport(ctx context.Context, d *schema.ResourceData, integration *sdk.SecurityIntegration, +func handleApiAuthImport(d *schema.ResourceData, integration *sdk.SecurityIntegration, properties []sdk.SecurityIntegrationProperty, ) error { if err := d.Set("name", integration.Name); err != nil { @@ -335,34 +334,14 @@ func handleApiAuthRead(d *schema.ResourceData, return err } } - if !d.GetRawConfig().IsNull() { - if v := d.GetRawConfig().AsValueMap()["oauth_access_token_validity"]; !v.IsNull() { - intVal, _ := v.AsBigFloat().Int64() - if err := d.Set("oauth_access_token_validity", intVal); err != nil { - return err - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_refresh_token_validity"]; !v.IsNull() { - intVal, _ := v.AsBigFloat().Int64() - if err := d.Set("oauth_refresh_token_validity", intVal); err != nil { - return err - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_client_id"]; !v.IsNull() { - if err := d.Set("oauth_client_id", v.AsString()); err != nil { - return err - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_client_auth_method"]; !v.IsNull() { - if err := d.Set("oauth_client_auth_method", v.AsString()); err != nil { - return err - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_token_endpoint"]; !v.IsNull() { - if err := d.Set("oauth_token_endpoint", v.AsString()); err != nil { - return err - } - } + if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ + "oauth_access_token_validity", + "oauth_refresh_token_validity", + "oauth_client_id", + "oauth_client_auth_method", + "oauth_token_endpoint", + }); err != nil { + return err } if err := d.Set(ShowOutputAttributeName, []map[string]any{schemas.SecurityIntegrationToSchema(integration)}); err != nil { diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go index e463e8974b..cdb0eba0df 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go @@ -79,7 +79,7 @@ func ImportApiAuthenticationWithAuthorizationCodeGrant(ctx context.Context, d *s return nil, err } - if err := handleApiAuthImport(ctx, d, integration, properties); err != nil { + if err := handleApiAuthImport(d, integration, properties); err != nil { return nil, err } oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { @@ -92,7 +92,7 @@ func ImportApiAuthenticationWithAuthorizationCodeGrant(ctx context.Context, d *s } oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) if err == nil { - if err = d.Set("oauth_allowed_scopes", listValueToSlice(oauthAllowedScopes.Value, false)); err != nil { + if err = d.Set("oauth_allowed_scopes", sdk.ParseCommaSeparatedStringArray(oauthAllowedScopes.Value, false)); err != nil { return nil, err } } @@ -192,29 +192,20 @@ func ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(withExter } if err := handleApiAuthRead(d, integration, properties, withExternalChangesMarking, []describeMapping{ {"oauth_authorization_endpoint", "oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value, oauthAuthorizationEndpoint.Value, nil}, - {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, listValueToSlice(oauthAllowedScopes.Value, false), nil}, + {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, sdk.ParseCommaSeparatedStringArray(oauthAllowedScopes.Value, false), nil}, {"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, }); err != nil { return diag.FromErr(err) } - if !d.GetRawConfig().IsNull() { - if v := d.GetRawConfig().AsValueMap()["oauth_authorization_endpoint"]; !v.IsNull() { - if err = d.Set("oauth_authorization_endpoint", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_allowed_scopes"]; !v.IsNull() { - if err = d.Set("oauth_allowed_scopes", ctyValToSliceString(v.AsValueSlice())); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_grant"]; !v.IsNull() { - if err = d.Set("oauth_grant", v.AsString()); err != nil { - return diag.FromErr(err) - } - } + if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ + "oauth_authorization_endpoint", + "oauth_allowed_scopes", + "oauth_grant", + "oauth_client_auth_method", + "oauth_token_endpoint", + }); err != nil { + return diag.FromErr(err) } - return nil } } diff --git a/pkg/resources/api_authentication_integration_with_client_credentials.go b/pkg/resources/api_authentication_integration_with_client_credentials.go index ae9d169fc7..9ce0d0b889 100644 --- a/pkg/resources/api_authentication_integration_with_client_credentials.go +++ b/pkg/resources/api_authentication_integration_with_client_credentials.go @@ -71,12 +71,12 @@ func ImportApiAuthenticationWithClientCredentials(ctx context.Context, d *schema if err != nil { return nil, err } - if err := handleApiAuthImport(ctx, d, integration, properties); err != nil { + if err := handleApiAuthImport(d, integration, properties); err != nil { return nil, err } oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) if err == nil { - if err = d.Set("oauth_allowed_scopes", listValueToSlice(oauthAllowedScopes.Value, false)); err != nil { + if err = d.Set("oauth_allowed_scopes", sdk.ParseCommaSeparatedStringArray(oauthAllowedScopes.Value, false)); err != nil { return nil, err } } @@ -164,23 +164,18 @@ func ReadContextApiAuthenticationIntegrationWithClientCredentials(withExternalCh } if err := handleApiAuthRead(d, integration, properties, withExternalChangesMarking, []describeMapping{ - {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, listValueToSlice(oauthAllowedScopes.Value, false), nil}, + {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, sdk.ParseCommaSeparatedStringArray(oauthAllowedScopes.Value, false), nil}, {"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, }); err != nil { return diag.FromErr(err) } - if !d.GetRawConfig().IsNull() { - if v := d.GetRawConfig().AsValueMap()["oauth_allowed_scopes"]; !v.IsNull() { - if err := d.Set("oauth_allowed_scopes", ctyValToSliceString(v.AsValueSlice())); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_grant"]; !v.IsNull() { - if err := d.Set("oauth_grant", v.AsString()); err != nil { - return diag.FromErr(err) - } - } + if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ + "oauth_allowed_scopes", + "oauth_grant", + }); err != nil { + return diag.FromErr(err) } + return nil } } diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer.go b/pkg/resources/api_authentication_integration_with_jwt_bearer.go index 31e9e73fbb..a80dedb4f5 100644 --- a/pkg/resources/api_authentication_integration_with_jwt_bearer.go +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer.go @@ -76,7 +76,7 @@ func ImportApiAuthenticationWithJwtBearer(ctx context.Context, d *schema.Resourc if err != nil { return nil, err } - if err := handleApiAuthImport(ctx, d, integration, properties); err != nil { + if err := handleApiAuthImport(d, integration, properties); err != nil { return nil, err } oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { @@ -184,24 +184,13 @@ func ReadContextApiAuthenticationIntegrationWithJwtBearer(withExternalChangesMar }); err != nil { return diag.FromErr(err) } - if !d.GetRawConfig().IsNull() { - if v := d.GetRawConfig().AsValueMap()["oauth_authorization_endpoint"]; !v.IsNull() { - if err = d.Set("oauth_authorization_endpoint", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_assertion_issuer"]; !v.IsNull() { - if err = d.Set("oauth_assertion_issuer", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["oauth_grant"]; !v.IsNull() { - if err = d.Set("oauth_grant", v.AsString()); err != nil { - return diag.FromErr(err) - } - } + if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ + "oauth_authorization_endpoint", + "oauth_assertion_issuer", + "oauth_grant", + }); err != nil { + return diag.FromErr(err) } - return nil } } diff --git a/pkg/resources/common.go b/pkg/resources/common.go index 6f4731bbc4..a2a33a7c1d 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -54,23 +54,6 @@ func suppressQuoting(_, oldValue, newValue string, _ *schema.ResourceData) bool } } -func listValueToSlice(value string, trimQuotes bool) []string { - value = strings.TrimLeft(value, "[") - value = strings.TrimRight(value, "]") - if value == "" { - return nil - } - elems := strings.Split(value, ",") - for i := range elems { - if trimQuotes { - elems[i] = strings.Trim(elems[i], " '") - } else { - elems[i] = strings.Trim(elems[i], " ") - } - } - return elems -} - func ctyValToSliceString(valueElems []cty.Value) []string { elems := make([]string, len(valueElems)) for i, v := range valueElems { diff --git a/pkg/resources/common_test.go b/pkg/resources/common_test.go index 50bcaffdf4..1212114e11 100644 --- a/pkg/resources/common_test.go +++ b/pkg/resources/common_test.go @@ -1,7 +1,6 @@ package resources import ( - "reflect" "testing" "github.com/stretchr/testify/require" @@ -48,73 +47,3 @@ func Test_suppressIdentifierQuoting(t *testing.T) { require.False(t, result) }) } - -func Test_listValueToSlice(t *testing.T) { - tests := []struct { - name string - value string - trimQuotes bool - want []string - }{ - { - name: "empty list", - want: nil, - }, - { - name: "empty list with brackets", - value: "[]", - want: nil, - }, - { - name: "one element in list", - value: "a", - want: []string{"a"}, - }, - { - name: "one element in list, wrapped", - value: "'a'", - trimQuotes: true, - want: []string{"a"}, - }, - { - name: "one element in list with brackets", - value: "[a]", - want: []string{"a"}, - }, - { - name: "one element in list wrapped, with brackets", - value: "['a']", - trimQuotes: true, - want: []string{"a"}, - }, - { - name: "multiple elements in list", - value: "a, b", - want: []string{"a", "b"}, - }, - { - name: "multiple elements in list wrapped", - value: "'a', 'b'", - trimQuotes: true, - want: []string{"a", "b"}, - }, - { - name: "multiple elements in list with brackets", - value: "[a, b]", - want: []string{"a", "b"}, - }, - { - name: "multiple elements in list wrapped, with brackets", - value: "['a', 'b' ]", - trimQuotes: true, - want: []string{"a", "b"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := listValueToSlice(tt.value, tt.trimQuotes); !reflect.DeepEqual(got, tt.want) { - t.Errorf("listValueToSlice() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/resources/saml2_integration.go b/pkg/resources/saml2_integration.go index 259c8847df..6b03ef3c75 100644 --- a/pkg/resources/saml2_integration.go +++ b/pkg/resources/saml2_integration.go @@ -320,7 +320,7 @@ func ImportSaml2Integration(ctx context.Context, d *schema.ResourceData, meta an 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 { + if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value, false)); err != nil { return nil, err } @@ -330,7 +330,7 @@ func ImportSaml2Integration(ctx context.Context, d *schema.ResourceData, meta an 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 { + if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value, false)); err != nil { return nil, err } @@ -525,7 +525,7 @@ func ReadContextSAML2Integration(withExternalChangesMarking bool) schema.ReadCon 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 { + if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value, false)); err != nil { return diag.FromErr(err) } @@ -535,7 +535,7 @@ func ReadContextSAML2Integration(withExternalChangesMarking bool) schema.ReadCon 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 { + if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value, false)); err != nil { return diag.FromErr(err) } diff --git a/pkg/resources/show_and_describe_handlers.go b/pkg/resources/show_and_describe_handlers.go index 91a0fc9cb1..0e90c9e169 100644 --- a/pkg/resources/show_and_describe_handlers.go +++ b/pkg/resources/show_and_describe_handlers.go @@ -106,6 +106,10 @@ func setStateToValuesFromConfig(d *schema.ResourceData, resourceSchema map[strin if err := d.Set(field, v.AsString()); err != nil { return err } + case schema.TypeSet: + if err := d.Set(field, ctyValToSliceString(v.AsValueSlice())); err != nil { + return err + } default: log.Printf("[DEBUG] field %s has unsupported schema type %v not found", field, schemaField.Type) } diff --git a/pkg/sdk/parsers.go b/pkg/sdk/parsers.go index 8e415373f3..c1590d63c4 100644 --- a/pkg/sdk/parsers.go +++ b/pkg/sdk/parsers.go @@ -22,18 +22,18 @@ func ParseTimestampWithOffset(s string, dateTimeFormat string) (string, error) { // 1. The list is enclosed by [] brackets, and they shouldn't be a part of any item's value // 2. Items are separated by commas, and they shouldn't be a part of any item's value // 3. Items can have as many spaces in between, but after separation they will be trimmed and shouldn't be a part of any item's value -func ParseCommaSeparatedStringArray(value string) []string { - if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") { - if value == "[]" { - return make([]string, 0) - } - list := strings.Trim(value, "[]") - listItems := strings.Split(list, ",") - trimmedListItems := make([]string, len(listItems)) - for i, item := range listItems { - trimmedListItems[i] = strings.TrimSpace(item) +func ParseCommaSeparatedStringArray(value string, trimQuotes bool) []string { + value = strings.Trim(value, "[]") + if value == "" { + return make([]string, 0) + } + listItems := strings.Split(value, ",") + trimmedListItems := make([]string, len(listItems)) + for i, item := range listItems { + trimmedListItems[i] = strings.TrimSpace(item) + if trimQuotes { + trimmedListItems[i] = strings.Trim(item, "'") } - return trimmedListItems } - return make([]string, 0) + return trimmedListItems } diff --git a/pkg/sdk/parsers_test.go b/pkg/sdk/parsers_test.go index 6ff3a59c16..c82a3e50a3 100644 --- a/pkg/sdk/parsers_test.go +++ b/pkg/sdk/parsers_test.go @@ -8,9 +8,10 @@ import ( func TestParseCommaSeparatedStringArray(t *testing.T) { testCases := []struct { - Name string - Value string - Result []string + Name string + Value string + TrimQuotes bool + Result []string }{ { Name: "empty list", @@ -51,7 +52,7 @@ func TestParseCommaSeparatedStringArray(t *testing.T) { for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { - assert.Equal(t, tc.Result, ParseCommaSeparatedStringArray(tc.Value)) + assert.Equal(t, tc.Result, ParseCommaSeparatedStringArray(tc.Value, tc.TrimQuotes)) }) } } From c4fa9720ae501f3ecfb95328528c4a9f2517c1d8 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Fri, 5 Jul 2024 14:48:55 +0200 Subject: [PATCH 13/18] Use newer methods --- ...ntication_integration_with_authorization_code_grant.md | 8 ++++---- ..._authentication_integration_with_client_credentials.md | 6 +++--- .../api_authentication_integration_with_jwt_bearer.md | 6 +++--- pkg/resources/api_authentication_integration_common.go | 4 ++-- ...ntication_integration_with_authorization_code_grant.go | 4 ++-- ..._authentication_integration_with_client_credentials.go | 2 +- .../api_authentication_integration_with_jwt_bearer.go | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/resources/api_authentication_integration_with_authorization_code_grant.md b/docs/resources/api_authentication_integration_with_authorization_code_grant.md index cbbbfd11e5..c63c383d9a 100644 --- a/docs/resources/api_authentication_integration_with_authorization_code_grant.md +++ b/docs/resources/api_authentication_integration_with_authorization_code_grant.md @@ -50,11 +50,11 @@ resource "snowflake_api_authentication_integration_with_authorization_code_grant - `comment` (String) Specifies a comment for the integration. - `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_allowed_scopes` (Set of String) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. -- `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. -- `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. Valid options are: [CLIENT_SECRET_POST] -- `oauth_grant` (String) Specifies the type of OAuth flow. +- `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. If removed from the config, the resource is recreated. +- `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. If removed from the config, the resource is recreated. Valid values are (case-insensitive): `CLIENT_SECRET_POST`. +- `oauth_grant` (String) Specifies the type of OAuth flow. If removed from the config, the resource is recreated. Valid values are (case-insensitive): AUTHORIZATION_CODE. - `oauth_refresh_token_validity` (Number) Specifies the value to determine the validity of the refresh token obtained from the OAuth server. -- `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). +- `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). If removed from the config, the resource is recreated. ### Read-Only diff --git a/docs/resources/api_authentication_integration_with_client_credentials.md b/docs/resources/api_authentication_integration_with_client_credentials.md index d6bffd9b18..bc38102a3e 100644 --- a/docs/resources/api_authentication_integration_with_client_credentials.md +++ b/docs/resources/api_authentication_integration_with_client_credentials.md @@ -48,10 +48,10 @@ resource "snowflake_api_authentication_integration_with_client_credentials" "tes - `comment` (String) Specifies a comment for the integration. - `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_allowed_scopes` (Set of String) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. -- `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. Valid options are: [CLIENT_SECRET_POST] -- `oauth_grant` (String) Specifies the type of OAuth flow. +- `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. If removed from the config, the resource is recreated. Valid values are (case-insensitive): `CLIENT_SECRET_POST`. +- `oauth_grant` (String) Specifies the type of OAuth flow. If removed from the config, the resource is recreated. Valid values are (case-insensitive): CLIENT_CREDENTIALS. - `oauth_refresh_token_validity` (Number) Specifies the value to determine the validity of the refresh token obtained from the OAuth server. -- `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). +- `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). If removed from the config, the resource is recreated. ### Read-Only diff --git a/docs/resources/api_authentication_integration_with_jwt_bearer.md b/docs/resources/api_authentication_integration_with_jwt_bearer.md index 28fc7276f9..4f73252155 100644 --- a/docs/resources/api_authentication_integration_with_jwt_bearer.md +++ b/docs/resources/api_authentication_integration_with_jwt_bearer.md @@ -52,10 +52,10 @@ resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { - `comment` (String) Specifies a comment for the integration. - `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. -- `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. Valid options are: [CLIENT_SECRET_POST] -- `oauth_grant` (String) Specifies the type of OAuth flow. +- `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. If removed from the config, the resource is recreated. Valid values are (case-insensitive): `CLIENT_SECRET_POST`. +- `oauth_grant` (String) Specifies the type of OAuth flow. If removed from the config, the resource is recreated. Valid values are (case-insensitive): JWT_BEARER. - `oauth_refresh_token_validity` (Number) Specifies the value to determine the validity of the refresh token obtained from the OAuth server. -- `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). +- `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). If removed from the config, the resource is recreated. ### Read-Only diff --git a/pkg/resources/api_authentication_integration_common.go b/pkg/resources/api_authentication_integration_common.go index 1657b555b1..46a2117c38 100644 --- a/pkg/resources/api_authentication_integration_common.go +++ b/pkg/resources/api_authentication_integration_common.go @@ -36,13 +36,13 @@ var apiAuthCommonSchema = map[string]*schema.Schema{ "oauth_token_endpoint": { Type: schema.TypeString, Optional: true, - Description: "Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly).", + Description: "Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). If removed from the config, the resource is recreated.", }, "oauth_client_auth_method": { Type: schema.TypeString, Optional: true, ValidateDiagFunc: sdkValidation(sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption), - Description: fmt.Sprintf("Specifies that POST is used as the authentication method to the external service. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.AsStringList(sdk.AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption))), + Description: fmt.Sprintf("Specifies that POST is used as the authentication method to the external service. If removed from the config, the resource is recreated. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.AsStringList(sdk.AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption))), }, "oauth_access_token_validity": { Type: schema.TypeInt, diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go index cdb0eba0df..98a9f7ac1e 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go @@ -23,7 +23,7 @@ var apiAuthAuthorizationCodeGrantSchema = func() map[string]*schema.Schema { "oauth_authorization_endpoint": { Type: schema.TypeString, Optional: true, - Description: "Specifies the URL for authenticating to the external service.", + Description: "Specifies the URL for authenticating to the external service. If removed from the config, the resource is recreated.", }, "oauth_allowed_scopes": { Type: schema.TypeSet, @@ -35,7 +35,7 @@ var apiAuthAuthorizationCodeGrantSchema = func() map[string]*schema.Schema { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringInSlice([]string{sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode}, true), - Description: "Specifies the type of OAuth flow.", + Description: fmt.Sprintf("Specifies the type of OAuth flow. If removed from the config, the resource is recreated. Valid values are (case-insensitive): %s.", sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode), }, } return MergeMaps(apiAuthCommonSchema, apiAuthAuthorizationCodeGrant) diff --git a/pkg/resources/api_authentication_integration_with_client_credentials.go b/pkg/resources/api_authentication_integration_with_client_credentials.go index 9ce0d0b889..c3a35b9620 100644 --- a/pkg/resources/api_authentication_integration_with_client_credentials.go +++ b/pkg/resources/api_authentication_integration_with_client_credentials.go @@ -30,7 +30,7 @@ var apiAuthClientCredentialsSchema = func() map[string]*schema.Schema { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringInSlice([]string{sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials}, true), - Description: "Specifies the type of OAuth flow.", + Description: fmt.Sprintf("Specifies the type of OAuth flow. If removed from the config, the resource is recreated. Valid values are (case-insensitive): %s.", sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials), }, } return MergeMaps(apiAuthCommonSchema, apiAuthClientCredentials) diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer.go b/pkg/resources/api_authentication_integration_with_jwt_bearer.go index a80dedb4f5..3db4624e28 100644 --- a/pkg/resources/api_authentication_integration_with_jwt_bearer.go +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer.go @@ -33,7 +33,7 @@ var apiAuthJwtBearerSchema = func() map[string]*schema.Schema { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringInSlice([]string{sdk.ApiAuthenticationSecurityIntegrationOauthGrantJwtBearer}, true), - Description: "Specifies the type of OAuth flow.", + Description: fmt.Sprintf("Specifies the type of OAuth flow. If removed from the config, the resource is recreated. Valid values are (case-insensitive): %s.", sdk.ApiAuthenticationSecurityIntegrationOauthGrantJwtBearer), }, } return MergeMaps(apiAuthCommonSchema, apiAuthJwtBearer) From c218ed7f81fc9d854632bf746553bedde71f1f8f Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Fri, 5 Jul 2024 15:18:02 +0200 Subject: [PATCH 14/18] Fix tests --- .../api_authentication_integration_common.go | 6 +++--- pkg/sdk/parsers.go | 2 +- pkg/sdk/parsers_test.go | 20 ++++++++++++++++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/pkg/resources/api_authentication_integration_common.go b/pkg/resources/api_authentication_integration_common.go index 46a2117c38..2f93c69627 100644 --- a/pkg/resources/api_authentication_integration_common.go +++ b/pkg/resources/api_authentication_integration_common.go @@ -48,7 +48,7 @@ var apiAuthCommonSchema = map[string]*schema.Schema{ Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntAtLeast(0), - Default: -1, + Default: IntDefault, Description: "Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server.", DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("oauth_access_token_validity"), }, @@ -124,7 +124,7 @@ func handleApiAuthUpdate(d *schema.ResourceData) (commonApiAuthSet, commonApiAut } if d.HasChange("oauth_access_token_validity") { - if v := d.Get("oauth_access_token_validity").(int); v != -1 { + if v := d.Get("oauth_access_token_validity").(int); v != IntDefault { set.oauthAccessTokenValidity = sdk.Pointer(v) } else { // TODO(SNOW-1515781): use UNSET @@ -185,7 +185,7 @@ func handleApiAuthCreate(d *schema.ResourceData) (commonApiAuthCreate, error) { create.comment = sdk.Pointer(v.(string)) } - if v := d.Get("oauth_access_token_validity").(int); v != -1 { + if v := d.Get("oauth_access_token_validity").(int); v != IntDefault { create.oauthAccessTokenValidity = sdk.Pointer(v) } if v, ok := d.GetOk("oauth_refresh_token_validity"); ok { diff --git a/pkg/sdk/parsers.go b/pkg/sdk/parsers.go index c1590d63c4..26dc8b4a07 100644 --- a/pkg/sdk/parsers.go +++ b/pkg/sdk/parsers.go @@ -32,7 +32,7 @@ func ParseCommaSeparatedStringArray(value string, trimQuotes bool) []string { for i, item := range listItems { trimmedListItems[i] = strings.TrimSpace(item) if trimQuotes { - trimmedListItems[i] = strings.Trim(item, "'") + trimmedListItems[i] = strings.Trim(trimmedListItems[i], "'") } } return trimmedListItems diff --git a/pkg/sdk/parsers_test.go b/pkg/sdk/parsers_test.go index c82a3e50a3..c68ce19e47 100644 --- a/pkg/sdk/parsers_test.go +++ b/pkg/sdk/parsers_test.go @@ -28,6 +28,18 @@ func TestParseCommaSeparatedStringArray(t *testing.T) { Value: "[one]", Result: []string{"one"}, }, + { + Name: "one element in list - with quotes", + Value: "['one']", + TrimQuotes: true, + Result: []string{"one"}, + }, + { + Name: "multiple elements in list - with quotes", + Value: "['one', 'two', 'three']", + TrimQuotes: true, + Result: []string{"one", "two", "three"}, + }, { Name: "multiple elements in list", Value: "[one, two, three]", @@ -46,7 +58,13 @@ func TestParseCommaSeparatedStringArray(t *testing.T) { { Name: "list without brackets", Value: "one,two,three", - Result: []string{}, + Result: []string{"one", "two", "three"}, + }, + { + Name: "list without brackets - with quotes", + Value: "'one','two','three'", + TrimQuotes: true, + Result: []string{"one", "two", "three"}, }, } From 319a5b7d88461588993968e43ae2f5a950159a6a Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Mon, 8 Jul 2024 08:29:08 +0200 Subject: [PATCH 15/18] Fix tests --- pkg/resources/api_authentication_integration_common.go | 4 ++-- pkg/resources/database_acceptance_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/resources/api_authentication_integration_common.go b/pkg/resources/api_authentication_integration_common.go index 2f93c69627..a860e4f7f2 100644 --- a/pkg/resources/api_authentication_integration_common.go +++ b/pkg/resources/api_authentication_integration_common.go @@ -324,8 +324,8 @@ func handleApiAuthRead(d *schema.ResourceData, if err = handleExternalChangesToObjectInDescribe(d, append(extraFieldsDescribeMappings, - describeMapping{"oauth_access_token_validity", "oauth_access_token_validity", oauthAccessTokenValidityInt, oauthAccessTokenValidityInt, nil}, - describeMapping{"oauth_refresh_token_validity", "oauth_refresh_token_validity", oauthRefreshTokenValidityInt, oauthRefreshTokenValidityInt, nil}, + describeMapping{"oauth_access_token_validity", "oauth_access_token_validity", oauthAccessTokenValidity.Value, oauthAccessTokenValidityInt, nil}, + describeMapping{"oauth_refresh_token_validity", "oauth_refresh_token_validity", oauthRefreshTokenValidity.Value, oauthRefreshTokenValidityInt, nil}, describeMapping{"oauth_client_id", "oauth_client_id", oauthClientId.Value, oauthClientId.Value, nil}, describeMapping{"oauth_client_auth_method", "oauth_client_auth_method", oauthClientAuthMethod.Value, oauthClientAuthMethod.Value, nil}, describeMapping{"oauth_token_endpoint", "oauth_token_endpoint", oauthTokenEndpoint.Value, oauthTokenEndpoint.Value, nil}, diff --git a/pkg/resources/database_acceptance_test.go b/pkg/resources/database_acceptance_test.go index 3ca0128b94..4f4e0f4148 100644 --- a/pkg/resources/database_acceptance_test.go +++ b/pkg/resources/database_acceptance_test.go @@ -725,7 +725,7 @@ func TestAcc_Database_IntParameter(t *testing.T) { { PreConfig: func() { param := acc.TestClient().Parameter.ShowAccountParameter(t, sdk.AccountParameterDataRetentionTimeInDays) - require.Equal(t, "", string(param.Level)) + require.Equal(t, sdk.ParameterTypeAccount, string(param.Level)) revert := acc.TestClient().Parameter.UpdateAccountParameterTemporarily(t, sdk.AccountParameterDataRetentionTimeInDays, "50") t.Cleanup(revert) }, @@ -1138,7 +1138,7 @@ func databaseStateUpgraderWithReplicationOld(id sdk.AccountObjectIdentifier, ena resource "snowflake_database" "test" { name = "%s" replication_configuration { - accounts = ["%s"] + accounts = ["%s"] ignore_edition_check = true } } From 326bae9c1af8e5b15fe5b10128d997956e5dbf0b Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Mon, 8 Jul 2024 11:24:23 +0200 Subject: [PATCH 16/18] Fix tests --- pkg/resources/database_acceptance_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/resources/database_acceptance_test.go b/pkg/resources/database_acceptance_test.go index 4f4e0f4148..79a3143219 100644 --- a/pkg/resources/database_acceptance_test.go +++ b/pkg/resources/database_acceptance_test.go @@ -725,7 +725,7 @@ func TestAcc_Database_IntParameter(t *testing.T) { { PreConfig: func() { param := acc.TestClient().Parameter.ShowAccountParameter(t, sdk.AccountParameterDataRetentionTimeInDays) - require.Equal(t, sdk.ParameterTypeAccount, string(param.Level)) + require.Equal(t, string(sdk.ParameterTypeAccount), string(param.Level)) revert := acc.TestClient().Parameter.UpdateAccountParameterTemporarily(t, sdk.AccountParameterDataRetentionTimeInDays, "50") t.Cleanup(revert) }, @@ -770,7 +770,7 @@ func TestAcc_Database_IntParameter(t *testing.T) { { PreConfig: func() { param := acc.TestClient().Parameter.ShowAccountParameter(t, sdk.AccountParameterDataRetentionTimeInDays) - require.Equal(t, "", string(param.Level)) + require.Equal(t, string(sdk.ParameterTypeAccount), string(param.Level)) }, ConfigVariables: databaseBasicConfig, ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Database/int_parameter/unset"), @@ -815,7 +815,7 @@ func TestAcc_Database_IntParameter(t *testing.T) { { PreConfig: func() { param := acc.TestClient().Parameter.ShowAccountParameter(t, sdk.AccountParameterDataRetentionTimeInDays) - require.Equal(t, "", string(param.Level)) + require.Equal(t, string(sdk.ParameterTypeAccount), string(param.Level)) }, ConfigVariables: databaseBasicConfig, ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Database/int_parameter/unset"), @@ -835,7 +835,7 @@ func TestAcc_Database_IntParameter(t *testing.T) { { PreConfig: func() { param := acc.TestClient().Parameter.ShowAccountParameter(t, sdk.AccountParameterDataRetentionTimeInDays) - require.Equal(t, "", string(param.Level)) + require.Equal(t, string(sdk.ParameterTypeAccount), string(param.Level)) revert := acc.TestClient().Parameter.UpdateAccountParameterTemporarily(t, sdk.AccountParameterDataRetentionTimeInDays, "50") t.Cleanup(revert) }, From 068841fdf0a520bf08bdb70b80bcc5a94ee3d475 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Mon, 8 Jul 2024 15:11:15 +0200 Subject: [PATCH 17/18] Address review comments and add disclaimers --- MIGRATION_GUIDE.md | 16 ++++++--- ...tegration_with_authorization_code_grant.md | 3 +- ...ion_integration_with_client_credentials.md | 3 +- ...hentication_integration_with_jwt_bearer.md | 3 +- .../api_authentication_integration_common.go | 1 + ...tegration_with_authorization_code_grant.go | 35 ++---------------- ...uthorization_code_grant_acceptance_test.go | 7 ++-- ...ion_integration_with_client_credentials.go | 36 ++----------------- ...with_client_credentials_acceptance_test.go | 7 ++-- ...hentication_integration_with_jwt_bearer.go | 35 ++---------------- pkg/resources/database_acceptance_test.go | 8 ++--- .../complete/test.tf | 1 - .../complete/variables.tf | 3 -- .../complete/test.tf | 1 - .../complete/variables.tf | 3 -- ...tion_with_authorization_code_grant.md.tmpl | 32 +++++++++++++++++ ...ntegration_with_client_credentials.md.tmpl | 32 +++++++++++++++++ ...cation_integration_with_jwt_bearer.md.tmpl | 32 +++++++++++++++++ 18 files changed, 129 insertions(+), 129 deletions(-) create mode 100644 templates/resources/api_authentication_integration_with_authorization_code_grant.md.tmpl create mode 100644 templates/resources/api_authentication_integration_with_client_credentials.md.tmpl create mode 100644 templates/resources/api_authentication_integration_with_jwt_bearer.md.tmpl diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index bbd93e1eeb..9790aa2f10 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -20,6 +20,14 @@ They are all described in short in the [changes before v1 doc](./v1-preparations ### old grant resources removal Following the [announcement](https://github.com/Snowflake-Labs/terraform-provider-snowflake/discussions/2736) we have removed the old grant resources. The two resources [snowflake_role_ownership_grant](https://registry.terraform.io/providers/Snowflake-Labs/snowflake/latest/docs/resources/role_ownership_grant) and [snowflake_user_ownership_grant](https://registry.terraform.io/providers/Snowflake-Labs/snowflake/latest/docs/resources/user_ownership_grant) were not listed in the announcement, but they were also marked as deprecated ones. We are removing them too to conclude the grants redesign saga. +### *(new feature)* Api authentication resources +Added new api authentication resources, i.e.: +- `snowflake_api_authentication_integration_with_authorization_code_grant` +- `snowflake_api_authentication_integration_with_client_credentials` +- `snowflake_api_authentication_integration_with_jwt_bearer` + +See reference [doc](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-api-auth). + ### *(new feature)* snowflake_security_integrations datasource Added a new datasource enabling querying and filtering all types of security integrations. Notes: - all results are stored in `security_integrations` field. @@ -129,10 +137,10 @@ All the field changes in comparison to the previous database resource are: - removed: the field is removed from `snowflake_shared_database` as it doesn't have any effect on shared databases. - `from_database` - database cloning was entirely removed and is not possible by any of the new database resources. - `from_share` - the parameter was moved to the dedicated resource for databases created from shares `snowflake_shared_database`. Right now, it's a text field instead of a map. Additionally, instead of legacy account identifier format we're expecting the new one that with share looks like this: `..`. For more information on account identifiers, visit the [official documentation](https://docs.snowflake.com/en/user-guide/admin-account-identifier). -- p, +- p, - `from_replication` - the parameter was moved to the dedicated resource for databases created from primary databases `snowflake_secondary_database` - `replication_configuration` - renamed: was renamed to `configuration` and is only available in the `snowflake_database`. Its internal schema changed that instead of list of accounts, we expect a list of nested objects with accounts for which replication (and optionally failover) should be enabled. More information about converting between both versions [here](#resource-renamed-snowflake_database---snowflake_database_old). Additionally, instead of legacy account identifier format we're expecting the new one that looks like this: `.` (it will be automatically migrated to the recommended format by the state upgrader). For more information on account identifiers, visit the [official documentation](https://docs.snowflake.com/en/user-guide/admin-account-identifier). -- `data_retention_time_in_days` +- `data_retention_time_in_days` - in `snowflake_shared_database` - removed: the field is removed from `snowflake_shared_database` as it doesn't have any effect on shared databases. - in `snowflake_database` and `snowflake_secondary_database` @@ -213,7 +221,7 @@ The only difference would be that instead of writing/generating new configuratio - `pattern` was replaced by `like` field. - Additional filtering options added (`limit`). - Added missing fields returned by SHOW DATABASES and enclosed its output in `show_output` field. -- Added outputs from **DESC DATABASE** and **SHOW PARAMETERS IN DATABASE** (they can be turned off by declaring `with_describe = false` and `with_parameters = false`, **they're turned on by default**). +- Added outputs from **DESC DATABASE** and **SHOW PARAMETERS IN DATABASE** (they can be turned off by declaring `with_describe = false` and `with_parameters = false`, **they're turned on by default**). The additional parameters call **DESC DATABASE** (with `with_describe` turned on) and **SHOW PARAMETERS IN DATABASE** (with `with_parameters` turned on) **per database** returned by **SHOW DATABASES**. The outputs of both commands are held in `databases` entry, where **DESC DATABASE** is saved in the `describe_output` field, and **SHOW PARAMETERS IN DATABASE** in the `parameters` field. It's important to limit the records and calls to Snowflake to the minimum. That's why we recommend assessing which information you need from the data source and then providing strong filters and turning off additional fields for better plan performance. @@ -237,7 +245,7 @@ resource "snowflake_tag_masking_policy_association" "name" { masking_policy_id = snowflake_masking_policy.example_masking_policy.id } ``` - + After ```terraform resource "snowflake_tag_masking_policy_association" "name" { diff --git a/docs/resources/api_authentication_integration_with_authorization_code_grant.md b/docs/resources/api_authentication_integration_with_authorization_code_grant.md index c63c383d9a..d011430f6a 100644 --- a/docs/resources/api_authentication_integration_with_authorization_code_grant.md +++ b/docs/resources/api_authentication_integration_with_authorization_code_grant.md @@ -5,6 +5,8 @@ description: |- --- +!> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. + # snowflake_api_authentication_integration_with_authorization_code_grant (Resource) @@ -52,7 +54,6 @@ resource "snowflake_api_authentication_integration_with_authorization_code_grant - `oauth_allowed_scopes` (Set of String) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. - `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. If removed from the config, the resource is recreated. - `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. If removed from the config, the resource is recreated. Valid values are (case-insensitive): `CLIENT_SECRET_POST`. -- `oauth_grant` (String) Specifies the type of OAuth flow. If removed from the config, the resource is recreated. Valid values are (case-insensitive): AUTHORIZATION_CODE. - `oauth_refresh_token_validity` (Number) Specifies the value to determine the validity of the refresh token obtained from the OAuth server. - `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). If removed from the config, the resource is recreated. diff --git a/docs/resources/api_authentication_integration_with_client_credentials.md b/docs/resources/api_authentication_integration_with_client_credentials.md index bc38102a3e..cb6a466a94 100644 --- a/docs/resources/api_authentication_integration_with_client_credentials.md +++ b/docs/resources/api_authentication_integration_with_client_credentials.md @@ -5,6 +5,8 @@ description: |- --- +!> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. + # snowflake_api_authentication_integration_with_client_credentials (Resource) @@ -49,7 +51,6 @@ resource "snowflake_api_authentication_integration_with_client_credentials" "tes - `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_allowed_scopes` (Set of String) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. - `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. If removed from the config, the resource is recreated. Valid values are (case-insensitive): `CLIENT_SECRET_POST`. -- `oauth_grant` (String) Specifies the type of OAuth flow. If removed from the config, the resource is recreated. Valid values are (case-insensitive): CLIENT_CREDENTIALS. - `oauth_refresh_token_validity` (Number) Specifies the value to determine the validity of the refresh token obtained from the OAuth server. - `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). If removed from the config, the resource is recreated. diff --git a/docs/resources/api_authentication_integration_with_jwt_bearer.md b/docs/resources/api_authentication_integration_with_jwt_bearer.md index 4f73252155..be734aab19 100644 --- a/docs/resources/api_authentication_integration_with_jwt_bearer.md +++ b/docs/resources/api_authentication_integration_with_jwt_bearer.md @@ -5,6 +5,8 @@ description: |- --- +!> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. + # snowflake_api_authentication_integration_with_jwt_bearer (Resource) @@ -53,7 +55,6 @@ resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { - `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. - `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. - `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. If removed from the config, the resource is recreated. Valid values are (case-insensitive): `CLIENT_SECRET_POST`. -- `oauth_grant` (String) Specifies the type of OAuth flow. If removed from the config, the resource is recreated. Valid values are (case-insensitive): JWT_BEARER. - `oauth_refresh_token_validity` (Number) Specifies the value to determine the validity of the refresh token obtained from the OAuth server. - `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). If removed from the config, the resource is recreated. diff --git a/pkg/resources/api_authentication_integration_common.go b/pkg/resources/api_authentication_integration_common.go index a860e4f7f2..1ac5ce976d 100644 --- a/pkg/resources/api_authentication_integration_common.go +++ b/pkg/resources/api_authentication_integration_common.go @@ -42,6 +42,7 @@ var apiAuthCommonSchema = map[string]*schema.Schema{ Type: schema.TypeString, Optional: true, ValidateDiagFunc: sdkValidation(sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption), + DiffSuppressFunc: SuppressIfAny(ignoreCaseSuppressFunc, IgnoreChangeToCurrentSnowflakeValueInDescribe("oauth_client_auth_method")), Description: fmt.Sprintf("Specifies that POST is used as the authentication method to the external service. If removed from the config, the resource is recreated. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.AsStringList(sdk.AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption))), }, "oauth_access_token_validity": { diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go index 98a9f7ac1e..8bad64fd68 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go @@ -15,7 +15,6 @@ import ( "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" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) var apiAuthAuthorizationCodeGrantSchema = func() map[string]*schema.Schema { @@ -31,12 +30,6 @@ var apiAuthAuthorizationCodeGrantSchema = func() map[string]*schema.Schema { Optional: true, Description: "Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow.", }, - "oauth_grant": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode}, true), - Description: fmt.Sprintf("Specifies the type of OAuth flow. If removed from the config, the resource is recreated. Valid values are (case-insensitive): %s.", sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode), - }, } return MergeMaps(apiAuthCommonSchema, apiAuthAuthorizationCodeGrant) }() @@ -51,11 +44,10 @@ func ApiAuthenticationIntegrationWithAuthorizationCodeGrant() *schema.Resource { ForceNewIfChangeToEmptyString("oauth_token_endpoint"), ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), ForceNewIfChangeToEmptyString("oauth_client_auth_method"), - ForceNewIfChangeToEmptyString("oauth_grant"), ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "enabled", "comment"), ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", "oauth_client_id", "oauth_client_auth_method", "oauth_authorization_endpoint", - "oauth_token_endpoint", "oauth_allowed_scopes", "oauth_grant"), + "oauth_token_endpoint", "oauth_allowed_scopes"), ), Schema: apiAuthAuthorizationCodeGrantSchema, Importer: &schema.ResourceImporter{ @@ -96,12 +88,6 @@ func ImportApiAuthenticationWithAuthorizationCodeGrant(ctx context.Context, d *s return nil, err } } - oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) - if err == nil { - if err = d.Set("oauth_grant", oauthGrant.Value); err != nil { - return nil, err - } - } return []*schema.ResourceData{d}, nil } @@ -114,6 +100,7 @@ func CreateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx con } id := sdk.NewAccountObjectIdentifier(commonCreate.name) req := sdk.NewCreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest(id, commonCreate.enabled, commonCreate.oauthClientId, commonCreate.oauthClientSecret) + req.WithOauthGrantAuthorizationCode(true) req.Comment = commonCreate.comment req.OauthAccessTokenValidity = commonCreate.oauthAccessTokenValidity req.OauthRefreshTokenValidity = commonCreate.oauthRefreshTokenValidity @@ -124,12 +111,6 @@ func CreateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx con req.WithOauthAuthorizationEndpoint(v.(string)) } - if v, ok := d.GetOk("oauth_grant"); ok { - if v.(string) == sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode { - req.WithOauthGrantAuthorizationCode(true) - } - } - if v, ok := d.GetOk("oauth_allowed_scopes"); ok { elems := expandStringList(v.(*schema.Set).List()) allowedScopes := make([]sdk.AllowedScope, len(elems)) @@ -186,21 +167,15 @@ func ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(withExter return diag.FromErr(err) } - oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) - if err != nil { - return diag.FromErr(err) - } if err := handleApiAuthRead(d, integration, properties, withExternalChangesMarking, []describeMapping{ {"oauth_authorization_endpoint", "oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value, oauthAuthorizationEndpoint.Value, nil}, {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, sdk.ParseCommaSeparatedStringArray(oauthAllowedScopes.Value, false), nil}, - {"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, }); err != nil { return diag.FromErr(err) } if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ "oauth_authorization_endpoint", "oauth_allowed_scopes", - "oauth_grant", "oauth_client_auth_method", "oauth_token_endpoint", }); err != nil { @@ -233,12 +208,6 @@ func UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx con if d.HasChange("oauth_authorization_endpoint") { set.WithOauthAuthorizationEndpoint(d.Get("oauth_authorization_endpoint").(string)) } - if d.HasChange("oauth_grant") { - if v := d.Get("oauth_grant").(string); v == sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode { - set.WithOauthGrantAuthorizationCode(true) - } - // else: force new - } if d.HasChange("oauth_allowed_scopes") { elems := expandStringList(d.Get("oauth_allowed_scopes").(*schema.Set).List()) diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go index b8e6166208..ee2caa26a1 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go @@ -30,7 +30,6 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes c["oauth_refresh_token_validity"] = config.IntegerVariable(12345) c["oauth_token_endpoint"] = config.StringVariable("https://example.com") c["oauth_allowed_scopes"] = config.SetVariable(config.StringVariable("foo")) - c["oauth_grant"] = config.StringVariable(sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode) } return c } @@ -67,7 +66,7 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_authorization_endpoint.0.value", ""), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_token_endpoint.0.value", ""), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_allowed_scopes.0.value", ""), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_grant.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_grant.0.value", sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.parent_integration.0.value", ""), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.auth_type.0.value", "OAUTH2"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.comment.0.value", ""), @@ -98,7 +97,7 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_auth_method.0.value", ""), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_token_endpoint.0.value", ""), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_allowed_scopes.0.value", ""), - importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_grant.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_grant.0.value", sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.parent_integration.0.value", ""), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.auth_type.0.value", "OAUTH2"), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.comment.0.value", ""), @@ -118,7 +117,6 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *tes resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity", "12345"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint", "https://example.com"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_grant", sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_allowed_scopes.#", "1"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_allowed_scopes.0", "foo"), @@ -187,7 +185,6 @@ func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_complete(t * "oauth_client_secret": config.StringVariable("foo"), "oauth_refresh_token_validity": config.IntegerVariable(12345), "oauth_token_endpoint": config.StringVariable("https://example.com"), - "oauth_grant": config.StringVariable(sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode), "oauth_allowed_scopes": config.SetVariable(config.StringVariable("foo")), } } diff --git a/pkg/resources/api_authentication_integration_with_client_credentials.go b/pkg/resources/api_authentication_integration_with_client_credentials.go index c3a35b9620..862c2ed21f 100644 --- a/pkg/resources/api_authentication_integration_with_client_credentials.go +++ b/pkg/resources/api_authentication_integration_with_client_credentials.go @@ -15,7 +15,6 @@ import ( "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" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) var apiAuthClientCredentialsSchema = func() map[string]*schema.Schema { @@ -26,12 +25,6 @@ var apiAuthClientCredentialsSchema = func() map[string]*schema.Schema { Optional: true, Description: "Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow.", }, - "oauth_grant": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials}, true), - Description: fmt.Sprintf("Specifies the type of OAuth flow. If removed from the config, the resource is recreated. Valid values are (case-insensitive): %s.", sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials), - }, } return MergeMaps(apiAuthCommonSchema, apiAuthClientCredentials) }() @@ -46,10 +39,9 @@ func ApiAuthenticationIntegrationWithClientCredentials() *schema.Resource { CustomizeDiff: customdiff.All( ForceNewIfChangeToEmptyString("oauth_token_endpoint"), ForceNewIfChangeToEmptyString("oauth_client_auth_method"), - ForceNewIfChangeToEmptyString("oauth_grant"), ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "enabled", "comment"), ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", - "oauth_client_id", "oauth_client_auth_method", "oauth_token_endpoint", "oauth_allowed_scopes", "oauth_grant"), + "oauth_client_id", "oauth_client_auth_method", "oauth_token_endpoint", "oauth_allowed_scopes"), ), Importer: &schema.ResourceImporter{ StateContext: ImportApiAuthenticationWithClientCredentials, @@ -80,12 +72,6 @@ func ImportApiAuthenticationWithClientCredentials(ctx context.Context, d *schema return nil, err } } - oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) - if err == nil { - if err = d.Set("oauth_grant", oauthGrant.Value); err != nil { - return nil, err - } - } return []*schema.ResourceData{d}, nil } @@ -98,18 +84,13 @@ func CreateContextApiAuthenticationIntegrationWithClientCredentials(ctx context. } id := sdk.NewAccountObjectIdentifier(commonCreate.name) req := sdk.NewCreateApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(id, commonCreate.enabled, commonCreate.oauthClientId, commonCreate.oauthClientSecret) + req.WithOauthGrantClientCredentials(true) req.Comment = commonCreate.comment req.OauthAccessTokenValidity = commonCreate.oauthAccessTokenValidity req.OauthRefreshTokenValidity = commonCreate.oauthRefreshTokenValidity req.OauthTokenEndpoint = commonCreate.oauthTokenEndpoint req.OauthClientAuthMethod = commonCreate.oauthClientAuthMethod - if v, ok := d.GetOk("oauth_grant"); ok { - if v.(string) == sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials { - req.WithOauthGrantClientCredentials(true) - } - } - if v, ok := d.GetOk("oauth_allowed_scopes"); ok { elems := expandStringList(v.(*schema.Set).List()) allowedScopes := make([]sdk.AllowedScope, len(elems)) @@ -158,20 +139,13 @@ func ReadContextApiAuthenticationIntegrationWithClientCredentials(withExternalCh return diag.FromErr(err) } - oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) - if err != nil { - return diag.FromErr(err) - } - if err := handleApiAuthRead(d, integration, properties, withExternalChangesMarking, []describeMapping{ {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, sdk.ParseCommaSeparatedStringArray(oauthAllowedScopes.Value, false), nil}, - {"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, }); err != nil { return diag.FromErr(err) } if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ "oauth_allowed_scopes", - "oauth_grant", }); err != nil { return diag.FromErr(err) } @@ -201,12 +175,6 @@ func UpdateContextApiAuthenticationIntegrationWithClientCredentials(ctx context. unset := &sdk.ApiAuthenticationWithClientCredentialsFlowIntegrationUnsetRequest{ Comment: commonUnset.comment, } - if d.HasChange("oauth_grant") { - if v := d.Get("oauth_grant").(string); v == sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials { - set.WithOauthGrantClientCredentials(true) - } - // else: force new - } if d.HasChange("oauth_allowed_scopes") { elems := expandStringList(d.Get("oauth_allowed_scopes").(*schema.Set).List()) diff --git a/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go b/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go index 06dce53d48..1609e678ab 100644 --- a/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go +++ b/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go @@ -29,7 +29,6 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_basic(t *testing. c["oauth_refresh_token_validity"] = config.IntegerVariable(12345) c["oauth_token_endpoint"] = config.StringVariable("https://example.com") c["oauth_allowed_scopes"] = config.SetVariable(config.StringVariable("foo")) - c["oauth_grant"] = config.StringVariable(sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials) } return c } @@ -65,7 +64,7 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_basic(t *testing. resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_client_auth_method.0.value", ""), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_token_endpoint.0.value", ""), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_allowed_scopes.0.value", ""), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_grant.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_grant.0.value", sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.parent_integration.0.value", ""), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.auth_type.0.value", "OAUTH2"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.comment.0.value", ""), @@ -96,7 +95,7 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_basic(t *testing. importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_auth_method.0.value", ""), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_token_endpoint.0.value", ""), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_allowed_scopes.0.value", ""), - importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_grant.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_grant.0.value", sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.parent_integration.0.value", ""), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.auth_type.0.value", "OAUTH2"), importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.comment.0.value", ""), @@ -115,7 +114,6 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_basic(t *testing. resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_refresh_token_validity", "12345"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint", "https://example.com"), - resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_grant", sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes.#", "1"), resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes.0", "foo"), @@ -181,7 +179,6 @@ func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_complete(t *testi "oauth_client_secret": config.StringVariable("foo"), "oauth_refresh_token_validity": config.IntegerVariable(12345), "oauth_token_endpoint": config.StringVariable("https://example.com"), - "oauth_grant": config.StringVariable(sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials), "oauth_allowed_scopes": config.SetVariable(config.StringVariable("foo")), } } diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer.go b/pkg/resources/api_authentication_integration_with_jwt_bearer.go index 3db4624e28..e93d40ced9 100644 --- a/pkg/resources/api_authentication_integration_with_jwt_bearer.go +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer.go @@ -15,7 +15,6 @@ import ( "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" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) var apiAuthJwtBearerSchema = func() map[string]*schema.Schema { @@ -29,12 +28,6 @@ var apiAuthJwtBearerSchema = func() map[string]*schema.Schema { Type: schema.TypeString, Required: true, }, - "oauth_grant": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{sdk.ApiAuthenticationSecurityIntegrationOauthGrantJwtBearer}, true), - Description: fmt.Sprintf("Specifies the type of OAuth flow. If removed from the config, the resource is recreated. Valid values are (case-insensitive): %s.", sdk.ApiAuthenticationSecurityIntegrationOauthGrantJwtBearer), - }, } return MergeMaps(apiAuthCommonSchema, apiAuthJwtBearer) }() @@ -50,11 +43,10 @@ func ApiAuthenticationIntegrationWithJwtBearer() *schema.Resource { ForceNewIfChangeToEmptyString("oauth_token_endpoint"), ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), ForceNewIfChangeToEmptyString("oauth_client_auth_method"), - ForceNewIfChangeToEmptyString("oauth_grant"), ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "enabled", "comment"), ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", "oauth_client_id", "oauth_client_auth_method", "oauth_authorization_endpoint", - "oauth_token_endpoint", "oauth_grant", "oauth_assertion_issuer"), + "oauth_token_endpoint", "oauth_assertion_issuer"), ), Importer: &schema.ResourceImporter{ StateContext: ImportApiAuthenticationWithJwtBearer, @@ -93,12 +85,6 @@ func ImportApiAuthenticationWithJwtBearer(ctx context.Context, d *schema.Resourc return nil, err } } - oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) - if err == nil { - if err = d.Set("oauth_grant", oauthGrant.Value); err != nil { - return nil, err - } - } return []*schema.ResourceData{d}, nil } @@ -111,6 +97,7 @@ func CreateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, } id := sdk.NewAccountObjectIdentifier(commonCreate.name) req := sdk.NewCreateApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest(id, commonCreate.enabled, d.Get("oauth_assertion_issuer").(string), commonCreate.oauthClientId, commonCreate.oauthClientSecret) + req.WithOauthGrantJwtBearer(true) req.Comment = commonCreate.comment req.OauthAccessTokenValidity = commonCreate.oauthAccessTokenValidity req.OauthRefreshTokenValidity = commonCreate.oauthRefreshTokenValidity @@ -121,12 +108,6 @@ func CreateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, req.WithOauthAuthorizationEndpoint(v.(string)) } - if v, ok := d.GetOk("oauth_grant"); ok { - if v.(string) == sdk.ApiAuthenticationSecurityIntegrationOauthGrantJwtBearer { - req.WithOauthGrantJwtBearer(true) - } - } - if err := client.SecurityIntegrations.CreateApiAuthenticationWithJwtBearerFlow(ctx, req); err != nil { return diag.FromErr(err) } @@ -173,21 +154,15 @@ func ReadContextApiAuthenticationIntegrationWithJwtBearer(withExternalChangesMar if err != nil { return diag.FromErr(err) } - oauthGrant, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_GRANT" }) - if err != nil { - return diag.FromErr(err) - } if err := handleApiAuthRead(d, integration, properties, withExternalChangesMarking, []describeMapping{ {"oauth_authorization_endpoint", "oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value, oauthAuthorizationEndpoint.Value, nil}, {"oauth_assertion_issuer", "oauth_assertion_issuer", oauthAssertionIssuer.Value, oauthAssertionIssuer.Value, nil}, - {"oauth_grant", "oauth_grant", oauthGrant.Value, oauthGrant.Value, nil}, }); err != nil { return diag.FromErr(err) } if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ "oauth_authorization_endpoint", "oauth_assertion_issuer", - "oauth_grant", }); err != nil { return diag.FromErr(err) } @@ -218,12 +193,6 @@ func UpdateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, if d.HasChange("oauth_authorization_endpoint") { set.WithOauthAuthorizationEndpoint(d.Get("oauth_authorization_endpoint").(string)) } - if d.HasChange("oauth_grant") { - if v := d.Get("oauth_grant").(string); v == sdk.ApiAuthenticationSecurityIntegrationOauthGrantJwtBearer { - set.WithOauthGrantJwtBearer(true) - } - // else: force new - } if !reflect.DeepEqual(*set, sdk.ApiAuthenticationWithJwtBearerFlowIntegrationSetRequest{}) { if err := client.SecurityIntegrations.AlterApiAuthenticationWithJwtBearerFlow(ctx, sdk.NewAlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest(id).WithSet(*set)); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/database_acceptance_test.go b/pkg/resources/database_acceptance_test.go index 79a3143219..d79e3d4797 100644 --- a/pkg/resources/database_acceptance_test.go +++ b/pkg/resources/database_acceptance_test.go @@ -725,7 +725,7 @@ func TestAcc_Database_IntParameter(t *testing.T) { { PreConfig: func() { param := acc.TestClient().Parameter.ShowAccountParameter(t, sdk.AccountParameterDataRetentionTimeInDays) - require.Equal(t, string(sdk.ParameterTypeAccount), string(param.Level)) + require.Equal(t, "", string(param.Level)) revert := acc.TestClient().Parameter.UpdateAccountParameterTemporarily(t, sdk.AccountParameterDataRetentionTimeInDays, "50") t.Cleanup(revert) }, @@ -770,7 +770,7 @@ func TestAcc_Database_IntParameter(t *testing.T) { { PreConfig: func() { param := acc.TestClient().Parameter.ShowAccountParameter(t, sdk.AccountParameterDataRetentionTimeInDays) - require.Equal(t, string(sdk.ParameterTypeAccount), string(param.Level)) + require.Equal(t, "", string(param.Level)) }, ConfigVariables: databaseBasicConfig, ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Database/int_parameter/unset"), @@ -815,7 +815,7 @@ func TestAcc_Database_IntParameter(t *testing.T) { { PreConfig: func() { param := acc.TestClient().Parameter.ShowAccountParameter(t, sdk.AccountParameterDataRetentionTimeInDays) - require.Equal(t, string(sdk.ParameterTypeAccount), string(param.Level)) + require.Equal(t, "", string(param.Level)) }, ConfigVariables: databaseBasicConfig, ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Database/int_parameter/unset"), @@ -835,7 +835,7 @@ func TestAcc_Database_IntParameter(t *testing.T) { { PreConfig: func() { param := acc.TestClient().Parameter.ShowAccountParameter(t, sdk.AccountParameterDataRetentionTimeInDays) - require.Equal(t, string(sdk.ParameterTypeAccount), string(param.Level)) + require.Equal(t, "", string(param.Level)) revert := acc.TestClient().Parameter.UpdateAccountParameterTemporarily(t, sdk.AccountParameterDataRetentionTimeInDays, "50") t.Cleanup(revert) }, diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf index 99720dc203..0346d55f28 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf @@ -9,6 +9,5 @@ resource "snowflake_api_authentication_integration_with_authorization_code_grant oauth_client_secret = var.oauth_client_secret oauth_refresh_token_validity = var.oauth_refresh_token_validity oauth_token_endpoint = var.oauth_token_endpoint - oauth_grant = var.oauth_grant oauth_allowed_scopes = var.oauth_allowed_scopes } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf index 38b4993f9e..06e9998ff5 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf @@ -28,9 +28,6 @@ variable "oauth_refresh_token_validity" { variable "oauth_token_endpoint" { type = string } -variable "oauth_grant" { - type = string -} variable "oauth_allowed_scopes" { type = set(string) } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf index 7bcdc528ca..44c4b6c513 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf @@ -9,5 +9,4 @@ resource "snowflake_api_authentication_integration_with_client_credentials" "tes oauth_client_secret = var.oauth_client_secret oauth_token_endpoint = var.oauth_token_endpoint oauth_allowed_scopes = var.oauth_allowed_scopes - oauth_grant = var.oauth_grant } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf index 53e4acbe3e..4ccccac436 100644 --- a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf @@ -28,6 +28,3 @@ variable "oauth_token_endpoint" { variable "oauth_allowed_scopes" { type = set(string) } -variable "oauth_grant" { - type = string -} diff --git a/templates/resources/api_authentication_integration_with_authorization_code_grant.md.tmpl b/templates/resources/api_authentication_integration_with_authorization_code_grant.md.tmpl new file mode 100644 index 0000000000..e7f66bbf91 --- /dev/null +++ b/templates/resources/api_authentication_integration_with_authorization_code_grant.md.tmpl @@ -0,0 +1,32 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ if gt (len (split .Description "")) 1 -}} +{{ index (split .Description "") 1 | plainmarkdown | trimspace | prefixlines " " }} +{{- else -}} +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +{{- end }} +--- + +!> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile (printf "examples/resources/%s/resource.tf" .Name)}} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} +{{- if .HasImport }} + +## Import + +Import is supported using the following syntax: + +{{ codefile "shell" (printf "examples/resources/%s/import.sh" .Name)}} +{{- end }} diff --git a/templates/resources/api_authentication_integration_with_client_credentials.md.tmpl b/templates/resources/api_authentication_integration_with_client_credentials.md.tmpl new file mode 100644 index 0000000000..e7f66bbf91 --- /dev/null +++ b/templates/resources/api_authentication_integration_with_client_credentials.md.tmpl @@ -0,0 +1,32 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ if gt (len (split .Description "")) 1 -}} +{{ index (split .Description "") 1 | plainmarkdown | trimspace | prefixlines " " }} +{{- else -}} +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +{{- end }} +--- + +!> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile (printf "examples/resources/%s/resource.tf" .Name)}} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} +{{- if .HasImport }} + +## Import + +Import is supported using the following syntax: + +{{ codefile "shell" (printf "examples/resources/%s/import.sh" .Name)}} +{{- end }} diff --git a/templates/resources/api_authentication_integration_with_jwt_bearer.md.tmpl b/templates/resources/api_authentication_integration_with_jwt_bearer.md.tmpl new file mode 100644 index 0000000000..e7f66bbf91 --- /dev/null +++ b/templates/resources/api_authentication_integration_with_jwt_bearer.md.tmpl @@ -0,0 +1,32 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ if gt (len (split .Description "")) 1 -}} +{{ index (split .Description "") 1 | plainmarkdown | trimspace | prefixlines " " }} +{{- else -}} +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +{{- end }} +--- + +!> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile (printf "examples/resources/%s/resource.tf" .Name)}} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} +{{- if .HasImport }} + +## Import + +Import is supported using the following syntax: + +{{ codefile "shell" (printf "examples/resources/%s/import.sh" .Name)}} +{{- end }} From 255b14a7a3c240cb96ab9e89b9545ea14fc98e06 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Tue, 9 Jul 2024 09:04:47 +0200 Subject: [PATCH 18/18] Add normalizeandcompare to client auth method diff suppress --- pkg/resources/api_authentication_integration_common.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/resources/api_authentication_integration_common.go b/pkg/resources/api_authentication_integration_common.go index 1ac5ce976d..bfb01a1814 100644 --- a/pkg/resources/api_authentication_integration_common.go +++ b/pkg/resources/api_authentication_integration_common.go @@ -42,7 +42,7 @@ var apiAuthCommonSchema = map[string]*schema.Schema{ Type: schema.TypeString, Optional: true, ValidateDiagFunc: sdkValidation(sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption), - DiffSuppressFunc: SuppressIfAny(ignoreCaseSuppressFunc, IgnoreChangeToCurrentSnowflakeValueInDescribe("oauth_client_auth_method")), + DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption), IgnoreChangeToCurrentSnowflakeValueInDescribe("oauth_client_auth_method")), Description: fmt.Sprintf("Specifies that POST is used as the authentication method to the external service. If removed from the config, the resource is recreated. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.AsStringList(sdk.AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption))), }, "oauth_access_token_validity": {