Skip to content

Commit

Permalink
azuread_user support for immutable_id property (#207)
Browse files Browse the repository at this point in the history
Implements #206
  • Loading branch information
derek-burdick authored Mar 10, 2020
1 parent 5d7b986 commit 9f78aa1
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 55 deletions.
6 changes: 6 additions & 0 deletions azuread/data_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ func dataUser() *schema.Resource {
Computed: true,
},

"immutable_id": {
Type: schema.TypeString,
Computed: true,
},

"mail": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -112,6 +117,7 @@ func dataSourceUserRead(d *schema.ResourceData, meta interface{}) error {
d.Set("user_principal_name", user.UserPrincipalName)
d.Set("account_enabled", user.AccountEnabled)
d.Set("display_name", user.DisplayName)
d.Set("immutable_id", user.ImmutableID)
d.Set("mail", user.Mail)
d.Set("mail_nickname", user.MailNickname)
d.Set("usage_location", user.UsageLocation)
Expand Down
15 changes: 15 additions & 0 deletions azuread/helpers/p/p.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,25 @@ func Bool(input bool) *bool {
return &input
}

func BoolI(i interface{}) *bool {
b := i.(bool)
return &b
}

func Int32(input int32) *int32 {
return &input
}

func Int32I(i interface{}) *int32 {
i32 := i.(int32)
return &i32
}

func String(input string) *string {
return &input
}

func StringI(i interface{}) *string {
s := i.(string)
return &s
}
112 changes: 58 additions & 54 deletions azuread/resource_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ func resourceUser() *schema.Resource {
Computed: true,
},

"immutable_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "This must be specified if you are using a federated domain for the user's userPrincipalName (UPN) property when creating a new user account. " +
"It is used to associate an on-premises Active Directory user account with their Azure AD user object.",
},

"object_id": {
Type: schema.TypeString,
Computed: true,
Expand All @@ -102,30 +110,30 @@ func resourceUserCreate(d *schema.ResourceData, meta interface{}) error {
ctx := meta.(*ArmClient).StopContext

upn := d.Get("user_principal_name").(string)
displayName := d.Get("display_name").(string)
mailNickName := d.Get("mail_nickname").(string)
accountEnabled := d.Get("account_enabled").(bool)
password := d.Get("password").(string)
forcePasswordChange := d.Get("force_password_change").(bool)

//default mail nickname to the first part of the UPN (matches the portal)
if mailNickName == "" {
mailNickName = strings.Split(upn, "@")[0]
}

userCreateParameters := graphrbac.UserCreateParameters{
AccountEnabled: &accountEnabled,
DisplayName: &displayName,
AccountEnabled: p.BoolI(d.Get("account_enabled")),
DisplayName: p.StringI(d.Get("display_name")),
MailNickname: &mailNickName,
PasswordProfile: &graphrbac.PasswordProfile{
ForceChangePasswordNextLogin: &forcePasswordChange,
Password: &password,
ForceChangePasswordNextLogin: p.BoolI(d.Get("force_password_change")),
Password: p.StringI(d.Get("password")),
},
UserPrincipalName: &upn,
}

if v, ok := d.GetOk("usage_location"); ok {
userCreateParameters.UsageLocation = p.String(v.(string))
userCreateParameters.UsageLocation = p.StringI(v)
}

if v, ok := d.GetOk("immutable_id"); ok {
userCreateParameters.ImmutableID = p.StringI(v)
}

user, err := client.Create(ctx, userCreateParameters)
Expand All @@ -147,6 +155,46 @@ func resourceUserCreate(d *schema.ResourceData, meta interface{}) error {
return resourceUserRead(d, meta)
}

func resourceUserUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).usersClient
ctx := meta.(*ArmClient).StopContext

var userUpdateParameters graphrbac.UserUpdateParameters

if d.HasChange("display_name") {
userUpdateParameters.DisplayName = p.StringI(d.Get("display_name"))
}

if d.HasChange("mail_nickname") {
userUpdateParameters.MailNickname = p.StringI(d.Get("mail_nickname"))
}

if d.HasChange("account_enabled") {
userUpdateParameters.AccountEnabled = p.BoolI(d.Get("account_enabled"))
}

if d.HasChange("password") {
userUpdateParameters.PasswordProfile = &graphrbac.PasswordProfile{
ForceChangePasswordNextLogin: p.BoolI(d.Get("force_password_change")),
Password: p.StringI(d.Get("password")),
}
}

if d.HasChange("usage_location") {
userUpdateParameters.UsageLocation = p.StringI(d.Get("usage_location"))
}

if d.HasChange("immutable_id") {
userUpdateParameters.ImmutableID = p.StringI(d.Get("immutable_id"))
}

if _, err := client.Update(ctx, d.Id(), userUpdateParameters); err != nil {
return fmt.Errorf("Error updating User with ID %q: %+v", d.Id(), err)
}

return resourceUserRead(d, meta)
}

func resourceUserRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).usersClient
ctx := meta.(*ArmClient).StopContext
Expand All @@ -170,58 +218,14 @@ func resourceUserRead(d *schema.ResourceData, meta interface{}) error {
d.Set("account_enabled", user.AccountEnabled)
d.Set("object_id", user.ObjectID)
d.Set("usage_location", user.UsageLocation)
d.Set("immutable_id", user.ImmutableID)

d.Set("onpremises_sam_account_name", user.AdditionalProperties["onPremisesSamAccountName"])
d.Set("onpremises_user_principal_name", user.AdditionalProperties["onPremisesUserPrincipalName"])

return nil
}

func resourceUserUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).usersClient
ctx := meta.(*ArmClient).StopContext

var userUpdateParameters graphrbac.UserUpdateParameters

if d.HasChange("display_name") {
displayName := d.Get("display_name").(string)
userUpdateParameters.DisplayName = p.String(displayName)
}

if d.HasChange("mail_nickname") {
mailNickName := d.Get("mail_nickname").(string)
userUpdateParameters.MailNickname = p.String(mailNickName)
}

if d.HasChange("account_enabled") {
accountEnabled := d.Get("account_enabled").(bool)
userUpdateParameters.AccountEnabled = p.Bool(accountEnabled)
}

if d.HasChange("password") {
password := d.Get("password").(string)
forcePasswordChange := d.Get("force_password_change").(bool)

passwordProfile := &graphrbac.PasswordProfile{
ForceChangePasswordNextLogin: &forcePasswordChange,
Password: &password,
}

userUpdateParameters.PasswordProfile = passwordProfile
}

if d.HasChange("usage_location") {
usageLocation := d.Get("usage_location").(string)
userUpdateParameters.UsageLocation = p.String(usageLocation)
}

if _, err := client.Update(ctx, d.Id(), userUpdateParameters); err != nil {
return fmt.Errorf("Error updating User with ID %q: %+v", d.Id(), err)
}

return resourceUserRead(d, meta)
}

func resourceUserDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).usersClient
ctx := meta.(*ArmClient).StopContext
Expand Down
4 changes: 4 additions & 0 deletions azuread/resource_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package azuread

import (
"fmt"
"strconv"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
Expand Down Expand Up @@ -65,6 +66,7 @@ func TestAccAzureADUser_complete(t *testing.T) {
resource.TestCheckResourceAttr(rn, "display_name", fmt.Sprintf("acctestUser-%d-Updated", id)),
resource.TestCheckResourceAttr(rn, "mail_nickname", fmt.Sprintf("acctestUser-%d-Updated", id)),
resource.TestCheckResourceAttr(rn, "account_enabled", "false"),
resource.TestCheckResourceAttr(rn, "immutable_id", strconv.Itoa(id)),
),
},
{
Expand Down Expand Up @@ -111,6 +113,7 @@ func TestAccAzureADUser_update(t *testing.T) {
resource.TestCheckResourceAttr(rn, "display_name", fmt.Sprintf("acctestUser-%d-Updated", id)),
resource.TestCheckResourceAttr(rn, "mail_nickname", fmt.Sprintf("acctestUser-%d-Updated", id)),
resource.TestCheckResourceAttr(rn, "account_enabled", "false"),
resource.TestCheckResourceAttr(rn, "immutable_id", strconv.Itoa(id)),
),
},
{
Expand Down Expand Up @@ -204,6 +207,7 @@ resource "azuread_user" "test" {
password = "%[2]s"
force_password_change = true
usage_location = "NO"
immutable_id = "%[1]d"
}
`, id, password)
}
Expand Down
3 changes: 2 additions & 1 deletion website/docs/d/user.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The following arguments are supported:

* `mail_nickname` - (Optional) The email alias of the Azure AD User.

-> **NOTE:** Either `user_principal_name`, `object_id` or `mail_nickname` must be specified.
-> **NOTE:** One of `user_principal_name`, `object_id` or `mail_nickname` must be specified.

## Attributes Reference

Expand All @@ -47,3 +47,4 @@ The following attributes are exported:
* `onpremises_sam_account_name` - The on premise sam account name of the Azure AD User.
* `onpremises_user_principal_name` - The on premise user principal name of the Azure AD User.
* `usage_location` - The usage location of the Azure AD User.
* `immutable_id` - The value used to associate an on-premises Active Directory user account with their Azure AD user object.
1 change: 1 addition & 0 deletions website/docs/r/user.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The following arguments are supported:
* `mail_nickname`- (Optional) The mail alias for the user. Defaults to the user name part of the User Principal Name.
* `password` - (Required) The password for the User. The password must satisfy minimum requirements as specified by the password policy. The maximum length is 256 characters.
* `force_password_change` - (Optional) `true` if the User is forced to change the password during the next sign-in. Defaults to `false`.
* `immutable_id` - (Optional) The value used to associate an on-premises 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 userPrincipalName (UPN) property when creating a new user account.
* `usage_location` - (Optional) The usage location of the User. Required for users that will be assigned licenses due to legal requirement to check for availability of services in countries. The usage location is a two letter country code (ISO standard 3166). Examples include: `NO`, `JP`, and `GB`. Cannot be reset to null once set.

## Attributes Reference
Expand Down

0 comments on commit 9f78aa1

Please sign in to comment.