From 42f92e7bad69da51bc2cf0a422212b9f9b97a445 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 19 Jul 2024 11:44:52 -0700 Subject: [PATCH 1/4] Fix user password validation So it works with interpolated value as well Also fix validation logic to work with non-contiguous patterns. For example, for 2 min upper case instead of matching 'AA' now also match 'AbA'. --- docs/resources/managed_user.md | 9 +- docs/resources/user.md | 7 +- .../resource_artifactory_managed_user_test.go | 110 ++++++++ .../user/resource_artifactory_user_test.go | 113 +++++++- pkg/artifactory/resource/user/user.go | 243 ++++++++++-------- 5 files changed, 360 insertions(+), 122 deletions(-) diff --git a/docs/resources/managed_user.md b/docs/resources/managed_user.md index a291da495..ba201e7e2 100644 --- a/docs/resources/managed_user.md +++ b/docs/resources/managed_user.md @@ -7,10 +7,11 @@ subcategory: "User" Provides an Artifactory managed user resource. This can be used to create and maintain Artifactory users. For example, service account where password is known and managed externally. -Unlike `artifactory_unmanaged_user` and `artifactory_user`, the `password` attribute is required and cannot be empty. -Consider using a separate provider to generate and manage passwords. +Unlike `artifactory_unmanaged_user` and `artifactory_user`, the `password` attribute is required and cannot be empty. Consider using a separate provider to generate and manage passwords. -~> The password is stored in the Terraform state file. Make sure you secure it, please refer to the official [Terraform documentation](https://developer.hashicorp.com/terraform/language/state/sensitive-data). +~>The password is stored in the Terraform state file. Make sure you secure it, please refer to the official [Terraform documentation](https://developer.hashicorp.com/terraform/language/state/sensitive-data). + +->Due to Terraform limitation with interpolated value, we can only validate interpolated value prior to making API requests. This means `terraform validate` or `terraform plan` will not return error if `password` does not meet `password_policy` criteria. ## Example Usage @@ -62,7 +63,7 @@ Optional: - `digit` (Number) Minimum number of digits that the password must contain - `length` (Number) Minimum length of the password - `lowercase` (Number) Minimum number of lowercase letters that the password must contain -- `special_char` (Number) Minimum number of special char that the password must contain. Special chars list: `!"#$%&'()*+,-./:;<=>?@[\]^_``{|}~` +- `special_char` (Number) Minimum number of special char that the password must contain. Special chars list: ``!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~`` - `uppercase` (Number) Minimum number of uppercase letters that the password must contain ## Import diff --git a/docs/resources/user.md b/docs/resources/user.md index c46e852e9..daf6fa4aa 100644 --- a/docs/resources/user.md +++ b/docs/resources/user.md @@ -7,9 +7,12 @@ subcategory: "User" Provides an Artifactory user resource. This can be used to create and manage Artifactory users. The password is a required field by the [Artifactory API](https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-CreateorReplaceUser), but we made it optional in this resource to accommodate the scenario where the password is not needed and will be reset by the actual user later. + When the optional attribute `password` is omitted, a random password is generated according to current Artifactory password policy. -~> The generated password won't be stored in the TF state and can not be recovered. The user must reset the password to be able to log in. An admin can always generate the access key for the user as well. The password change won't trigger state drift. We don't recommend to use this resource unless there is a specific use case for it. Recommended resource is `artifactory_managed_user`. +~>The generated password won't be stored in the TF state and can not be recovered. The user must reset the password to be able to log in. An admin can always generate the access key for the user as well. The password change won't trigger state drift. We don't recommend to use this resource unless there is a specific use case for it. Recommended resource is `artifactory_managed_user`. + +->Due to Terraform limitation with interpolated value, we can only validate interpolated value prior to making API requests. This means `terraform validate` or `terraform plan` will not return error if `password` does not meet `password_policy` criteria. ## Example Usage @@ -65,7 +68,7 @@ Optional: - `digit` (Number) Minimum number of digits that the password must contain - `length` (Number) Minimum length of the password - `lowercase` (Number) Minimum number of lowercase letters that the password must contain -- `special_char` (Number) Minimum number of special char that the password must contain. Special chars list: `!"#$%&'()*+,-./:;<=>?@[\]^_``{|}~` +- `special_char` (Number) Minimum number of special char that the password must contain. Special chars list: ``!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~`` - `uppercase` (Number) Minimum number of uppercase letters that the password must contain ## Import diff --git a/pkg/artifactory/resource/user/resource_artifactory_managed_user_test.go b/pkg/artifactory/resource/user/resource_artifactory_managed_user_test.go index 72e9e4a7e..26e5eb772 100644 --- a/pkg/artifactory/resource/user/resource_artifactory_managed_user_test.go +++ b/pkg/artifactory/resource/user/resource_artifactory_managed_user_test.go @@ -282,6 +282,116 @@ func testAccManagedUserPasswordPolicy(password, errorRegex string) func(t *testi } } +func TestAccManagedUser_password_policy_interpolated(t *testing.T) { + testCase := []struct { + name string + passwordCriteria string + errorRegex string + }{ + { + "Uppercase", + `length = 10 + min_lower = 2 + upper = false + min_numeric = 2 + min_special = 2 + override_special = "!"#$%%&'()*+,-./:;<=>?@[\]^_\x60{|}~"`, + `.*Attribute password string must have at least 2 uppercase letters.*`, + }, + { + "Lowercase", + `length = 10 + lower = false + min_upper = 2 + min_numeric = 2 + min_special = 2 + override_special = "!"#$%%&'()*+,-./:;<=>?@[\]^_\x60{|}~"`, + `.*Attribute password string must have at least 2 lowercase letters.*`, + }, + { + "Special Char", + `length = 10 + min_lower = 2 + min_upper = 2 + min_numeric = 2 + special = false + override_special = "!"#$%%&'()*+,-./:;<=>?@[\]^_\x60{|}~"`, + `.*Attribute password string must have at least 2 special characters.*`, + }, + { + "Digit", + `length = 10 + min_lower = 2 + min_upper = 2 + numeric = false + min_special = 2 + override_special = "!"#$%%&'()*+,-./:;<=>?@[\]^_\x60{|}~"`, + `.*Attribute password string must have at least 2 digits.*`, + }, + { + "Length", + `length = 9 + min_lower = 2 + min_upper = 2 + min_numeric = 2 + min_special = 2 + override_special = "!"#$%%&'()*+,-./:;<=>?@[\]^_\x60{|}~"`, + `.*Attribute password string length must be at least.*`, + }, + } + + for _, tc := range testCase { + t.Run(tc.name, testAccManagedUserPasswordPolicyInterpolated(tc.passwordCriteria, tc.errorRegex)) + } +} + +func testAccManagedUserPasswordPolicyInterpolated(passwordCriteria, errorRegex string) func(t *testing.T) { + return func(t *testing.T) { + id, _, name := testutil.MkNames("test-", "artifactory_user") + + temp := ` + resource "random_password" "test" { + {{ .passwordCriteria }} + } + + resource "artifactory_managed_user" "{{ .resourceName }}" { + name = "{{ .name }}" + password = random_password.test.result + password_policy = { + uppercase = 2 + lowercase = 2 + special_char = 2 + digit = 2 + length = 10 + } + email = "{{ .email }}" + }` + + config := util.ExecuteTemplate("TestAccUser_password_policy", temp, map[string]string{ + "resourceName": name, + "name": fmt.Sprintf("test-%d", id), + "email": fmt.Sprintf("test-%d@test.com", id), + "passwordCriteria": passwordCriteria, + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + Source: "hashicorp/random", + }, + }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(errorRegex), + }, + }, + }) + } +} + func TestAccManagedUser_basic(t *testing.T) { id, fqrn, name := testutil.MkNames("test-user-", "artifactory_managed_user") _, _, groupName := testutil.MkNames("test-group-", "artifactory_group") diff --git a/pkg/artifactory/resource/user/resource_artifactory_user_test.go b/pkg/artifactory/resource/user/resource_artifactory_user_test.go index 380a2fd93..834bddd5d 100644 --- a/pkg/artifactory/resource/user/resource_artifactory_user_test.go +++ b/pkg/artifactory/resource/user/resource_artifactory_user_test.go @@ -561,7 +561,7 @@ func TestAccUser_password_policy(t *testing.T) { func testAccUserPasswordPolicy(password, errorRegex string) func(t *testing.T) { return func(t *testing.T) { - id, fqrn, name := testutil.MkNames("test-", "artifactory_user") + id, _, name := testutil.MkNames("test-", "artifactory_user") temp := ` resource "artifactory_user" "{{ .resourceName }}" { @@ -588,7 +588,116 @@ func testAccUserPasswordPolicy(password, errorRegex string) func(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, - CheckDestroy: testAccCheckUserDestroy(fqrn), + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(errorRegex), + }, + }, + }) + } +} + +func TestAccUser_password_policy_interpolated(t *testing.T) { + testCase := []struct { + name string + passwordCriteria string + errorRegex string + }{ + { + "Uppercase", + `length = 10 + min_lower = 2 + upper = false + min_numeric = 2 + min_special = 2 + override_special = "!"#$%%&'()*+,-./:;<=>?@[\]^_\x60{|}~"`, + `.*Attribute password string must have at least 2 uppercase letters.*`, + }, + { + "Lowercase", + `length = 10 + lower = false + min_upper = 2 + min_numeric = 2 + min_special = 2 + override_special = "!"#$%%&'()*+,-./:;<=>?@[\]^_\x60{|}~"`, + `.*Attribute password string must have at least 2 lowercase letters.*`, + }, + { + "Special Char", + `length = 10 + min_lower = 2 + min_upper = 2 + min_numeric = 2 + special = false + override_special = "!"#$%%&'()*+,-./:;<=>?@[\]^_\x60{|}~"`, + `.*Attribute password string must have at least 2 special characters.*`, + }, + { + "Digit", + `length = 10 + min_lower = 2 + min_upper = 2 + numeric = false + min_special = 2 + override_special = "!"#$%%&'()*+,-./:;<=>?@[\]^_\x60{|}~"`, + `.*Attribute password string must have at least 2 digits.*`, + }, + { + "Length", + `length = 9 + min_lower = 2 + min_upper = 2 + min_numeric = 2 + min_special = 2 + override_special = "!"#$%%&'()*+,-./:;<=>?@[\]^_\x60{|}~"`, + `.*Attribute password string length must be at least.*`, + }, + } + + for _, tc := range testCase { + t.Run(tc.name, testAccUserPasswordPolicyInterpolated(tc.passwordCriteria, tc.errorRegex)) + } +} + +func testAccUserPasswordPolicyInterpolated(passwordCriteria, errorRegex string) func(t *testing.T) { + return func(t *testing.T) { + id, _, name := testutil.MkNames("test-", "artifactory_user") + + temp := ` + resource "random_password" "test" { + {{ .passwordCriteria }} + } + + resource "artifactory_user" "{{ .resourceName }}" { + name = "{{ .name }}" + password = random_password.test.result + password_policy = { + uppercase = 2 + lowercase = 2 + special_char = 2 + digit = 2 + length = 10 + } + email = "{{ .email }}" + }` + + config := util.ExecuteTemplate("TestAccUser_password_policy", temp, map[string]string{ + "resourceName": name, + "name": fmt.Sprintf("test-%d", id), + "email": fmt.Sprintf("test-%d@test.com", id), + "passwordCriteria": passwordCriteria, + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + Source: "hashicorp/random", + }, + }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: config, diff --git a/pkg/artifactory/resource/user/user.go b/pkg/artifactory/resource/user/user.go index 039c4ebfc..365806c6a 100644 --- a/pkg/artifactory/resource/user/user.go +++ b/pkg/artifactory/resource/user/user.go @@ -39,7 +39,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/jfrog/terraform-provider-artifactory/v11/pkg/artifactory" "github.com/jfrog/terraform-provider-shared/util" utilfw "github.com/jfrog/terraform-provider-shared/util/fw" @@ -152,7 +151,7 @@ var baseUserSchemaFramework = lo.Assign( }, "special_char": schema.Int64Attribute{ Optional: true, - MarkdownDescription: "Minimum number of special char that the password must contain. Special chars list: `!\"#$%&'()*+,-./:;<=>?@[\\]^_``{|}~`", + MarkdownDescription: "Minimum number of special char that the password must contain. Special chars list: ``!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~``", }, "digit": schema.Int64Attribute{ Optional: true, @@ -163,8 +162,10 @@ var baseUserSchemaFramework = lo.Assign( Description: "Minimum length of the password", }, }, - Optional: true, - MarkdownDescription: "Password policy to match JFrog Access to provide pre-apply validation. Default values: `uppercase=1`, `lowercase=1`, `special_char=0`, `digit=1`, `length=8`. Also see [Supported Access Configurations](https://jfrog.com/help/r/jfrog-installation-setup-documentation/supported-access-configurations) for more details", + Optional: true, + MarkdownDescription: "Password policy to match JFrog Access to provide validation before API request.\n\n" + + "->Due to Terraform limitation with interpolated value, we can only validate interpolated value prior to making API requests. This means `terraform validate` or `terraform plan` will not return error if `password` does not meet `password_policy` criteria.\n\n" + + "Default values: `uppercase=1`, `lowercase=1`, `special_char=0`, `digit=1`, `length=8`. Also see [Supported Access Configurations](https://jfrog.com/help/r/jfrog-installation-setup-documentation/supported-access-configurations) for more details", }, }, ) @@ -293,110 +294,8 @@ func (r ArtifactoryBaseUserResource) ValidateConfig(ctx context.Context, req res return } - // If password is not configured then no need to validate - if data.Password.IsNull() { - return - } - - // Default password policy should match Access default configuration: - // https://jfrog.com/help/r/jfrog-installation-setup-documentation/supported-access-configurations - minLength := int64(8) - lowercaseLength := int64(1) - uppercaseLength := int64(1) - specialCharLength := int64(0) - digitLength := int64(1) - - // If password_policy is configured, overwrite default values - if !data.PasswordPolicy.IsNull() { - attrs := data.PasswordPolicy.Attributes() - - if v, ok := attrs["length"]; ok { - minLength = v.(types.Int64).ValueInt64() - } - - if v, ok := attrs["lowercase"]; ok { - lowercaseLength = v.(types.Int64).ValueInt64() - } - - if v, ok := attrs["uppercase"]; ok { - uppercaseLength = v.(types.Int64).ValueInt64() - } - - if v, ok := attrs["special_char"]; ok { - specialCharLength = v.(types.Int64).ValueInt64() - } - - if v, ok := attrs["digit"]; ok { - digitLength = v.(types.Int64).ValueInt64() - } - } - - password := data.Password.ValueString() - - if len(password) < int(minLength) { - resp.Diagnostics.AddAttributeError( - path.Root("password"), - "Invalid Attribute Value Length", - fmt.Sprintf( - "Attribute password string length must be at least %d, got %d", - minLength, - len(password), - ), - ) - return - } - - lowercaseRegex := regexp.MustCompile(fmt.Sprintf("[a-z]{%d,}", lowercaseLength)) - if !lowercaseRegex.MatchString(password) { - resp.Diagnostics.AddAttributeError( - path.Root("password"), - "Invalid Attribute Value Match", - fmt.Sprintf( - "Attribute password string must have at least %d lowercase letters", - lowercaseLength, - ), - ) - return - } - - uppercaseRegex := regexp.MustCompile(fmt.Sprintf("[A-Z]{%d,}", uppercaseLength)) - - if !uppercaseRegex.MatchString(password) { - resp.Diagnostics.AddAttributeError( - path.Root("password"), - "Invalid Attribute Value Match", - fmt.Sprintf( - "Attribute password string must have at least %d uppercase letters", - uppercaseLength, - ), - ) - return - } - - specialCharRegex := regexp.MustCompile(fmt.Sprintf(`[!"#$%%&'()\*\+,\-\./:;<=>?@\[\\\]^_\x60{|}~]{%d,}`, specialCharLength)) - - if !specialCharRegex.MatchString(password) { - resp.Diagnostics.AddAttributeError( - path.Root("password"), - "Invalid Attribute Value Match", - fmt.Sprintf( - "Attribute password string must have at least %d special characters", - specialCharLength, - ), - ) - return - } - - digitRegex := regexp.MustCompile(fmt.Sprintf(`\d{%d,}`, digitLength)) - if !digitRegex.MatchString(password) { - resp.Diagnostics.AddAttributeError( - path.Root("password"), - "Invalid Attribute Value Match", - fmt.Sprintf( - "Attribute password string must have at least %d digits", - digitLength, - ), - ) + resp.Diagnostics.Append(r.validatePasswordByPolicy(data)) + if resp.Diagnostics.HasError() { return } } @@ -416,12 +315,6 @@ func (r *ArtifactoryBaseUserResource) syncReadersGroup(ctx context.Context, clie actualGroups = *actual.Groups } toAdd, toRemove := lo.Difference(planGroups, actualGroups) - tflog.Debug(ctx, "syncReadersGroup", map[string]any{ - "plan.Groups": plan.Groups, - "actual.Groups": actual.Groups, - "toAdd": toAdd, - "toRemove": toRemove, - }) if len(toAdd) == 0 && len(toRemove) == 0 { return nil @@ -605,6 +498,114 @@ func (r *ArtifactoryBaseUserResource) updateUser(req *resty.Request, artifactory return res, err } +func (r *ArtifactoryBaseUserResource) validatePasswordByPolicy(plan ArtifactoryUserResourceModel) diag.Diagnostic { + // If password is not configured then no need to validate + if plan.Password.IsNull() || plan.Password.IsUnknown() { + return nil + } + + // Default password policy should match Access default configuration: + // https://jfrog.com/help/r/jfrog-installation-setup-documentation/supported-access-configurations + minLength := int64(8) + lowercaseLength := int64(1) + uppercaseLength := int64(1) + specialCharLength := int64(0) + digitLength := int64(1) + + // If password_policy is configured, overwrite default values + if !plan.PasswordPolicy.IsNull() { + attrs := plan.PasswordPolicy.Attributes() + + if v, ok := attrs["length"]; ok { + minLength = v.(types.Int64).ValueInt64() + } + + if v, ok := attrs["lowercase"]; ok { + lowercaseLength = v.(types.Int64).ValueInt64() + } + + if v, ok := attrs["uppercase"]; ok { + uppercaseLength = v.(types.Int64).ValueInt64() + } + + if v, ok := attrs["special_char"]; ok { + specialCharLength = v.(types.Int64).ValueInt64() + } + + if v, ok := attrs["digit"]; ok { + digitLength = v.(types.Int64).ValueInt64() + } + } + + password := plan.Password.ValueString() + + if len(password) < int(minLength) { + return diag.NewAttributeErrorDiagnostic( + path.Root("password"), + "Invalid Attribute Value Length", + fmt.Sprintf( + "Attribute password string length must be at least %d, got %d", + minLength, + len(password), + ), + ) + } + + lowercaseRegex := regexp.MustCompile("[a-z]") + matched := lowercaseRegex.FindAllString(password, -1) + if len(matched) < int(lowercaseLength) { + return diag.NewAttributeErrorDiagnostic( + path.Root("password"), + "Invalid Attribute Value Match", + fmt.Sprintf( + "Attribute password string must have at least %d lowercase letters", + lowercaseLength, + ), + ) + } + + uppercaseRegex := regexp.MustCompile("[A-Z]") + matched = uppercaseRegex.FindAllString(password, -1) + if len(matched) < int(uppercaseLength) { + return diag.NewAttributeErrorDiagnostic( + path.Root("password"), + "Invalid Attribute Value Match", + fmt.Sprintf( + "Attribute password string must have at least %d uppercase letters", + uppercaseLength, + ), + ) + } + + specialCharRegex := regexp.MustCompile(`[!"#$%%&'()\*\+,\-\./:;<=>?@\[\\\]^_\x60{|}~]`) + matched = specialCharRegex.FindAllString(password, -1) + if len(matched) < int(specialCharLength) { + return diag.NewAttributeErrorDiagnostic( + path.Root("password"), + "Invalid Attribute Value Match", + fmt.Sprintf( + "Attribute password string must have at least %d special characters", + specialCharLength, + ), + ) + } + + digitRegex := regexp.MustCompile(`\d`) + matched = digitRegex.FindAllString(password, -1) + if len(matched) < int(digitLength) { + return diag.NewAttributeErrorDiagnostic( + path.Root("password"), + "Invalid Attribute Value Match", + fmt.Sprintf( + "Attribute password string must have at least %d digits", + digitLength, + ), + ) + } + + return nil +} + func (r *ArtifactoryBaseUserResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { go util.SendUsageResourceCreate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) @@ -615,6 +616,13 @@ func (r *ArtifactoryBaseUserResource) Create(ctx context.Context, req resource.C return } + if !plan.InternalPasswordDisabled.ValueBool() { + resp.Diagnostics.Append(r.validatePasswordByPolicy(plan)) + if resp.Diagnostics.HasError() { + return + } + } + // Convert from Terraform data model into API data model user := ArtifactoryUserResourceAPIModel{ Name: plan.Name.ValueString(), @@ -768,6 +776,13 @@ func (r *ArtifactoryBaseUserResource) Update(ctx context.Context, req resource.U return } + if !plan.InternalPasswordDisabled.ValueBool() { + resp.Diagnostics.Append(r.validatePasswordByPolicy(plan)) + if resp.Diagnostics.HasError() { + return + } + } + var groups *[]string if !plan.Groups.IsNull() && len(plan.Groups.Elements()) > 0 { g := utilfw.StringSetToStrings(plan.Groups) From 5708c9c63fe18de3ba9b3d01881e47a5d61e6dd8 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 19 Jul 2024 12:06:52 -0700 Subject: [PATCH 2/4] Improved password validation testings --- .../user/resource_artifactory_managed_user_test.go | 10 +++++----- .../resource/user/resource_artifactory_user_test.go | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/artifactory/resource/user/resource_artifactory_managed_user_test.go b/pkg/artifactory/resource/user/resource_artifactory_managed_user_test.go index 26e5eb772..54cf9d2ff 100644 --- a/pkg/artifactory/resource/user/resource_artifactory_managed_user_test.go +++ b/pkg/artifactory/resource/user/resource_artifactory_managed_user_test.go @@ -230,11 +230,11 @@ func TestAccManagedUser_password_policy(t *testing.T) { password string errorRegex string }{ - {"Uppercase", "Abcde1234--", `.*Attribute password string must have at least 2 uppercase letters.*`}, - {"Lowercase", "ABCDe1234--", `.*Attribute password string must have at least 2 lowercase letters.*`}, - {"Special Char", "ABCDefgh12-", `.*Attribute password string must have at least 2 special characters.*`}, - {"Digit", "ABCDEfghi1--", `.*Attribute password string must have at least 2 digits.*`}, - {"Length", "ABcd123--", `.*Attribute password string length must be at least.*`}, + {"Uppercase", "-A1b2c3d4e-", `.*Attribute password string must have at least 2 uppercase letters.*`}, + {"Lowercase", "-A1B2C3D4e-", `.*Attribute password string must have at least 2 lowercase letters.*`}, + {"Special Char", "A1B2CDefgh-", `.*Attribute password string must have at least 2 special characters.*`}, + {"Digit", "-AfBgChDiE1-", `.*Attribute password string must have at least 2 digits.*`}, + {"Length", "-A1B2c3d-", `.*Attribute password string length must be at least.*`}, } for _, tc := range testCase { diff --git a/pkg/artifactory/resource/user/resource_artifactory_user_test.go b/pkg/artifactory/resource/user/resource_artifactory_user_test.go index 834bddd5d..8fa42e4b6 100644 --- a/pkg/artifactory/resource/user/resource_artifactory_user_test.go +++ b/pkg/artifactory/resource/user/resource_artifactory_user_test.go @@ -547,11 +547,11 @@ func TestAccUser_password_policy(t *testing.T) { password string errorRegex string }{ - {"Uppercase", "Abcde1234--", `.*Attribute password string must have at least 2 uppercase letters.*`}, - {"Lowercase", "ABCDe1234--", `.*Attribute password string must have at least 2 lowercase letters.*`}, - {"Special Char", "ABCDefgh12-", `.*Attribute password string must have at least 2 special characters.*`}, - {"Digit", "ABCDEfghi1--", `.*Attribute password string must have at least 2 digits.*`}, - {"Length", "ABcd123--", `.*Attribute password string length must be at least.*`}, + {"Uppercase", "-A1b2c3d4e-", `.*Attribute password string must have at least 2 uppercase letters.*`}, + {"Lowercase", "-A1B2C3D4e-", `.*Attribute password string must have at least 2 lowercase letters.*`}, + {"Special Char", "A1B2CDefgh-", `.*Attribute password string must have at least 2 special characters.*`}, + {"Digit", "-AfBgChDiE1-", `.*Attribute password string must have at least 2 digits.*`}, + {"Length", "-A1B2c3d-", `.*Attribute password string length must be at least.*`}, } for _, tc := range testCase { From ef15559edc3b47467b8d12e028bdb0454ff61343 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 19 Jul 2024 13:10:15 -0700 Subject: [PATCH 3/4] Update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a68939da..3c2285646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 11.2.1 (July 22, 2024) + +BUG FIXES: + +* resource/artifactory_user, resource/artifactory_managed_user, resource/artifactory_unmanaged_user: Fix `password` validation for interpolated value. Also improve validation logic. Issue: [#1031](https://github.com/jfrog/terraform-provider-artifactory/issues/1031) PR: [#1032](https://github.com/jfrog/terraform-provider-artifactory/pull/1032) + ## 11.2.0 (July 16, 2024). Tested on Artifactory 7.84.17 with Terraform 1.9.2 and OpenTofu 1.7.3 IMPROVEMENTS: From b88c8848f66fa9893e49744db431660d1b2361bf Mon Sep 17 00:00:00 2001 From: JFrog CI Date: Fri, 19 Jul 2024 20:46:28 +0000 Subject: [PATCH 4/4] JFrog Pipelines - Add Artifactory version to CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c2285646..d023d32fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 11.2.1 (July 22, 2024) +## 11.2.1 (July 22, 2024). Tested on Artifactory 7.84.17 with Terraform 1.9.2 and OpenTofu 1.7.3 BUG FIXES: