From 2a48c567a0c72e5c466c35f7670f345c81fb2a47 Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Tue, 4 Mar 2025 11:40:19 -0500 Subject: [PATCH 1/5] add pingone_population_default_identity_provider resource platform export support --- .../pingone/sso/pingone_sso_connector.go | 1 + ...ne_population_default_identity_provider.go | 113 ++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 internal/connector/pingone/sso/resources/pingone_population_default_identity_provider.go diff --git a/internal/connector/pingone/sso/pingone_sso_connector.go b/internal/connector/pingone/sso/pingone_sso_connector.go index 6fd004ce..d4aa38fc 100644 --- a/internal/connector/pingone/sso/pingone_sso_connector.go +++ b/internal/connector/pingone/sso/pingone_sso_connector.go @@ -57,6 +57,7 @@ func (c *PingOneSSOConnector) Export(format, outputDir string, overwriteExport b resources.PasswordPolicy(&c.clientInfo), resources.Population(&c.clientInfo), resources.PopulationDefault(&c.clientInfo), + resources.PopulationDefaultIdp(&c.clientInfo), resources.Resource(&c.clientInfo), resources.ResourceAttribute(&c.clientInfo), resources.ResourceScope(&c.clientInfo), diff --git a/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider.go b/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider.go new file mode 100644 index 00000000..10e1279b --- /dev/null +++ b/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider.go @@ -0,0 +1,113 @@ +package resources + +import ( + "fmt" + + "github.com/patrickcping/pingone-go-sdk-v2/management" + "github.com/pingidentity/pingcli/internal/connector" + "github.com/pingidentity/pingcli/internal/connector/common" + "github.com/pingidentity/pingcli/internal/connector/pingone" + "github.com/pingidentity/pingcli/internal/logger" +) + +// Verify that the resource satisfies the exportable resource interface +var ( + _ connector.ExportableResource = &PingOnePopulationDefaultIdp{} +) + +type PingOnePopulationDefaultIdp struct { + clientInfo *connector.PingOneClientInfo +} + +// Utility method for creating a PingOnePopulationDefaultIdp +func PopulationDefaultIdp(clientInfo *connector.PingOneClientInfo) *PingOnePopulationDefaultIdp { + return &PingOnePopulationDefaultIdp{ + clientInfo: clientInfo, + } +} + +func (r *PingOnePopulationDefaultIdp) ResourceType() string { + return "pingone_population_default_identity_provider" +} + +func (r *PingOnePopulationDefaultIdp) ExportAll() (*[]connector.ImportBlock, error) { + l := logger.Get() + l.Debug().Msgf("Exporting all '%s' Resources...", r.ResourceType()) + + importBlocks := []connector.ImportBlock{} + + populationData, err := r.getPopulationData() + if err != nil { + return nil, err + } + + for populationId, populationName := range populationData { + populationDefaultIdp, err := r.getPopulationDefaultIdp(populationId) + if err != nil { + return nil, err + } + if populationDefaultIdp == nil { + continue + } + + commentData := map[string]string{ + "Export Environment ID": r.clientInfo.ExportEnvironmentID, + "Population Default Identity Provider": *populationDefaultIdp, + "Population ID": populationId, + "Population Name": populationName, + "Resource Type": r.ResourceType(), + } + + importBlock := connector.ImportBlock{ + ResourceType: r.ResourceType(), + ResourceName: populationName, + ResourceID: fmt.Sprintf("%s/%s", r.clientInfo.ExportEnvironmentID, populationId), + CommentInformation: common.GenerateCommentInformation(commentData), + } + + importBlocks = append(importBlocks, importBlock) + } + + return &importBlocks, nil +} + +func (r *PingOnePopulationDefaultIdp) getPopulationData() (map[string]string, error) { + populationData := make(map[string]string) + + iter := r.clientInfo.ApiClient.ManagementAPIClient.PopulationsApi.ReadAllPopulations(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID).Execute() + populations, err := pingone.GetManagementAPIObjectsFromIterator[management.Population](iter, "ReadAllPopulations", "GetPopulations", r.ResourceType()) + if err != nil { + return nil, err + } + + for _, population := range populations { + populationId, populationIdOk := population.GetIdOk() + populationName, populationNameOk := population.GetNameOk() + + if populationIdOk && populationNameOk { + populationData[*populationId] = *populationName + } + } + + return populationData, nil +} + +func (r *PingOnePopulationDefaultIdp) getPopulationDefaultIdp(populationId string) (*string, error) { + populationDefaultIdp, resp, err := r.clientInfo.ApiClient.ManagementAPIClient.PopulationsApi.ReadOnePopulationDefaultIdp(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID, populationId).Execute() + ok, err := common.HandleClientResponse(resp, err, "ReadOnePopulationDefaultIdp", r.ResourceType()) + if err != nil { + return nil, err + } + if !ok { + return nil, nil + } + + if populationDefaultIdp != nil { + populationDefaultIdpId, populationDefaultIdpIdOk := populationDefaultIdp.GetIdOk() + if populationDefaultIdpIdOk { + return populationDefaultIdpId, nil + } + } + + return nil, nil +} From 095519da8430af45ea829f5d8c9c1825e5368826 Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Tue, 4 Mar 2025 17:24:24 -0500 Subject: [PATCH 2/5] add test --- ...ingone_identity_provider_attribute_test.go | 2 +- .../pingone_identity_provider_test.go | 2 +- ...pulation_default_identity_provider_test.go | 27 +++++++++++++++++++ .../sso/resources/pingone_population_test.go | 5 ++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 internal/connector/pingone/sso/resources/pingone_population_default_identity_provider_test.go diff --git a/internal/connector/pingone/sso/resources/pingone_identity_provider_attribute_test.go b/internal/connector/pingone/sso/resources/pingone_identity_provider_attribute_test.go index a989b488..61aba367 100644 --- a/internal/connector/pingone/sso/resources/pingone_identity_provider_attribute_test.go +++ b/internal/connector/pingone/sso/resources/pingone_identity_provider_attribute_test.go @@ -18,7 +18,7 @@ func TestIdentityProviderAttributeExport(t *testing.T) { expectedImportBlocks := []connector.ImportBlock{ { ResourceType: "pingone_identity_provider_attribute", - ResourceName: "Test IdP_username", + ResourceName: "Default Idp Test_username", ResourceID: fmt.Sprintf("%s/a99df558-7090-4303-8f35-860ac660e371/51a036c6-41ed-44f7-bd1d-eacaa2a1feab", testutils.GetEnvironmentID()), }, } diff --git a/internal/connector/pingone/sso/resources/pingone_identity_provider_test.go b/internal/connector/pingone/sso/resources/pingone_identity_provider_test.go index 73b023f4..8cc66e23 100644 --- a/internal/connector/pingone/sso/resources/pingone_identity_provider_test.go +++ b/internal/connector/pingone/sso/resources/pingone_identity_provider_test.go @@ -18,7 +18,7 @@ func TestIdentityProviderExport(t *testing.T) { expectedImportBlocks := []connector.ImportBlock{ { ResourceType: "pingone_identity_provider", - ResourceName: "Test IdP", + ResourceName: "Default Idp Test", ResourceID: fmt.Sprintf("%s/a99df558-7090-4303-8f35-860ac660e371", testutils.GetEnvironmentID()), }, } diff --git a/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider_test.go b/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider_test.go new file mode 100644 index 00000000..259a8efa --- /dev/null +++ b/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider_test.go @@ -0,0 +1,27 @@ +package resources_test + +import ( + "fmt" + "testing" + + "github.com/pingidentity/pingcli/internal/connector" + "github.com/pingidentity/pingcli/internal/connector/pingone/sso/resources" + "github.com/pingidentity/pingcli/internal/testing/testutils" +) + +func TestPopulationDefaultIdpExport(t *testing.T) { + // Get initialized apiClient and resource + PingOneClientInfo := testutils.GetPingOneClientInfo(t) + resource := resources.PopulationDefaultIdp(PingOneClientInfo) + + // Defined the expected ImportBlocks for the resource + expectedImportBlocks := []connector.ImportBlock{ + { + ResourceType: "pingone_population_default_identity_provider", + ResourceName: "Test Default Idp Population", + ResourceID: fmt.Sprintf("%s/2814912d-4a0f-4104-a779-80c13b2a6dcd", testutils.GetEnvironmentID()), + }, + } + + testutils.ValidateImportBlocks(t, resource, &expectedImportBlocks) +} diff --git a/internal/connector/pingone/sso/resources/pingone_population_test.go b/internal/connector/pingone/sso/resources/pingone_population_test.go index 384f2010..2c445d51 100644 --- a/internal/connector/pingone/sso/resources/pingone_population_test.go +++ b/internal/connector/pingone/sso/resources/pingone_population_test.go @@ -26,6 +26,11 @@ func TestPopulationExport(t *testing.T) { ResourceName: "LDAP Gateway Population", ResourceID: fmt.Sprintf("%s/374fdb3c-4e94-4547-838a-0c200b9a7c70", testutils.GetEnvironmentID()), }, + { + ResourceType: "pingone_population", + ResourceName: "Test Default Idp Population", + ResourceID: fmt.Sprintf("%s/2814912d-4a0f-4104-a779-80c13b2a6dcd", testutils.GetEnvironmentID()), + }, } testutils.ValidateImportBlocks(t, resource, &expectedImportBlocks) From 846cbb0d6a4c3115f30fe5ce42b1125331e3d479 Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Wed, 12 Mar 2025 10:30:19 -0500 Subject: [PATCH 3/5] pr edits, remove 403 error check on worker app temporarily --- .../pingone/sso/pingone_sso_connector_test.go | 5 ++ .../resources/pingone_application_secret.go | 15 +++--- ...ne_population_default_identity_provider.go | 54 +++++++------------ ...pulation_default_identity_provider_test.go | 12 ++++- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/internal/connector/pingone/sso/pingone_sso_connector_test.go b/internal/connector/pingone/sso/pingone_sso_connector_test.go index 86b350dd..2edc68ca 100644 --- a/internal/connector/pingone/sso/pingone_sso_connector_test.go +++ b/internal/connector/pingone/sso/pingone_sso_connector_test.go @@ -94,6 +94,11 @@ func TestSSOTerraformPlan(t *testing.T) { resource: resources.PopulationDefault(PingOneClientInfo), ignoredErrors: nil, }, + { + name: "PopulationDefaultIdp", + resource: resources.PopulationDefaultIdp(PingOneClientInfo), + ignoredErrors: nil, + }, { name: "Resource", resource: resources.Resource(PingOneClientInfo), diff --git a/internal/connector/pingone/sso/resources/pingone_application_secret.go b/internal/connector/pingone/sso/resources/pingone_application_secret.go index 2dff1ddd..8a107b5f 100644 --- a/internal/connector/pingone/sso/resources/pingone_application_secret.go +++ b/internal/connector/pingone/sso/resources/pingone_application_secret.go @@ -117,14 +117,15 @@ func (r *PingOneApplicationSecretResource) checkApplicationSecretData(appId stri _, response, err := r.clientInfo.ApiClient.ManagementAPIClient.ApplicationSecretApi.ReadApplicationSecret(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID, appId).Execute() defer response.Body.Close() + // TEMPORARY COMMENT UNTIL PINGONE IMPLEMENTS FIX // If the appId is the same as the worker ID, make sure the API response is a 403 and ignore the error - if appId == *r.clientInfo.ApiClientId { - if response.StatusCode == 403 { - return false, nil - } else { - return false, fmt.Errorf("error: Expected 403 Forbidden response - worker apps cannot read their own secret\n%s Response Code: %s\nResponse Body: %s", "ReadApplicationSecret", response.Status, response.Body) - } - } + // if appId == *r.clientInfo.ApiClientId { + // if response.StatusCode == 403 { + // return false, nil + // } else { + // return false, fmt.Errorf("error: Expected 403 Forbidden response - worker apps cannot read their own secret\n%s Response Code: %s\nResponse Body: %s", "ReadApplicationSecret", response.Status, response.Body) + // } + // } // Use output package to warn the user of any errors or non-200 response codes // Expected behavior in this case is to skip the resource, and continue exporting the other resources diff --git a/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider.go b/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider.go index 10e1279b..39379f4b 100644 --- a/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider.go +++ b/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider.go @@ -12,25 +12,25 @@ import ( // Verify that the resource satisfies the exportable resource interface var ( - _ connector.ExportableResource = &PingOnePopulationDefaultIdp{} + _ connector.ExportableResource = &PingOnePopulationDefaultIdpResource{} ) -type PingOnePopulationDefaultIdp struct { +type PingOnePopulationDefaultIdpResource struct { clientInfo *connector.PingOneClientInfo } -// Utility method for creating a PingOnePopulationDefaultIdp -func PopulationDefaultIdp(clientInfo *connector.PingOneClientInfo) *PingOnePopulationDefaultIdp { - return &PingOnePopulationDefaultIdp{ +// Utility method for creating a PingOnePopulationDefaultIdpResource +func PopulationDefaultIdp(clientInfo *connector.PingOneClientInfo) *PingOnePopulationDefaultIdpResource { + return &PingOnePopulationDefaultIdpResource{ clientInfo: clientInfo, } } -func (r *PingOnePopulationDefaultIdp) ResourceType() string { +func (r *PingOnePopulationDefaultIdpResource) ResourceType() string { return "pingone_population_default_identity_provider" } -func (r *PingOnePopulationDefaultIdp) ExportAll() (*[]connector.ImportBlock, error) { +func (r *PingOnePopulationDefaultIdpResource) ExportAll() (*[]connector.ImportBlock, error) { l := logger.Get() l.Debug().Msgf("Exporting all '%s' Resources...", r.ResourceType()) @@ -42,25 +42,24 @@ func (r *PingOnePopulationDefaultIdp) ExportAll() (*[]connector.ImportBlock, err } for populationId, populationName := range populationData { - populationDefaultIdp, err := r.getPopulationDefaultIdp(populationId) + ok, err := r.getPopulationDefaultIdp(populationId) if err != nil { return nil, err } - if populationDefaultIdp == nil { - continue + if !ok { + return &importBlocks, nil } commentData := map[string]string{ - "Export Environment ID": r.clientInfo.ExportEnvironmentID, - "Population Default Identity Provider": *populationDefaultIdp, - "Population ID": populationId, - "Population Name": populationName, - "Resource Type": r.ResourceType(), + "Export Environment ID": r.clientInfo.ExportEnvironmentID, + "Population ID": populationId, + "Population Name": populationName, + "Resource Type": r.ResourceType(), } importBlock := connector.ImportBlock{ ResourceType: r.ResourceType(), - ResourceName: populationName, + ResourceName: fmt.Sprintf("%s_default_identity_provider", populationName), ResourceID: fmt.Sprintf("%s/%s", r.clientInfo.ExportEnvironmentID, populationId), CommentInformation: common.GenerateCommentInformation(commentData), } @@ -71,7 +70,7 @@ func (r *PingOnePopulationDefaultIdp) ExportAll() (*[]connector.ImportBlock, err return &importBlocks, nil } -func (r *PingOnePopulationDefaultIdp) getPopulationData() (map[string]string, error) { +func (r *PingOnePopulationDefaultIdpResource) getPopulationData() (map[string]string, error) { populationData := make(map[string]string) iter := r.clientInfo.ApiClient.ManagementAPIClient.PopulationsApi.ReadAllPopulations(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID).Execute() @@ -92,22 +91,7 @@ func (r *PingOnePopulationDefaultIdp) getPopulationData() (map[string]string, er return populationData, nil } -func (r *PingOnePopulationDefaultIdp) getPopulationDefaultIdp(populationId string) (*string, error) { - populationDefaultIdp, resp, err := r.clientInfo.ApiClient.ManagementAPIClient.PopulationsApi.ReadOnePopulationDefaultIdp(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID, populationId).Execute() - ok, err := common.HandleClientResponse(resp, err, "ReadOnePopulationDefaultIdp", r.ResourceType()) - if err != nil { - return nil, err - } - if !ok { - return nil, nil - } - - if populationDefaultIdp != nil { - populationDefaultIdpId, populationDefaultIdpIdOk := populationDefaultIdp.GetIdOk() - if populationDefaultIdpIdOk { - return populationDefaultIdpId, nil - } - } - - return nil, nil +func (r *PingOnePopulationDefaultIdpResource) getPopulationDefaultIdp(populationId string) (bool, error) { + _, resp, err := r.clientInfo.ApiClient.ManagementAPIClient.PopulationsApi.ReadOnePopulationDefaultIdp(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID, populationId).Execute() + return common.HandleClientResponse(resp, err, "ReadOnePopulationDefaultIdp", r.ResourceType()) } diff --git a/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider_test.go b/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider_test.go index 259a8efa..4f1aaea4 100644 --- a/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider_test.go +++ b/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider_test.go @@ -18,7 +18,17 @@ func TestPopulationDefaultIdpExport(t *testing.T) { expectedImportBlocks := []connector.ImportBlock{ { ResourceType: "pingone_population_default_identity_provider", - ResourceName: "Test Default Idp Population", + ResourceName: "Default_default_identity_provider", + ResourceID: fmt.Sprintf("%s/720da2ce-4dd0-48d9-af75-aeadbda1860d", testutils.GetEnvironmentID()), + }, + { + ResourceType: "pingone_population_default_identity_provider", + ResourceName: "LDAP Gateway Population_default_identity_provider", + ResourceID: fmt.Sprintf("%s/374fdb3c-4e94-4547-838a-0c200b9a7c70", testutils.GetEnvironmentID()), + }, + { + ResourceType: "pingone_population_default_identity_provider", + ResourceName: "Test Default Idp Population_default_identity_provider", ResourceID: fmt.Sprintf("%s/2814912d-4a0f-4104-a779-80c13b2a6dcd", testutils.GetEnvironmentID()), }, } From 60e5fb7378aac74f6d279848793d0cbb4d699465 Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Wed, 12 Mar 2025 10:34:28 -0500 Subject: [PATCH 4/5] update app secret test --- .../pingone/sso/resources/pingone_application_secret_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/connector/pingone/sso/resources/pingone_application_secret_test.go b/internal/connector/pingone/sso/resources/pingone_application_secret_test.go index 73413b46..4baec63b 100644 --- a/internal/connector/pingone/sso/resources/pingone_application_secret_test.go +++ b/internal/connector/pingone/sso/resources/pingone_application_secret_test.go @@ -46,6 +46,11 @@ func TestApplicationSecretExport(t *testing.T) { ResourceName: "Test MFA_secret", ResourceID: fmt.Sprintf("%s/11cfc8c7-ec0c-43ff-b49a-64f5e243f932", testutils.GetEnvironmentID()), }, + { + ResourceType: "pingone_application_secret", + ResourceName: "Worker App_secret", + ResourceID: fmt.Sprintf("%s/c45c2f8c-dee0-4a12-b169-bae693a13d57", testutils.GetEnvironmentID()), + }, } testutils.ValidateImportBlocks(t, resource, &expectedImportBlocks) From f0455fd9f81de53d3845b950f39be8602a1a64da Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Wed, 12 Mar 2025 11:31:26 -0500 Subject: [PATCH 5/5] uncomment 403 error check, minor test edits --- .../sso/resources/pingone_application_secret.go | 15 +++++++-------- ...ingone_population_default_identity_provider.go | 6 +++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/internal/connector/pingone/sso/resources/pingone_application_secret.go b/internal/connector/pingone/sso/resources/pingone_application_secret.go index 8a107b5f..2dff1ddd 100644 --- a/internal/connector/pingone/sso/resources/pingone_application_secret.go +++ b/internal/connector/pingone/sso/resources/pingone_application_secret.go @@ -117,15 +117,14 @@ func (r *PingOneApplicationSecretResource) checkApplicationSecretData(appId stri _, response, err := r.clientInfo.ApiClient.ManagementAPIClient.ApplicationSecretApi.ReadApplicationSecret(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID, appId).Execute() defer response.Body.Close() - // TEMPORARY COMMENT UNTIL PINGONE IMPLEMENTS FIX // If the appId is the same as the worker ID, make sure the API response is a 403 and ignore the error - // if appId == *r.clientInfo.ApiClientId { - // if response.StatusCode == 403 { - // return false, nil - // } else { - // return false, fmt.Errorf("error: Expected 403 Forbidden response - worker apps cannot read their own secret\n%s Response Code: %s\nResponse Body: %s", "ReadApplicationSecret", response.Status, response.Body) - // } - // } + if appId == *r.clientInfo.ApiClientId { + if response.StatusCode == 403 { + return false, nil + } else { + return false, fmt.Errorf("error: Expected 403 Forbidden response - worker apps cannot read their own secret\n%s Response Code: %s\nResponse Body: %s", "ReadApplicationSecret", response.Status, response.Body) + } + } // Use output package to warn the user of any errors or non-200 response codes // Expected behavior in this case is to skip the resource, and continue exporting the other resources diff --git a/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider.go b/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider.go index 39379f4b..e70b5f4a 100644 --- a/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider.go +++ b/internal/connector/pingone/sso/resources/pingone_population_default_identity_provider.go @@ -42,7 +42,7 @@ func (r *PingOnePopulationDefaultIdpResource) ExportAll() (*[]connector.ImportBl } for populationId, populationName := range populationData { - ok, err := r.getPopulationDefaultIdp(populationId) + ok, err := r.checkPopulationDefaultIdp(populationId) if err != nil { return nil, err } @@ -91,7 +91,7 @@ func (r *PingOnePopulationDefaultIdpResource) getPopulationData() (map[string]st return populationData, nil } -func (r *PingOnePopulationDefaultIdpResource) getPopulationDefaultIdp(populationId string) (bool, error) { +func (r *PingOnePopulationDefaultIdpResource) checkPopulationDefaultIdp(populationId string) (bool, error) { _, resp, err := r.clientInfo.ApiClient.ManagementAPIClient.PopulationsApi.ReadOnePopulationDefaultIdp(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID, populationId).Execute() - return common.HandleClientResponse(resp, err, "ReadOnePopulationDefaultIdp", r.ResourceType()) + return pingone.CheckSingletonResource(resp, err, "ReadOnePopulationDefaultIdp", r.ResourceType()) }