diff --git a/docs/resources/password.md b/docs/resources/password.md index 0cbaf4d4..b0b43837 100644 --- a/docs/resources/password.md +++ b/docs/resources/password.md @@ -42,6 +42,8 @@ resource "aws_db_instance" "example" { ### Optional +- `exclusions` (Set of String) Supply your own set of exclusions to check against the generated password. If the generated password contains any of the exclusions, it will be regenerated until it does not contain any excluded substrings. Exclusions are case-insensitive by default , but can be configured as case-sensitive via the `exclusions_case_sensitive` argument. +- `exclusions_case_sensitive` (Boolean) Determines if the exclusions list should be case-sensitive. Default value is `false`. - `keepers` (Map of String) Arbitrary map of values that, when changed, will trigger recreation of resource. See [the main provider documentation](../index.html) for more information. - `lower` (Boolean) Include lowercase alphabet characters in the result. Default value is `true`. - `min_lower` (Number) Minimum number of lowercase alphabet characters in the result. Default value is `0`. diff --git a/docs/resources/string.md b/docs/resources/string.md index 0a8780be..f09b6777 100644 --- a/docs/resources/string.md +++ b/docs/resources/string.md @@ -34,6 +34,8 @@ resource "random_string" "random" { ### Optional +- `exclusions` (Set of String) Supply your own set of exclusions to check against the generated string. If the generated string contains any of the exclusions, it will be regenerated until it does not contain any excluded substrings. Exclusions are case-insensitive by default , but can be configured as case-sensitive via the `exclusions_case_sensitive` argument. +- `exclusions_case_sensitive` (Boolean) Determines if the exclusions list should be case-sensitive. Default value is `false`. - `keepers` (Map of String) Arbitrary map of values that, when changed, will trigger recreation of resource. See [the main provider documentation](../index.html) for more information. - `lower` (Boolean) Include lowercase alphabet characters in the result. Default value is `true`. - `min_lower` (Number) Minimum number of lowercase alphabet characters in the result. Default value is `0`. diff --git a/internal/planmodifiers/bool/boolplanmodifiers.go b/internal/planmodifiers/bool/boolplanmodifiers.go index 86b99fe4..5a588302 100644 --- a/internal/planmodifiers/bool/boolplanmodifiers.go +++ b/internal/planmodifiers/bool/boolplanmodifiers.go @@ -5,8 +5,8 @@ package boolplanmodifiers import ( "context" - "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -69,3 +69,12 @@ func (d *numberNumericAttributePlanModifier) PlanModifyBool(ctx context.Context, return } } + +func RequiresReplaceIfResultMatchesExclusions() boolplanmodifier.RequiresReplaceIfFunc { + return func(ctx context.Context, req planmodifier.BoolRequest, resp *boolplanmodifier.RequiresReplaceIfFuncResponse) { + // TODO: Implement this plan modifier. + // if existing result matches the exclusion set, enforce recreation of the resource + // else recreation is not required + resp.RequiresReplace = true + } +} diff --git a/internal/planmodifiers/set/setplanmodifiers.go b/internal/planmodifiers/set/setplanmodifiers.go new file mode 100644 index 00000000..9af1324e --- /dev/null +++ b/internal/planmodifiers/set/setplanmodifiers.go @@ -0,0 +1,20 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setplanmodifiers + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" +) + +func RequiresReplaceIfResultMatchesExclusions() setplanmodifier.RequiresReplaceIfFunc { + return func(ctx context.Context, req planmodifier.SetRequest, resp *setplanmodifier.RequiresReplaceIfFuncResponse) { + // TODO: Implement this plan modifier. + // if existing result matches the exclusion set, enforce recreation of the resource + // else recreation is not required + resp.RequiresReplace = true + } +} diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 8c325d91..220fb436 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" @@ -24,6 +25,7 @@ import ( "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" boolplanmodifiers "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers/bool" mapplanmodifiers "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers/map" + setplanmodifiers "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers/set" stringplanmodifiers "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers/string" "github.com/terraform-providers/terraform-provider-random/internal/random" "github.com/terraform-providers/terraform-provider-random/internal/validators" @@ -58,17 +60,24 @@ func (r *passwordResource) Create(ctx context.Context, req resource.CreateReques return } + exclusions := make([]string, len(plan.Exclusions.Elements())) + for i, exclusion := range plan.Exclusions.Elements() { + exclusions[i] = exclusion.(types.String).ValueString() + } + params := random.StringParams{ - Length: plan.Length.ValueInt64(), - Upper: plan.Upper.ValueBool(), - MinUpper: plan.MinUpper.ValueInt64(), - Lower: plan.Lower.ValueBool(), - MinLower: plan.MinLower.ValueInt64(), - Numeric: plan.Numeric.ValueBool(), - MinNumeric: plan.MinNumeric.ValueInt64(), - Special: plan.Special.ValueBool(), - MinSpecial: plan.MinSpecial.ValueInt64(), - OverrideSpecial: plan.OverrideSpecial.ValueString(), + Length: plan.Length.ValueInt64(), + Upper: plan.Upper.ValueBool(), + MinUpper: plan.MinUpper.ValueInt64(), + Lower: plan.Lower.ValueBool(), + MinLower: plan.MinLower.ValueInt64(), + Numeric: plan.Numeric.ValueBool(), + MinNumeric: plan.MinNumeric.ValueInt64(), + Special: plan.Special.ValueBool(), + MinSpecial: plan.MinSpecial.ValueInt64(), + OverrideSpecial: plan.OverrideSpecial.ValueString(), + Exclusions: exclusions, + ExclusionsCaseSensitive: plan.ExclusionsCaseSensitive.ValueBool(), } result, err := random.CreateString(params) @@ -115,20 +124,22 @@ func (r *passwordResource) ImportState(ctx context.Context, req resource.ImportS id := req.ID state := passwordModelV3{ - ID: types.StringValue("none"), - Result: types.StringValue(id), - Length: types.Int64Value(int64(len(id))), - Special: types.BoolValue(true), - Upper: types.BoolValue(true), - Lower: types.BoolValue(true), - Number: types.BoolValue(true), - Numeric: types.BoolValue(true), - MinSpecial: types.Int64Value(0), - MinUpper: types.Int64Value(0), - MinLower: types.Int64Value(0), - MinNumeric: types.Int64Value(0), - Keepers: types.MapNull(types.StringType), - OverrideSpecial: types.StringNull(), + ID: types.StringValue("none"), + Result: types.StringValue(id), + Length: types.Int64Value(int64(len(id))), + Special: types.BoolValue(true), + Upper: types.BoolValue(true), + Lower: types.BoolValue(true), + Number: types.BoolValue(true), + Numeric: types.BoolValue(true), + MinSpecial: types.Int64Value(0), + MinUpper: types.Int64Value(0), + MinLower: types.Int64Value(0), + MinNumeric: types.Int64Value(0), + Keepers: types.MapNull(types.StringType), + OverrideSpecial: types.StringNull(), + Exclusions: types.SetNull(types.StringType), + ExclusionsCaseSensitive: types.BoolValue(false), } hash, err := generateHash(id) @@ -248,21 +259,26 @@ func upgradePasswordStateV0toV3(ctx context.Context, req resource.UpgradeStateRe number = types.BoolValue(true) } + exclusions := types.SetNull(types.StringType) + exclusionsCaseSensitive := types.BoolValue(false) + passwordDataV3 := passwordModelV3{ - Keepers: passwordDataV0.Keepers, - Length: length, - Special: special, - Upper: upper, - Lower: lower, - Number: number, - Numeric: number, - MinNumeric: minNumeric, - MinUpper: minUpper, - MinLower: minLower, - MinSpecial: minSpecial, - OverrideSpecial: passwordDataV0.OverrideSpecial, - Result: passwordDataV0.Result, - ID: passwordDataV0.ID, + Keepers: passwordDataV0.Keepers, + Length: length, + Special: special, + Upper: upper, + Lower: lower, + Number: number, + Numeric: number, + MinNumeric: minNumeric, + MinUpper: minUpper, + MinLower: minLower, + MinSpecial: minSpecial, + OverrideSpecial: passwordDataV0.OverrideSpecial, + Exclusions: exclusions, + ExclusionsCaseSensitive: exclusionsCaseSensitive, + Result: passwordDataV0.Result, + ID: passwordDataV0.ID, } hash, err := generateHash(passwordDataV3.Result.ValueString()) @@ -360,22 +376,27 @@ func upgradePasswordStateV1toV3(ctx context.Context, req resource.UpgradeStateRe number = types.BoolValue(true) } + exclusions := types.SetNull(types.StringType) + exclusionsCaseSensitive := types.BoolValue(false) + passwordDataV3 := passwordModelV3{ - Keepers: passwordDataV1.Keepers, - Length: length, - Special: special, - Upper: upper, - Lower: lower, - Number: number, - Numeric: number, - MinNumeric: minNumeric, - MinUpper: minUpper, - MinLower: minLower, - MinSpecial: minSpecial, - OverrideSpecial: passwordDataV1.OverrideSpecial, - BcryptHash: passwordDataV1.BcryptHash, - Result: passwordDataV1.Result, - ID: passwordDataV1.ID, + Keepers: passwordDataV1.Keepers, + Length: length, + Special: special, + Upper: upper, + Lower: lower, + Number: number, + Numeric: number, + MinNumeric: minNumeric, + MinUpper: minUpper, + MinLower: minLower, + MinSpecial: minSpecial, + OverrideSpecial: passwordDataV1.OverrideSpecial, + Exclusions: exclusions, + ExclusionsCaseSensitive: exclusionsCaseSensitive, + BcryptHash: passwordDataV1.BcryptHash, + Result: passwordDataV1.Result, + ID: passwordDataV1.ID, } diags := resp.State.Set(ctx, passwordDataV3) @@ -473,25 +494,30 @@ func upgradePasswordStateV2toV3(ctx context.Context, req resource.UpgradeStateRe numeric = types.BoolValue(true) } + exclusions := types.SetNull(types.StringType) + exclusionsCaseSensitive := types.BoolValue(false) + // Schema version 2 to schema version 3 is a duplicate of the data, // however the BcryptHash value may have been incorrectly generated. //nolint:gosimple // V3 model will expand over time so all fields are written out to help future code changes. passwordDataV3 := passwordModelV3{ - BcryptHash: passwordDataV2.BcryptHash, - ID: passwordDataV2.ID, - Keepers: passwordDataV2.Keepers, - Length: length, - Lower: lower, - MinLower: minLower, - MinNumeric: minNumeric, - MinSpecial: minSpecial, - MinUpper: minUpper, - Number: number, - Numeric: numeric, - OverrideSpecial: passwordDataV2.OverrideSpecial, - Result: passwordDataV2.Result, - Special: special, - Upper: upper, + BcryptHash: passwordDataV2.BcryptHash, + ID: passwordDataV2.ID, + Keepers: passwordDataV2.Keepers, + Length: length, + Lower: lower, + MinLower: minLower, + MinNumeric: minNumeric, + MinSpecial: minSpecial, + MinUpper: minUpper, + Number: number, + Numeric: numeric, + OverrideSpecial: passwordDataV2.OverrideSpecial, + Exclusions: exclusions, + ExclusionsCaseSensitive: exclusionsCaseSensitive, + Result: passwordDataV2.Result, + Special: special, + Upper: upper, } // Set the duplicated data now so we can easily return early below. @@ -719,6 +745,36 @@ func passwordSchemaV3() schema.Schema { }, }, + "exclusions": schema.SetAttribute{ + Description: "Supply your own set of exclusions to check against the generated password. " + + "If the generated password contains any of the exclusions, it will be regenerated until " + + "it does not contain any excluded substrings. Exclusions are case-insensitive by default " + + ", but can be configured as case-sensitive via the `exclusions_case_sensitive` argument.", + Optional: true, + ElementType: types.StringType, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplaceIf( + setplanmodifiers.RequiresReplaceIfResultMatchesExclusions(), + "Replace on modification if result matches exclusion list.", + "Replace on modification if result matches exclusion list.", + ), + }, + }, + + "exclusions_case_sensitive": schema.BoolAttribute{ + Description: "Determines if the exclusions list should be case-sensitive. Default value is `false`.", + Optional: true, + Default: booldefault.StaticBool(false), + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplaceIf( + boolplanmodifiers.RequiresReplaceIfResultMatchesExclusions(), + "Replace on modification if result matches exclusion list.", + "Replace on modification if result matches exclusion list.", + ), + }, + }, + "result": schema.StringAttribute{ Description: "The generated random string.", Computed: true, @@ -1050,19 +1106,21 @@ func passwordSchemaV0() schema.Schema { } type passwordModelV3 struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Length types.Int64 `tfsdk:"length"` - Special types.Bool `tfsdk:"special"` - Upper types.Bool `tfsdk:"upper"` - Lower types.Bool `tfsdk:"lower"` - Number types.Bool `tfsdk:"number"` - Numeric types.Bool `tfsdk:"numeric"` - MinNumeric types.Int64 `tfsdk:"min_numeric"` - MinUpper types.Int64 `tfsdk:"min_upper"` - MinLower types.Int64 `tfsdk:"min_lower"` - MinSpecial types.Int64 `tfsdk:"min_special"` - OverrideSpecial types.String `tfsdk:"override_special"` - Result types.String `tfsdk:"result"` - BcryptHash types.String `tfsdk:"bcrypt_hash"` + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Number types.Bool `tfsdk:"number"` + Numeric types.Bool `tfsdk:"numeric"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Exclusions types.Set `tf:"exclusions"` + ExclusionsCaseSensitive types.Bool `tf:"exclusions_case_sensitive"` + Result types.String `tfsdk:"result"` + BcryptHash types.String `tfsdk:"bcrypt_hash"` } diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index 275e9b29..745137b8 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" @@ -22,6 +23,7 @@ import ( "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" boolplanmodifiers "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers/bool" mapplanmodifiers "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers/map" + setplanmodifiers "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers/set" stringplanmodifiers "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers/string" "github.com/terraform-providers/terraform-provider-random/internal/random" "github.com/terraform-providers/terraform-provider-random/internal/validators" @@ -56,17 +58,24 @@ func (r *stringResource) Create(ctx context.Context, req resource.CreateRequest, return } + exclusions := make([]string, len(plan.Exclusions.Elements())) + for i, exclusion := range plan.Exclusions.Elements() { + exclusions[i] = exclusion.(types.String).ValueString() + } + params := random.StringParams{ - Length: plan.Length.ValueInt64(), - Upper: plan.Upper.ValueBool(), - MinUpper: plan.MinUpper.ValueInt64(), - Lower: plan.Lower.ValueBool(), - MinLower: plan.MinLower.ValueInt64(), - Numeric: plan.Numeric.ValueBool(), - MinNumeric: plan.MinNumeric.ValueInt64(), - Special: plan.Special.ValueBool(), - MinSpecial: plan.MinSpecial.ValueInt64(), - OverrideSpecial: plan.OverrideSpecial.ValueString(), + Length: plan.Length.ValueInt64(), + Upper: plan.Upper.ValueBool(), + MinUpper: plan.MinUpper.ValueInt64(), + Lower: plan.Lower.ValueBool(), + MinLower: plan.MinLower.ValueInt64(), + Numeric: plan.Numeric.ValueBool(), + MinNumeric: plan.MinNumeric.ValueInt64(), + Special: plan.Special.ValueBool(), + MinSpecial: plan.MinSpecial.ValueInt64(), + OverrideSpecial: plan.OverrideSpecial.ValueString(), + Exclusions: exclusions, + ExclusionsCaseSensitive: plan.ExclusionsCaseSensitive.ValueBool(), } result, err := random.CreateString(params) @@ -107,20 +116,22 @@ func (r *stringResource) ImportState(ctx context.Context, req resource.ImportSta id := req.ID state := stringModelV3{ - ID: types.StringValue(id), - Result: types.StringValue(id), - Length: types.Int64Value(int64(len(id))), - Special: types.BoolValue(true), - Upper: types.BoolValue(true), - Lower: types.BoolValue(true), - Number: types.BoolValue(true), - Numeric: types.BoolValue(true), - MinSpecial: types.Int64Value(0), - MinUpper: types.Int64Value(0), - MinLower: types.Int64Value(0), - MinNumeric: types.Int64Value(0), - OverrideSpecial: types.StringNull(), - Keepers: types.MapNull(types.StringType), + ID: types.StringValue(id), + Result: types.StringValue(id), + Length: types.Int64Value(int64(len(id))), + Special: types.BoolValue(true), + Upper: types.BoolValue(true), + Lower: types.BoolValue(true), + Number: types.BoolValue(true), + Numeric: types.BoolValue(true), + MinSpecial: types.Int64Value(0), + MinUpper: types.Int64Value(0), + MinLower: types.Int64Value(0), + MinNumeric: types.Int64Value(0), + OverrideSpecial: types.StringNull(), + Exclusions: types.SetNull(types.StringType), + ExclusionsCaseSensitive: types.BoolValue(false), + Keepers: types.MapNull(types.StringType), } diags := resp.State.Set(ctx, &state) @@ -228,21 +239,26 @@ func upgradeStringStateV1toV3(ctx context.Context, req resource.UpgradeStateRequ number = types.BoolValue(true) } + exclusions := types.SetNull(types.StringType) + exclusionsCaseSensitive := types.BoolValue(false) + stringDataV3 := stringModelV3{ - Keepers: stringDataV1.Keepers, - Length: length, - Special: special, - Upper: upper, - Lower: lower, - Number: number, - Numeric: number, - MinNumeric: minNumeric, - MinUpper: minUpper, - MinLower: minLower, - MinSpecial: minSpecial, - OverrideSpecial: stringDataV1.OverrideSpecial, - Result: stringDataV1.Result, - ID: stringDataV1.ID, + Keepers: stringDataV1.Keepers, + Length: length, + Special: special, + Upper: upper, + Lower: lower, + Number: number, + Numeric: number, + MinNumeric: minNumeric, + MinUpper: minUpper, + MinLower: minLower, + MinSpecial: minSpecial, + OverrideSpecial: stringDataV1.OverrideSpecial, + Exclusions: exclusions, + ExclusionsCaseSensitive: exclusionsCaseSensitive, + Result: stringDataV1.Result, + ID: stringDataV1.ID, } diags := resp.State.Set(ctx, stringDataV3) @@ -332,21 +348,26 @@ func upgradeStringStateV2toV3(ctx context.Context, req resource.UpgradeStateRequ number = types.BoolValue(true) } + exclusions := types.SetNull(types.StringType) + exclusionsCaseSensitive := types.BoolValue(false) + stringDataV3 := stringModelV3{ - Keepers: stringDataV2.Keepers, - Length: length, - Special: special, - Upper: upper, - Lower: lower, - Number: number, - Numeric: number, - MinNumeric: minNumeric, - MinUpper: minUpper, - MinLower: minLower, - MinSpecial: minSpecial, - OverrideSpecial: stringDataV2.OverrideSpecial, - Result: stringDataV2.Result, - ID: stringDataV2.ID, + Keepers: stringDataV2.Keepers, + Length: length, + Special: special, + Upper: upper, + Lower: lower, + Number: number, + Numeric: number, + MinNumeric: minNumeric, + MinUpper: minUpper, + MinLower: minLower, + MinSpecial: minSpecial, + OverrideSpecial: stringDataV2.OverrideSpecial, + Exclusions: exclusions, + ExclusionsCaseSensitive: exclusionsCaseSensitive, + Result: stringDataV2.Result, + ID: stringDataV2.ID, } diags := resp.State.Set(ctx, stringDataV3) @@ -517,6 +538,36 @@ func stringSchemaV3() schema.Schema { }, }, + "exclusions": schema.SetAttribute{ + Description: "Supply your own set of exclusions to check against the generated string. " + + "If the generated string contains any of the exclusions, it will be regenerated until " + + "it does not contain any excluded substrings. Exclusions are case-insensitive by default " + + ", but can be configured as case-sensitive via the `exclusions_case_sensitive` argument.", + Optional: true, + ElementType: types.StringType, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplaceIf( + setplanmodifiers.RequiresReplaceIfResultMatchesExclusions(), + "Replace on modification if result matches exclusion list.", + "Replace on modification if result matches exclusion list.", + ), + }, + }, + + "exclusions_case_sensitive": schema.BoolAttribute{ + Description: "Determines if the exclusions list should be case-sensitive. Default value is `false`.", + Optional: true, + Default: booldefault.StaticBool(false), + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplaceIf( + boolplanmodifiers.RequiresReplaceIfResultMatchesExclusions(), + "Replace on modification if result matches exclusion list.", + "Replace on modification if result matches exclusion list.", + ), + }, + }, + "result": schema.StringAttribute{ Description: "The generated random string.", Computed: true, @@ -732,18 +783,20 @@ func stringSchemaV1() schema.Schema { } type stringModelV3 struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Length types.Int64 `tfsdk:"length"` - Special types.Bool `tfsdk:"special"` - Upper types.Bool `tfsdk:"upper"` - Lower types.Bool `tfsdk:"lower"` - Number types.Bool `tfsdk:"number"` - Numeric types.Bool `tfsdk:"numeric"` - MinNumeric types.Int64 `tfsdk:"min_numeric"` - MinUpper types.Int64 `tfsdk:"min_upper"` - MinLower types.Int64 `tfsdk:"min_lower"` - MinSpecial types.Int64 `tfsdk:"min_special"` - OverrideSpecial types.String `tfsdk:"override_special"` - Result types.String `tfsdk:"result"` + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Length types.Int64 `tfsdk:"length"` + Special types.Bool `tfsdk:"special"` + Upper types.Bool `tfsdk:"upper"` + Lower types.Bool `tfsdk:"lower"` + Number types.Bool `tfsdk:"number"` + Numeric types.Bool `tfsdk:"numeric"` + MinNumeric types.Int64 `tfsdk:"min_numeric"` + MinUpper types.Int64 `tfsdk:"min_upper"` + MinLower types.Int64 `tfsdk:"min_lower"` + MinSpecial types.Int64 `tfsdk:"min_special"` + OverrideSpecial types.String `tfsdk:"override_special"` + Exclusions types.Set `tfsdk:"exclusions"` + ExclusionsCaseSensitive types.Bool `tfsdk:"exclusions_case_sensitive"` + Result types.String `tfsdk:"result"` } diff --git a/internal/random/string.go b/internal/random/string.go index 491c981b..9435218f 100644 --- a/internal/random/string.go +++ b/internal/random/string.go @@ -8,19 +8,22 @@ import ( "errors" "math/big" "sort" + "strings" ) type StringParams struct { - Length int64 - Upper bool - MinUpper int64 - Lower bool - MinLower int64 - Numeric bool - MinNumeric int64 - Special bool - MinSpecial int64 - OverrideSpecial string + Length int64 + Upper bool + MinUpper int64 + Lower bool + MinLower int64 + Numeric bool + MinNumeric int64 + Special bool + MinSpecial int64 + OverrideSpecial string + Exclusions []string + ExclusionsCaseSensitive bool } func CreateString(input StringParams) ([]byte, error) { @@ -85,9 +88,27 @@ func CreateString(input StringParams) ([]byte, error) { return order[i] < order[j] }) + if containsExclusions(result, input.Exclusions, input.ExclusionsCaseSensitive) { + return CreateString(input) + } + return result, nil } +func containsExclusions(result []byte, exclusions []string, caseSensitive bool) bool { + resultStr := string(result) + for _, exclusion := range exclusions { + if !caseSensitive { + exclusion = strings.ToLower(exclusion) + resultStr = strings.ToLower(resultStr) + } + if strings.Contains(resultStr, exclusion) { + return true + } + } + return false +} + func generateRandomBytes(charSet *string, length int64) ([]byte, error) { if charSet == nil { return nil, errors.New("charSet is nil")