From 9a46a774ba6530312ea2a62b03592e2d7f80e9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Fri, 26 Jul 2024 12:26:56 +0200 Subject: [PATCH 01/20] add resource wiz_saml_group_mapping --- internal/provider/provider.go | 1 + .../provider/resource_saml_group_mapping.go | 379 ++++++++++++++++++ .../resource_saml_group_mapping_test.go | 53 +++ internal/wiz/structs.go | 27 ++ 4 files changed, 460 insertions(+) create mode 100644 internal/provider/resource_saml_group_mapping.go create mode 100644 internal/provider/resource_saml_group_mapping_test.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 5fa812d..d31b54e 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -299,6 +299,7 @@ yLyKQXhw2W2Xs0qLeC1etA+jTGDK4UfLeC0SF7FSi8o5LL21L8IzApar2pR/ "wiz_report_graph_query": resourceWizReportGraphQuery(), "wiz_project": resourceWizProject(), "wiz_saml_idp": resourceWizSAMLIdP(), + "wiz_saml_group_mapping": resourceWizSAMLGroupMapping(), "wiz_security_framework": resourceWizSecurityFramework(), "wiz_service_account": resourceWizServiceAccount(), "wiz_user": resourceWizUser(), diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go new file mode 100644 index 0000000..534f261 --- /dev/null +++ b/internal/provider/resource_saml_group_mapping.go @@ -0,0 +1,379 @@ +package provider + +import ( + "context" + "errors" + "github.com/google/uuid" + "slices" + "strings" + "wiz.io/hashicorp/terraform-provider-wiz/internal/utils" + + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "wiz.io/hashicorp/terraform-provider-wiz/internal" + "wiz.io/hashicorp/terraform-provider-wiz/internal/client" + "wiz.io/hashicorp/terraform-provider-wiz/internal/wiz" +) + +// ReadSAMLGroupMappings represents the structure of a SAML group mappings read operation. +// It includes a SAMLGroupMappings object. +type ReadSAMLGroupMappings struct { + SAMLGroupMappings SAMLGroupMappings `json:"samlIdentityProviderGroupMappings"` +} + +// SAMLGroupMappings represents the structure of SAML group mappings. +// It includes PageInfo and a list of Nodes. +type SAMLGroupMappings struct { + PageInfo wiz.PageInfo `json:"pageInfo"` + Nodes []*wiz.SAMLGroupMapping `json:"nodes,omitempty"` +} + +// SAMLGroupMappingsImport represents the structure of a SAML group mapping import. +// It includes the SAML IdP ID, provider group ID, project IDs, and role. +type SAMLGroupMappingsImport struct { + SamlIdpID string + ProviderGroupID string + ProjectIDs []string + Role string +} + +func resourceWizSAMLGroupMapping() *schema.Resource { + return &schema.Resource{ + Description: "Configure SAML Group Role Mapping. If you use SSO to authenticate to Wiz, you can bind group memberships in SAML tokens to Wiz roles over certain scopes.", + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Description: "Unique tf-internal identifier for the saml group mapping", + Computed: true, + }, + "saml_idp_id": { + Type: schema.TypeString, + Description: "Identifier for the Saml Provider", + Required: true, + ForceNew: true, + }, + "provider_group_id": { + Type: schema.TypeString, + Description: "Provider group ID", + Required: true, + ForceNew: true, + }, + "role": { + Type: schema.TypeString, + Description: "Wiz Role name", + Required: true, + }, + "projects": { + Type: schema.TypeList, + Optional: true, + Description: "Project mapping", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + CreateContext: resourceSAMLGroupMappingCreate, + ReadContext: resourceSAMLGroupMappingRead, + UpdateContext: resourceSAMLGroupMappingUpdate, + DeleteContext: resourceSAMLGroupMappingDelete, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + // schema for import id: mapping|||| + + mappingToImport, err := extractIDsFromSamlIdpGroupMappingImportID(d.Id()) + if err != nil { + return nil, err + } + + err = d.Set("saml_idp_id", mappingToImport.SamlIdpID) + if err != nil { + return nil, err + } + + err = d.Set("provider_group_id", mappingToImport.ProviderGroupID) + if err != nil { + return nil, err + } + + err = d.Set("role", mappingToImport.Role) + if err != nil { + return nil, err + } + + err = d.Set("projects", mappingToImport.ProjectIDs) + if err != nil { + return nil, err + } + + d.SetId(uuid.NewString()) + + return []*schema.ResourceData{d}, nil + }, + }, + } +} + +func resourceSAMLGroupMappingCreate(ctx context.Context, d *schema.ResourceData, m interface{}) (diags diag.Diagnostics) { + tflog.Info(ctx, "resourceWizSAMLGroupMappingCreate called...") + + samlIdpID := d.Get("saml_idp_id").(string) + providerGroupID := d.Get("provider_group_id").(string) + role := d.Get("role").(string) + projectIDs := utils.ConvertListToString(d.Get("projects").([]interface{})) + + // verify the mapping doesn't already exist + matchingNode, diags := querySAMLGroupMappings(ctx, m, samlIdpID, providerGroupID, role, projectIDs) + if len(diags) != 0 { + return diags + } + + if matchingNode != nil { + return diag.Errorf("saml group mapping for group: %s and role: %s to project(s): %s already exists for saml idp provider: %s and should be imported instead", + providerGroupID, role, strings.Join(projectIDs, ", "), samlIdpID) + } + + // define the graphql query + query := `mutation SetSAMLGroupMapping ($input: ModifySAMLGroupMappingInput!) { + modifySAMLIdentityProviderGroupMappings(input: $input) { + _stub + } + }` + // populate the graphql variables + vars := &wiz.UpdateSAMLGroupMappingInput{} + vars.ID = samlIdpID + vars.Patch.Upsert.ProviderGroupID = providerGroupID + vars.Patch.Upsert.Role = role + vars.Patch.Upsert.Projects = projectIDs + + // process the request + data := &wiz.UpdateSAMLGroupMappingPayload{} + requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "create") + diags = append(diags, requestDiags...) + if len(diags) > 0 { + return diags + } + + // set the id + d.SetId(uuid.NewString()) + + return resourceSAMLGroupMappingRead(ctx, d, m) +} + +func extractIDsFromSamlIdpGroupMappingImportID(id string) (SAMLGroupMappingsImport, error) { + parts := strings.Split(id, "|") + if len(parts) != 5 { + return SAMLGroupMappingsImport{}, errors.New("invalid ID format") + } + + // if user species the mapping to be global we return an empty slice + var projectIDs []string + if parts[3] != "global" { + for _, projectID := range strings.Split(parts[3], ",") { + projectIDs = append(projectIDs, strings.TrimSpace(projectID)) + } + } + + return SAMLGroupMappingsImport{ + SamlIdpID: parts[1], + ProviderGroupID: parts[2], + ProjectIDs: projectIDs, + Role: parts[4], + }, nil +} + +func extractProjectIDs(projects []wiz.Project) []string { + projectIDs := make([]string, len(projects)) + for i, project := range projects { + projectIDs[i] = project.ID + } + + return projectIDs +} + +func resourceSAMLGroupMappingRead(ctx context.Context, d *schema.ResourceData, m interface{}) (diags diag.Diagnostics) { + tflog.Info(ctx, "resourceWizSAMLGroupMappingRead called...") + + // check the id + if d.Id() == "" { + return nil + } + samlIdpID := d.Get("saml_idp_id").(string) + providerGroupID := d.Get("provider_group_id").(string) + role := d.Get("role").(string) + projectIDs := utils.ConvertListToString(d.Get("projects").([]interface{})) + + matchingNode, diags := querySAMLGroupMappings(ctx, m, samlIdpID, providerGroupID, role, projectIDs) + if len(diags) > 0 { + return diags + } + + // If no matching node was found, return error + if matchingNode == nil { + return diag.Errorf("saml group mapping for group: %s not found for saml idp provider: %s", providerGroupID, samlIdpID) + } + + // set the resource parameters + err := d.Set("saml_idp_id", samlIdpID) + if err != nil { + return append(diags, diag.FromErr(err)...) + } + + err = d.Set("provider_group_id", matchingNode.ProviderGroupID) + if err != nil { + return append(diags, diag.FromErr(err)...) + } + + err = d.Set("role", matchingNode.Role.ID) + if err != nil { + return append(diags, diag.FromErr(err)...) + } + + projectIDs = extractProjectIDs(matchingNode.Projects) + err = d.Set("projects", projectIDs) + if err != nil { + return append(diags, diag.FromErr(err)...) + } + + return diags +} + +func resourceSAMLGroupMappingUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) (diags diag.Diagnostics) { + tflog.Info(ctx, "resourceWizSAMLGroupMappingUpdate called...") + + // check the id + if d.Id() == "" { + return nil + } + + // define the graphql query + query := `mutation SetSAMLGroupMapping ($input: ModifySAMLGroupMappingInput!) { + modifySAMLIdentityProviderGroupMappings(input: $input) { + _stub + } + }` + + samlIdpID := d.Get("saml_idp_id").(string) + providerGroupID := d.Get("provider_group_id").(string) + role := d.Get("role").(string) + projects := utils.ConvertListToString(d.Get("projects").([]interface{})) + + // populate the graphql variables + vars := &wiz.UpdateSAMLGroupMappingInput{} + vars.ID = samlIdpID + vars.Patch.Upsert.ProviderGroupID = providerGroupID + vars.Patch.Upsert.Role = role + vars.Patch.Upsert.Projects = projects + + // process the request + data := &wiz.UpdateSAMLGroupMappingPayload{} + requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "update") + diags = append(diags, requestDiags...) + if len(diags) > 0 { + return diags + } + + return resourceSAMLGroupMappingRead(ctx, d, m) +} + +func resourceSAMLGroupMappingDelete(ctx context.Context, d *schema.ResourceData, m interface{}) (diags diag.Diagnostics) { + tflog.Info(ctx, "resourceWizSAMLGroupMappingDelete called...") + + // check the id + if d.Id() == "" { + return nil + } + + // define the graphql query + query := `mutation SetSAMLGroupMapping ($input: ModifySAMLGroupMappingInput!) { + modifySAMLIdentityProviderGroupMappings(input: $input) { + _stub + } + }` + + samlIdpID := d.Get("saml_idp_id").(string) + providerGroupID := d.Get("provider_group_id").(string) + + // populate the graphql variables + vars := &wiz.DeleteSAMLGroupMappingInput{} + vars.ID = samlIdpID + vars.Patch.Delete = []string{providerGroupID} + + // process the request + data := &wiz.UpdateSAMLGroupMappingPayload{} + requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "delete") + diags = append(diags, requestDiags...) + if len(diags) > 0 { + return diags + } + + return diags +} + +func querySAMLGroupMappings(ctx context.Context, m interface{}, samlIdpID string, providerGroupID string, roleId string, projectIDs []string) (*wiz.SAMLGroupMapping, diag.Diagnostics) { + // define the graphql query + query := `query samlIdentityProviderGroupMappings ($id: ID!, $first: Int! $after: String){ + samlIdentityProviderGroupMappings ( + id: $id, + first: $first + after: $after + ) { + pageInfo { + hasNextPage + endCursor + } + nodes { + providerGroupId + role { + description + id + isProjectScoped + name + scopes + } + projects { + id + } + } + } + }` + + // populate the graphql variables + vars := &internal.QueryVariables{} + vars.ID = samlIdpID + vars.First = 100 + + var matchingNode *wiz.SAMLGroupMapping + // Since we can't filter by providerGroupId server side we have to do it client side + // Execute the query in a loop until we found the group we are looking for, or all pages have been fetched + + found := false + for !found { + data := &ReadSAMLGroupMappings{} + requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_idp", "read") + if len(requestDiags) > 0 { + return nil, requestDiags + } + // Process the data... + for _, node := range data.SAMLGroupMappings.Nodes { + nodeProjectIDs := extractProjectIDs(node.Projects) + // If we find a match, store the node and break the loop + if node.ProviderGroupID == providerGroupID && node.Role.ID == roleId && slices.Equal(projectIDs, nodeProjectIDs) { + matchingNode = node + found = true + break + } + } + + // If there are no more pages, break the loop + if !data.SAMLGroupMappings.PageInfo.HasNextPage { + break + } + + // Set the cursor for the next page + vars.After = data.SAMLGroupMappings.PageInfo.EndCursor + } + + return matchingNode, nil +} diff --git a/internal/provider/resource_saml_group_mapping_test.go b/internal/provider/resource_saml_group_mapping_test.go new file mode 100644 index 0000000..df53a86 --- /dev/null +++ b/internal/provider/resource_saml_group_mapping_test.go @@ -0,0 +1,53 @@ +package provider + +import ( + "reflect" + "testing" +) + +func TestExtractIdsFromSamlIdpGroupMappingImportId(t *testing.T) { + testCases := []struct { + name string + input string + expectedMapping SAMLGroupMappingsImport + expectErr bool + }{ + { + name: "Valid ID", + input: "link|samlIdpId|providerGroupId|projectId1,projectId2|role", + expectedMapping: SAMLGroupMappingsImport{SamlIdpID: "samlIdpId", ProviderGroupID: "providerGroupId", ProjectIDs: []string{"projectId1", "projectId2"}, Role: "role"}, + expectErr: false, + }, + { + name: "Valid ID global mapping", + input: "link|samlIdpId|providerGroupId|global|role", + expectedMapping: SAMLGroupMappingsImport{SamlIdpID: "samlIdpId", ProviderGroupID: "providerGroupId", ProjectIDs: nil, Role: "role"}, + expectErr: false, + }, + { + name: "Invalid ID", + input: "invalidId", + expectedMapping: SAMLGroupMappingsImport{}, + expectErr: true, + }, + { + name: "Invalid ID length", + input: "link|samlIdpId|providerGroupId", + expectedMapping: SAMLGroupMappingsImport{}, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mapping, err := extractIDsFromSamlIdpGroupMappingImportID(tc.input) + if (err != nil) != tc.expectErr { + t.Errorf("Expected error: %v, got: %v", tc.expectErr, err) + } + + if !reflect.DeepEqual(mapping, tc.expectedMapping) { + t.Errorf("Expected mapping: %+v, got: %+v", tc.expectedMapping, mapping) + } + }) + } +} diff --git a/internal/wiz/structs.go b/internal/wiz/structs.go index eee05d0..684dcac 100644 --- a/internal/wiz/structs.go +++ b/internal/wiz/structs.go @@ -229,6 +229,33 @@ type SAMLGroupMappingCreateInput struct { Projects []string `json:"projects"` } +// UpdateSAMLGroupMappingPayload struct +type UpdateSAMLGroupMappingPayload struct { + SAMLGroupMapping SAMLGroupMapping `json:"samlGroupMapping,omitempty"` +} + +// UpdateSAMLGroupMappingInput struct +type UpdateSAMLGroupMappingInput struct { + ID string `json:"id"` + Patch UpdateSAMLGroupMappingUpsert `json:"patch"` +} + +// UpdateSAMLGroupMappingUpsert struct +type UpdateSAMLGroupMappingUpsert struct { + Upsert SAMLGroupMappingUpdateInput `json:"upsert"` +} + +// DeleteSAMLGroupMappingInput struct +type DeleteSAMLGroupMappingInput struct { + ID string `json:"id"` + Patch DeleteSAMLGroupMapping `json:"patch"` +} + +// DeleteSAMLGroupMapping struct +type DeleteSAMLGroupMapping struct { + Delete []string `json:"delete"` +} + // SAMLIdentityProvider struct -- updates type SAMLIdentityProvider struct { AllowManualRoleOverride *bool `json:"allowManualRoleOverride"` From 2799c75a8d6c86cab809d33fc4959d3e97e26c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Fri, 26 Jul 2024 12:29:21 +0200 Subject: [PATCH 02/20] add examples and generate doc --- docs/resources/saml_group_mapping.md | 76 +++++++++++++++++++ .../wiz_saml_group_mapping/import.sh | 9 +++ .../wiz_saml_group_mapping/resource.tf | 27 +++++++ 3 files changed, 112 insertions(+) create mode 100644 docs/resources/saml_group_mapping.md create mode 100644 examples/resources/wiz_saml_group_mapping/import.sh create mode 100644 examples/resources/wiz_saml_group_mapping/resource.tf diff --git a/docs/resources/saml_group_mapping.md b/docs/resources/saml_group_mapping.md new file mode 100644 index 0000000..0857ebe --- /dev/null +++ b/docs/resources/saml_group_mapping.md @@ -0,0 +1,76 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "wiz_saml_group_mapping Resource - terraform-provider-wiz" +subcategory: "" +description: |- + Configure SAML Group Role Mapping. If you use SSO to authenticate to Wiz, you can bind group memberships in SAML tokens to Wiz roles over certain scopes. +--- + +# wiz_saml_group_mapping (Resource) + +Configure SAML Group Role Mapping. If you use SSO to authenticate to Wiz, you can bind group memberships in SAML tokens to Wiz roles over certain scopes. + +## Example Usage + +```terraform +# Configure SAML Group Role Mapping on a global scope +resource "wiz_saml_group_mapping" "test_global_scope" { + saml_idp_id = "test-saml-identity-provider" + provider_group_id = "global-reader-group-id" + role = "PROJECT_READER" +} + +# Configure SAML Group Role Mapping for a single project +resource "wiz_saml_group_mapping" "test_single_project" { + saml_idp_id = "test-saml-identity-provider" + provider_group_id = "admin-group-id" + role = "PROJECT_ADMIN" + projects = [ + "ee25cc95-82b0-4543-8934-5bc655b86786" + ] +} + +# Configure SAML Group Role Mapping for multiple projects +resource "wiz_saml_group_mapping" "test_multi_project" { + saml_idp_id = "test-saml-identity-provider" + provider_group_id = "member-group-id" + role = "PROJECT_MEMBER" + projects = [ + "ee25cc95-82b0-4543-8934-5bc655b86786", + "e7f6542c-81f6-43cf-af48-bdd77f09650d" + ] +} +``` + + +## Schema + +### Required + +- `provider_group_id` (String) Provider group ID +- `role` (String) Wiz Role name +- `saml_idp_id` (String) Identifier for the Saml Provider + +### Optional + +- `projects` (List of String) Project mapping + +### Read-Only + +- `id` (String) Unique tf-internal identifier for the saml group mapping + +## Import + +Import is supported using the following syntax: + +```shell +# The id for importing resources has to be in this format: 'mapping||||'. +# Import with saml mapping to multiple projects +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7|bb62aac7-e8bd-5d5e-b205-2dbafe106e1a,ee25cc95-82b0-4543-8934-5bc655b86786|PROJECT_READER" + +# Import with mapping to single project +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7|bb62aac7-e8bd-5d5e-b205-2dbafe106e1a|PROJECT_READER" + +# Import with global mapping +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7|global|PROJECT_READER" +``` diff --git a/examples/resources/wiz_saml_group_mapping/import.sh b/examples/resources/wiz_saml_group_mapping/import.sh new file mode 100644 index 0000000..8d695e9 --- /dev/null +++ b/examples/resources/wiz_saml_group_mapping/import.sh @@ -0,0 +1,9 @@ +# The id for importing resources has to be in this format: 'mapping||||'. +# Import with saml mapping to multiple projects +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7|bb62aac7-e8bd-5d5e-b205-2dbafe106e1a,ee25cc95-82b0-4543-8934-5bc655b86786|PROJECT_READER" + +# Import with mapping to single project +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7|bb62aac7-e8bd-5d5e-b205-2dbafe106e1a|PROJECT_READER" + +# Import with global mapping +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7|global|PROJECT_READER" diff --git a/examples/resources/wiz_saml_group_mapping/resource.tf b/examples/resources/wiz_saml_group_mapping/resource.tf new file mode 100644 index 0000000..775f696 --- /dev/null +++ b/examples/resources/wiz_saml_group_mapping/resource.tf @@ -0,0 +1,27 @@ +# Configure SAML Group Role Mapping on a global scope +resource "wiz_saml_group_mapping" "test_global_scope" { + saml_idp_id = "test-saml-identity-provider" + provider_group_id = "global-reader-group-id" + role = "PROJECT_READER" +} + +# Configure SAML Group Role Mapping for a single project +resource "wiz_saml_group_mapping" "test_single_project" { + saml_idp_id = "test-saml-identity-provider" + provider_group_id = "admin-group-id" + role = "PROJECT_ADMIN" + projects = [ + "ee25cc95-82b0-4543-8934-5bc655b86786" + ] +} + +# Configure SAML Group Role Mapping for multiple projects +resource "wiz_saml_group_mapping" "test_multi_project" { + saml_idp_id = "test-saml-identity-provider" + provider_group_id = "member-group-id" + role = "PROJECT_MEMBER" + projects = [ + "ee25cc95-82b0-4543-8934-5bc655b86786", + "e7f6542c-81f6-43cf-af48-bdd77f09650d" + ] +} From 936f483d6a1e4e7cd5fc808e0db4a01c9ede2622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Mon, 29 Jul 2024 09:24:43 +0200 Subject: [PATCH 03/20] add acceptance test --- internal/acceptance/common.go | 2 + internal/acceptance/provider_test.go | 2 + .../resource_wiz_saml_group_mapping_test.go | 55 +++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 internal/acceptance/resource_wiz_saml_group_mapping_test.go diff --git a/internal/acceptance/common.go b/internal/acceptance/common.go index 1c8c305..d5b2392 100644 --- a/internal/acceptance/common.go +++ b/internal/acceptance/common.go @@ -25,4 +25,6 @@ const ( TcReportGraphQuery TestCase = "REPORT_GRAPH_QUERY" // TcCloudConfigRule test case TcCloudConfigRule TestCase = "CLOUD_CONFIG_RULE" + // TcSAMLGroupMapping test case + TcSAMLGroupMapping TestCase = "SAML_GROUP_MAPPING" ) diff --git a/internal/acceptance/provider_test.go b/internal/acceptance/provider_test.go index 551526e..ecf09ad 100644 --- a/internal/acceptance/provider_test.go +++ b/internal/acceptance/provider_test.go @@ -47,6 +47,8 @@ func testAccPreCheck(t *testing.T, tc TestCase) { envVars = append(commonEnvVars, "WIZ_SUBSCRIPTION_ID") case TcReportGraphQuery: envVars = append(commonEnvVars, "WIZ_PROJECT_ID") + case TcSAMLGroupMapping: + envVars = append(commonEnvVars, "WIZ_PROJECT_ID", "PROVIDER_GROUP_ID", "WIZ_SAML_IDP_ID") default: t.Fatalf("unknown testCase: %s", tc) } diff --git a/internal/acceptance/resource_wiz_saml_group_mapping_test.go b/internal/acceptance/resource_wiz_saml_group_mapping_test.go new file mode 100644 index 0000000..ad932ab --- /dev/null +++ b/internal/acceptance/resource_wiz_saml_group_mapping_test.go @@ -0,0 +1,55 @@ +package acceptance + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccResourceWizSAMLGroupMapping_basic(t *testing.T) { + samlIdpID := os.Getenv("WIZ_SAML_IDP_ID") + providerGroupID := os.Getenv("PROVIDER_GROUP_ID") + projectID := os.Getenv("WIZ_PROJECT_ID") + + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t, TcSAMLGroupMapping) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testResourceWizSAMLGroupMappingBasic(samlIdpID, providerGroupID, projectID), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "wiz_saml_group_mapping.foo", + "saml_idp_id", + samlIdpID, + ), + resource.TestCheckResourceAttr( + "wiz_saml_group_mapping.foo", + "provider_group_id", + providerGroupID, + ), + resource.TestCheckResourceAttr( + "wiz_saml_group_mapping.foo", + "projects.0", + projectID, + ), + ), + }, + }, + }) +} + +func testResourceWizSAMLGroupMappingBasic(samlIdpID string, providerGroupID string, projectID string) string { + return fmt.Sprintf(` +resource "wiz_saml_group_mapping" "foo" { + saml_idp_id = "%s" + provider_group_id = "%s" + role = "PROJECT_READER" + projects = [ + "%s" + ] +} +`, samlIdpID, providerGroupID, projectID) +} From d166f24756b0e81d1f7fe644c42b09a77a606d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Tue, 30 Jul 2024 09:19:07 +0200 Subject: [PATCH 04/20] format imports + upper case IDs --- internal/provider/resource_saml_group_mapping.go | 3 ++- internal/provider/resource_saml_group_mapping_test.go | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go index 534f261..4bb6244 100644 --- a/internal/provider/resource_saml_group_mapping.go +++ b/internal/provider/resource_saml_group_mapping.go @@ -3,9 +3,10 @@ package provider import ( "context" "errors" - "github.com/google/uuid" "slices" "strings" + + "github.com/google/uuid" "wiz.io/hashicorp/terraform-provider-wiz/internal/utils" "github.com/hashicorp/terraform-plugin-log/tflog" diff --git a/internal/provider/resource_saml_group_mapping_test.go b/internal/provider/resource_saml_group_mapping_test.go index df53a86..e73e28a 100644 --- a/internal/provider/resource_saml_group_mapping_test.go +++ b/internal/provider/resource_saml_group_mapping_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func TestExtractIdsFromSamlIdpGroupMappingImportId(t *testing.T) { +func TestExtractIDsFromSamlIdpGroupMappingImportID(t *testing.T) { testCases := []struct { name string input string @@ -14,14 +14,14 @@ func TestExtractIdsFromSamlIdpGroupMappingImportId(t *testing.T) { }{ { name: "Valid ID", - input: "link|samlIdpId|providerGroupId|projectId1,projectId2|role", - expectedMapping: SAMLGroupMappingsImport{SamlIdpID: "samlIdpId", ProviderGroupID: "providerGroupId", ProjectIDs: []string{"projectId1", "projectId2"}, Role: "role"}, + input: "link|samlIdpID|providerGroupID|projectID1,projectID2|role", + expectedMapping: SAMLGroupMappingsImport{SamlIdpID: "samlIdpID", ProviderGroupID: "providerGroupID", ProjectIDs: []string{"projectID1", "projectID2"}, Role: "role"}, expectErr: false, }, { name: "Valid ID global mapping", - input: "link|samlIdpId|providerGroupId|global|role", - expectedMapping: SAMLGroupMappingsImport{SamlIdpID: "samlIdpId", ProviderGroupID: "providerGroupId", ProjectIDs: nil, Role: "role"}, + input: "link|samlIdpID|providerGroupID|global|role", + expectedMapping: SAMLGroupMappingsImport{SamlIdpID: "samlIdpID", ProviderGroupID: "providerGroupID", ProjectIDs: nil, Role: "role"}, expectErr: false, }, { From 7d0b9412640d8f65d2c9af89b8d9039c28c86453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Tue, 6 Aug 2024 10:24:13 +0200 Subject: [PATCH 05/20] move and rename structs --- ...go => resource_saml_group_mapping_test.go} | 0 .../provider/resource_saml_group_mapping.go | 24 +++++++++++++++---- internal/wiz/structs.go | 24 ++++--------------- 3 files changed, 24 insertions(+), 24 deletions(-) rename internal/acceptance/{resource_wiz_saml_group_mapping_test.go => resource_saml_group_mapping_test.go} (100%) diff --git a/internal/acceptance/resource_wiz_saml_group_mapping_test.go b/internal/acceptance/resource_saml_group_mapping_test.go similarity index 100% rename from internal/acceptance/resource_wiz_saml_group_mapping_test.go rename to internal/acceptance/resource_saml_group_mapping_test.go diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go index 4bb6244..eaaeb5e 100644 --- a/internal/provider/resource_saml_group_mapping.go +++ b/internal/provider/resource_saml_group_mapping.go @@ -40,6 +40,22 @@ type SAMLGroupMappingsImport struct { Role string } +// UpdateSAMLGroupMappingPayload struct +type UpdateSAMLGroupMappingPayload struct { + SAMLGroupMapping wiz.SAMLGroupMapping `json:"samlGroupMapping,omitempty"` +} + +// DeleteSAMLGroupMappingInput struct +type DeleteSAMLGroupMappingInput struct { + ID string `json:"id"` + Patch DeleteSAMLGroupMapping `json:"patch"` +} + +// DeleteSAMLGroupMapping struct +type DeleteSAMLGroupMapping struct { + Delete []string `json:"delete"` +} + func resourceWizSAMLGroupMapping() *schema.Resource { return &schema.Resource{ Description: "Configure SAML Group Role Mapping. If you use SSO to authenticate to Wiz, you can bind group memberships in SAML tokens to Wiz roles over certain scopes.", @@ -149,7 +165,7 @@ func resourceSAMLGroupMappingCreate(ctx context.Context, d *schema.ResourceData, vars.Patch.Upsert.Projects = projectIDs // process the request - data := &wiz.UpdateSAMLGroupMappingPayload{} + data := &UpdateSAMLGroupMappingPayload{} requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "create") diags = append(diags, requestDiags...) if len(diags) > 0 { @@ -268,7 +284,7 @@ func resourceSAMLGroupMappingUpdate(ctx context.Context, d *schema.ResourceData, vars.Patch.Upsert.Projects = projects // process the request - data := &wiz.UpdateSAMLGroupMappingPayload{} + data := &UpdateSAMLGroupMappingPayload{} requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "update") diags = append(diags, requestDiags...) if len(diags) > 0 { @@ -297,12 +313,12 @@ func resourceSAMLGroupMappingDelete(ctx context.Context, d *schema.ResourceData, providerGroupID := d.Get("provider_group_id").(string) // populate the graphql variables - vars := &wiz.DeleteSAMLGroupMappingInput{} + vars := &DeleteSAMLGroupMappingInput{} vars.ID = samlIdpID vars.Patch.Delete = []string{providerGroupID} // process the request - data := &wiz.UpdateSAMLGroupMappingPayload{} + data := &UpdateSAMLGroupMappingPayload{} requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "delete") diags = append(diags, requestDiags...) if len(diags) > 0 { diff --git a/internal/wiz/structs.go b/internal/wiz/structs.go index 684dcac..14e2d08 100644 --- a/internal/wiz/structs.go +++ b/internal/wiz/structs.go @@ -229,33 +229,17 @@ type SAMLGroupMappingCreateInput struct { Projects []string `json:"projects"` } -// UpdateSAMLGroupMappingPayload struct -type UpdateSAMLGroupMappingPayload struct { - SAMLGroupMapping SAMLGroupMapping `json:"samlGroupMapping,omitempty"` -} - // UpdateSAMLGroupMappingInput struct type UpdateSAMLGroupMappingInput struct { - ID string `json:"id"` - Patch UpdateSAMLGroupMappingUpsert `json:"patch"` + ID string `json:"id"` + Patch ModifySAMLGroupMappingPatch `json:"patch"` } -// UpdateSAMLGroupMappingUpsert struct -type UpdateSAMLGroupMappingUpsert struct { +// ModifySAMLGroupMappingPatch struct +type ModifySAMLGroupMappingPatch struct { Upsert SAMLGroupMappingUpdateInput `json:"upsert"` } -// DeleteSAMLGroupMappingInput struct -type DeleteSAMLGroupMappingInput struct { - ID string `json:"id"` - Patch DeleteSAMLGroupMapping `json:"patch"` -} - -// DeleteSAMLGroupMapping struct -type DeleteSAMLGroupMapping struct { - Delete []string `json:"delete"` -} - // SAMLIdentityProvider struct -- updates type SAMLIdentityProvider struct { AllowManualRoleOverride *bool `json:"allowManualRoleOverride"` From 0aca19e714fde3982909cb7b2f328e784a79188c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Tue, 6 Aug 2024 10:25:20 +0200 Subject: [PATCH 06/20] rename provider group env var --- internal/acceptance/provider_test.go | 2 +- internal/acceptance/resource_saml_group_mapping_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/acceptance/provider_test.go b/internal/acceptance/provider_test.go index ecf09ad..8418eee 100644 --- a/internal/acceptance/provider_test.go +++ b/internal/acceptance/provider_test.go @@ -48,7 +48,7 @@ func testAccPreCheck(t *testing.T, tc TestCase) { case TcReportGraphQuery: envVars = append(commonEnvVars, "WIZ_PROJECT_ID") case TcSAMLGroupMapping: - envVars = append(commonEnvVars, "WIZ_PROJECT_ID", "PROVIDER_GROUP_ID", "WIZ_SAML_IDP_ID") + envVars = append(commonEnvVars, "WIZ_PROJECT_ID", "WIZ_PROVIDER_GROUP_ID", "WIZ_SAML_IDP_ID") default: t.Fatalf("unknown testCase: %s", tc) } diff --git a/internal/acceptance/resource_saml_group_mapping_test.go b/internal/acceptance/resource_saml_group_mapping_test.go index ad932ab..cd6bec4 100644 --- a/internal/acceptance/resource_saml_group_mapping_test.go +++ b/internal/acceptance/resource_saml_group_mapping_test.go @@ -10,7 +10,7 @@ import ( func TestAccResourceWizSAMLGroupMapping_basic(t *testing.T) { samlIdpID := os.Getenv("WIZ_SAML_IDP_ID") - providerGroupID := os.Getenv("PROVIDER_GROUP_ID") + providerGroupID := os.Getenv("WIZ_PROVIDER_GROUP_ID") projectID := os.Getenv("WIZ_PROJECT_ID") resource.UnitTest(t, resource.TestCase{ From 9508b34247a4f65f53934d2fc5e6902f407279cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Tue, 6 Aug 2024 10:46:24 +0200 Subject: [PATCH 07/20] trim graph query to read group mappings --- internal/provider/resource_saml_group_mapping.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go index eaaeb5e..4b6686c 100644 --- a/internal/provider/resource_saml_group_mapping.go +++ b/internal/provider/resource_saml_group_mapping.go @@ -343,11 +343,7 @@ func querySAMLGroupMappings(ctx context.Context, m interface{}, samlIdpID string nodes { providerGroupId role { - description id - isProjectScoped - name - scopes } projects { id From b2931911513179b484053a994a2ea8e6ef47a240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Tue, 6 Aug 2024 10:51:08 +0200 Subject: [PATCH 08/20] update resource description --- internal/provider/resource_saml_group_mapping.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go index 4b6686c..b2d1e6f 100644 --- a/internal/provider/resource_saml_group_mapping.go +++ b/internal/provider/resource_saml_group_mapping.go @@ -58,7 +58,7 @@ type DeleteSAMLGroupMapping struct { func resourceWizSAMLGroupMapping() *schema.Resource { return &schema.Resource{ - Description: "Configure SAML Group Role Mapping. If you use SSO to authenticate to Wiz, you can bind group memberships in SAML tokens to Wiz roles over certain scopes.", + Description: "Configure SAML Group Role Mapping. When using SSO to authenticate with Wiz, you can map group memberships in SAML assertions to Wiz roles across specific scopes.", Schema: map[string]*schema.Schema{ "id": { Type: schema.TypeString, From 48f810f24a731aa63aa4292c315d78a059278ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Tue, 6 Aug 2024 14:01:13 +0200 Subject: [PATCH 09/20] use ProcessPagedRequest function --- .../provider/resource_saml_group_mapping.go | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go index b2d1e6f..77a3e55 100644 --- a/internal/provider/resource_saml_group_mapping.go +++ b/internal/provider/resource_saml_group_mapping.go @@ -357,35 +357,28 @@ func querySAMLGroupMappings(ctx context.Context, m interface{}, samlIdpID string vars.ID = samlIdpID vars.First = 100 + // Call ProcessPagedRequest + diags, allData := client.ProcessPagedRequest(ctx, m, vars, &ReadSAMLGroupMappings{}, query, "saml_idp", "read", 0) + if diags.HasError() { + return nil, diags + } + var matchingNode *wiz.SAMLGroupMapping - // Since we can't filter by providerGroupId server side we have to do it client side - // Execute the query in a loop until we found the group we are looking for, or all pages have been fetched - - found := false - for !found { - data := &ReadSAMLGroupMappings{} - requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_idp", "read") - if len(requestDiags) > 0 { - return nil, requestDiags + // Process the data... + for _, data := range allData { + typedData, ok := data.(*ReadSAMLGroupMappings) + if !ok { + return nil, diag.Errorf("data is not of type *ReadSAMLGroupMappings") } - // Process the data... - for _, node := range data.SAMLGroupMappings.Nodes { + nodes := typedData.SAMLGroupMappings.Nodes + for _, node := range nodes { nodeProjectIDs := extractProjectIDs(node.Projects) // If we find a match, store the node and break the loop if node.ProviderGroupID == providerGroupID && node.Role.ID == roleId && slices.Equal(projectIDs, nodeProjectIDs) { matchingNode = node - found = true break } } - - // If there are no more pages, break the loop - if !data.SAMLGroupMappings.PageInfo.HasNextPage { - break - } - - // Set the cursor for the next page - vars.After = data.SAMLGroupMappings.PageInfo.EndCursor } return matchingNode, nil From a96c6d52b1e368ab55c3ee029880e328f1d29475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Tue, 6 Aug 2024 14:03:06 +0200 Subject: [PATCH 10/20] run go generate --- docs/resources/saml_group_mapping.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/resources/saml_group_mapping.md b/docs/resources/saml_group_mapping.md index 0857ebe..07a1bdb 100644 --- a/docs/resources/saml_group_mapping.md +++ b/docs/resources/saml_group_mapping.md @@ -3,12 +3,12 @@ page_title: "wiz_saml_group_mapping Resource - terraform-provider-wiz" subcategory: "" description: |- - Configure SAML Group Role Mapping. If you use SSO to authenticate to Wiz, you can bind group memberships in SAML tokens to Wiz roles over certain scopes. + Configure SAML Group Role Mapping. When using SSO to authenticate with Wiz, you can map group memberships in SAML assertions to Wiz roles across specific scopes. --- # wiz_saml_group_mapping (Resource) -Configure SAML Group Role Mapping. If you use SSO to authenticate to Wiz, you can bind group memberships in SAML tokens to Wiz roles over certain scopes. +Configure SAML Group Role Mapping. When using SSO to authenticate with Wiz, you can map group memberships in SAML assertions to Wiz roles across specific scopes. ## Example Usage From a48de62e69bbfae3d2c9a7449b47e7177041a747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Tue, 6 Aug 2024 14:05:39 +0200 Subject: [PATCH 11/20] resolve linter complaints --- internal/provider/resource_saml_group_mapping.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go index 77a3e55..057821c 100644 --- a/internal/provider/resource_saml_group_mapping.go +++ b/internal/provider/resource_saml_group_mapping.go @@ -328,7 +328,7 @@ func resourceSAMLGroupMappingDelete(ctx context.Context, d *schema.ResourceData, return diags } -func querySAMLGroupMappings(ctx context.Context, m interface{}, samlIdpID string, providerGroupID string, roleId string, projectIDs []string) (*wiz.SAMLGroupMapping, diag.Diagnostics) { +func querySAMLGroupMappings(ctx context.Context, m interface{}, samlIdpID string, providerGroupID string, roleID string, projectIDs []string) (*wiz.SAMLGroupMapping, diag.Diagnostics) { // define the graphql query query := `query samlIdentityProviderGroupMappings ($id: ID!, $first: Int! $after: String){ samlIdentityProviderGroupMappings ( @@ -374,7 +374,7 @@ func querySAMLGroupMappings(ctx context.Context, m interface{}, samlIdpID string for _, node := range nodes { nodeProjectIDs := extractProjectIDs(node.Projects) // If we find a match, store the node and break the loop - if node.ProviderGroupID == providerGroupID && node.Role.ID == roleId && slices.Equal(projectIDs, nodeProjectIDs) { + if node.ProviderGroupID == providerGroupID && node.Role.ID == roleID && slices.Equal(projectIDs, nodeProjectIDs) { matchingNode = node break } From bac51d1b8aa5fba05018e13b9c59c1b12424eab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Tue, 6 Aug 2024 16:19:10 +0200 Subject: [PATCH 12/20] implement types as per graphql schema --- .../provider/resource_saml_group_mapping.go | 35 +++++++++---------- internal/wiz/structs.go | 10 +++++- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go index 057821c..0ef08ef 100644 --- a/internal/provider/resource_saml_group_mapping.go +++ b/internal/provider/resource_saml_group_mapping.go @@ -45,17 +45,6 @@ type UpdateSAMLGroupMappingPayload struct { SAMLGroupMapping wiz.SAMLGroupMapping `json:"samlGroupMapping,omitempty"` } -// DeleteSAMLGroupMappingInput struct -type DeleteSAMLGroupMappingInput struct { - ID string `json:"id"` - Patch DeleteSAMLGroupMapping `json:"patch"` -} - -// DeleteSAMLGroupMapping struct -type DeleteSAMLGroupMapping struct { - Delete []string `json:"delete"` -} - func resourceWizSAMLGroupMapping() *schema.Resource { return &schema.Resource{ Description: "Configure SAML Group Role Mapping. When using SSO to authenticate with Wiz, you can map group memberships in SAML assertions to Wiz roles across specific scopes.", @@ -160,9 +149,13 @@ func resourceSAMLGroupMappingCreate(ctx context.Context, d *schema.ResourceData, // populate the graphql variables vars := &wiz.UpdateSAMLGroupMappingInput{} vars.ID = samlIdpID - vars.Patch.Upsert.ProviderGroupID = providerGroupID - vars.Patch.Upsert.Role = role - vars.Patch.Upsert.Projects = projectIDs + vars.Patch = wiz.ModifySAMLGroupMappingPatch{ + Upsert: &wiz.SAMLGroupMappingDetailsInput{ + ProviderGroupID: providerGroupID, + Role: role, + Projects: projectIDs, + }, + } // process the request data := &UpdateSAMLGroupMappingPayload{} @@ -279,9 +272,13 @@ func resourceSAMLGroupMappingUpdate(ctx context.Context, d *schema.ResourceData, // populate the graphql variables vars := &wiz.UpdateSAMLGroupMappingInput{} vars.ID = samlIdpID - vars.Patch.Upsert.ProviderGroupID = providerGroupID - vars.Patch.Upsert.Role = role - vars.Patch.Upsert.Projects = projects + vars.Patch = wiz.ModifySAMLGroupMappingPatch{ + Upsert: &wiz.SAMLGroupMappingDetailsInput{ + ProviderGroupID: providerGroupID, + Role: role, + Projects: projects, + }, + } // process the request data := &UpdateSAMLGroupMappingPayload{} @@ -313,9 +310,9 @@ func resourceSAMLGroupMappingDelete(ctx context.Context, d *schema.ResourceData, providerGroupID := d.Get("provider_group_id").(string) // populate the graphql variables - vars := &DeleteSAMLGroupMappingInput{} + vars := &wiz.UpdateSAMLGroupMappingInput{} vars.ID = samlIdpID - vars.Patch.Delete = []string{providerGroupID} + vars.Patch.Delete = &[]string{providerGroupID} // process the request data := &UpdateSAMLGroupMappingPayload{} diff --git a/internal/wiz/structs.go b/internal/wiz/structs.go index 280eb0d..720ef8f 100644 --- a/internal/wiz/structs.go +++ b/internal/wiz/structs.go @@ -250,9 +250,17 @@ type UpdateSAMLGroupMappingInput struct { Patch ModifySAMLGroupMappingPatch `json:"patch"` } +// SAMLGroupMappingDetailsInput struct +type SAMLGroupMappingDetailsInput struct { + ProviderGroupID string `json:"providerGroupId"` + Role string `json:"role"` + Projects []string `json:"projects"` +} + // ModifySAMLGroupMappingPatch struct type ModifySAMLGroupMappingPatch struct { - Upsert SAMLGroupMappingUpdateInput `json:"upsert"` + Upsert *SAMLGroupMappingDetailsInput `json:"upsert,omitempty"` + Delete *[]string `json:"delete,omitempty"` } // SAMLIdentityProvider struct -- updates From 67943569f9b9aad0ea5c1a9e41ff9284352d5d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Wed, 7 Aug 2024 11:03:35 +0200 Subject: [PATCH 13/20] new type SAMLIdentityProviderGroupMappingsConnection --- internal/provider/resource_saml_group_mapping.go | 11 ++--------- internal/wiz/structs.go | 6 ++++++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go index 0ef08ef..50700c4 100644 --- a/internal/provider/resource_saml_group_mapping.go +++ b/internal/provider/resource_saml_group_mapping.go @@ -19,16 +19,9 @@ import ( ) // ReadSAMLGroupMappings represents the structure of a SAML group mappings read operation. -// It includes a SAMLGroupMappings object. +// It includes a SAMLIdentityProviderGroupMappingsConnection object. type ReadSAMLGroupMappings struct { - SAMLGroupMappings SAMLGroupMappings `json:"samlIdentityProviderGroupMappings"` -} - -// SAMLGroupMappings represents the structure of SAML group mappings. -// It includes PageInfo and a list of Nodes. -type SAMLGroupMappings struct { - PageInfo wiz.PageInfo `json:"pageInfo"` - Nodes []*wiz.SAMLGroupMapping `json:"nodes,omitempty"` + SAMLGroupMappings wiz.SAMLIdentityProviderGroupMappingsConnection `json:"samlIdentityProviderGroupMappings"` } // SAMLGroupMappingsImport represents the structure of a SAML group mapping import. diff --git a/internal/wiz/structs.go b/internal/wiz/structs.go index 720ef8f..7a0c4d7 100644 --- a/internal/wiz/structs.go +++ b/internal/wiz/structs.go @@ -263,6 +263,12 @@ type ModifySAMLGroupMappingPatch struct { Delete *[]string `json:"delete,omitempty"` } +// SAMLIdentityProviderGroupMappingsConnection struct +type SAMLIdentityProviderGroupMappingsConnection struct { + PageInfo PageInfo `json:"pageInfo"` + Nodes []*SAMLGroupMapping `json:"nodes,omitempty"` +} + // SAMLIdentityProvider struct -- updates type SAMLIdentityProvider struct { AllowManualRoleOverride *bool `json:"allowManualRoleOverride"` From ceaca1869fc33aed93403f718c87cd342d0acae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Thu, 8 Aug 2024 15:51:02 +0200 Subject: [PATCH 14/20] rename SAMLGroupMappingDetailsInput to SAMLGroupDetailsInput + move UpdateSAMLGroupMappingInput to resource impl --- internal/provider/resource_saml_group_mapping.go | 16 +++++++++++----- internal/wiz/structs.go | 15 +++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go index 50700c4..e8db79b 100644 --- a/internal/provider/resource_saml_group_mapping.go +++ b/internal/provider/resource_saml_group_mapping.go @@ -24,6 +24,12 @@ type ReadSAMLGroupMappings struct { SAMLGroupMappings wiz.SAMLIdentityProviderGroupMappingsConnection `json:"samlIdentityProviderGroupMappings"` } +// UpdateSAMLGroupMappingInput struct +type UpdateSAMLGroupMappingInput struct { + ID string `json:"id"` + Patch wiz.ModifySAMLGroupMappingPatch `json:"patch"` +} + // SAMLGroupMappingsImport represents the structure of a SAML group mapping import. // It includes the SAML IdP ID, provider group ID, project IDs, and role. type SAMLGroupMappingsImport struct { @@ -140,10 +146,10 @@ func resourceSAMLGroupMappingCreate(ctx context.Context, d *schema.ResourceData, } }` // populate the graphql variables - vars := &wiz.UpdateSAMLGroupMappingInput{} + vars := &UpdateSAMLGroupMappingInput{} vars.ID = samlIdpID vars.Patch = wiz.ModifySAMLGroupMappingPatch{ - Upsert: &wiz.SAMLGroupMappingDetailsInput{ + Upsert: &wiz.SAMLGroupDetailsInput{ ProviderGroupID: providerGroupID, Role: role, Projects: projectIDs, @@ -263,10 +269,10 @@ func resourceSAMLGroupMappingUpdate(ctx context.Context, d *schema.ResourceData, projects := utils.ConvertListToString(d.Get("projects").([]interface{})) // populate the graphql variables - vars := &wiz.UpdateSAMLGroupMappingInput{} + vars := &UpdateSAMLGroupMappingInput{} vars.ID = samlIdpID vars.Patch = wiz.ModifySAMLGroupMappingPatch{ - Upsert: &wiz.SAMLGroupMappingDetailsInput{ + Upsert: &wiz.SAMLGroupDetailsInput{ ProviderGroupID: providerGroupID, Role: role, Projects: projects, @@ -303,7 +309,7 @@ func resourceSAMLGroupMappingDelete(ctx context.Context, d *schema.ResourceData, providerGroupID := d.Get("provider_group_id").(string) // populate the graphql variables - vars := &wiz.UpdateSAMLGroupMappingInput{} + vars := &UpdateSAMLGroupMappingInput{} vars.ID = samlIdpID vars.Patch.Delete = &[]string{providerGroupID} diff --git a/internal/wiz/structs.go b/internal/wiz/structs.go index 7a0c4d7..51eefde 100644 --- a/internal/wiz/structs.go +++ b/internal/wiz/structs.go @@ -244,14 +244,9 @@ type SAMLGroupMappingCreateInput struct { Projects []string `json:"projects"` } -// UpdateSAMLGroupMappingInput struct -type UpdateSAMLGroupMappingInput struct { - ID string `json:"id"` - Patch ModifySAMLGroupMappingPatch `json:"patch"` -} - -// SAMLGroupMappingDetailsInput struct -type SAMLGroupMappingDetailsInput struct { +// SAMLGroupDetailsInput struct +// Incomplete because 'description' field is missing as in the schema +type SAMLGroupDetailsInput struct { ProviderGroupID string `json:"providerGroupId"` Role string `json:"role"` Projects []string `json:"projects"` @@ -259,8 +254,8 @@ type SAMLGroupMappingDetailsInput struct { // ModifySAMLGroupMappingPatch struct type ModifySAMLGroupMappingPatch struct { - Upsert *SAMLGroupMappingDetailsInput `json:"upsert,omitempty"` - Delete *[]string `json:"delete,omitempty"` + Upsert *SAMLGroupDetailsInput `json:"upsert,omitempty"` + Delete *[]string `json:"delete,omitempty"` } // SAMLIdentityProviderGroupMappingsConnection struct From c612da5b1e6cbd235eed49ae9594a2965dfbcfe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Tue, 20 Aug 2024 15:56:52 +0200 Subject: [PATCH 15/20] update schema to support multiple mappings in a set --- .../provider/resource_saml_group_mapping.go | 334 ++++++++++-------- .../resource_saml_group_mapping_test.go | 13 +- 2 files changed, 197 insertions(+), 150 deletions(-) diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go index e8db79b..0fb2d6c 100644 --- a/internal/provider/resource_saml_group_mapping.go +++ b/internal/provider/resource_saml_group_mapping.go @@ -30,13 +30,9 @@ type UpdateSAMLGroupMappingInput struct { Patch wiz.ModifySAMLGroupMappingPatch `json:"patch"` } -// SAMLGroupMappingsImport represents the structure of a SAML group mapping import. -// It includes the SAML IdP ID, provider group ID, project IDs, and role. type SAMLGroupMappingsImport struct { - SamlIdpID string - ProviderGroupID string - ProjectIDs []string - Role string + SamlIdpID string + GroupMappings []wiz.SAMLGroupDetailsInput } // UpdateSAMLGroupMappingPayload struct @@ -59,23 +55,30 @@ func resourceWizSAMLGroupMapping() *schema.Resource { Required: true, ForceNew: true, }, - "provider_group_id": { - Type: schema.TypeString, - Description: "Provider group ID", - Required: true, - ForceNew: true, - }, - "role": { - Type: schema.TypeString, - Description: "Wiz Role name", - Required: true, - }, - "projects": { - Type: schema.TypeList, - Optional: true, - Description: "Project mapping", - Elem: &schema.Schema{ - Type: schema.TypeString, + "group_mapping": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "provider_group_id": { + Type: schema.TypeString, + Description: "Provider group ID", + Required: true, + }, + "role": { + Type: schema.TypeString, + Description: "Wiz Role name", + Required: true, + }, + "projects": { + Type: schema.TypeList, + Optional: true, + Description: "Project mapping", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, }, }, }, @@ -84,9 +87,9 @@ func resourceWizSAMLGroupMapping() *schema.Resource { UpdateContext: resourceSAMLGroupMappingUpdate, DeleteContext: resourceSAMLGroupMappingDelete, Importer: &schema.ResourceImporter{ - StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { - // schema for import id: mapping|||| + StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + // schema for import id: | mappingToImport, err := extractIDsFromSamlIdpGroupMappingImportID(d.Id()) if err != nil { return nil, err @@ -97,23 +100,22 @@ func resourceWizSAMLGroupMapping() *schema.Resource { return nil, err } - err = d.Set("provider_group_id", mappingToImport.ProviderGroupID) - if err != nil { - return nil, err - } - - err = d.Set("role", mappingToImport.Role) - if err != nil { - return nil, err + var groupMappings []map[string]interface{} + for _, groupMapping := range mappingToImport.GroupMappings { + groupMappingMap := map[string]interface{}{ + "provider_group_id": groupMapping.ProviderGroupID, + "role": groupMapping.Role, + "projects": groupMapping.Projects, + } + groupMappings = append(groupMappings, groupMappingMap) } - err = d.Set("projects", mappingToImport.ProjectIDs) + err = d.Set("group_mappings", groupMappings) if err != nil { return nil, err } d.SetId(uuid.NewString()) - return []*schema.ResourceData{d}, nil }, }, @@ -122,46 +124,53 @@ func resourceWizSAMLGroupMapping() *schema.Resource { func resourceSAMLGroupMappingCreate(ctx context.Context, d *schema.ResourceData, m interface{}) (diags diag.Diagnostics) { tflog.Info(ctx, "resourceWizSAMLGroupMappingCreate called...") - samlIdpID := d.Get("saml_idp_id").(string) - providerGroupID := d.Get("provider_group_id").(string) - role := d.Get("role").(string) - projectIDs := utils.ConvertListToString(d.Get("projects").([]interface{})) - - // verify the mapping doesn't already exist - matchingNode, diags := querySAMLGroupMappings(ctx, m, samlIdpID, providerGroupID, role, projectIDs) - if len(diags) != 0 { - return diags - } + groupMappings := d.Get("group_mapping").(*schema.Set).List() + + for _, item := range groupMappings { + groupMapping := item.(map[string]interface{}) + providerGroupID := groupMapping["provider_group_id"].(string) + role := groupMapping["role"].(string) + projectIDs := utils.ConvertListToString(groupMapping["projects"].([]interface{})) + + // verify the mapping doesn't already exist + matchingNodes, diags := querySAMLGroupMappings(ctx, m, samlIdpID, groupMappings) + if len(diags) != 0 { + return diags + } - if matchingNode != nil { - return diag.Errorf("saml group mapping for group: %s and role: %s to project(s): %s already exists for saml idp provider: %s and should be imported instead", - providerGroupID, role, strings.Join(projectIDs, ", "), samlIdpID) - } + for _, matchingNode := range matchingNodes { + if matchingNode.ProviderGroupID == providerGroupID && matchingNode.Role.ID == role && slices.Equal(projectIDs, extractProjectIDs(matchingNode.Projects)) { + return diag.Errorf("saml group mapping for group: %s and role: %s to project(s): %s already exists for saml idp provider: %s and should be imported instead", + providerGroupID, role, strings.Join(projectIDs, ", "), samlIdpID) + } + } - // define the graphql query - query := `mutation SetSAMLGroupMapping ($input: ModifySAMLGroupMappingInput!) { - modifySAMLIdentityProviderGroupMappings(input: $input) { - _stub - } - }` - // populate the graphql variables - vars := &UpdateSAMLGroupMappingInput{} - vars.ID = samlIdpID - vars.Patch = wiz.ModifySAMLGroupMappingPatch{ - Upsert: &wiz.SAMLGroupDetailsInput{ - ProviderGroupID: providerGroupID, - Role: role, - Projects: projectIDs, - }, - } + // define the graphql query + query := `mutation SetSAMLGroupMapping ($input: ModifySAMLGroupMappingInput!) { + modifySAMLIdentityProviderGroupMappings(input: $input) { + _stub + } + }` + + // populate the graphql variables + vars := &UpdateSAMLGroupMappingInput{} + vars.ID = samlIdpID + vars.Patch = wiz.ModifySAMLGroupMappingPatch{ + Upsert: &wiz.SAMLGroupDetailsInput{ + ProviderGroupID: providerGroupID, + Role: role, + Projects: projectIDs, + }, + } - // process the request - data := &UpdateSAMLGroupMappingPayload{} - requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "create") - diags = append(diags, requestDiags...) - if len(diags) > 0 { - return diags + // process the request + data := &UpdateSAMLGroupMappingPayload{} + requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "create") + diags = append(diags, requestDiags...) + if len(diags) > 0 { + return diags + } } // set the id @@ -172,23 +181,38 @@ func resourceSAMLGroupMappingCreate(ctx context.Context, d *schema.ResourceData, func extractIDsFromSamlIdpGroupMappingImportID(id string) (SAMLGroupMappingsImport, error) { parts := strings.Split(id, "|") - if len(parts) != 5 { + + if len(parts) != 3 { return SAMLGroupMappingsImport{}, errors.New("invalid ID format") } - // if user species the mapping to be global we return an empty slice - var projectIDs []string - if parts[3] != "global" { - for _, projectID := range strings.Split(parts[3], ",") { - projectIDs = append(projectIDs, strings.TrimSpace(projectID)) + groupMappingStrings := strings.Split(parts[2], "#") + var groupMappings []wiz.SAMLGroupDetailsInput + for _, groupMappingString := range groupMappingStrings { + groupMappingParts := strings.Split(groupMappingString, ":") + if len(groupMappingParts) < 2 { + return SAMLGroupMappingsImport{}, errors.New("invalid group mapping format") + } + + providerGroupID := groupMappingParts[0] + role := groupMappingParts[1] + var projectIDs []string + if len(groupMappingParts) > 2 && groupMappingParts[2] != "" { + projectIDs = strings.Split(groupMappingParts[2], ",") + } + + groupMapping := wiz.SAMLGroupDetailsInput{ + ProviderGroupID: providerGroupID, + Role: role, + Projects: projectIDs, } + groupMappings = append(groupMappings, groupMapping) + } return SAMLGroupMappingsImport{ - SamlIdpID: parts[1], - ProviderGroupID: parts[2], - ProjectIDs: projectIDs, - Role: parts[4], + SamlIdpID: parts[1], + GroupMappings: groupMappings, }, nil } @@ -208,44 +232,48 @@ func resourceSAMLGroupMappingRead(ctx context.Context, d *schema.ResourceData, m if d.Id() == "" { return nil } + samlIdpID := d.Get("saml_idp_id").(string) - providerGroupID := d.Get("provider_group_id").(string) - role := d.Get("role").(string) - projectIDs := utils.ConvertListToString(d.Get("projects").([]interface{})) + groupMappings := d.Get("group_mapping").(*schema.Set).List() - matchingNode, diags := querySAMLGroupMappings(ctx, m, samlIdpID, providerGroupID, role, projectIDs) + var newGroupMappings []interface{} + + matchingNodes, diags := querySAMLGroupMappings(ctx, m, samlIdpID, groupMappings) if len(diags) > 0 { return diags } - // If no matching node was found, return error - if matchingNode == nil { - return diag.Errorf("saml group mapping for group: %s not found for saml idp provider: %s", providerGroupID, samlIdpID) + for _, item := range groupMappings { + groupMapping := item.(map[string]interface{}) + providerGroupID := groupMapping["provider_group_id"].(string) + role := groupMapping["role"].(string) + projectIDs := utils.ConvertListToString(groupMapping["projects"].([]interface{})) + + for _, matchingNode := range matchingNodes { + if matchingNode.ProviderGroupID == providerGroupID && matchingNode.Role.ID == role && slices.Equal(projectIDs, extractProjectIDs(matchingNode.Projects)) { + // set the resource parameters + newGroupMapping := map[string]interface{}{ + "provider_group_id": matchingNode.ProviderGroupID, + "role": matchingNode.Role.ID, + "projects": extractProjectIDs(matchingNode.Projects), + } + newGroupMappings = append(newGroupMappings, newGroupMapping) + } + } } - // set the resource parameters err := d.Set("saml_idp_id", samlIdpID) if err != nil { return append(diags, diag.FromErr(err)...) } - err = d.Set("provider_group_id", matchingNode.ProviderGroupID) - if err != nil { - return append(diags, diag.FromErr(err)...) - } - - err = d.Set("role", matchingNode.Role.ID) - if err != nil { - return append(diags, diag.FromErr(err)...) - } - - projectIDs = extractProjectIDs(matchingNode.Projects) - err = d.Set("projects", projectIDs) + err = d.Set("group_mapping", newGroupMappings) if err != nil { return append(diags, diag.FromErr(err)...) } return diags + } func resourceSAMLGroupMappingUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) (diags diag.Diagnostics) { @@ -256,6 +284,9 @@ func resourceSAMLGroupMappingUpdate(ctx context.Context, d *schema.ResourceData, return nil } + samlIdpID := d.Get("saml_idp_id").(string) + groupMappings := d.Get("group_mapping").(*schema.Set).List() + // define the graphql query query := `mutation SetSAMLGroupMapping ($input: ModifySAMLGroupMappingInput!) { modifySAMLIdentityProviderGroupMappings(input: $input) { @@ -263,28 +294,30 @@ func resourceSAMLGroupMappingUpdate(ctx context.Context, d *schema.ResourceData, } }` - samlIdpID := d.Get("saml_idp_id").(string) - providerGroupID := d.Get("provider_group_id").(string) - role := d.Get("role").(string) - projects := utils.ConvertListToString(d.Get("projects").([]interface{})) - - // populate the graphql variables - vars := &UpdateSAMLGroupMappingInput{} - vars.ID = samlIdpID - vars.Patch = wiz.ModifySAMLGroupMappingPatch{ - Upsert: &wiz.SAMLGroupDetailsInput{ - ProviderGroupID: providerGroupID, - Role: role, - Projects: projects, - }, - } + for _, item := range groupMappings { + groupMapping := item.(map[string]interface{}) + providerGroupID := groupMapping["provider_group_id"].(string) + role := groupMapping["role"].(string) + projects := utils.ConvertListToString(groupMapping["projects"].([]interface{})) + + // populate the graphql variables + vars := &UpdateSAMLGroupMappingInput{} + vars.ID = samlIdpID + vars.Patch = wiz.ModifySAMLGroupMappingPatch{ + Upsert: &wiz.SAMLGroupDetailsInput{ + ProviderGroupID: providerGroupID, + Role: role, + Projects: projects, + }, + } - // process the request - data := &UpdateSAMLGroupMappingPayload{} - requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "update") - diags = append(diags, requestDiags...) - if len(diags) > 0 { - return diags + // process the request + data := &UpdateSAMLGroupMappingPayload{} + requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "update") + diags = append(diags, requestDiags...) + if len(diags) > 0 { + return diags + } } return resourceSAMLGroupMappingRead(ctx, d, m) @@ -298,6 +331,9 @@ func resourceSAMLGroupMappingDelete(ctx context.Context, d *schema.ResourceData, return nil } + samlIdpID := d.Get("saml_idp_id").(string) + groupMappings := d.Get("group_mapping").(*schema.Set).List() + // define the graphql query query := `mutation SetSAMLGroupMapping ($input: ModifySAMLGroupMappingInput!) { modifySAMLIdentityProviderGroupMappings(input: $input) { @@ -305,26 +341,28 @@ func resourceSAMLGroupMappingDelete(ctx context.Context, d *schema.ResourceData, } }` - samlIdpID := d.Get("saml_idp_id").(string) - providerGroupID := d.Get("provider_group_id").(string) - - // populate the graphql variables - vars := &UpdateSAMLGroupMappingInput{} - vars.ID = samlIdpID - vars.Patch.Delete = &[]string{providerGroupID} - - // process the request - data := &UpdateSAMLGroupMappingPayload{} - requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "delete") - diags = append(diags, requestDiags...) - if len(diags) > 0 { - return diags + for _, item := range groupMappings { + groupMapping := item.(map[string]interface{}) + providerGroupID := groupMapping["provider_group_id"].(string) + + // populate the graphql variables + vars := &UpdateSAMLGroupMappingInput{} + vars.ID = samlIdpID + vars.Patch.Delete = &[]string{providerGroupID} + + // process the request + data := &UpdateSAMLGroupMappingPayload{} + requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "delete") + diags = append(diags, requestDiags...) + if len(diags) > 0 { + return diags + } } return diags } -func querySAMLGroupMappings(ctx context.Context, m interface{}, samlIdpID string, providerGroupID string, roleID string, projectIDs []string) (*wiz.SAMLGroupMapping, diag.Diagnostics) { +func querySAMLGroupMappings(ctx context.Context, m interface{}, samlIdpID string, groupMappings []interface{}) ([]*wiz.SAMLGroupMapping, diag.Diagnostics) { // define the graphql query query := `query samlIdentityProviderGroupMappings ($id: ID!, $first: Int! $after: String){ samlIdentityProviderGroupMappings ( @@ -359,23 +397,31 @@ func querySAMLGroupMappings(ctx context.Context, m interface{}, samlIdpID string return nil, diags } - var matchingNode *wiz.SAMLGroupMapping + var matchingNodes []*wiz.SAMLGroupMapping + // Process the data... for _, data := range allData { typedData, ok := data.(*ReadSAMLGroupMappings) if !ok { return nil, diag.Errorf("data is not of type *ReadSAMLGroupMappings") } + nodes := typedData.SAMLGroupMappings.Nodes for _, node := range nodes { - nodeProjectIDs := extractProjectIDs(node.Projects) - // If we find a match, store the node and break the loop - if node.ProviderGroupID == providerGroupID && node.Role.ID == roleID && slices.Equal(projectIDs, nodeProjectIDs) { - matchingNode = node - break + for _, item := range groupMappings { + groupMapping := item.(map[string]interface{}) + providerGroupID := groupMapping["provider_group_id"].(string) + roleID := groupMapping["role"].(string) + projectIDs := utils.ConvertListToString(groupMapping["projects"].([]interface{})) + nodeProjectIDs := extractProjectIDs(node.Projects) + + // If we find a match, store the node + if node.ProviderGroupID == providerGroupID && node.Role.ID == roleID && slices.Equal(projectIDs, nodeProjectIDs) { + matchingNodes = append(matchingNodes, node) + } } } } - return matchingNode, nil + return matchingNodes, nil } diff --git a/internal/provider/resource_saml_group_mapping_test.go b/internal/provider/resource_saml_group_mapping_test.go index e73e28a..f28f88c 100644 --- a/internal/provider/resource_saml_group_mapping_test.go +++ b/internal/provider/resource_saml_group_mapping_test.go @@ -2,6 +2,8 @@ package provider import ( "reflect" + "wiz.io/hashicorp/terraform-provider-wiz/internal/wiz" + "testing" ) @@ -14,14 +16,14 @@ func TestExtractIDsFromSamlIdpGroupMappingImportID(t *testing.T) { }{ { name: "Valid ID", - input: "link|samlIdpID|providerGroupID|projectID1,projectID2|role", - expectedMapping: SAMLGroupMappingsImport{SamlIdpID: "samlIdpID", ProviderGroupID: "providerGroupID", ProjectIDs: []string{"projectID1", "projectID2"}, Role: "role"}, + input: "link|samlIdpID|providerGroupID:role:projectID1,projectID2", + expectedMapping: SAMLGroupMappingsImport{SamlIdpID: "samlIdpID", GroupMappings: []wiz.SAMLGroupDetailsInput{{ProviderGroupID: "providerGroupID", Role: "role", Projects: []string{"projectID1", "projectID2"}}}}, expectErr: false, }, { name: "Valid ID global mapping", - input: "link|samlIdpID|providerGroupID|global|role", - expectedMapping: SAMLGroupMappingsImport{SamlIdpID: "samlIdpID", ProviderGroupID: "providerGroupID", ProjectIDs: nil, Role: "role"}, + input: "link|samlIdpID|providerGroupID:role", + expectedMapping: SAMLGroupMappingsImport{SamlIdpID: "samlIdpID", GroupMappings: []wiz.SAMLGroupDetailsInput{{ProviderGroupID: "providerGroupID", Role: "role", Projects: nil}}}, expectErr: false, }, { @@ -32,7 +34,7 @@ func TestExtractIDsFromSamlIdpGroupMappingImportID(t *testing.T) { }, { name: "Invalid ID length", - input: "link|samlIdpId|providerGroupId", + input: "link|samlIdpId", expectedMapping: SAMLGroupMappingsImport{}, expectErr: true, }, @@ -44,7 +46,6 @@ func TestExtractIDsFromSamlIdpGroupMappingImportID(t *testing.T) { if (err != nil) != tc.expectErr { t.Errorf("Expected error: %v, got: %v", tc.expectErr, err) } - if !reflect.DeepEqual(mapping, tc.expectedMapping) { t.Errorf("Expected mapping: %+v, got: %+v", tc.expectedMapping, mapping) } From fb149e964239031353a9f7c4025ff00bd5bf869d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Tue, 20 Aug 2024 15:57:18 +0200 Subject: [PATCH 16/20] update docs --- docs/resources/saml_group_mapping.md | 96 ++++++++++++++----- .../wiz_saml_group_mapping/import.sh | 11 ++- .../wiz_saml_group_mapping/resource.tf | 66 ++++++++++--- 3 files changed, 131 insertions(+), 42 deletions(-) diff --git a/docs/resources/saml_group_mapping.md b/docs/resources/saml_group_mapping.md index 07a1bdb..f8e1555 100644 --- a/docs/resources/saml_group_mapping.md +++ b/docs/resources/saml_group_mapping.md @@ -15,29 +15,67 @@ Configure SAML Group Role Mapping. When using SSO to authenticate with Wiz, you ```terraform # Configure SAML Group Role Mapping on a global scope resource "wiz_saml_group_mapping" "test_global_scope" { - saml_idp_id = "test-saml-identity-provider" - provider_group_id = "global-reader-group-id" - role = "PROJECT_READER" + saml_idp_id = "test-saml-identity-provider" + group_mappings = [ + { + provider_group_id = "global-reader-group-id" + role = "PROJECT_READER" + } + ] } # Configure SAML Group Role Mapping for a single project resource "wiz_saml_group_mapping" "test_single_project" { - saml_idp_id = "test-saml-identity-provider" - provider_group_id = "admin-group-id" - role = "PROJECT_ADMIN" - projects = [ - "ee25cc95-82b0-4543-8934-5bc655b86786" + saml_idp_id = "test-saml-identity-provider" + group_mappings = [ + { + provider_group_id = "admin-group-id" + role = "PROJECT_ADMIN" + projects = [ + "ee25cc95-82b0-4543-8934-5bc655b86786" + ] + } ] } # Configure SAML Group Role Mapping for multiple projects resource "wiz_saml_group_mapping" "test_multi_project" { - saml_idp_id = "test-saml-identity-provider" - provider_group_id = "member-group-id" - role = "PROJECT_MEMBER" - projects = [ - "ee25cc95-82b0-4543-8934-5bc655b86786", - "e7f6542c-81f6-43cf-af48-bdd77f09650d" + saml_idp_id = "test-saml-identity-provider" + group_mappings = [ + { + provider_group_id = "member-group-id" + role = "PROJECT_MEMBER" + projects = [ + "ee25cc95-82b0-4543-8934-5bc655b86786", + "e7f6542c-81f6-43cf-af48-bdd77f09650d" + ] + } + ] +} + +# Configure multiple SAML Group Role Mappings +resource "wiz_saml_group_mapping" "test_multi_mappings" { + saml_idp_id = "test-saml-identity-provider" + group_mappings = [ + { + provider_group_id = "global-reader-group-id" + role = "PROJECT_READER" + }, + { + provider_group_id = "admin-group-id" + role = "PROJECT_ADMIN" + projects = [ + "ee25cc95-82b0-4543-8934-5bc655b86786" + ] + }, + { + provider_group_id = "member-group-id" + role = "PROJECT_MEMBER" + projects = [ + "ee25cc95-82b0-4543-8934-5bc655b86786", + "e7f6542c-81f6-43cf-af48-bdd77f09650d" + ] + } ] } ``` @@ -47,30 +85,40 @@ resource "wiz_saml_group_mapping" "test_multi_project" { ### Required -- `provider_group_id` (String) Provider group ID -- `role` (String) Wiz Role name +- `group_mapping` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--group_mapping)) - `saml_idp_id` (String) Identifier for the Saml Provider -### Optional - -- `projects` (List of String) Project mapping - ### Read-Only - `id` (String) Unique tf-internal identifier for the saml group mapping + +### Nested Schema for `group_mapping` + +Required: + +- `provider_group_id` (String) Provider group ID +- `role` (String) Wiz Role name + +Optional: + +- `projects` (List of String) Project mapping + ## Import Import is supported using the following syntax: ```shell -# The id for importing resources has to be in this format: 'mapping||||'. +# The id for importing resources has to be in this format: 'mapping||::#...'. # Import with saml mapping to multiple projects -terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7|bb62aac7-e8bd-5d5e-b205-2dbafe106e1a,ee25cc95-82b0-4543-8934-5bc655b86786|PROJECT_READER" +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7:bb62aac7-e8bd-5d5e-b205-2dbafe106e1a,ee25cc95-82b0-4543-8934-5bc655b86786:PROJECT_READER" # Import with mapping to single project -terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7|bb62aac7-e8bd-5d5e-b205-2dbafe106e1a|PROJECT_READER" +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7:bb62aac7-e8bd-5d5e-b205-2dbafe106e1a:PROJECT_READER" # Import with global mapping -terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7|global|PROJECT_READER" +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7::PROJECT_READER" + +# Import with multiple group mappings +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7:bb62aac7-e8bd-5d5e-b205-2dbafe106e1a:PROJECT_READER#12345678-1234-1234-1234-123456789012:ee25cc95-82b0-4543-8934-5bc655b86786:PROJECT_WRITER" ``` diff --git a/examples/resources/wiz_saml_group_mapping/import.sh b/examples/resources/wiz_saml_group_mapping/import.sh index 8d695e9..0652095 100644 --- a/examples/resources/wiz_saml_group_mapping/import.sh +++ b/examples/resources/wiz_saml_group_mapping/import.sh @@ -1,9 +1,12 @@ -# The id for importing resources has to be in this format: 'mapping||||'. +# The id for importing resources has to be in this format: 'mapping||::#...'. # Import with saml mapping to multiple projects -terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7|bb62aac7-e8bd-5d5e-b205-2dbafe106e1a,ee25cc95-82b0-4543-8934-5bc655b86786|PROJECT_READER" +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7:bb62aac7-e8bd-5d5e-b205-2dbafe106e1a,ee25cc95-82b0-4543-8934-5bc655b86786:PROJECT_READER" # Import with mapping to single project -terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7|bb62aac7-e8bd-5d5e-b205-2dbafe106e1a|PROJECT_READER" +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7:bb62aac7-e8bd-5d5e-b205-2dbafe106e1a:PROJECT_READER" # Import with global mapping -terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7|global|PROJECT_READER" +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7::PROJECT_READER" + +# Import with multiple group mappings +terraform import wiz_saml_group_mapping.example_import "mapping|wiz-azure-ad-saml|88990357-fe36-421b-aedc-fcdd602b91d7:bb62aac7-e8bd-5d5e-b205-2dbafe106e1a:PROJECT_READER#12345678-1234-1234-1234-123456789012:ee25cc95-82b0-4543-8934-5bc655b86786:PROJECT_WRITER" \ No newline at end of file diff --git a/examples/resources/wiz_saml_group_mapping/resource.tf b/examples/resources/wiz_saml_group_mapping/resource.tf index 775f696..b0da5d4 100644 --- a/examples/resources/wiz_saml_group_mapping/resource.tf +++ b/examples/resources/wiz_saml_group_mapping/resource.tf @@ -1,27 +1,65 @@ # Configure SAML Group Role Mapping on a global scope resource "wiz_saml_group_mapping" "test_global_scope" { - saml_idp_id = "test-saml-identity-provider" - provider_group_id = "global-reader-group-id" - role = "PROJECT_READER" + saml_idp_id = "test-saml-identity-provider" + group_mappings = [ + { + provider_group_id = "global-reader-group-id" + role = "PROJECT_READER" + } + ] } # Configure SAML Group Role Mapping for a single project resource "wiz_saml_group_mapping" "test_single_project" { - saml_idp_id = "test-saml-identity-provider" - provider_group_id = "admin-group-id" - role = "PROJECT_ADMIN" - projects = [ - "ee25cc95-82b0-4543-8934-5bc655b86786" + saml_idp_id = "test-saml-identity-provider" + group_mappings = [ + { + provider_group_id = "admin-group-id" + role = "PROJECT_ADMIN" + projects = [ + "ee25cc95-82b0-4543-8934-5bc655b86786" + ] + } ] } # Configure SAML Group Role Mapping for multiple projects resource "wiz_saml_group_mapping" "test_multi_project" { - saml_idp_id = "test-saml-identity-provider" - provider_group_id = "member-group-id" - role = "PROJECT_MEMBER" - projects = [ - "ee25cc95-82b0-4543-8934-5bc655b86786", - "e7f6542c-81f6-43cf-af48-bdd77f09650d" + saml_idp_id = "test-saml-identity-provider" + group_mappings = [ + { + provider_group_id = "member-group-id" + role = "PROJECT_MEMBER" + projects = [ + "ee25cc95-82b0-4543-8934-5bc655b86786", + "e7f6542c-81f6-43cf-af48-bdd77f09650d" + ] + } ] } + +# Configure multiple SAML Group Role Mappings +resource "wiz_saml_group_mapping" "test_multi_mappings" { + saml_idp_id = "test-saml-identity-provider" + group_mappings = [ + { + provider_group_id = "global-reader-group-id" + role = "PROJECT_READER" + }, + { + provider_group_id = "admin-group-id" + role = "PROJECT_ADMIN" + projects = [ + "ee25cc95-82b0-4543-8934-5bc655b86786" + ] + }, + { + provider_group_id = "member-group-id" + role = "PROJECT_MEMBER" + projects = [ + "ee25cc95-82b0-4543-8934-5bc655b86786", + "e7f6542c-81f6-43cf-af48-bdd77f09650d" + ] + } + ] +} \ No newline at end of file From c3ebf2c351398bd49308c4f697b2c6be865a33c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Tue, 20 Aug 2024 16:00:26 +0200 Subject: [PATCH 17/20] add comment + fix imports --- internal/provider/resource_saml_group_mapping.go | 1 + internal/provider/resource_saml_group_mapping_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go index 0fb2d6c..bf886a9 100644 --- a/internal/provider/resource_saml_group_mapping.go +++ b/internal/provider/resource_saml_group_mapping.go @@ -30,6 +30,7 @@ type UpdateSAMLGroupMappingInput struct { Patch wiz.ModifySAMLGroupMappingPatch `json:"patch"` } +// SAMLGroupMappingsImport struct type SAMLGroupMappingsImport struct { SamlIdpID string GroupMappings []wiz.SAMLGroupDetailsInput diff --git a/internal/provider/resource_saml_group_mapping_test.go b/internal/provider/resource_saml_group_mapping_test.go index f28f88c..c05d799 100644 --- a/internal/provider/resource_saml_group_mapping_test.go +++ b/internal/provider/resource_saml_group_mapping_test.go @@ -2,6 +2,7 @@ package provider import ( "reflect" + "wiz.io/hashicorp/terraform-provider-wiz/internal/wiz" "testing" From f4e31411ea6f7baa94cb674461a0cd9e4ec05881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Tue, 20 Aug 2024 16:06:30 +0200 Subject: [PATCH 18/20] update acceptance test --- .../resource_saml_group_mapping_test.go | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/internal/acceptance/resource_saml_group_mapping_test.go b/internal/acceptance/resource_saml_group_mapping_test.go index cd6bec4..4e59fcb 100644 --- a/internal/acceptance/resource_saml_group_mapping_test.go +++ b/internal/acceptance/resource_saml_group_mapping_test.go @@ -27,12 +27,12 @@ func TestAccResourceWizSAMLGroupMapping_basic(t *testing.T) { ), resource.TestCheckResourceAttr( "wiz_saml_group_mapping.foo", - "provider_group_id", + "group_mapping.0.provider_group_id", providerGroupID, ), resource.TestCheckResourceAttr( "wiz_saml_group_mapping.foo", - "projects.0", + "group_mapping.0.projects.0", projectID, ), ), @@ -43,13 +43,14 @@ func TestAccResourceWizSAMLGroupMapping_basic(t *testing.T) { func testResourceWizSAMLGroupMappingBasic(samlIdpID string, providerGroupID string, projectID string) string { return fmt.Sprintf(` -resource "wiz_saml_group_mapping" "foo" { - saml_idp_id = "%s" - provider_group_id = "%s" - role = "PROJECT_READER" - projects = [ - "%s" - ] -} -`, samlIdpID, providerGroupID, projectID) + resource "wiz_saml_group_mapping" "foo" { + saml_idp_id = "%s" + group_mapping { + provider_group_id = "%s" + role = "PROJECT_READER" + projects = [ + "%s" + ] + } + }`, samlIdpID, providerGroupID, projectID) } From 7b1152ee230c632f54f22f4a662df712b5423c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=B6bel=2C=20Jeremia?= Date: Wed, 21 Aug 2024 11:14:51 +0200 Subject: [PATCH 19/20] single api call for create, update and delete --- .../provider/resource_saml_group_mapping.go | 140 ++++++++++-------- internal/wiz/structs.go | 4 +- 2 files changed, 77 insertions(+), 67 deletions(-) diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go index bf886a9..10e575e 100644 --- a/internal/provider/resource_saml_group_mapping.go +++ b/internal/provider/resource_saml_group_mapping.go @@ -128,6 +128,7 @@ func resourceSAMLGroupMappingCreate(ctx context.Context, d *schema.ResourceData, samlIdpID := d.Get("saml_idp_id").(string) groupMappings := d.Get("group_mapping").(*schema.Set).List() + var upsertGroupMappings []wiz.SAMLGroupDetailsInput for _, item := range groupMappings { groupMapping := item.(map[string]interface{}) providerGroupID := groupMapping["provider_group_id"].(string) @@ -147,31 +148,34 @@ func resourceSAMLGroupMappingCreate(ctx context.Context, d *schema.ResourceData, } } - // define the graphql query - query := `mutation SetSAMLGroupMapping ($input: ModifySAMLGroupMappingInput!) { - modifySAMLIdentityProviderGroupMappings(input: $input) { - _stub - } - }` - - // populate the graphql variables - vars := &UpdateSAMLGroupMappingInput{} - vars.ID = samlIdpID - vars.Patch = wiz.ModifySAMLGroupMappingPatch{ - Upsert: &wiz.SAMLGroupDetailsInput{ - ProviderGroupID: providerGroupID, - Role: role, - Projects: projectIDs, - }, + upsertGroupMapping := wiz.SAMLGroupDetailsInput{ + ProviderGroupID: providerGroupID, + Role: role, + Projects: projectIDs, } + upsertGroupMappings = append(upsertGroupMappings, upsertGroupMapping) + } - // process the request - data := &UpdateSAMLGroupMappingPayload{} - requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "create") - diags = append(diags, requestDiags...) - if len(diags) > 0 { - return diags - } + // define the graphql query + query := `mutation SetSAMLGroupMapping ($input: ModifySAMLGroupMappingInput!) { + modifySAMLIdentityProviderGroupMappings(input: $input) { + _stub + } + }` + + // populate the graphql variables + vars := &UpdateSAMLGroupMappingInput{} + vars.ID = samlIdpID + vars.Patch = wiz.ModifySAMLGroupMappingPatch{ + Upsert: &upsertGroupMappings, + } + + // process the request + data := &UpdateSAMLGroupMappingPayload{} + requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "create") + diags = append(diags, requestDiags...) + if len(diags) > 0 { + return diags } // set the id @@ -287,38 +291,40 @@ func resourceSAMLGroupMappingUpdate(ctx context.Context, d *schema.ResourceData, samlIdpID := d.Get("saml_idp_id").(string) groupMappings := d.Get("group_mapping").(*schema.Set).List() - - // define the graphql query - query := `mutation SetSAMLGroupMapping ($input: ModifySAMLGroupMappingInput!) { - modifySAMLIdentityProviderGroupMappings(input: $input) { - _stub - } - }` - + var upsertGroupMappings []wiz.SAMLGroupDetailsInput for _, item := range groupMappings { groupMapping := item.(map[string]interface{}) providerGroupID := groupMapping["provider_group_id"].(string) role := groupMapping["role"].(string) projects := utils.ConvertListToString(groupMapping["projects"].([]interface{})) - - // populate the graphql variables - vars := &UpdateSAMLGroupMappingInput{} - vars.ID = samlIdpID - vars.Patch = wiz.ModifySAMLGroupMappingPatch{ - Upsert: &wiz.SAMLGroupDetailsInput{ - ProviderGroupID: providerGroupID, - Role: role, - Projects: projects, - }, + upsertGroupMapping := wiz.SAMLGroupDetailsInput{ + ProviderGroupID: providerGroupID, + Role: role, + Projects: projects, } + upsertGroupMappings = append(upsertGroupMappings, upsertGroupMapping) + } - // process the request - data := &UpdateSAMLGroupMappingPayload{} - requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "update") - diags = append(diags, requestDiags...) - if len(diags) > 0 { - return diags - } + // define the graphql query + query := `mutation SetSAMLGroupMapping ($input: ModifySAMLGroupMappingInput!) { + modifySAMLIdentityProviderGroupMappings(input: $input) { + _stub + } + }` + + // populate the graphql variables + vars := &UpdateSAMLGroupMappingInput{} + vars.ID = samlIdpID + vars.Patch = wiz.ModifySAMLGroupMappingPatch{ + Upsert: &upsertGroupMappings, + } + + // process the request + data := &UpdateSAMLGroupMappingPayload{} + requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "update") + diags = append(diags, requestDiags...) + if len(diags) > 0 { + return diags } return resourceSAMLGroupMappingRead(ctx, d, m) @@ -335,29 +341,33 @@ func resourceSAMLGroupMappingDelete(ctx context.Context, d *schema.ResourceData, samlIdpID := d.Get("saml_idp_id").(string) groupMappings := d.Get("group_mapping").(*schema.Set).List() + var deleteGroupMappings []string + for _, item := range groupMappings { + groupMapping := item.(map[string]interface{}) + providerGroupID := groupMapping["provider_group_id"].(string) + deleteGroupMappings = append(deleteGroupMappings, providerGroupID) + } + // define the graphql query query := `mutation SetSAMLGroupMapping ($input: ModifySAMLGroupMappingInput!) { modifySAMLIdentityProviderGroupMappings(input: $input) { - _stub - } + _stub + } }` - for _, item := range groupMappings { - groupMapping := item.(map[string]interface{}) - providerGroupID := groupMapping["provider_group_id"].(string) - - // populate the graphql variables - vars := &UpdateSAMLGroupMappingInput{} - vars.ID = samlIdpID - vars.Patch.Delete = &[]string{providerGroupID} + // populate the graphql variables + vars := &UpdateSAMLGroupMappingInput{} + vars.ID = samlIdpID + vars.Patch = wiz.ModifySAMLGroupMappingPatch{ + Delete: &deleteGroupMappings, + } - // process the request - data := &UpdateSAMLGroupMappingPayload{} - requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "delete") - diags = append(diags, requestDiags...) - if len(diags) > 0 { - return diags - } + // process the request + data := &UpdateSAMLGroupMappingPayload{} + requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "saml_group_mapping", "delete") + diags = append(diags, requestDiags...) + if len(diags) > 0 { + return diags } return diags diff --git a/internal/wiz/structs.go b/internal/wiz/structs.go index 51eefde..8195831 100644 --- a/internal/wiz/structs.go +++ b/internal/wiz/structs.go @@ -254,8 +254,8 @@ type SAMLGroupDetailsInput struct { // ModifySAMLGroupMappingPatch struct type ModifySAMLGroupMappingPatch struct { - Upsert *SAMLGroupDetailsInput `json:"upsert,omitempty"` - Delete *[]string `json:"delete,omitempty"` + Upsert *[]SAMLGroupDetailsInput `json:"upsert,omitempty"` + Delete *[]string `json:"delete,omitempty"` } // SAMLIdentityProviderGroupMappingsConnection struct From 3f2a9b39597e3cd4244b06f17e534df9b9655d68 Mon Sep 17 00:00:00 2001 From: Jeremia Noebel Date: Wed, 13 Nov 2024 09:51:41 +0100 Subject: [PATCH 20/20] set first to 500 in querySAMLGroupMappings --- internal/provider/resource_saml_group_mapping.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/resource_saml_group_mapping.go b/internal/provider/resource_saml_group_mapping.go index 10e575e..e14f265 100644 --- a/internal/provider/resource_saml_group_mapping.go +++ b/internal/provider/resource_saml_group_mapping.go @@ -400,7 +400,7 @@ func querySAMLGroupMappings(ctx context.Context, m interface{}, samlIdpID string // populate the graphql variables vars := &internal.QueryVariables{} vars.ID = samlIdpID - vars.First = 100 + vars.First = 500 // Call ProcessPagedRequest diags, allData := client.ProcessPagedRequest(ctx, m, vars, &ReadSAMLGroupMappings{}, query, "saml_idp", "read", 0)