From b7927ea991da337264c9636daf4cdc4c4502da34 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Wed, 1 Sep 2021 11:44:10 +0100 Subject: [PATCH 1/2] Bugfix: password validation at apply-time because it's unknown at plan time when interpolated --- internal/services/users/user_resource.go | 15 +++-- internal/services/users/user_resource_test.go | 64 +++++++++++++++++++ 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/internal/services/users/user_resource.go b/internal/services/users/user_resource.go index e7ffe0dc2e..d2b339f3d7 100644 --- a/internal/services/users/user_resource.go +++ b/internal/services/users/user_resource.go @@ -341,10 +341,6 @@ func userResource() *schema.Resource { } func userResourceCustomizeDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error { - if diff.Id() == "" && diff.Get("password").(string) == "" { - return fmt.Errorf("`password` is required when creating a new user") - } - ageGroup := diff.Get("age_group").(string) consentRequired := diff.Get("consent_provided_for_minor").(string) @@ -358,6 +354,11 @@ func userResourceCustomizeDiff(ctx context.Context, diff *schema.ResourceDiff, m func userResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*clients.Client).Users.UsersClient + password := d.Get("password").(string) + if password == "" { + return tf.ErrorDiagPathF(errors.New("`password` is required when creating a new user"), "password", "Could not create user") + } + upn := d.Get("user_principal_name").(string) mailNickName := d.Get("mail_nickname").(string) @@ -395,7 +396,7 @@ func userResourceCreate(ctx context.Context, d *schema.ResourceData, meta interf PasswordProfile: &msgraph.UserPasswordProfile{ ForceChangePasswordNextSignIn: utils.Bool(d.Get("force_password_change").(bool)), - Password: utils.String(d.Get("password").(string)), + Password: utils.String(password), }, } @@ -453,10 +454,10 @@ func userResourceUpdate(ctx context.Context, d *schema.ResourceData, meta interf UsageLocation: utils.NullableString(d.Get("usage_location").(string)), } - if d.HasChange("password") { + if password := d.Get("password").(string); d.HasChange("password") && password != "" { properties.PasswordProfile = &msgraph.UserPasswordProfile{ ForceChangePasswordNextSignIn: utils.Bool(d.Get("force_password_change").(bool)), - Password: utils.String(d.Get("password").(string)), + Password: utils.String(password), } } diff --git a/internal/services/users/user_resource_test.go b/internal/services/users/user_resource_test.go index 39b2d1c965..76fe378f1e 100644 --- a/internal/services/users/user_resource_test.go +++ b/internal/services/users/user_resource_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "regexp" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -98,6 +99,33 @@ func TestAccUser_threeUsersABC(t *testing.T) { }) } +func TestAccUser_withRandomProvider(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_user", "test") + r := UserResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.withRandomProvider(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("force_password_change", "password"), + }) +} + +func TestAccUser_passwordOmitted(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_user", "test") + r := UserResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.passwordOmitted(data), + ExpectError: regexp.MustCompile("`password` is required when creating a new user"), + }, + }) +} + func (r UserResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { client := clients.Users.UsersClient client.BaseClient.DisableRetries = true @@ -202,3 +230,39 @@ resource "azuread_user" "testC" { } `, data.RandomInteger, data.RandomPassword) } + +func (UserResource) withRandomProvider(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azuread" {} +provider "random" {} + +data "azuread_domains" "test" { + only_initial = true +} + +resource "random_password" "test" { + length = 32 +} + +resource "azuread_user" "test" { + user_principal_name = "acctestUser.%[1]d@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "acctestUser-%[1]d" + password = random_password.test.result +} +`, data.RandomInteger) +} + +func (UserResource) passwordOmitted(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azuread" {} + +data "azuread_domains" "test" { + only_initial = true +} + +resource "azuread_user" "test" { + user_principal_name = "acctestUser.%[1]d@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "acctestUser-%[1]d" +} +`, data.RandomInteger) +} From ff29b559999462eef970447ef422d62f9e34134e Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Wed, 1 Sep 2021 16:45:33 +0100 Subject: [PATCH 2/2] Documentation note on changing passwords and importing users --- docs/resources/user.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/resources/user.md b/docs/resources/user.md index 5a0334e917..4c54bfc248 100644 --- a/docs/resources/user.md +++ b/docs/resources/user.md @@ -50,6 +50,9 @@ The following arguments are supported: * `onpremises_immutable_id` - (Optional) The value used to associate an on-premise Active Directory user account with their Azure AD user object. This must be specified if you are using a federated domain for the user's `user_principal_name` property when creating a new user account. * `other_mails` - (Optional) A list of additional email addresses for the user. * `password` - (Optional) The password for the user. The password must satisfy minimum requirements as specified by the password policy. The maximum length is 256 characters. This property is required when creating a new user. + +-> **Passwords and importing users** Passwords can be changed but not cleared. Removing the `password` property for an existing user resource, or setting the password value to a blank string, will not remove the password. When importing a user, Terraform will not reset the password unless the value is subsequently changed in your configuration. + * `postal_code` - (Optional) The postal code for the user's postal address. The postal code is specific to the user's country/region. In the United States of America, this attribute contains the ZIP code. * `preferred_language` - (Optional) The user's preferred language, in ISO 639-1 notation. * `show_in_address_list` - (Optional) Whether or not the Outlook global address list should include this user. Defaults to `true`.