From 09413b406dd6be6237ec8b79cb344159e4cac4d5 Mon Sep 17 00:00:00 2001 From: Jason Rauen Date: Fri, 22 May 2026 13:18:15 -0400 Subject: [PATCH 1/2] refactor: share AuthenticationObj across muxed providers One signin in Configure, one signout in main, shared cookie jar. Fixes 401s caused by framework and sdkv2 each owning their own jar. --- main.go | 13 +- .../provider_framework/assets_datasource.go | 16 - .../provider_framework/assets_resource.go | 46 +-- .../databases_datasource.go | 12 - .../provider_framework/databases_resource.go | 25 -- .../provider_framework/folders_datasource.go | 13 - .../functional_accounts_datasource.go | 12 - .../functional_accounts_resource.go | 25 -- .../managed_account_datasource.go | 12 - .../managed_account_ephemeral.go | 13 - .../managed_system_datasource.go | 12 - .../managed_systems_by_asset_resource.go | 16 +- .../managed_systems_by_database_resource.go | 37 +- .../managed_systems_by_workgroup_resource.go | 16 +- .../platforms_datasource.go | 13 - providers/provider_framework/provider.go | 38 +- .../provider_framework/safes_datasources.go | 13 - .../provider_framework/secrets_ephemeral.go | 13 - .../workgroups_datasources.go | 13 - .../provider_framework/workgroups_resource.go | 13 - providers/provider_sdkv2/common.go | 36 +- providers/provider_sdkv2/folders.go | 30 +- .../provider_sdkv2/folders_safes_test.go | 12 +- providers/provider_sdkv2/managed_account.go | 52 +-- .../provider_sdkv2/managed_account_test.go | 12 +- providers/provider_sdkv2/provider.go | 28 +- providers/provider_sdkv2/provider_test.go | 35 +- providers/provider_sdkv2/safes.go | 30 +- providers/provider_sdkv2/secrets.go | 97 +---- providers/provider_sdkv2/secrets_test.go | 22 +- providers/utils/methods.go | 107 ------ providers/utils/methods_test.go | 363 ++++++------------ providers/utils/session.go | 106 +++++ 33 files changed, 391 insertions(+), 910 deletions(-) create mode 100644 providers/utils/session.go diff --git a/main.go b/main.go index 1f0edc0c..f6f1d3bb 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( providerFramework "terraform-provider-passwordsafe/providers/provider_framework" // new version of provider, ephemeral resources implemented. (terraform-plugin-framework) providerSdkv2 "terraform-provider-passwordsafe/providers/provider_sdkv2" // first version of provider. (terraform-plugin-sdk/v2) + "terraform-provider-passwordsafe/providers/utils" "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" ) @@ -34,13 +35,19 @@ func main() { var serveOpts []tf5server.ServeOpt - err = tf5server.Serve( + serveErr := tf5server.Serve( "registry.terraform.io/providers/BeyondTrust/passwordsafe", muxServer.ProviderServer, serveOpts..., ) - if err != nil { - log.Fatal(err) + // Signout unconditionally before checking serveErr — log.Fatal would call + // os.Exit and skip any deferreds, so do not defer this. + if shutdownErr := utils.ShutdownSharedAuth(); shutdownErr != nil { + log.Printf("warning: shared auth signout failed: %v", shutdownErr) + } + + if serveErr != nil { + log.Fatal(serveErr) } } diff --git a/providers/provider_framework/assets_datasource.go b/providers/provider_framework/assets_datasource.go index dab6a391..9e7e94e2 100644 --- a/providers/provider_framework/assets_datasource.go +++ b/providers/provider_framework/assets_datasource.go @@ -109,22 +109,6 @@ func (d *AssetDataSource) Read(ctx context.Context, req datasource.ReadRequest, return } - if resp.Diagnostics.HasError() { - return - } - - _, err := utils.Authenticate(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - - defer func() { - if err := utils.SignOut(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger); err != nil { - resp.Diagnostics.AddError("Error signing out", err.Error()) - } - }() - // instantiating asset obj asssetObj, _ := assets.NewAssetObj(*d.providerInfo.authenticationObj, zapLogger) diff --git a/providers/provider_framework/assets_resource.go b/providers/provider_framework/assets_resource.go index da81809d..c7167343 100644 --- a/providers/provider_framework/assets_resource.go +++ b/providers/provider_framework/assets_resource.go @@ -4,7 +4,6 @@ package provider_framework import ( "context" - "terraform-provider-passwordsafe/providers/utils" "github.com/BeyondTrust/go-client-library-passwordsafe/api/assets" "github.com/BeyondTrust/go-client-library-passwordsafe/api/authentication" @@ -71,8 +70,13 @@ func (r *assetResource) Delete(ctx context.Context, req resource.DeleteRequest, return } - err := utils.DeleteAssetByID(*r.providerInfo.authenticationObj, data.AssetID.ValueInt32(), &utils.AuthMu, &utils.SignInCount, zapLogger) + assetObj, err := assets.NewAssetObj(*r.providerInfo.authenticationObj, zapLogger) if err != nil { + resp.Diagnostics.AddError("Error creating asset object", err.Error()) + return + } + + if err := assetObj.DeleteAssetById(int(data.AssetID.ValueInt32())); err != nil { resp.Diagnostics.AddError("Error deleting asset", err.Error()) return } @@ -148,23 +152,11 @@ func NewAssetByWorkgGroypIdResource() resource.Resource { } func getAssetObj(resp *resource.CreateResponse, authenticationObj authentication.AuthenticationObj, dataInterface interface{}) (*assets.AssetObj, error) { - - _, err := utils.Authenticate(authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return nil, err - } - assetGroupObj, err := assets.NewAssetObj(authenticationObj, zapLogger) - if err != nil { resp.Diagnostics.AddError("Error creating authentication object", err.Error()) - if signOutErr := utils.SignOut(authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger); signOutErr != nil { - resp.Diagnostics.AddError("Error Signing Out", signOutErr.Error()) - } return nil, err } - return assetGroupObj, nil } @@ -183,12 +175,6 @@ func (r *assetResourceByWorkGroupId) Create(ctx context.Context, req resource.Cr return } - defer func() { - if err := utils.SignOut(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger); err != nil { - resp.Diagnostics.AddError("Error Signing Out", err.Error()) - } - }() - assetDetails := entities.AssetDetails{ IPAddress: data.IPAddress.ValueString(), AssetName: data.AssetName.ValueString(), @@ -219,8 +205,13 @@ func (r *assetResourceByWorkGroupId) Delete(ctx context.Context, req resource.De return } - err := utils.DeleteAssetByID(*r.providerInfo.authenticationObj, data.AssetID.ValueInt32(), &utils.AuthMu, &utils.SignInCount, zapLogger) + assetObj, err := assets.NewAssetObj(*r.providerInfo.authenticationObj, zapLogger) if err != nil { + resp.Diagnostics.AddError("Error creating asset object", err.Error()) + return + } + + if err := assetObj.DeleteAssetById(int(data.AssetID.ValueInt32())); err != nil { resp.Diagnostics.AddError("Error deleting asset", err.Error()) return } @@ -307,12 +298,6 @@ func (r *assetResourceByWorkGroupName) Create(ctx context.Context, req resource. return } - defer func() { - if err := utils.SignOut(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger); err != nil { - resp.Diagnostics.AddError("Error Signing Out", err.Error()) - } - }() - assetDetails := entities.AssetDetails{ IPAddress: data.IPAddress.ValueString(), AssetName: data.AssetName.ValueString(), @@ -343,8 +328,13 @@ func (r *assetResourceByWorkGroupName) Delete(ctx context.Context, req resource. return } - err := utils.DeleteAssetByID(*r.providerInfo.authenticationObj, data.AssetID.ValueInt32(), &utils.AuthMu, &utils.SignInCount, zapLogger) + assetObj, err := assets.NewAssetObj(*r.providerInfo.authenticationObj, zapLogger) if err != nil { + resp.Diagnostics.AddError("Error creating asset object", err.Error()) + return + } + + if err := assetObj.DeleteAssetById(int(data.AssetID.ValueInt32())); err != nil { resp.Diagnostics.AddError("Error deleting asset", err.Error()) return } diff --git a/providers/provider_framework/databases_datasource.go b/providers/provider_framework/databases_datasource.go index 6da93ac1..c0dac9fa 100644 --- a/providers/provider_framework/databases_datasource.go +++ b/providers/provider_framework/databases_datasource.go @@ -92,18 +92,6 @@ func (d *DatabaseDataSource) Read(ctx context.Context, req datasource.ReadReques return } - _, err := utils.Authenticate(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - - defer func() { - if err := utils.SignOut(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger); err != nil { - resp.Diagnostics.AddError("Error signing out", err.Error()) - } - }() - // instantiating database obj databaseObj, _ := databases.NewDatabaseObj(*d.providerInfo.authenticationObj, zapLogger) diff --git a/providers/provider_framework/databases_resource.go b/providers/provider_framework/databases_resource.go index 3c92ffb1..ababc58e 100644 --- a/providers/provider_framework/databases_resource.go +++ b/providers/provider_framework/databases_resource.go @@ -4,7 +4,6 @@ package provider_framework import ( "context" - "terraform-provider-passwordsafe/providers/utils" "github.com/BeyondTrust/go-client-library-passwordsafe/api/entities" "github.com/hashicorp/terraform-plugin-framework/path" @@ -107,12 +106,6 @@ func (r *databaseResource) Create(ctx context.Context, req resource.CreateReques return } - _, err := utils.Authenticate(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - // instantiating database obj databaseObj, err := databases.NewDatabaseObj(*r.providerInfo.authenticationObj, zapLogger) @@ -140,12 +133,6 @@ func (r *databaseResource) Create(ctx context.Context, req resource.CreateReques data.DatabaseID = types.Int32Value(int32(createdDataBase.DatabaseID)) - err = utils.SignOut(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error Signing Out", err.Error()) - return - } - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -166,12 +153,6 @@ func (r *databaseResource) Delete(ctx context.Context, req resource.DeleteReques return } - _, err := utils.Authenticate(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - // instantiating database obj databaseObj, err := databases.NewDatabaseObj(*r.providerInfo.authenticationObj, zapLogger) if err != nil { @@ -185,12 +166,6 @@ func (r *databaseResource) Delete(ctx context.Context, req resource.DeleteReques resp.Diagnostics.AddError("Error deleting database", err.Error()) return } - - err = utils.SignOut(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error Signing Out", err.Error()) - return - } } func (r *databaseResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { diff --git a/providers/provider_framework/folders_datasource.go b/providers/provider_framework/folders_datasource.go index 85634f2a..2aec5143 100644 --- a/providers/provider_framework/folders_datasource.go +++ b/providers/provider_framework/folders_datasource.go @@ -4,7 +4,6 @@ package provider_framework import ( "context" - "terraform-provider-passwordsafe/providers/utils" "github.com/BeyondTrust/go-client-library-passwordsafe/api/secrets" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -96,18 +95,6 @@ func (d *FolderDataSource) Read(ctx context.Context, req datasource.ReadRequest, return } - _, err := utils.Authenticate(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - - defer func() { - if err := utils.SignOut(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger); err != nil { - resp.Diagnostics.AddError("Error signing out", err.Error()) - } - }() - // instantiating secrets obj (contains folder methods) secretObj, _ := secrets.NewSecretObj(*d.providerInfo.authenticationObj, zapLogger, maxFileSecretSizeBytes, false) diff --git a/providers/provider_framework/functional_accounts_datasource.go b/providers/provider_framework/functional_accounts_datasource.go index 8ea57fec..faf8b26b 100644 --- a/providers/provider_framework/functional_accounts_datasource.go +++ b/providers/provider_framework/functional_accounts_datasource.go @@ -80,18 +80,6 @@ func (d *FunctionalAccountDataResource) Read(ctx context.Context, req datasource return } - _, err := utils.Authenticate(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - - defer func() { - if err := utils.SignOut(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger); err != nil { - resp.Diagnostics.AddError("Error signing out", err.Error()) - } - }() - // instantiating functional account obj functionalAccountObj, _ := functional_accounts.NewFuncionalAccount(*d.providerInfo.authenticationObj, zapLogger) diff --git a/providers/provider_framework/functional_accounts_resource.go b/providers/provider_framework/functional_accounts_resource.go index c4012a2c..898a52cb 100644 --- a/providers/provider_framework/functional_accounts_resource.go +++ b/providers/provider_framework/functional_accounts_resource.go @@ -4,7 +4,6 @@ package provider_framework import ( "context" - "terraform-provider-passwordsafe/providers/utils" "github.com/BeyondTrust/go-client-library-passwordsafe/api/entities" "github.com/BeyondTrust/go-client-library-passwordsafe/api/functional_accounts" @@ -146,12 +145,6 @@ func (r *FunctionalAccountResource) Create(ctx context.Context, req resource.Cre return } - _, err := utils.Authenticate(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - // instantiating functional account obj functionalAccountObj, err := functional_accounts.NewFuncionalAccount(*r.providerInfo.authenticationObj, zapLogger) @@ -187,12 +180,6 @@ func (r *FunctionalAccountResource) Create(ctx context.Context, req resource.Cre data.FunctionalAccountID = types.Int32Value(int32(createdFunctionalAccount.FunctionalAccountID)) - err = utils.SignOut(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error Signing Out", err.Error()) - return - } - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -214,12 +201,6 @@ func (r *FunctionalAccountResource) Delete(ctx context.Context, req resource.Del return } - _, err := utils.Authenticate(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - // instantiating functional account obj functionalAccountObj, err := functional_accounts.NewFuncionalAccount(*r.providerInfo.authenticationObj, zapLogger) if err != nil { @@ -233,12 +214,6 @@ func (r *FunctionalAccountResource) Delete(ctx context.Context, req resource.Del resp.Diagnostics.AddError("Error deleting functional account", err.Error()) return } - - err = utils.SignOut(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error Signing Out", err.Error()) - return - } } func (r *FunctionalAccountResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { diff --git a/providers/provider_framework/managed_account_datasource.go b/providers/provider_framework/managed_account_datasource.go index 7ffcd26f..dbd12e28 100644 --- a/providers/provider_framework/managed_account_datasource.go +++ b/providers/provider_framework/managed_account_datasource.go @@ -114,18 +114,6 @@ func (d *ManagedAccountDataSource) Read(ctx context.Context, req datasource.Read return } - _, err := utils.Authenticate(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - - defer func() { - if err := utils.SignOut(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger); err != nil { - resp.Diagnostics.AddError("Error signing out", err.Error()) - } - }() - // instantiating managed acocunt obj. managedAccountObj, _ := managed_accounts.NewManagedAccountObj(*d.providerInfo.authenticationObj, zapLogger) diff --git a/providers/provider_framework/managed_account_ephemeral.go b/providers/provider_framework/managed_account_ephemeral.go index 439db90c..4ee05f44 100644 --- a/providers/provider_framework/managed_account_ephemeral.go +++ b/providers/provider_framework/managed_account_ephemeral.go @@ -4,7 +4,6 @@ package provider_framework import ( "context" - "terraform-provider-passwordsafe/providers/utils" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/ephemeral" @@ -85,12 +84,6 @@ func (e *EphemeralManagedAccount) Open(ctx context.Context, request ephemeral.Op return } - _, err := utils.Authenticate(*e.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - response.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - // instantiating managed account obj manageAccountObj, err := managed_accounts.NewManagedAccountObj(*e.providerInfo.authenticationObj, zapLogger) @@ -110,12 +103,6 @@ func (e *EphemeralManagedAccount) Open(ctx context.Context, request ephemeral.Op // setting secret to value attribute data.Value = types.StringValue(gotManagedAccount) - err = utils.SignOut(*e.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - response.Diagnostics.AddError("Error Signing Out", err.Error()) - return - } - response.Diagnostics.Append(response.Result.Set(ctx, &data)...) } diff --git a/providers/provider_framework/managed_system_datasource.go b/providers/provider_framework/managed_system_datasource.go index 5793a62c..9029c4e3 100644 --- a/providers/provider_framework/managed_system_datasource.go +++ b/providers/provider_framework/managed_system_datasource.go @@ -161,18 +161,6 @@ func (d *ManagedSystemDataSource) Read(ctx context.Context, req datasource.ReadR return } - _, err := utils.Authenticate(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - - defer func() { - if err := utils.SignOut(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger); err != nil { - resp.Diagnostics.AddError("Error signing out", err.Error()) - } - }() - // instantiating managed system obj. managedSystemObj, _ := managed_systems.NewManagedSystem(*d.providerInfo.authenticationObj, zapLogger) diff --git a/providers/provider_framework/managed_systems_by_asset_resource.go b/providers/provider_framework/managed_systems_by_asset_resource.go index d593c091..ace460e7 100644 --- a/providers/provider_framework/managed_systems_by_asset_resource.go +++ b/providers/provider_framework/managed_systems_by_asset_resource.go @@ -219,8 +219,6 @@ func (r *managedSystemResource) Create(ctx context.Context, req resource.CreateR data.ManagedSystemID = types.Int32Value(int32(createdDataBase.ManagedSystemID)) data.ManagedSystemName = types.StringValue(createdDataBase.SystemName) - APISignOut(resp, *r.providerInfo.authenticationObj) - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -241,24 +239,12 @@ func (r *managedSystemResource) Delete(ctx context.Context, req resource.DeleteR return } - _, err := utils.Authenticate(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - // Delete managed system using helper function - err = utils.DeleteManagedSystemByID(*r.providerInfo.authenticationObj, int(data.ManagedSystemID.ValueInt32()), zapLogger) + err := utils.DeleteManagedSystemByID(*r.providerInfo.authenticationObj, int(data.ManagedSystemID.ValueInt32()), zapLogger) if err != nil { resp.Diagnostics.AddError("Error deleting managed system", err.Error()) return } - - err = utils.SignOut(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error Signing Out", err.Error()) - return - } } func (r *managedSystemResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { diff --git a/providers/provider_framework/managed_systems_by_database_resource.go b/providers/provider_framework/managed_systems_by_database_resource.go index ee87269e..9312a1ae 100644 --- a/providers/provider_framework/managed_systems_by_database_resource.go +++ b/providers/provider_framework/managed_systems_by_database_resource.go @@ -130,8 +130,6 @@ func (r *managedSystemByDatabaseResource) Create(ctx context.Context, req resour data.ManagedSystemID = types.Int32Value(int32(createdDataBase.ManagedSystemID)) data.ManagedSystemName = types.StringValue(createdDataBase.SystemName) - APISignOut(resp, *r.providerInfo.authenticationObj) - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -152,24 +150,12 @@ func (r *managedSystemByDatabaseResource) Delete(ctx context.Context, req resour return } - _, err := utils.Authenticate(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - // Delete managed system using helper function - err = utils.DeleteManagedSystemByID(*r.providerInfo.authenticationObj, int(data.ManagedSystemID.ValueInt32()), zapLogger) + err := utils.DeleteManagedSystemByID(*r.providerInfo.authenticationObj, int(data.ManagedSystemID.ValueInt32()), zapLogger) if err != nil { resp.Diagnostics.AddError("Error deleting managed system", err.Error()) return } - - err = utils.SignOut(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error Signing Out", err.Error()) - return - } } func (r *managedSystemByDatabaseResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { @@ -178,34 +164,15 @@ func (r *managedSystemByDatabaseResource) ImportState(ctx context.Context, req r // getManagedSystemObj get managedSystemObj for create manage system by asset, workgroup, database. func getManagedSystemObj(changeFrequencyType string, changeFrequencyDays int, resp *resource.CreateResponse, authenticationObj authentication.AuthenticationObj) (*managed_systems.ManagedSystemObj, error) { - err := utils.ValidateChangeFrequencyDays(changeFrequencyType, changeFrequencyDays) - - if err != nil { + if err := utils.ValidateChangeFrequencyDays(changeFrequencyType, changeFrequencyDays); err != nil { resp.Diagnostics.AddError("Error in inputs", err.Error()) return nil, err } - _, err = utils.Authenticate(authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return nil, err - } - - // Instantiating managed system obj managedSystemObj, err := managed_systems.NewManagedSystem(authenticationObj, zapLogger) - if err != nil { resp.Diagnostics.AddError("Error creating managed account object", err.Error()) return nil, err } return managedSystemObj, nil } - -// APISignOut close connection with Password Safe API. -func APISignOut(resp *resource.CreateResponse, authenticationObj authentication.AuthenticationObj) { - err := utils.SignOut(authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error Signing Out", err.Error()) - return - } -} diff --git a/providers/provider_framework/managed_systems_by_workgroup_resource.go b/providers/provider_framework/managed_systems_by_workgroup_resource.go index c51b6ee3..e75ef425 100644 --- a/providers/provider_framework/managed_systems_by_workgroup_resource.go +++ b/providers/provider_framework/managed_systems_by_workgroup_resource.go @@ -309,8 +309,6 @@ func (r *managedSystemByWorkGroupResource) Create(ctx context.Context, req resou data.ManagedSystemID = types.Int32Value(int32(createdDataBase.ManagedSystemID)) data.ManagedSystemName = types.StringValue(createdDataBase.SystemName) - APISignOut(resp, *r.providerInfo.authenticationObj) - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -331,24 +329,12 @@ func (r *managedSystemByWorkGroupResource) Delete(ctx context.Context, req resou return } - _, err := utils.Authenticate(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - // Delete managed system using helper function - err = utils.DeleteManagedSystemByID(*r.providerInfo.authenticationObj, int(data.ManagedSystemID.ValueInt32()), zapLogger) + err := utils.DeleteManagedSystemByID(*r.providerInfo.authenticationObj, int(data.ManagedSystemID.ValueInt32()), zapLogger) if err != nil { resp.Diagnostics.AddError("Error deleting managed system", err.Error()) return } - - err = utils.SignOut(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error Signing Out", err.Error()) - return - } } func (r *managedSystemByWorkGroupResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { diff --git a/providers/provider_framework/platforms_datasource.go b/providers/provider_framework/platforms_datasource.go index db783bf2..d8ed5063 100644 --- a/providers/provider_framework/platforms_datasource.go +++ b/providers/provider_framework/platforms_datasource.go @@ -4,7 +4,6 @@ package provider_framework import ( "context" - "terraform-provider-passwordsafe/providers/utils" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -164,18 +163,6 @@ func (d *PlatformDataSource) Read(ctx context.Context, req datasource.ReadReques return } - _, err := utils.Authenticate(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - - defer func() { - if err := utils.SignOut(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger); err != nil { - resp.Diagnostics.AddError("Error signing out", err.Error()) - } - }() - // instantiating platform obj platformObj, _ := platforms.NewPlatformObj(*d.providerInfo.authenticationObj, zapLogger) diff --git a/providers/provider_framework/provider.go b/providers/provider_framework/provider.go index 2585e3d6..68635150 100644 --- a/providers/provider_framework/provider.go +++ b/providers/provider_framework/provider.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/types" "go.uber.org/zap" + localutils "terraform-provider-passwordsafe/providers/utils" "github.com/hashicorp/terraform-plugin-framework/provider/schema" @@ -249,29 +250,30 @@ func (p *PasswordSafeProvider) Configure(ctx context.Context, req provider.Confi backoffDefinition.MaxElapsedTime = time.Duration(retryMaxElapsedTimeMinutes) * time.Second backoffDefinition.RandomizationFactor = 0.5 - // creating a http client - httpClientObj, err := utils.GetHttpClient(clientTimeOutInSeconds, providerData.verifyca, certificate, certificateKey, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error creating HTTP client", err.Error()) - return - } - - authenticate, err := p.buildAuthenticationObj(*httpClientObj, backoffDefinition, data) - if err != nil { - resp.Diagnostics.AddError("Error in Provider", err.Error()) - return - } - - // authenticating - userObject, err := authenticate.GetPasswordSafeAuthentication() + cacheKey := strings.Join([]string{ + providerData.url, + providerData.apiVersion, + providerData.apiKey, + providerData.clientId, + providerData.clientSecret, + providerData.accountname, + fmt.Sprintf("%t", providerData.verifyca), + providerData.clientCertificateName, + }, "|") + + authenticate, signAppin, err := localutils.InitSharedAuth(cacheKey, func() (*auth.AuthenticationObj, error) { + httpClientObj, err := utils.GetHttpClient(clientTimeOutInSeconds, providerData.verifyca, certificate, certificateKey, zapLogger) + if err != nil { + return nil, err + } + return p.buildAuthenticationObj(*httpClientObj, backoffDefinition, data) + }) if err != nil { resp.Diagnostics.AddError("Error in Provider", err.Error()) return } - providerData.userName = userObject.UserName - - // pass authentication obj to ephemeral resources + providerData.userName = signAppin.UserName providerData.authenticationObj = authenticate // pass data to ephemeral resources diff --git a/providers/provider_framework/safes_datasources.go b/providers/provider_framework/safes_datasources.go index 879cdd62..e200e94a 100644 --- a/providers/provider_framework/safes_datasources.go +++ b/providers/provider_framework/safes_datasources.go @@ -4,7 +4,6 @@ package provider_framework import ( "context" - "terraform-provider-passwordsafe/providers/utils" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -87,18 +86,6 @@ func (d *SafesDataSource) Read(ctx context.Context, req datasource.ReadRequest, return } - _, err := utils.Authenticate(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - - defer func() { - if err := utils.SignOut(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger); err != nil { - resp.Diagnostics.AddError("Error signing out", err.Error()) - } - }() - // instantiating secrets obj (contains safes methods) secretObj, _ := secrets.NewSecretObj(*d.providerInfo.authenticationObj, zapLogger, maxFileSecretSizeBytes, false) diff --git a/providers/provider_framework/secrets_ephemeral.go b/providers/provider_framework/secrets_ephemeral.go index 4a20573a..1401d63c 100644 --- a/providers/provider_framework/secrets_ephemeral.go +++ b/providers/provider_framework/secrets_ephemeral.go @@ -4,7 +4,6 @@ package provider_framework import ( "context" - "terraform-provider-passwordsafe/providers/utils" "github.com/BeyondTrust/go-client-library-passwordsafe/api/secrets" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -98,12 +97,6 @@ func (e *EphemeralSecret) Open(ctx context.Context, request ephemeral.OpenReques return } - _, err := utils.Authenticate(*e.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - response.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - var decryptValue bool if data.Decrypt.IsNull() { decryptValue = true @@ -135,12 +128,6 @@ func (e *EphemeralSecret) Open(ctx context.Context, request ephemeral.OpenReques // setting secret to value attribute data.Value = types.StringValue(secret) - err = utils.SignOut(*e.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - response.Diagnostics.AddError("Error Signing Out", err.Error()) - return - } - response.Diagnostics.Append(response.Result.Set(ctx, &data)...) } diff --git a/providers/provider_framework/workgroups_datasources.go b/providers/provider_framework/workgroups_datasources.go index f36077a8..089cfa30 100644 --- a/providers/provider_framework/workgroups_datasources.go +++ b/providers/provider_framework/workgroups_datasources.go @@ -4,7 +4,6 @@ package provider_framework import ( "context" - "terraform-provider-passwordsafe/providers/utils" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -85,18 +84,6 @@ func (d *WorkgroupDataSource) Read(ctx context.Context, req datasource.ReadReque return } - _, err := utils.Authenticate(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - - defer func() { - if err := utils.SignOut(*d.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger); err != nil { - resp.Diagnostics.AddError("Error signing out", err.Error()) - } - }() - // instantiating workgroup obj workgroupObj, _ := workgroups.NewWorkGroupObj(*d.providerInfo.authenticationObj, zapLogger) diff --git a/providers/provider_framework/workgroups_resource.go b/providers/provider_framework/workgroups_resource.go index 56a3a631..5d1d4161 100644 --- a/providers/provider_framework/workgroups_resource.go +++ b/providers/provider_framework/workgroups_resource.go @@ -4,7 +4,6 @@ package provider_framework import ( "context" - "terraform-provider-passwordsafe/providers/utils" "github.com/BeyondTrust/go-client-library-passwordsafe/api/entities" "github.com/BeyondTrust/go-client-library-passwordsafe/api/workgroups" @@ -83,12 +82,6 @@ func (r *WorkGroupResource) Create(ctx context.Context, req resource.CreateReque return } - _, err := utils.Authenticate(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error getting Authentication", err.Error()) - return - } - // instantiating workgroup obj workGroupObj, err := workgroups.NewWorkGroupObj(*r.providerInfo.authenticationObj, zapLogger) @@ -116,12 +109,6 @@ func (r *WorkGroupResource) Create(ctx context.Context, req resource.CreateReque data.Id = types.Int32Value(int32(createdWorkGroup.ID)) - err = utils.SignOut(*r.providerInfo.authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - resp.Diagnostics.AddError("Error Signing Out", err.Error()) - return - } - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } diff --git a/providers/provider_sdkv2/common.go b/providers/provider_sdkv2/common.go index 8ead273d..10bcfc88 100644 --- a/providers/provider_sdkv2/common.go +++ b/providers/provider_sdkv2/common.go @@ -3,40 +3,18 @@ package provider import ( - "terraform-provider-passwordsafe/providers/utils" - auth "github.com/BeyondTrust/go-client-library-passwordsafe/api/authentication" "github.com/BeyondTrust/go-client-library-passwordsafe/api/entities" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -// authenticate get Password Safe authentication. -func authenticate(d *schema.ResourceData, m interface{}) (entities.SignAppinResponse, error) { - authenticationObj := m.(*auth.AuthenticationObj) - var err error - var signAppinResponse entities.SignAppinResponse - - signAppinResponse, err = utils.Authenticate(*authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - zapLogger.Error(err.Error()) - return signAppinResponse, err - } - - return signAppinResponse, nil -} - -// signOut sign Password Safe out -func signOut(d *schema.ResourceData, m interface{}) error { - authenticationObj := m.(*auth.AuthenticationObj) - - err := utils.SignOut(*authenticationObj, &utils.AuthMu, &utils.SignInCount, zapLogger) - if err != nil { - zapLogger.Error(err.Error()) - return err - } - - return nil - +// providerMeta is what providerConfigure stores in the schema.ResourceData meta +// slot. Both halves are needed at resource-call time: authObj holds the shared +// HTTP client (with its cookie jar carrying the session), and signAppin carries +// the UserName/UserId/EmailAddress used when constructing owner records. +type providerMeta struct { + authObj *auth.AuthenticationObj + signAppin entities.SignAppinResponse } // getOwnersSchema get Owners schema. diff --git a/providers/provider_sdkv2/folders.go b/providers/provider_sdkv2/folders.go index 73154425..f3be3044 100644 --- a/providers/provider_sdkv2/folders.go +++ b/providers/provider_sdkv2/folders.go @@ -5,7 +5,6 @@ package provider import ( "fmt" - auth "github.com/BeyondTrust/go-client-library-passwordsafe/api/authentication" "github.com/BeyondTrust/go-client-library-passwordsafe/api/entities" "github.com/BeyondTrust/go-client-library-passwordsafe/api/secrets" @@ -45,15 +44,10 @@ func resourceFolder() *schema.Resource { // Create context for resourceFolder Resource. func resourceFolderCreate(d *schema.ResourceData, m interface{}) error { - authenticationObj := m.(*auth.AuthenticationObj) + meta := m.(*providerMeta) parent_folder_name := d.Get("parent_folder_name").(string) - _, err := authenticate(d, m) - if err != nil { - return err - } - - secretObj, _ := secrets.NewSecretObj(*authenticationObj, zapLogger, 5000000, false) + secretObj, _ := secrets.NewSecretObj(*meta.authObj, zapLogger, 5000000, false) name := d.Get("name").(string) description := d.Get("description").(string) @@ -67,12 +61,6 @@ func resourceFolderCreate(d *schema.ResourceData, m interface{}) error { } createdFolder, err := secretObj.CreateFolderFlow(parent_folder_name, folder) - - if err != nil { - return err - } - - err = signOut(d, m) if err != nil { return err } @@ -97,14 +85,9 @@ func resourceFolderDelete(d *schema.ResourceData, m interface{}) error { return fmt.Errorf("authentication object is nil") } - authenticationObj := m.(*auth.AuthenticationObj) + meta := m.(*providerMeta) - _, err := authenticate(d, m) - if err != nil { - return err - } - - secretObj, err := secrets.NewSecretObj(*authenticationObj, zapLogger, 5000000, false) + secretObj, err := secrets.NewSecretObj(*meta.authObj, zapLogger, 5000000, false) if err != nil { return err } @@ -121,11 +104,6 @@ func resourceFolderDelete(d *schema.ResourceData, m interface{}) error { return err } - err = signOut(d, m) - if err != nil { - return err - } - d.SetId("") return nil } diff --git a/providers/provider_sdkv2/folders_safes_test.go b/providers/provider_sdkv2/folders_safes_test.go index 68ca3960..b1376981 100644 --- a/providers/provider_sdkv2/folders_safes_test.go +++ b/providers/provider_sdkv2/folders_safes_test.go @@ -72,7 +72,7 @@ func TestSecretSafeFlow(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceSafeCreate(data, authenticate) + err := resourceSafeCreate(data, &providerMeta{authObj: authenticate}) if err != nil { t.Errorf("Test case Failed: %v", err) @@ -153,7 +153,7 @@ func TestSecretFolderFlow(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceFolderCreate(data, authenticate) + err := resourceFolderCreate(data, &providerMeta{authObj: authenticate}) if err != nil { t.Errorf("Test case Failed: %v", err) @@ -234,7 +234,7 @@ func TestSecretFolderFlowError(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceFolderCreate(data, authenticate) + err := resourceFolderCreate(data, &providerMeta{authObj: authenticate}) if err.Error() != "parent folder name must not be empty" { t.Errorf("Test case Failed %v, %v", err.Error(), "parent folder name must not be empty") @@ -319,7 +319,7 @@ func TestResourceFolderDelete(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceFolderDelete(data, authenticate) + err := resourceFolderDelete(data, &providerMeta{authObj: authenticate}) if err != nil { t.Errorf("Test case Failed: %v", err) @@ -392,7 +392,7 @@ func TestResourceFolderDeleteEmptyID(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceFolderDelete(data, authenticate) + err := resourceFolderDelete(data, &providerMeta{authObj: authenticate}) if err == nil || err.Error() != "folder ID is empty" { t.Errorf("Expected 'folder ID is empty' error, but got: %v", err) @@ -469,7 +469,7 @@ func TestResourceFolderDeleteError(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceFolderDelete(data, authenticate) + err := resourceFolderDelete(data, &providerMeta{authObj: authenticate}) if err == nil { t.Errorf("Expected error when deleting non-existent folder, but got nil") diff --git a/providers/provider_sdkv2/managed_account.go b/providers/provider_sdkv2/managed_account.go index a9210dac..2355aa07 100644 --- a/providers/provider_sdkv2/managed_account.go +++ b/providers/provider_sdkv2/managed_account.go @@ -9,7 +9,6 @@ import ( "fmt" "strconv" - auth "github.com/BeyondTrust/go-client-library-passwordsafe/api/authentication" "github.com/BeyondTrust/go-client-library-passwordsafe/api/entities" managed_accounts "github.com/BeyondTrust/go-client-library-passwordsafe/api/managed_account" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -55,15 +54,10 @@ func resourceManagedAccount() *schema.Resource { // Create context for resourceManagedAccount Resource. func resourceManagedAccountCreate(d *schema.ResourceData, m interface{}) error { - authenticationObj := m.(*auth.AuthenticationObj) + meta := m.(*providerMeta) system_name := d.Get("system_name").(string) - _, err := authenticate(d, m) - if err != nil { - return err - } - - manageAccountObj, _ := managed_accounts.NewManagedAccountObj(*authenticationObj, zapLogger) + manageAccountObj, _ := managed_accounts.NewManagedAccountObj(*meta.authObj, zapLogger) accountDetailsObj := entities.AccountDetails{ AccountName: d.Get("account_name").(string), @@ -103,14 +97,7 @@ func resourceManagedAccountCreate(d *schema.ResourceData, m interface{}) error { ObjectID: d.Get("object_id").(string), } - var createResponse entities.CreateManagedAccountsResponse - createResponse, err = manageAccountObj.ManageAccountCreateFlow(system_name, accountDetailsObj) - - if err != nil { - return err - } - - err = signOut(d, m) + createResponse, err := manageAccountObj.ManageAccountCreateFlow(system_name, accountDetailsObj) if err != nil { return err } @@ -135,14 +122,9 @@ func resourceManagedAccountDelete(d *schema.ResourceData, m interface{}) error { return fmt.Errorf("authentication object is nil") } - authenticationObj := m.(*auth.AuthenticationObj) + meta := m.(*providerMeta) - _, err := authenticate(d, m) - if err != nil { - return err - } - - manageAccountObj, err := managed_accounts.NewManagedAccountObj(*authenticationObj, zapLogger) + manageAccountObj, err := managed_accounts.NewManagedAccountObj(*meta.authObj, zapLogger) if err != nil { return err } @@ -158,11 +140,6 @@ func resourceManagedAccountDelete(d *schema.ResourceData, m interface{}) error { return err } - err = signOut(d, m) - if err != nil { - return err - } - d.SetId("") return nil } @@ -172,31 +149,18 @@ func getManagedAccountReadContext(ctx context.Context, d *schema.ResourceData, m var diags diag.Diagnostics - authenticationObj := m.(*auth.AuthenticationObj) + meta := m.(*providerMeta) system_name := d.Get("system_name").(string) account_name := d.Get("account_name").(string) - _, err := authenticate(d, m) - if err != nil { - return diag.FromErr(err) - } - - manageAccountObj, _ := managed_accounts.NewManagedAccountObj(*authenticationObj, zapLogger) + manageAccountObj, _ := managed_accounts.NewManagedAccountObj(*meta.authObj, zapLogger) gotManagedAccount, err := manageAccountObj.GetSecret(system_name+"/"+account_name, "/") - if err != nil { return diag.FromErr(err) } - err = d.Set("value", gotManagedAccount) - - if err != nil { - return diag.FromErr(err) - } - - err = signOut(d, m) - if err != nil { + if err := d.Set("value", gotManagedAccount); err != nil { return diag.FromErr(err) } diff --git a/providers/provider_sdkv2/managed_account_test.go b/providers/provider_sdkv2/managed_account_test.go index e325aba5..19aa2210 100644 --- a/providers/provider_sdkv2/managed_account_test.go +++ b/providers/provider_sdkv2/managed_account_test.go @@ -110,7 +110,7 @@ func TestResourceManagedAccountCreate(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceManagedAccountCreate(data, authenticate) + err := resourceManagedAccountCreate(data, &providerMeta{authObj: authenticate}) if err != nil { t.Errorf("Test case Failed: %v", err) @@ -181,7 +181,7 @@ func TestResourceManagedAccountCreateError(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceManagedAccountCreate(data, authenticate) + err := resourceManagedAccountCreate(data, &providerMeta{authObj: authenticate}) if err.Error() != "managed system system0101 was not found in managed system list" { t.Errorf("Test case Failed %v, %v", err.Error(), " managed system system0101 was not found in managed system list") } @@ -274,7 +274,7 @@ func TestGetManagedAccountReadContext(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := getManagedAccountReadContext(context.Background(), data, authenticate) + err := getManagedAccountReadContext(context.Background(), data, &providerMeta{authObj: authenticate}) if err != nil { t.Errorf("Test case Failed: %v", err) @@ -340,7 +340,7 @@ func TestResourceManagedAccountDelete(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceManagedAccountDelete(data, authenticate) + err := resourceManagedAccountDelete(data, &providerMeta{authObj: authenticate}) if err != nil { t.Errorf("Test case Failed: %v", err) @@ -369,7 +369,7 @@ func TestResourceManagedAccountDeleteInvalidID(t *testing.T) { var authenticate, _ = authentication.Authenticate(*authParams) - err := resourceManagedAccountDelete(data, authenticate) + err := resourceManagedAccountDelete(data, &providerMeta{authObj: authenticate}) if err == nil { t.Errorf("Expected error for invalid ID, but got nil") @@ -427,7 +427,7 @@ func TestResourceManagedAccountDeleteError(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceManagedAccountDelete(data, authenticate) + err := resourceManagedAccountDelete(data, &providerMeta{authObj: authenticate}) if err == nil { t.Errorf("Expected error when deleting non-existent managed account, but got nil") diff --git a/providers/provider_sdkv2/provider.go b/providers/provider_sdkv2/provider.go index dd14119d..74d43b58 100644 --- a/providers/provider_sdkv2/provider.go +++ b/providers/provider_sdkv2/provider.go @@ -14,6 +14,8 @@ import ( backoff "github.com/cenkalti/backoff/v4" "go.uber.org/zap" + localutils "terraform-provider-passwordsafe/providers/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -223,15 +225,27 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{} return nil, diag.FromErr(errorsInInputs) } - httpClientObj, err := utils.GetHttpClient(45, verifyca, certificate, certificateKey, zapLogger) - if err != nil { - return nil, diag.FromErr(err) - } - - authenticate, err := buildAuthenticationObj(*httpClientObj, backoffDefinition, apikey, url, apiVersion, accountName, clientId, clientSecret, retryMaxElapsedTimeMinutes) + cacheKey := strings.Join([]string{ + url, + apiVersion, + apikey, + clientId, + clientSecret, + accountName, + fmt.Sprintf("%t", verifyca), + clientCertificateName, + }, "|") + + authenticate, signAppin, err := localutils.InitSharedAuth(cacheKey, func() (*auth.AuthenticationObj, error) { + httpClientObj, err := utils.GetHttpClient(45, verifyca, certificate, certificateKey, zapLogger) + if err != nil { + return nil, err + } + return buildAuthenticationObj(*httpClientObj, backoffDefinition, apikey, url, apiVersion, accountName, clientId, clientSecret, retryMaxElapsedTimeMinutes) + }) if err != nil { return nil, diag.FromErr(err) } - return authenticate, diags + return &providerMeta{authObj: authenticate, signAppin: signAppin}, diags } diff --git a/providers/provider_sdkv2/provider_test.go b/providers/provider_sdkv2/provider_test.go index eed47041..b347c362 100644 --- a/providers/provider_sdkv2/provider_test.go +++ b/providers/provider_sdkv2/provider_test.go @@ -2,9 +2,12 @@ package provider import ( "context" + "net/http" + "net/http/httptest" "testing" "terraform-provider-passwordsafe/providers/constants" + "terraform-provider-passwordsafe/providers/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/assert" @@ -18,14 +21,34 @@ func TestProvider(t *testing.T) { assert.NoError(t, err, "Provider validation should not return an error") } +// newSignInMockServer returns a mock Password Safe server that handles the +// OAuth token exchange and SignAppIn. Tests that drive providerConfigure end +// to end need this because the new InitSharedAuth performs the handshake +// during Configure (the old providerConfigure only built the authObj). +func newSignInMockServer(t *testing.T) *httptest.Server { + t.Helper() + return httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case constants.APIPath + "/Auth/connect/token": + _, _ = w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer", "scope": "publicapi"}`)) + case constants.APIPath + "/Auth/SignAppIn": + _, _ = w.Write([]byte(`{"UserId":1, "UserName":"test", "EmailAddress":"test@beyondtrust.com"}`)) + } + })) +} + func TestProviderConfigureWithApiKey(t *testing.T) { + server := newSignInMockServer(t) + defer server.Close() + utils.ResetSharedAuthForTest() + resourceData := schema.TestResourceDataRaw(t, Provider().Schema, map[string]interface{}{ - "url": constants.FakeApiUrl, + "url": server.URL + constants.APIPath, "api_account_name": "test-account", "client_id": "", "client_secret": "", "api_key": "test-api-key", - "verify_ca": true, + "verify_ca": false, "client_certificate_name": "", "client_certificates_folder_path": "", "client_certificate_password": "", @@ -38,13 +61,17 @@ func TestProviderConfigureWithApiKey(t *testing.T) { } func TestProviderConfigureWithCredentials(t *testing.T) { + server := newSignInMockServer(t) + defer server.Close() + utils.ResetSharedAuthForTest() + resourceData := schema.TestResourceDataRaw(t, Provider().Schema, map[string]interface{}{ - "url": constants.FakeApiUrl, + "url": server.URL + constants.APIPath, "api_account_name": "test-account", "client_id": "00000000-0000-0000-0000-000000000001", "client_secret": "00000000-0000-0000-0000-000000000002", "api_key": "", - "verify_ca": true, + "verify_ca": false, "client_certificate_name": "", "client_certificates_folder_path": "", "client_certificate_password": "", diff --git a/providers/provider_sdkv2/safes.go b/providers/provider_sdkv2/safes.go index 9b71074b..5777b366 100644 --- a/providers/provider_sdkv2/safes.go +++ b/providers/provider_sdkv2/safes.go @@ -5,7 +5,6 @@ package provider import ( "fmt" - auth "github.com/BeyondTrust/go-client-library-passwordsafe/api/authentication" "github.com/BeyondTrust/go-client-library-passwordsafe/api/entities" "github.com/BeyondTrust/go-client-library-passwordsafe/api/secrets" @@ -38,14 +37,9 @@ func resourceSafe() *schema.Resource { // Create context for resourceSafe Resource. func resourceSafeCreate(d *schema.ResourceData, m interface{}) error { - authenticationObj := m.(*auth.AuthenticationObj) + meta := m.(*providerMeta) - _, err := authenticate(d, m) - if err != nil { - return err - } - - secretObj, _ := secrets.NewSecretObj(*authenticationObj, zapLogger, 5000000, false) + secretObj, _ := secrets.NewSecretObj(*meta.authObj, zapLogger, 5000000, false) name := d.Get("name").(string) description := d.Get("description").(string) @@ -56,12 +50,6 @@ func resourceSafeCreate(d *schema.ResourceData, m interface{}) error { } createdSafe, err := secretObj.CreateFolderFlow("", safe) - - if err != nil { - return err - } - - err = signOut(d, m) if err != nil { return err } @@ -86,14 +74,9 @@ func resourceSafeDelete(d *schema.ResourceData, m interface{}) error { return fmt.Errorf("authentication object is nil") } - authenticationObj := m.(*auth.AuthenticationObj) + meta := m.(*providerMeta) - _, err := authenticate(d, m) - if err != nil { - return err - } - - secretObj, err := secrets.NewSecretObj(*authenticationObj, zapLogger, 5000000, false) + secretObj, err := secrets.NewSecretObj(*meta.authObj, zapLogger, 5000000, false) if err != nil { return err } @@ -110,11 +93,6 @@ func resourceSafeDelete(d *schema.ResourceData, m interface{}) error { return err } - err = signOut(d, m) - if err != nil { - return err - } - d.SetId("") return nil } diff --git a/providers/provider_sdkv2/secrets.go b/providers/provider_sdkv2/secrets.go index 249e2b1e..fc8c4ce2 100644 --- a/providers/provider_sdkv2/secrets.go +++ b/providers/provider_sdkv2/secrets.go @@ -7,7 +7,6 @@ import ( "fmt" "maps" - auth "github.com/BeyondTrust/go-client-library-passwordsafe/api/authentication" "github.com/BeyondTrust/go-client-library-passwordsafe/api/entities" "github.com/BeyondTrust/go-client-library-passwordsafe/api/secrets" "github.com/google/uuid" @@ -136,15 +135,10 @@ func resourceFileSecret() *schema.Resource { // Create context for resourceCredentialSecret Resource. func resourceCredentialSecretCreate(d *schema.ResourceData, m interface{}) error { - authenticationObj := m.(*auth.AuthenticationObj) + meta := m.(*providerMeta) folderName := d.Get("folder_name").(string) - signAppinResponse, err := authenticate(d, m) - if err != nil { - return err - } - - secretObj, _ := secrets.NewSecretObj(*authenticationObj, zapLogger, 5000000, false) + secretObj, _ := secrets.NewSecretObj(*meta.authObj, zapLogger, 5000000, false) username := d.Get("username").(string) password := d.Get("password").(string) @@ -159,24 +153,18 @@ func resourceCredentialSecretCreate(d *schema.ResourceData, m interface{}) error SecretDetailsBaseConfig: entities.SecretDetailsBaseConfig{ Title: title, Description: description, - Urls: getUrlsDetailsList(d, ownerType, groupId, signAppinResponse), + Urls: getUrlsDetailsList(d, ownerType, groupId, meta.signAppin), Notes: notes, }, Username: username, Password: password, OwnerId: ownerId, OwnerType: ownerType, - OwnersByOwnerId: getOwnerDetailsOwnerIdList(d, ownerType, groupId, signAppinResponse), - OwnersByGroupId: getOwnerDetailsGroupIdList(d, ownerType, groupId, signAppinResponse), + OwnersByOwnerId: getOwnerDetailsOwnerIdList(d, ownerType, groupId, meta.signAppin), + OwnersByGroupId: getOwnerDetailsGroupIdList(d, ownerType, groupId, meta.signAppin), } createdSecret, err := secretObj.CreateSecretFlow(folderName, credentialSecretInput) - - if err != nil { - return err - } - - err = signOut(d, m) if err != nil { return err } @@ -187,15 +175,10 @@ func resourceCredentialSecretCreate(d *schema.ResourceData, m interface{}) error // Create context for resourceTextSecret Resource. func resourceTextSecretCreate(d *schema.ResourceData, m interface{}) error { - authenticationObj := m.(*auth.AuthenticationObj) + meta := m.(*providerMeta) folderName := d.Get("folder_name").(string) - signAppinResponse, err := authenticate(d, m) - if err != nil { - return err - } - - secretObj, _ := secrets.NewSecretObj(*authenticationObj, zapLogger, 5000000, false) + secretObj, _ := secrets.NewSecretObj(*meta.authObj, zapLogger, 5000000, false) text := d.Get("text").(string) title := d.Get("title").(string) @@ -209,23 +192,17 @@ func resourceTextSecretCreate(d *schema.ResourceData, m interface{}) error { SecretDetailsBaseConfig: entities.SecretDetailsBaseConfig{ Title: title, Description: description, - Urls: getUrlsDetailsList(d, ownerType, groupId, signAppinResponse), + Urls: getUrlsDetailsList(d, ownerType, groupId, meta.signAppin), Notes: notes, }, Text: text, OwnerId: ownerId, OwnerType: ownerType, - OwnersByOwnerId: getOwnerDetailsOwnerIdList(d, ownerType, groupId, signAppinResponse), - OwnersByGroupId: getOwnerDetailsGroupIdList(d, ownerType, groupId, signAppinResponse), + OwnersByOwnerId: getOwnerDetailsOwnerIdList(d, ownerType, groupId, meta.signAppin), + OwnersByGroupId: getOwnerDetailsGroupIdList(d, ownerType, groupId, meta.signAppin), } createdSecret, err := secretObj.CreateSecretFlow(folderName, textSecretInput) - - if err != nil { - return err - } - - err = signOut(d, m) if err != nil { return err } @@ -236,15 +213,10 @@ func resourceTextSecretCreate(d *schema.ResourceData, m interface{}) error { // Create context for resourceFileSecret Resource. func resourceFileSecretCreate(d *schema.ResourceData, m interface{}) error { - authenticationObj := m.(*auth.AuthenticationObj) + meta := m.(*providerMeta) folderName := d.Get("folder_name").(string) - signAppinResponse, err := authenticate(d, m) - if err != nil { - return err - } - - secretObj, _ := secrets.NewSecretObj(*authenticationObj, zapLogger, 5000000, false) + secretObj, _ := secrets.NewSecretObj(*meta.authObj, zapLogger, 5000000, false) fileName := d.Get("file_name").(string) fileContent := d.Get("file_content").(string) @@ -259,24 +231,18 @@ func resourceFileSecretCreate(d *schema.ResourceData, m interface{}) error { SecretDetailsBaseConfig: entities.SecretDetailsBaseConfig{ Title: title, Description: description, - Urls: getUrlsDetailsList(d, ownerType, groupId, signAppinResponse), + Urls: getUrlsDetailsList(d, ownerType, groupId, meta.signAppin), Notes: notes, }, FileName: fileName, FileContent: fileContent, OwnerId: ownerId, OwnerType: ownerType, - OwnersByOwnerId: getOwnerDetailsOwnerIdList(d, ownerType, groupId, signAppinResponse), - OwnersByGroupId: getOwnerDetailsGroupIdList(d, ownerType, groupId, signAppinResponse), + OwnersByOwnerId: getOwnerDetailsOwnerIdList(d, ownerType, groupId, meta.signAppin), + OwnersByGroupId: getOwnerDetailsGroupIdList(d, ownerType, groupId, meta.signAppin), } createdSecret, err := secretObj.CreateSecretFlow(folderName, fileSecretInput) - - if err != nil { - return err - } - - err = signOut(d, m) if err != nil { return err } @@ -301,14 +267,9 @@ func resourceSecretDelete(d *schema.ResourceData, m interface{}) error { return fmt.Errorf("authentication object is nil") } - authenticationObj := m.(*auth.AuthenticationObj) - - _, err := authenticate(d, m) - if err != nil { - return err - } + meta := m.(*providerMeta) - secretObj, err := secrets.NewSecretObj(*authenticationObj, zapLogger, 5000000, false) + secretObj, err := secrets.NewSecretObj(*meta.authObj, zapLogger, 5000000, false) if err != nil { return err } @@ -325,11 +286,6 @@ func resourceSecretDelete(d *schema.ResourceData, m interface{}) error { return err } - err = signOut(d, m) - if err != nil { - return err - } - d.SetId("") return nil } @@ -339,33 +295,20 @@ func getSecretByPathReadContext(ctx context.Context, d *schema.ResourceData, m i var diags diag.Diagnostics - authenticationObj := m.(*auth.AuthenticationObj) + meta := m.(*providerMeta) secretPath := d.Get("path").(string) secretTitle := d.Get("title").(string) separator := d.Get("separator").(string) decrypt := d.Get("decrypt").(bool) - _, err := authenticate(d, m) - if err != nil { - return diag.FromErr(err) - } - - secretObj, _ := secrets.NewSecretObj(*authenticationObj, zapLogger, 5000000, decrypt) + secretObj, _ := secrets.NewSecretObj(*meta.authObj, zapLogger, 5000000, decrypt) secret, err := secretObj.GetSecret(secretPath+separator+secretTitle, separator) - if err != nil { return diag.FromErr(err) } - err = d.Set("value", secret) - - if err != nil { - return diag.FromErr(err) - } - - err = signOut(d, m) - if err != nil { + if err := d.Set("value", secret); err != nil { return diag.FromErr(err) } diff --git a/providers/provider_sdkv2/secrets_test.go b/providers/provider_sdkv2/secrets_test.go index 143d2a3d..819d3e7d 100644 --- a/providers/provider_sdkv2/secrets_test.go +++ b/providers/provider_sdkv2/secrets_test.go @@ -139,7 +139,7 @@ func TestResourceFileSecretCreate(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceFileSecretCreate(data, authenticate) + err := resourceFileSecretCreate(data, &providerMeta{authObj: authenticate}) if err != nil { t.Errorf("Test case Failed: %v", err) @@ -248,7 +248,7 @@ func TestResourceFileSecretCreateError(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceFileSecretCreate(data, authenticate) + err := resourceFileSecretCreate(data, &providerMeta{authObj: authenticate}) if err.Error() != "folder not_found_folder_test was not found in folder list" { t.Errorf("Test case Failed %v, %v", err.Error(), "folder not_found_folder_test was not found in folder list") @@ -382,7 +382,7 @@ func TestResourceCredentialSecretCreate(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceCredentialSecretCreate(data, authenticate) + err := resourceCredentialSecretCreate(data, &providerMeta{authObj: authenticate}) if err != nil { t.Errorf("Test case Failed: %v", err) @@ -516,7 +516,7 @@ func TestResourceCredentialSecretCreateError(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceCredentialSecretCreate(data, authenticate) + err := resourceCredentialSecretCreate(data, &providerMeta{authObj: authenticate}) if err.Error() != "The field 'Username' is required." { t.Errorf("Test case Failed %v, %v", err.Error(), "The field 'Username' is required.") @@ -606,7 +606,7 @@ func TestGetSecretByPathReadContext(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := getSecretByPathReadContext(context.Background(), data, authenticate) + err := getSecretByPathReadContext(context.Background(), data, &providerMeta{authObj: authenticate}) if err != nil { t.Errorf("Test case Failed: %v", err) @@ -696,7 +696,7 @@ func TestGetSecretByPathReadContextError(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - diags := getSecretByPathReadContext(context.Background(), data, authenticate) + diags := getSecretByPathReadContext(context.Background(), data, &providerMeta{authObj: authenticate}) if diags[0].Summary != "error SecretGetSecretByPath, Secret was not found: StatusCode: 404 " { t.Errorf("Test case Failed %v, %v", diags[0].Summary, "error SecretGetSecretByPath, Secret was not found: StatusCode: 404 ") @@ -825,7 +825,7 @@ func TestResourceTextSecretCreate(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceTextSecretCreate(data, authenticate) + err := resourceTextSecretCreate(data, &providerMeta{authObj: authenticate}) if err != nil { t.Errorf("Test case Failed: %v", err) @@ -954,7 +954,7 @@ func TestResourceTextSecretCreateError(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceTextSecretCreate(data, authenticate) + err := resourceTextSecretCreate(data, &providerMeta{authObj: authenticate}) if err.Error() != "The field 'Text' is required." { t.Errorf("Test case Failed %v, %v", err.Error(), "The field 'Text' is required.") @@ -1054,7 +1054,7 @@ func TestResourceSecretDelete(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceSecretDelete(data, authenticate) + err := resourceSecretDelete(data, &providerMeta{authObj: authenticate}) if err != nil { t.Errorf("Test case Failed: %v", err) @@ -1142,7 +1142,7 @@ func TestResourceSecretDeleteEmptyID(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceSecretDelete(data, authenticate) + err := resourceSecretDelete(data, &providerMeta{authObj: authenticate}) if err == nil || err.Error() != "secret ID is empty" { t.Errorf("Expected 'secret ID is empty' error, but got: %v", err) @@ -1234,7 +1234,7 @@ func TestResourceSecretDeleteError(t *testing.T) { apiUrl, _ := url.Parse(testConfig.server.URL + "/") authenticate.ApiUrl = *apiUrl - err := resourceSecretDelete(data, authenticate) + err := resourceSecretDelete(data, &providerMeta{authObj: authenticate}) if err == nil { t.Errorf("Expected error when deleting non-existent secret, but got nil") diff --git a/providers/utils/methods.go b/providers/utils/methods.go index 48da7820..29e9f8d8 100644 --- a/providers/utils/methods.go +++ b/providers/utils/methods.go @@ -5,13 +5,7 @@ package utils import ( "errors" "fmt" - "sync" "terraform-provider-passwordsafe/providers/entities" - - "github.com/BeyondTrust/go-client-library-passwordsafe/api/assets" - auth "github.com/BeyondTrust/go-client-library-passwordsafe/api/authentication" - libraryEntitites "github.com/BeyondTrust/go-client-library-passwordsafe/api/entities" - "github.com/BeyondTrust/go-client-library-passwordsafe/api/logging" ) func TestResourceConfig(config entities.PasswordSafeTestConfig) string { @@ -44,77 +38,6 @@ func TestResourceConfig(config entities.PasswordSafeTestConfig) string { ) } -var signAppinResponse libraryEntitites.SignAppinResponse - -// SignInCount tracks the number of concurrent callers currently holding a -// reference to the shared Password Safe session. It is package-level state -// because signAppinResponse (the cached session) is also package-level: every -// caller across both providers must share the same counter, otherwise two -// providers could each think they own the session and race on signout. -var SignInCount uint64 - -// AuthMu serializes access to SignInCount and signAppinResponse so signin and -// signout can never run concurrently — the API's signout is user-global and -// would otherwise tear down a session another worker is racing to use. -var AuthMu sync.Mutex - -// Authenticate gets Password Safe authentication, sharing a session across -// concurrent callers via the reference count guarded by mu. -func Authenticate(authenticationObj auth.AuthenticationObj, mu *sync.Mutex, signInCount *uint64, zapLogger logging.Logger) (libraryEntitites.SignAppinResponse, error) { - mu.Lock() - defer mu.Unlock() - - if *signInCount > 0 { - *signInCount++ - zapLogger.Debug(fmt.Sprintf("%v %v", "Already signed in", *signInCount)) - return signAppinResponse, nil - } - - resp, err := authenticationObj.GetPasswordSafeAuthentication() - if err != nil { - zapLogger.Error(err.Error()) - return libraryEntitites.SignAppinResponse{}, err - } - signAppinResponse = resp - *signInCount++ - zapLogger.Debug(fmt.Sprintf("%v %v", "signin", *signInCount)) - return signAppinResponse, nil -} - -// SignOut releases this caller's reference to the shared session. The same -// mutex used by Authenticate must be passed in so signin and signout can never -// run concurrently — the API's signout is user-global and would otherwise tear -// down a session another worker is racing to use. -func SignOut(authenticationObj auth.AuthenticationObj, mu *sync.Mutex, signInCount *uint64, zapLogger logging.Logger) error { - mu.Lock() - defer mu.Unlock() - - // Guard against underflow: if no one is holding the session there is - // nothing to sign out, and decrementing a uint64 zero would wrap to - // math.MaxUint64 and pin the counter open forever. - if *signInCount == 0 { - zapLogger.Debug("Signout called with no active sessions, no-op") - return nil - } - - if *signInCount > 1 { - zapLogger.Debug(fmt.Sprintf("%v %v", "Ignore signout", *signInCount)) - *signInCount-- - return nil - } - - err := authenticationObj.SignOut() - // Decrement regardless of error: this caller is leaving, and on signout - // failure the cached session state is unreliable, so the next Authenticate - // should establish a fresh one. - *signInCount-- - if err != nil { - return err - } - zapLogger.Debug(fmt.Sprintf("%v %v", "signout user", *signInCount)) - return nil -} - // ValidateChangeFrequencyDays validate Change Frequency Days field func ValidateChangeFrequencyDays(changeFrequencyType string, changeFrequencyDays int) error { if changeFrequencyType == "xdays" { @@ -124,33 +47,3 @@ func ValidateChangeFrequencyDays(changeFrequencyType string, changeFrequencyDays } return nil } - -// DeleteAssetByID deletes an asset by its ID using the provided authentication object -func DeleteAssetByID(authenticationObj auth.AuthenticationObj, assetID int32, mu *sync.Mutex, signInCount *uint64, zapLogger logging.Logger) (err error) { - _, err = Authenticate(authenticationObj, mu, signInCount, zapLogger) - if err != nil { - return fmt.Errorf("error getting Authentication: %w", err) - } - // Release the shared session ref-count on every return path. Surface a - // signout error only when no other error has occurred, preserving the - // original behavior where signout failure was returned to the caller. - defer func() { - if signOutErr := SignOut(authenticationObj, mu, signInCount, zapLogger); signOutErr != nil && err == nil { - err = fmt.Errorf("error signing out: %w", signOutErr) - } - }() - - // instantiating asset obj - assetObj, err := assets.NewAssetObj(authenticationObj, zapLogger) - if err != nil { - return fmt.Errorf("error creating asset object: %w", err) - } - - // deleting the asset by ID - err = assetObj.DeleteAssetById(int(assetID)) - if err != nil { - return fmt.Errorf("error deleting asset: %w", err) - } - - return nil -} diff --git a/providers/utils/methods_test.go b/providers/utils/methods_test.go index 18b15d78..345af147 100644 --- a/providers/utils/methods_test.go +++ b/providers/utils/methods_test.go @@ -1,11 +1,12 @@ package utils import ( + "errors" "net/http" "net/http/httptest" "net/url" "strings" - "sync" + "sync/atomic" "terraform-provider-passwordsafe/providers/constants" "terraform-provider-passwordsafe/providers/entities" "testing" @@ -86,180 +87,169 @@ func InitializeGlobalConfig() { } } -func TestAuthenticate(t *testing.T) { +// newAuthObjAtServer builds an *AuthenticationObj wired to the given test +// server. The test server is expected to handle /Auth/connect/token and +// /Auth/SignAppIn (and optionally /Auth/Signout). +func newAuthObjAtServer(t *testing.T, server *httptest.Server) *authentication.AuthenticationObj { + t.Helper() + authObj, err := authentication.Authenticate(*authParams) + if err != nil { + t.Fatalf("Authenticate: %v", err) + } + server.URL = server.URL + constants.APIPath + apiUrl, _ := url.Parse(server.URL) + authObj.ApiUrl = *apiUrl + return authObj +} +func TestInitSharedAuth_Success(t *testing.T) { InitializeGlobalConfig() + ResetSharedAuthForTest() - // mocking Password Safe API server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - // mocking Response according to the endpoint path switch r.URL.Path { case constants.APIPath + "/Auth/connect/token": - _, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer", "scope": "publicapi"}`)) - if err != nil { - t.Error(err.Error()) - } - + _, _ = w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer", "scope": "publicapi"}`)) case constants.APIPath + "/Auth/SignAppIn": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) - - if err != nil { - t.Error(err.Error()) - } - + _, _ = w.Write([]byte(`{"UserId":1, "UserName":"jdoe", "EmailAddress":"test@beyondtrust.com"}`)) } - })) + defer server.Close() - var authenticateObj, _ = authentication.Authenticate(*authParams) - server.URL = server.URL + constants.APIPath - apiUrl, _ := url.Parse(server.URL) - - authenticateObj.ApiUrl = *apiUrl + authObj := newAuthObjAtServer(t, server) - var signInCount uint64 - var mu sync.Mutex + var buildCalls int32 + build := func() (*authentication.AuthenticationObj, error) { + atomic.AddInt32(&buildCalls, 1) + return authObj, nil + } - _, err := Authenticate(*authenticateObj, &mu, &signInCount, zapLogger) + authObj1, signAppin, err := InitSharedAuth("test-key-success", build) if err != nil { - t.Error(err) + t.Fatalf("first InitSharedAuth: %v", err) + } + if authObj1 == nil { + t.Fatal("expected non-nil authObj") + } + if signAppin.UserName != "jdoe" { + t.Errorf("expected UserName 'jdoe', got %q", signAppin.UserName) } - // Increment counter - _, err = Authenticate(*authenticateObj, &mu, &signInCount, zapLogger) + authObj2, _, err := InitSharedAuth("test-key-success", build) if err != nil { - t.Error(err) + t.Fatalf("second InitSharedAuth: %v", err) + } + if authObj1 != authObj2 { + t.Errorf("expected same authObj pointer; got %p vs %p", authObj1, authObj2) + } + if got := atomic.LoadInt32(&buildCalls); got != 1 { + t.Errorf("expected build closure to fire once, fired %d times", got) } } -func TestAuthenticateErrorGettingToken(t *testing.T) { - - InitializeGlobalConfig() - - // mocking Password Safe API - server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - // mocking Response according to the endpoint path - switch r.URL.Path { - case constants.APIPath + "/Auth/connect/token": - w.WriteHeader(http.StatusBadRequest) - _, err := w.Write([]byte(`{"error": "invalid_client"}`)) - if err != nil { - t.Error(err.Error()) - } - } - - })) - - var authenticateObj, _ = authentication.Authenticate(*authParams) - server.URL = server.URL + constants.APIPath - apiUrl, _ := url.Parse(server.URL) - - authenticateObj.ApiUrl = *apiUrl +func TestInitSharedAuth_BuildError(t *testing.T) { + ResetSharedAuthForTest() + wantErr := errors.New("boom") - var signInCount uint64 - var mu sync.Mutex - - expectedError := `error - status code: 400 - {"error": "invalid_client"}` - - _, err := Authenticate(*authenticateObj, &mu, &signInCount, zapLogger) - if err.Error() != expectedError { - t.Errorf("Test case Failed %v, %v", err.Error(), expectedError) + authObj, _, err := InitSharedAuth("test-key-build-error", func() (*authentication.AuthenticationObj, error) { + return nil, wantErr + }) + if !errors.Is(err, wantErr) { + t.Errorf("expected build error to propagate, got %v", err) + } + if authObj != nil { + t.Error("expected nil authObj on build failure") } + // After a failed init, the next call retries with the supplied closure + // so a transient failure does not pin the cache to an error state. + var retryCalls int32 + _, _, err = InitSharedAuth("test-key-build-error", func() (*authentication.AuthenticationObj, error) { + atomic.AddInt32(&retryCalls, 1) + return nil, wantErr + }) + if got := atomic.LoadInt32(&retryCalls); got != 1 { + t.Errorf("expected retry build closure to fire once, got %d", got) + } + if !errors.Is(err, wantErr) { + t.Errorf("expected build error to propagate again, got %v", err) + } } -func TestSignOut(t *testing.T) { - +func TestInitSharedAuth_SignInError(t *testing.T) { InitializeGlobalConfig() + ResetSharedAuthForTest() - // mocking Password Safe API + // Token returns 400 — SignAppin can never succeed. server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - // mocking Response according to the endpoint path - switch r.URL.Path { - - case constants.APIPath + "/Auth/Signout": - _, err := w.Write([]byte(``)) - if err != nil { - t.Error(err.Error()) - } + if r.URL.Path == constants.APIPath+"/Auth/connect/token" { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"error": "invalid_client"}`)) } - })) + defer server.Close() - var authenticateObj, _ = authentication.Authenticate(*authParams) - server.URL = server.URL + constants.APIPath - apiUrl, _ := url.Parse(server.URL) - - authenticateObj.ApiUrl = *apiUrl - - var signInCount uint64 - var mu sync.Mutex + authObj := newAuthObjAtServer(t, server) - // First call: signInCount = 1 means this is the last caller, so SignOut - // should hit the API and decrement the counter back to 0. - signInCount = 1 - err := SignOut(*authenticateObj, &mu, &signInCount, zapLogger) - if err != nil { - t.Error(err) + gotObj, _, err := InitSharedAuth("test-key-signin-error", func() (*authentication.AuthenticationObj, error) { + return authObj, nil + }) + if err == nil { + t.Fatal("expected sign-in error, got nil") } - if signInCount != 0 { - t.Errorf("expected signInCount to be 0 after final signout, got %d", signInCount) + if gotObj != nil { + t.Errorf("expected nil authObj on sign-in failure, got %p", gotObj) } - // Second call: signInCount = 2 exercises the "decrement without signing - // out" branch — another caller still holds the session, so the counter - // drops to 1 but the API is not called. - signInCount = 2 - err = SignOut(*authenticateObj, &mu, &signInCount, zapLogger) - if err != nil { - t.Error(err) - } - if signInCount != 1 { - t.Errorf("expected signInCount to be 1 after non-final signout, got %d", signInCount) + // Calling Shutdown after a failed init should be a no-op (no panic, no API hit). + if shutdownErr := ShutdownSharedAuth(); shutdownErr != nil { + t.Errorf("ShutdownSharedAuth after failed init: %v", shutdownErr) } } -func TestSignOutError(t *testing.T) { - +func TestShutdownSharedAuth_Idempotent(t *testing.T) { InitializeGlobalConfig() + ResetSharedAuthForTest() - // mocking Password Safe API + var signoutCalls int32 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - // mocking Response according to the endpoint path switch r.URL.Path { - + case constants.APIPath + "/Auth/connect/token": + _, _ = w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer", "scope": "publicapi"}`)) + case constants.APIPath + "/Auth/SignAppIn": + _, _ = w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) case constants.APIPath + "/Auth/Signout": - w.WriteHeader(http.StatusBadRequest) - _, err := w.Write([]byte(``)) - if err != nil { - t.Error(err.Error()) - } + atomic.AddInt32(&signoutCalls, 1) + _, _ = w.Write([]byte(``)) } - })) + defer server.Close() - var authenticateObj, _ = authentication.Authenticate(*authParams) - server.URL = server.URL + constants.APIPath - apiUrl, _ := url.Parse(server.URL) - - authenticateObj.ApiUrl = *apiUrl - - var signInCount uint64 - var mu sync.Mutex + authObj := newAuthObjAtServer(t, server) - signInCount = 1 + if _, _, err := InitSharedAuth("test-key-shutdown", func() (*authentication.AuthenticationObj, error) { + return authObj, nil + }); err != nil { + t.Fatalf("InitSharedAuth: %v", err) + } - expectedError := `error - status code: 400 - ` + if err := ShutdownSharedAuth(); err != nil { + t.Errorf("first ShutdownSharedAuth: %v", err) + } + if err := ShutdownSharedAuth(); err != nil { + t.Errorf("second ShutdownSharedAuth: %v", err) + } - err := SignOut(*authenticateObj, &mu, &signInCount, zapLogger) - if err.Error() != expectedError { - t.Errorf("Test case Failed %v, %v", err.Error(), expectedError) + if got := atomic.LoadInt32(&signoutCalls); got != 1 { + t.Errorf("expected /Auth/Signout to be hit once, got %d", got) } +} +func TestShutdownSharedAuth_NoInit(t *testing.T) { + ResetSharedAuthForTest() + if err := ShutdownSharedAuth(); err != nil { + t.Errorf("expected nil error when init never ran, got %v", err) + } } // Test ValidateChangeFrequencyDays function @@ -645,126 +635,3 @@ func TestGetBoolAttribute(t *testing.T) { }) } } - -// Test DeleteAssetByID function -func TestDeleteAssetByID(t *testing.T) { - InitializeGlobalConfig() - - // Test case 1: Successful deletion - t.Run("Successful deletion", func(t *testing.T) { - server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case constants.APIPath + "/Auth/connect/token": - _, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer", "scope": "publicapi"}`)) - if err != nil { - t.Error(err.Error()) - } - case constants.APIPath + "/Auth/SignAppIn": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) - if err != nil { - t.Error(err.Error()) - } - case "/BeyondTrust/api/public/v3/Assets/123": - if r.Method == "DELETE" { - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte(`{}`)) - if err != nil { - t.Error(err.Error()) - } - } - case constants.APIPath + "/Auth/Signout": - _, err := w.Write([]byte(``)) - if err != nil { - t.Error(err.Error()) - } - } - })) - defer server.Close() - - authenticateObj, _ := authentication.Authenticate(*authParams) - server.URL = server.URL + constants.APIPath - apiUrl, _ := url.Parse(server.URL) - authenticateObj.ApiUrl = *apiUrl - - var signInCount uint64 - var mu sync.Mutex - - err := DeleteAssetByID(*authenticateObj, 123, &mu, &signInCount, zapLogger) - if err != nil { - t.Errorf("Expected no error, but got: %s", err.Error()) - } - }) - - // Test case 2: Error when deleting asset - t.Run("Delete error", func(t *testing.T) { - server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case constants.APIPath + "/Auth/connect/token": - _, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer", "scope": "publicapi"}`)) - if err != nil { - t.Error(err.Error()) - } - case constants.APIPath + "/Auth/SignAppIn": - _, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"test@beyondtrust.com"}`)) - if err != nil { - t.Error(err.Error()) - } - case "/BeyondTrust/api/public/v3/Assets/123": - if r.Method == "DELETE" { - w.WriteHeader(http.StatusBadRequest) - _, err := w.Write([]byte(`{"error": "not found"}`)) - if err != nil { - t.Error(err.Error()) - } - } - case constants.APIPath + "/Auth/Signout": - _, err := w.Write([]byte(``)) - if err != nil { - t.Error(err.Error()) - } - } - })) - defer server.Close() - - authenticateObj, _ := authentication.Authenticate(*authParams) - server.URL = server.URL + constants.APIPath - apiUrl, _ := url.Parse(server.URL) - authenticateObj.ApiUrl = *apiUrl - - var signInCount uint64 - var mu sync.Mutex - - err := DeleteAssetByID(*authenticateObj, 123, &mu, &signInCount, zapLogger) - if err == nil { - t.Error("Expected error when deleting asset, but got none") - } - }) - - // Test case 3: Authentication error - t.Run("Authentication error", func(t *testing.T) { - server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case constants.APIPath + "/Auth/connect/token": - w.WriteHeader(http.StatusBadRequest) - _, err := w.Write([]byte(`{"error": "invalid_client"}`)) - if err != nil { - t.Error(err.Error()) - } - } - })) - defer server.Close() - - authenticateObj, _ := authentication.Authenticate(*authParams) - server.URL = server.URL + constants.APIPath - apiUrl, _ := url.Parse(server.URL) - authenticateObj.ApiUrl = *apiUrl - - var signInCount uint64 - var mu sync.Mutex - - err := DeleteAssetByID(*authenticateObj, 123, &mu, &signInCount, zapLogger) - if err == nil { - t.Error("Expected error due to authentication failure, but got none") - } - }) -} diff --git a/providers/utils/session.go b/providers/utils/session.go new file mode 100644 index 00000000..eca4a6a5 --- /dev/null +++ b/providers/utils/session.go @@ -0,0 +1,106 @@ +// Copyright 2025 BeyondTrust. All rights reserved. +// Package utils. +package utils + +import ( + "fmt" + "sync" + + auth "github.com/BeyondTrust/go-client-library-passwordsafe/api/authentication" + libentities "github.com/BeyondTrust/go-client-library-passwordsafe/api/entities" +) + +var ( + sharedAuthMu sync.Mutex + sharedAuth *auth.AuthenticationObj + sharedSignAppinRsp libentities.SignAppinResponse + sharedInitErr error + sharedCacheKey string +) + +// InitSharedAuth lazily builds the shared AuthenticationObj on first call, +// performs the SignAppin handshake, and caches both for subsequent callers +// that pass the same cacheKey. Both muxed providers (framework + sdkv2) call +// this from their Configure; they receive identical provider-block config so +// they produce the same key and the second caller hits the cache. +// +// When cacheKey differs from the cached one (e.g., acceptance tests rotate +// mock-server URLs between cases) we sign the old session out and re-init. +// The signout against the prior (often dead) server is best-effort and any +// error is dropped — the cookie jar would be stale anyway. +// +// On init failure the cached state is cleared, so the next call retries +// rather than returning a half-built object. +func InitSharedAuth(cacheKey string, build func() (*auth.AuthenticationObj, error)) (*auth.AuthenticationObj, libentities.SignAppinResponse, error) { + sharedAuthMu.Lock() + defer sharedAuthMu.Unlock() + + if sharedAuth != nil && sharedCacheKey == cacheKey { + return sharedAuth, sharedSignAppinRsp, nil + } + + if sharedAuth != nil { + _ = sharedAuth.SignOut() + } + sharedAuth = nil + sharedSignAppinRsp = libentities.SignAppinResponse{} + sharedInitErr = nil + sharedCacheKey = "" + + authObj, err := build() + if err != nil { + sharedInitErr = err + return nil, libentities.SignAppinResponse{}, err + } + signAppin, err := authObj.GetPasswordSafeAuthentication() + if err != nil { + sharedInitErr = err + return nil, libentities.SignAppinResponse{}, err + } + + sharedAuth = authObj + sharedSignAppinRsp = signAppin + sharedCacheKey = cacheKey + return sharedAuth, sharedSignAppinRsp, nil +} + +// ResetSharedAuthForTest clears the cached shared session without calling +// SignOut on the (often dead) test server. Acceptance tests rotate mock +// servers between cases; this lets them start each case from a clean state +// without paying for an HTTP round-trip against a closed listener. +// +// Test-only. Production code never calls this — production relies on the +// cacheKey path in InitSharedAuth to handle config changes. +func ResetSharedAuthForTest() { + sharedAuthMu.Lock() + defer sharedAuthMu.Unlock() + sharedAuth = nil + sharedSignAppinRsp = libentities.SignAppinResponse{} + sharedInitErr = nil + sharedCacheKey = "" +} + +// ShutdownSharedAuth signs the shared session out of Password Safe and clears +// the cache so a later InitSharedAuth would rebuild from scratch. Safe to +// call multiple times — a no-op when no session is held. +// +// Called from main() after tf5server.Serve returns. Terraform may SIGKILL +// the plugin before Serve returns cleanly; in that case the server-side +// idle TTL handles cleanup. +func ShutdownSharedAuth() error { + sharedAuthMu.Lock() + defer sharedAuthMu.Unlock() + + if sharedAuth == nil { + return nil + } + + err := sharedAuth.SignOut() + sharedAuth = nil + sharedSignAppinRsp = libentities.SignAppinResponse{} + sharedCacheKey = "" + if err != nil { + return fmt.Errorf("shared signout: %w", err) + } + return nil +} From 00e125639f0375f3d6a4200e52bbd5b2e3472361 Mon Sep 17 00:00:00 2001 From: Felipe Hernandez Date: Fri, 29 May 2026 08:54:33 -0500 Subject: [PATCH 2/2] fix: solve linting issue --- providers/utils/session.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/providers/utils/session.go b/providers/utils/session.go index eca4a6a5..699a94ef 100644 --- a/providers/utils/session.go +++ b/providers/utils/session.go @@ -14,7 +14,6 @@ var ( sharedAuthMu sync.Mutex sharedAuth *auth.AuthenticationObj sharedSignAppinRsp libentities.SignAppinResponse - sharedInitErr error sharedCacheKey string ) @@ -44,17 +43,14 @@ func InitSharedAuth(cacheKey string, build func() (*auth.AuthenticationObj, erro } sharedAuth = nil sharedSignAppinRsp = libentities.SignAppinResponse{} - sharedInitErr = nil sharedCacheKey = "" authObj, err := build() if err != nil { - sharedInitErr = err return nil, libentities.SignAppinResponse{}, err } signAppin, err := authObj.GetPasswordSafeAuthentication() if err != nil { - sharedInitErr = err return nil, libentities.SignAppinResponse{}, err } @@ -76,7 +72,6 @@ func ResetSharedAuthForTest() { defer sharedAuthMu.Unlock() sharedAuth = nil sharedSignAppinRsp = libentities.SignAppinResponse{} - sharedInitErr = nil sharedCacheKey = "" }