From e1a3c7cabd1a31bcee19c971f12f444eebc728e5 Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Mon, 6 May 2024 21:31:20 +0000 Subject: [PATCH] Add ExtraAttributesOauthClient field for google_iam_workforce_pool_provider resource (#10592) [upstream:e66c6af5ec40690ee8a532daa231b21e3d03982d] Signed-off-by: Modular Magician --- .../resource_iam_workforce_pool_provider.go | 384 +++++++++++++++++- ..._workforce_pool_provider_generated_test.go | 139 +++++++ ...source_iam_workforce_pool_provider_test.go | 244 +++++++++++ .../iam_workforce_pool_provider.html.markdown | 151 +++++++ 4 files changed, 899 insertions(+), 19 deletions(-) diff --git a/google-beta/services/iamworkforcepool/resource_iam_workforce_pool_provider.go b/google-beta/services/iamworkforcepool/resource_iam_workforce_pool_provider.go index 02aea306ef..3e17264a99 100644 --- a/google-beta/services/iamworkforcepool/resource_iam_workforce_pool_provider.go +++ b/google-beta/services/iamworkforcepool/resource_iam_workforce_pool_provider.go @@ -190,6 +190,88 @@ However, existing tokens still grant access.`, Optional: true, Description: `A user-specified display name for the provider. Cannot exceed 32 characters.`, }, + "extra_attributes_oauth2_client": { + Type: schema.TypeList, + Optional: true, + Description: `The configuration for OAuth 2.0 client used to get the additional user +attributes. This should be used when users can't get the desired claims +in authentication credentials. Currently this configuration is only +supported with OIDC protocol.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "attributes_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidateEnum([]string{"AZURE_AD_GROUPS_MAIL"}), + Description: `Represents the IdP and type of claims that should be fetched. +* AZURE_AD_GROUPS_MAIL: Used to get the user's group claims from the Azure AD identity provider using configuration provided +in ExtraAttributesOAuth2Client and 'mail' property of the 'microsoft.graph.group' object is used for claim mapping. +See https://learn.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties for more details on +'microsoft.graph.group' properties. The attributes obtained from idntity provider are mapped to 'assertion.groups'. Possible values: ["AZURE_AD_GROUPS_MAIL"]`, + }, + "client_id": { + Type: schema.TypeString, + Required: true, + Description: `The OAuth 2.0 client ID for retrieving extra attributes from the identity provider. Required to get the Access Token using client credentials grant flow.`, + }, + "client_secret": { + Type: schema.TypeList, + Required: true, + Description: `The OAuth 2.0 client secret for retrieving extra attributes from the identity provider. Required to get the Access Token using client credentials grant flow.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Type: schema.TypeList, + Optional: true, + Description: `The value of the client secret.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "plain_text": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + Description: `The plain text of the client secret value.`, + }, + "thumbprint": { + Type: schema.TypeString, + Computed: true, + Description: `A thumbprint to represent the current client secret value.`, + }, + }, + }, + ExactlyOneOf: []string{"extra_attributes_oauth2_client.0.client_secret.0.value"}, + }, + }, + }, + }, + "issuer_uri": { + Type: schema.TypeString, + Required: true, + Description: `The OIDC identity provider's issuer URI. Must be a valid URI using the 'https' scheme. Required to get the OIDC discovery document.`, + }, + "query_parameters": { + Type: schema.TypeList, + Optional: true, + Description: `Represents the parameters to control which claims are fetched from an IdP.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "filter": { + Type: schema.TypeString, + Optional: true, + Description: `The filter used to request specific records from IdP. In case of attributes type as AZURE_AD_GROUPS_MAIL, it represents the +filter used to request specific groups for users from IdP. By default, all of the groups associated with the user are fetched. The +groups should be mail enabled and security enabled. See https://learn.microsoft.com/en-us/graph/search-query-parameter for more details.`, + }, + }, + }, + }, + }, + }, + }, "oidc": { Type: schema.TypeList, Optional: true, @@ -410,6 +492,12 @@ func resourceIAMWorkforcePoolWorkforcePoolProviderCreate(d *schema.ResourceData, } else if v, ok := d.GetOkExists("oidc"); !tpgresource.IsEmptyValue(reflect.ValueOf(oidcProp)) && (ok || !reflect.DeepEqual(v, oidcProp)) { obj["oidc"] = oidcProp } + extraAttributesOauth2ClientProp, err := expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2Client(d.Get("extra_attributes_oauth2_client"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("extra_attributes_oauth2_client"); !tpgresource.IsEmptyValue(reflect.ValueOf(extraAttributesOauth2ClientProp)) && (ok || !reflect.DeepEqual(v, extraAttributesOauth2ClientProp)) { + obj["extraAttributesOauth2Client"] = extraAttributesOauth2ClientProp + } url, err := tpgresource.ReplaceVars(d, config, "{{IAMWorkforcePoolBasePath}}locations/{{location}}/workforcePools/{{workforce_pool_id}}/providers?workforcePoolProviderId={{provider_id}}") if err != nil { @@ -456,20 +544,37 @@ func resourceIAMWorkforcePoolWorkforcePoolProviderCreate(d *schema.ResourceData, return fmt.Errorf("Error waiting to create WorkforcePoolProvider: %s", err) } - createdClientSecret := d.Get("oidc.0.client_secret.0.value.0.plain_text") - if createdClientSecret != nil && createdClientSecret != "" { + createdOidcClientSecret := d.Get("oidc.0.client_secret.0.value.0.plain_text") + createdExtraAttributesClientSecret := d.Get("extra_attributes_oauth2_client.0.client_secret.0.value.0.plain_text") + + if (createdOidcClientSecret != nil && createdOidcClientSecret != "") || (createdExtraAttributesClientSecret != nil && createdExtraAttributesClientSecret != "") { // After the create, reading from the API returns a new thumbprint // for the client secret value, which clears the plain_text. We set the plain_text since // this case should not warrant a diff. if err := resourceIAMWorkforcePoolWorkforcePoolProviderRead(d, meta); err != nil { return err } - oidc := d.Get("oidc") - clientSecret := oidc.([]interface{})[0].(map[string]interface{})["client_secret"] - clientSecretValue := clientSecret.([]interface{})[0].(map[string]interface{})["value"] - clientSecretValue.([]interface{})[0].(map[string]interface{})["plain_text"] = createdClientSecret - if err := d.Set("oidc", oidc); err != nil { - return err + + // Populate ExtraAttributesOauth2Client the client secret plain text + if createdExtraAttributesClientSecret != nil && createdExtraAttributesClientSecret != "" { + extraAttributesOauth2Client := d.Get("extra_attributes_oauth2_client") + clientSecret := extraAttributesOauth2Client.([]interface{})[0].(map[string]interface{})["client_secret"] + clientSecretValue := clientSecret.([]interface{})[0].(map[string]interface{})["value"] + clientSecretValue.([]interface{})[0].(map[string]interface{})["plain_text"] = createdExtraAttributesClientSecret + if err := d.Set("extra_attributes_oauth2_client", extraAttributesOauth2Client); err != nil { + return err + } + } + + // Populate OIDC the client secret plain text + if createdOidcClientSecret != nil && createdOidcClientSecret != "" { + oidc := d.Get("oidc") + clientSecret := oidc.([]interface{})[0].(map[string]interface{})["client_secret"] + clientSecretValue := clientSecret.([]interface{})[0].(map[string]interface{})["value"] + clientSecretValue.([]interface{})[0].(map[string]interface{})["plain_text"] = createdOidcClientSecret + if err := d.Set("oidc", oidc); err != nil { + return err + } } return nil } @@ -550,6 +655,9 @@ func resourceIAMWorkforcePoolWorkforcePoolProviderRead(d *schema.ResourceData, m if err := d.Set("oidc", flattenIAMWorkforcePoolWorkforcePoolProviderOidc(res["oidc"], d, config)); err != nil { return fmt.Errorf("Error reading WorkforcePoolProvider: %s", err) } + if err := d.Set("extra_attributes_oauth2_client", flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2Client(res["extraAttributesOauth2Client"], d, config)); err != nil { + return fmt.Errorf("Error reading WorkforcePoolProvider: %s", err) + } return nil } @@ -606,6 +714,12 @@ func resourceIAMWorkforcePoolWorkforcePoolProviderUpdate(d *schema.ResourceData, } else if v, ok := d.GetOkExists("oidc"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, oidcProp)) { obj["oidc"] = oidcProp } + extraAttributesOauth2ClientProp, err := expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2Client(d.Get("extra_attributes_oauth2_client"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("extra_attributes_oauth2_client"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, extraAttributesOauth2ClientProp)) { + obj["extraAttributesOauth2Client"] = extraAttributesOauth2ClientProp + } url, err := tpgresource.ReplaceVars(d, config, "{{IAMWorkforcePoolBasePath}}locations/{{location}}/workforcePools/{{workforce_pool_id}}/providers/{{provider_id}}") if err != nil { @@ -643,6 +757,10 @@ func resourceIAMWorkforcePoolWorkforcePoolProviderUpdate(d *schema.ResourceData, if d.HasChange("oidc") { updateMask = append(updateMask, "oidc") } + + if d.HasChange("extra_attributes_oauth2_client") { + updateMask = append(updateMask, "extraAttributesOauth2Client") + } // updateMask is a URL parameter but not present in the schema, so ReplaceVars // won't set it url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) @@ -683,24 +801,36 @@ func resourceIAMWorkforcePoolWorkforcePoolProviderUpdate(d *schema.ResourceData, } } - if d.HasChange("oidc") { - updatedClientSecret := d.Get("oidc.0.client_secret.0.value.0.plain_text") - if updatedClientSecret != nil && updatedClientSecret != "" { - // After the update, reading from the API returns a different thumbprint - // for the client secret value, which clears the plain_text. We set the plain_text since - // this case should not warrant a diff. - if err := resourceIAMWorkforcePoolWorkforcePoolProviderRead(d, meta); err != nil { - return err - } + if d.HasChange("oidc") || d.HasChange("extra_attributes_oauth2_client") { + updatedOidcClientSecret := d.Get("oidc.0.client_secret.0.value.0.plain_text") + updatedExtraAttributesOauth2ClientSecret := d.Get("extra_attributes_oauth2_client.0.client_secret.0.value.0.plain_text") + // After the update, reading from the API returns a different thumbprint + // for the client secret value, which clears the plain_text. We set the plain_text since + // this case should not warrant a diff. + if err := resourceIAMWorkforcePoolWorkforcePoolProviderRead(d, meta); err != nil { + return err + } + + if updatedOidcClientSecret != nil && updatedOidcClientSecret != "" { oidc := d.Get("oidc") clientSecret := oidc.([]interface{})[0].(map[string]interface{})["client_secret"] clientSecretValue := clientSecret.([]interface{})[0].(map[string]interface{})["value"] - clientSecretValue.([]interface{})[0].(map[string]interface{})["plain_text"] = updatedClientSecret + clientSecretValue.([]interface{})[0].(map[string]interface{})["plain_text"] = updatedOidcClientSecret if err := d.Set("oidc", oidc); err != nil { return err } - return nil } + + if updatedExtraAttributesOauth2ClientSecret != nil && updatedExtraAttributesOauth2ClientSecret != "" { + extraAttributesOauth2Client := d.Get("extra_attributes_oauth2_client") + clientSecret := extraAttributesOauth2Client.([]interface{})[0].(map[string]interface{})["client_secret"] + clientSecretValue := clientSecret.([]interface{})[0].(map[string]interface{})["value"] + clientSecretValue.([]interface{})[0].(map[string]interface{})["plain_text"] = updatedExtraAttributesOauth2ClientSecret + if err := d.Set("extra_attributes_oauth2_client", extraAttributesOauth2Client); err != nil { + return err + } + } + return nil } return resourceIAMWorkforcePoolWorkforcePoolProviderRead(d, meta) } @@ -912,6 +1042,87 @@ func flattenIAMWorkforcePoolWorkforcePoolProviderOidcJwksJson(v interface{}, d * return v } +func flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2Client(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["issuer_uri"] = + flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientIssuerUri(original["issuerUri"], d, config) + transformed["client_id"] = + flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientId(original["clientId"], d, config) + transformed["client_secret"] = + flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientSecret(original["clientSecret"], d, config) + transformed["attributes_type"] = + flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientAttributesType(original["attributesType"], d, config) + transformed["query_parameters"] = + flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientQueryParameters(original["queryParameters"], d, config) + return []interface{}{transformed} +} +func flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientIssuerUri(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientId(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientSecret(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["value"] = + flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientSecretValue(original["value"], d, config) + return []interface{}{transformed} +} +func flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientSecretValue(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["thumbprint"] = original["thumbprint"] + // Trigger a diff based on the plain_text if there is no change in the thumbprint, + // otherwise leave plain_text empty to always trigger a diff. + if original["thumbprint"].(string) == d.Get("extra_attributes_oauth2_client.0.client_secret.0.value.0.thumbprint").(string) { + transformed["plain_text"] = d.Get("extra_attributes_oauth2_client.0.client_secret.0.value.0.plain_text") + } + return []interface{}{transformed} +} + +func flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientAttributesType(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientQueryParameters(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["filter"] = + flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientQueryParametersFilter(original["filter"], d, config) + return []interface{}{transformed} +} +func flattenIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientQueryParametersFilter(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + func expandIAMWorkforcePoolWorkforcePoolProviderDisplayName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { return v, nil } @@ -1119,6 +1330,141 @@ func expandIAMWorkforcePoolWorkforcePoolProviderOidcJwksJson(v interface{}, d tp return v, nil } +func expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2Client(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedIssuerUri, err := expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientIssuerUri(original["issuer_uri"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedIssuerUri); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["issuerUri"] = transformedIssuerUri + } + + transformedClientId, err := expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientId(original["client_id"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedClientId); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["clientId"] = transformedClientId + } + + transformedClientSecret, err := expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientSecret(original["client_secret"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedClientSecret); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["clientSecret"] = transformedClientSecret + } + + transformedAttributesType, err := expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientAttributesType(original["attributes_type"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAttributesType); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["attributesType"] = transformedAttributesType + } + + transformedQueryParameters, err := expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientQueryParameters(original["query_parameters"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedQueryParameters); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["queryParameters"] = transformedQueryParameters + } + + return transformed, nil +} + +func expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientIssuerUri(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientId(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientSecret(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedValue, err := expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientSecretValue(original["value"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedValue); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["value"] = transformedValue + } + + return transformed, nil +} + +func expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientSecretValue(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedPlainText, err := expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientSecretValuePlainText(original["plain_text"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPlainText); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["plainText"] = transformedPlainText + } + + transformedThumbprint, err := expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientSecretValueThumbprint(original["thumbprint"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedThumbprint); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["thumbprint"] = transformedThumbprint + } + + return transformed, nil +} + +func expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientSecretValuePlainText(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientClientSecretValueThumbprint(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientAttributesType(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientQueryParameters(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedFilter, err := expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientQueryParametersFilter(original["filter"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedFilter); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["filter"] = transformedFilter + } + + return transformed, nil +} + +func expandIAMWorkforcePoolWorkforcePoolProviderExtraAttributesOauth2ClientQueryParametersFilter(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + func resourceIAMWorkforcePoolWorkforcePoolProviderDecoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { if v := res["state"]; v == "DELETED" { return nil, nil diff --git a/google-beta/services/iamworkforcepool/resource_iam_workforce_pool_provider_generated_test.go b/google-beta/services/iamworkforcepool/resource_iam_workforce_pool_provider_generated_test.go index 02d618d32a..d4a0ee9b5a 100644 --- a/google-beta/services/iamworkforcepool/resource_iam_workforce_pool_provider_generated_test.go +++ b/google-beta/services/iamworkforcepool/resource_iam_workforce_pool_provider_generated_test.go @@ -311,6 +311,145 @@ resource "google_iam_workforce_pool_provider" "example" { `, context) } +func TestAccIAMWorkforcePoolWorkforcePoolProvider_iamWorkforcePoolProviderExtraAttributesOauth2ConfigClientBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": envvar.GetTestOrgFromEnv(t), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckIAMWorkforcePoolWorkforcePoolProviderDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccIAMWorkforcePoolWorkforcePoolProvider_iamWorkforcePoolProviderExtraAttributesOauth2ConfigClientBasicExample(context), + }, + { + ResourceName: "google_iam_workforce_pool_provider.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "workforce_pool_id", "provider_id", "oidc.0.client_secret.0.value.0.plain_text", "extra_attributes_oauth2_client.0.client_secret.0.value.0.plain_text"}, + }, + }, + }) +} + +func testAccIAMWorkforcePoolWorkforcePoolProvider_iamWorkforcePoolProviderExtraAttributesOauth2ConfigClientBasicExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_iam_workforce_pool" "pool" { + workforce_pool_id = "tf-test-example-pool%{random_suffix}" + parent = "organizations/%{org_id}" + location = "global" +} + +resource "google_iam_workforce_pool_provider" "example" { + workforce_pool_id = google_iam_workforce_pool.pool.workforce_pool_id + location = google_iam_workforce_pool.pool.location + provider_id = "tf-test-example-prvdr%{random_suffix}" + attribute_mapping = { + "google.subject" = "assertion.sub" + } + oidc { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + web_sso_config { + response_type = "CODE" + assertion_claims_behavior = "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS" + } + client_secret { + value { + plain_text = "client-secret" + } + } + } + extra_attributes_oauth2_client { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + client_secret { + value { + plain_text = "client-secret" + } + } + attributes_type = "AZURE_AD_GROUPS_MAIL" + } +} +`, context) +} + +func TestAccIAMWorkforcePoolWorkforcePoolProvider_iamWorkforcePoolProviderExtraAttributesOauth2ConfigClientFullExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": envvar.GetTestOrgFromEnv(t), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckIAMWorkforcePoolWorkforcePoolProviderDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccIAMWorkforcePoolWorkforcePoolProvider_iamWorkforcePoolProviderExtraAttributesOauth2ConfigClientFullExample(context), + }, + { + ResourceName: "google_iam_workforce_pool_provider.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "workforce_pool_id", "provider_id", "oidc.0.client_secret.0.value.0.plain_text", "extra_attributes_oauth2_client.0.client_secret.0.value.0.plain_text"}, + }, + }, + }) +} + +func testAccIAMWorkforcePoolWorkforcePoolProvider_iamWorkforcePoolProviderExtraAttributesOauth2ConfigClientFullExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_iam_workforce_pool" "pool" { + workforce_pool_id = "tf-test-example-pool%{random_suffix}" + parent = "organizations/%{org_id}" + location = "global" +} + +resource "google_iam_workforce_pool_provider" "example" { + workforce_pool_id = google_iam_workforce_pool.pool.workforce_pool_id + location = google_iam_workforce_pool.pool.location + provider_id = "tf-test-example-prvdr%{random_suffix}" + attribute_mapping = { + "google.subject" = "assertion.sub" + } + oidc { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + client_secret { + value { + plain_text = "client-secret" + } + } + web_sso_config { + response_type = "CODE" + assertion_claims_behavior = "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS" + } + } + extra_attributes_oauth2_client { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + client_secret { + value { + plain_text = "client-secret" + } + } + attributes_type = "AZURE_AD_GROUPS_MAIL" + query_parameters { + filter = "mail:gcp" + } + } +} +`, context) +} + func testAccCheckIAMWorkforcePoolWorkforcePoolProviderDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { for name, rs := range s.RootModule().Resources { diff --git a/google-beta/services/iamworkforcepool/resource_iam_workforce_pool_provider_test.go b/google-beta/services/iamworkforcepool/resource_iam_workforce_pool_provider_test.go index bcdcbb3a41..1bc70fd3e6 100644 --- a/google-beta/services/iamworkforcepool/resource_iam_workforce_pool_provider_test.go +++ b/google-beta/services/iamworkforcepool/resource_iam_workforce_pool_provider_test.go @@ -122,6 +122,66 @@ func TestAccIAMWorkforcePoolWorkforcePoolProvider_saml(t *testing.T) { }) } +func TestAccIAMWorkforcePoolWorkforcePoolProvider_extraAttributesOauth2Client(t *testing.T) { + t.Parallel() + + random_suffix := acctest.RandString(t, 10) + context := map[string]interface{}{ + "org_id": envvar.GetTestOrgFromEnv(t), + "random_suffix": random_suffix, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckIAMWorkforcePoolWorkforcePoolDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccIAMWorkforcePoolWorkforcePoolProvider_extraAttributesOauth2Client_full(context), + }, + { + ResourceName: "google_iam_workforce_pool_provider.my_provider", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"oidc.0.client_secret.0.value.0.plain_text", "extra_attributes_oauth2_client.0.client_secret.0.value.0.plain_text"}, + }, + { + Config: testAccIAMWorkforcePoolWorkforcePoolProvider_extraAttributesOauth2Client_update(context), + }, + { + ResourceName: "google_iam_workforce_pool_provider.my_provider", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"oidc.0.client_secret.0.value.0.plain_text", "extra_attributes_oauth2_client.0.client_secret.0.value.0.plain_text"}, + }, + { + Config: testAccIAMWorkforcePoolWorkforcePoolProvider_extraAttributesOauth2Client_update_clearConfig(context), + }, + { + ResourceName: "google_iam_workforce_pool_provider.my_provider", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"oidc.0.client_secret.0.value.0.plain_text"}, + }, + { + Config: testAccIAMWorkforcePoolWorkforcePoolProvider_extraAttributesOauth2Client_basic(context), + }, + { + ResourceName: "google_iam_workforce_pool_provider.my_provider", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"oidc.0.client_secret.0.value.0.plain_text", "extra_attributes_oauth2_client.0.client_secret.0.value.0.plain_text"}, + }, + { + Config: testAccIAMWorkforcePoolWorkforcePoolProvider_destroy(context), + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMWorkforcePoolWorkforcePoolProviderAccess(t, random_suffix), + ), + }, + }, + }) +} + func testAccCheckIAMWorkforcePoolWorkforcePoolProviderAccess(t *testing.T, random_suffix string) resource.TestCheckFunc { return func(s *terraform.State) error { pool_resource_name := "google_iam_workforce_pool.my_pool" @@ -367,6 +427,190 @@ resource "google_iam_workforce_pool_provider" "my_provider" { `, context) } +func testAccIAMWorkforcePoolWorkforcePoolProvider_extraAttributesOauth2Client_full(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_iam_workforce_pool" "my_pool" { + workforce_pool_id = "my-pool-%{random_suffix}" + parent = "organizations/%{org_id}" + location = "global" +} + +resource "google_iam_workforce_pool_provider" "my_provider" { + workforce_pool_id = google_iam_workforce_pool.my_pool.workforce_pool_id + location = google_iam_workforce_pool.my_pool.location + provider_id = "my-provider-%{random_suffix}" + attribute_mapping = { + "google.subject" = "assertion.sub" + } + oidc { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + client_secret { + value { + plain_text = "client-secret" + } + } + web_sso_config { + response_type = "CODE" + assertion_claims_behavior = "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS" + additional_scopes = ["groups", "roles"] + } + } + extra_attributes_oauth2_client { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + client_secret { + value { + plain_text = "client-secret" + } + } + attributes_type = "AZURE_AD_GROUPS_MAIL" + query_parameters { + filter = "mail:gcp" + } + } + display_name = "Display name" + description = "A sample OIDC workforce pool provider." + disabled = false + attribute_condition = "true" +} +`, context) +} + +func testAccIAMWorkforcePoolWorkforcePoolProvider_extraAttributesOauth2Client_update(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_iam_workforce_pool" "my_pool" { + workforce_pool_id = "my-pool-%{random_suffix}" + parent = "organizations/%{org_id}" + location = "global" +} + +resource "google_iam_workforce_pool_provider" "my_provider" { + workforce_pool_id = google_iam_workforce_pool.my_pool.workforce_pool_id + location = google_iam_workforce_pool.my_pool.location + provider_id = "my-provider-%{random_suffix}" + attribute_mapping = { + "google.subject" = "false" + } + oidc { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + client_secret { + value { + plain_text = "client-secret" + } + } + web_sso_config { + response_type = "CODE" + assertion_claims_behavior = "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS" + additional_scopes = ["groups", "roles"] + } + } + extra_attributes_oauth2_client { + issuer_uri = "https://accounts.thirdparty.com/new" + client_id = "new-client-id" + client_secret { + value { + plain_text = "new-client-secret" + } + } + attributes_type = "AZURE_AD_GROUPS_MAIL" + query_parameters { + filter = "displayName:gcp" + } + } + display_name = "New Display name" + description = "A sample OIDC workforce pool provider with updated description." + disabled = true + attribute_condition = "false" +} +`, context) +} + +func testAccIAMWorkforcePoolWorkforcePoolProvider_extraAttributesOauth2Client_update_clearConfig(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_iam_workforce_pool" "my_pool" { + workforce_pool_id = "my-pool-%{random_suffix}" + parent = "organizations/%{org_id}" + location = "global" +} + +resource "google_iam_workforce_pool_provider" "my_provider" { + workforce_pool_id = google_iam_workforce_pool.my_pool.workforce_pool_id + location = google_iam_workforce_pool.my_pool.location + provider_id = "my-provider-%{random_suffix}" + attribute_mapping = { + "google.subject" = "false" + } + oidc { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + client_secret { + value { + plain_text = "client-secret" + } + } + web_sso_config { + response_type = "CODE" + assertion_claims_behavior = "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS" + additional_scopes = ["groups", "roles"] + } + } + display_name = "New Display name" + description = "A sample OIDC workforce pool provider with updated description." + disabled = true + attribute_condition = "false" +} +`, context) +} + +func testAccIAMWorkforcePoolWorkforcePoolProvider_extraAttributesOauth2Client_basic(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_iam_workforce_pool" "my_pool" { + workforce_pool_id = "my-pool-%{random_suffix}" + parent = "organizations/%{org_id}" + location = "global" +} + +resource "google_iam_workforce_pool_provider" "my_provider" { + workforce_pool_id = google_iam_workforce_pool.my_pool.workforce_pool_id + location = google_iam_workforce_pool.my_pool.location + provider_id = "my-provider-%{random_suffix}" + attribute_mapping = { + "google.subject" = "false" + } + oidc { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + client_secret { + value { + plain_text = "client-secret" + } + } + web_sso_config { + response_type = "CODE" + assertion_claims_behavior = "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS" + additional_scopes = ["groups", "roles"] + } + } + extra_attributes_oauth2_client { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + client_secret { + value { + plain_text = "client-secret" + } + } + attributes_type = "AZURE_AD_GROUPS_MAIL" + } + display_name = "New Display name" + description = "A sample OIDC workforce pool provider with updated description." + disabled = true + attribute_condition = "false" +} +`, context) +} + func testAccIAMWorkforcePoolWorkforcePoolProvider_destroy(context map[string]interface{}) string { return acctest.Nprintf(` resource "google_iam_workforce_pool" "my_pool" { diff --git a/website/docs/r/iam_workforce_pool_provider.html.markdown b/website/docs/r/iam_workforce_pool_provider.html.markdown index 6230e21c0d..8b054486ec 100644 --- a/website/docs/r/iam_workforce_pool_provider.html.markdown +++ b/website/docs/r/iam_workforce_pool_provider.html.markdown @@ -152,6 +152,93 @@ resource "google_iam_workforce_pool_provider" "example" { attribute_condition = "true" } ``` +## Example Usage - Iam Workforce Pool Provider Extra Attributes Oauth2 Config Client Basic + + +```hcl +resource "google_iam_workforce_pool" "pool" { + workforce_pool_id = "example-pool" + parent = "organizations/123456789" + location = "global" +} + +resource "google_iam_workforce_pool_provider" "example" { + workforce_pool_id = google_iam_workforce_pool.pool.workforce_pool_id + location = google_iam_workforce_pool.pool.location + provider_id = "example-prvdr" + attribute_mapping = { + "google.subject" = "assertion.sub" + } + oidc { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + web_sso_config { + response_type = "CODE" + assertion_claims_behavior = "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS" + } + client_secret { + value { + plain_text = "client-secret" + } + } + } + extra_attributes_oauth2_client { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + client_secret { + value { + plain_text = "client-secret" + } + } + attributes_type = "AZURE_AD_GROUPS_MAIL" + } +} +``` +## Example Usage - Iam Workforce Pool Provider Extra Attributes Oauth2 Config Client Full + + +```hcl +resource "google_iam_workforce_pool" "pool" { + workforce_pool_id = "example-pool" + parent = "organizations/123456789" + location = "global" +} + +resource "google_iam_workforce_pool_provider" "example" { + workforce_pool_id = google_iam_workforce_pool.pool.workforce_pool_id + location = google_iam_workforce_pool.pool.location + provider_id = "example-prvdr" + attribute_mapping = { + "google.subject" = "assertion.sub" + } + oidc { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + client_secret { + value { + plain_text = "client-secret" + } + } + web_sso_config { + response_type = "CODE" + assertion_claims_behavior = "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS" + } + } + extra_attributes_oauth2_client { + issuer_uri = "https://accounts.thirdparty.com" + client_id = "client-id" + client_secret { + value { + plain_text = "client-secret" + } + } + attributes_type = "AZURE_AD_GROUPS_MAIL" + query_parameters { + filter = "mail:gcp" + } + } +} +``` ## Argument Reference @@ -265,6 +352,14 @@ The following arguments are supported: Represents an OpenId Connect 1.0 identity provider. Structure is [documented below](#nested_oidc). +* `extra_attributes_oauth2_client` - + (Optional) + The configuration for OAuth 2.0 client used to get the additional user + attributes. This should be used when users can't get the desired claims + in authentication credentials. Currently this configuration is only + supported with OIDC protocol. + Structure is [documented below](#nested_extra_attributes_oauth2_client). + The `saml` block supports: @@ -372,6 +467,62 @@ The following arguments are supported: Additional scopes to request for in the OIDC authentication request on top of scopes requested by default. By default, the `openid`, `profile` and `email` scopes that are supported by the identity provider are requested. Each additional scope may be at most 256 characters. A maximum of 10 additional scopes may be configured. +The `extra_attributes_oauth2_client` block supports: + +* `issuer_uri` - + (Required) + The OIDC identity provider's issuer URI. Must be a valid URI using the `https` scheme. Required to get the OIDC discovery document. + +* `client_id` - + (Required) + The OAuth 2.0 client ID for retrieving extra attributes from the identity provider. Required to get the Access Token using client credentials grant flow. + +* `client_secret` - + (Required) + The OAuth 2.0 client secret for retrieving extra attributes from the identity provider. Required to get the Access Token using client credentials grant flow. + Structure is [documented below](#nested_client_secret). + +* `attributes_type` - + (Required) + Represents the IdP and type of claims that should be fetched. + * AZURE_AD_GROUPS_MAIL: Used to get the user's group claims from the Azure AD identity provider using configuration provided + in ExtraAttributesOAuth2Client and `mail` property of the `microsoft.graph.group` object is used for claim mapping. + See https://learn.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties for more details on + `microsoft.graph.group` properties. The attributes obtained from idntity provider are mapped to `assertion.groups`. + Possible values are: `AZURE_AD_GROUPS_MAIL`. + +* `query_parameters` - + (Optional) + Represents the parameters to control which claims are fetched from an IdP. + Structure is [documented below](#nested_query_parameters). + + +The `client_secret` block supports: + +* `value` - + (Optional) + The value of the client secret. + Structure is [documented below](#nested_value). + + +The `value` block supports: + +* `plain_text` - + (Required) + The plain text of the client secret value. + +* `thumbprint` - + (Output) + A thumbprint to represent the current client secret value. + +The `query_parameters` block supports: + +* `filter` - + (Optional) + The filter used to request specific records from IdP. In case of attributes type as AZURE_AD_GROUPS_MAIL, it represents the + filter used to request specific groups for users from IdP. By default, all of the groups associated with the user are fetched. The + groups should be mail enabled and security enabled. See https://learn.microsoft.com/en-us/graph/search-query-parameter for more details. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are exported: