auth0/resource_auth0_user.go
package auth0
import (
"fmt"
"net/http"
"strings"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/structure"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"gopkg.in/auth0.v5"
"gopkg.in/auth0.v5/management"
)
func newUser() *schema.Resource {
return &schema.Resource{
Create: createUser,
Read: readUser,
Update: updateUser,
Delete: deleteUser,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"user_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return old == "auth0|"+new
},
StateFunc: func(s interface{}) string {
return strings.ToLower(s.(string))
},
},
"connection_name": {
Type: schema.TypeString,
Required: true,
},
"username": {
Type: schema.TypeString,
Optional: true,
},
"name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"family_name": {
Type: schema.TypeString,
Optional: true,
},
"given_name": {
Type: schema.TypeString,
Optional: true,
},
"nickname": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"password": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
},
"email": {
Type: schema.TypeString,
Optional: true,
},
"email_verified": {
Type: schema.TypeBool,
Optional: true,
},
"verify_email": {
Type: schema.TypeBool,
Optional: true,
},
"phone_number": {
Type: schema.TypeString,
Optional: true,
},
"phone_verified": {
Type: schema.TypeBool,
Optional: true,
},
"user_metadata": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.ValidateJsonString,
DiffSuppressFunc: structure.SuppressJsonDiff,
},
"app_metadata": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.ValidateJsonString,
DiffSuppressFunc: structure.SuppressJsonDiff,
},
"blocked": {
Type: schema.TypeBool,
Optional: true,
},
"picture": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"roles": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
func readUser(d *schema.ResourceData, m interface{}) error {
api := m.(*management.Management)
u, err := api.User.Read(d.Id())
if err != nil {
if mErr, ok := err.(management.Error); ok {
if mErr.Status() == http.StatusNotFound {
d.SetId("")
return nil
}
}
return err
}
d.Set("user_id", u.ID)
d.Set("username", u.Username)
d.Set("name", u.Name)
d.Set("family_name", u.FamilyName)
d.Set("given_name", u.GivenName)
d.Set("nickname", u.Nickname)
d.Set("email", u.Email)
d.Set("email_verified", u.EmailVerified)
d.Set("verify_email", u.VerifyEmail)
d.Set("phone_number", u.PhoneNumber)
d.Set("phone_verified", u.PhoneVerified)
d.Set("blocked", u.Blocked)
d.Set("picture", u.Picture)
userMeta, err := structure.FlattenJsonToString(u.UserMetadata)
if err != nil {
return err
}
d.Set("user_metadata", userMeta)
appMeta, err := structure.FlattenJsonToString(u.AppMetadata)
if err != nil {
return err
}
d.Set("app_metadata", appMeta)
l, err := api.User.Roles(d.Id())
if err != nil {
return err
}
d.Set("roles", func() (v []interface{}) {
for _, role := range l.Roles {
v = append(v, auth0.StringValue(role.ID))
}
return
}())
return nil
}
func createUser(d *schema.ResourceData, m interface{}) error {
u, err := buildUser(d)
if err != nil {
return err
}
api := m.(*management.Management)
if err := api.User.Create(u); err != nil {
return err
}
d.SetId(*u.ID)
d.Partial(true)
err = assignUserRoles(d, m)
if err != nil {
return err
}
d.Partial(false)
return readUser(d, m)
}
func updateUser(d *schema.ResourceData, m interface{}) error {
u, err := buildUser(d)
if err != nil {
return err
}
if err = validateUser(u); err != nil {
return err
}
api := m.(*management.Management)
if userHasChange(u) {
if err := api.User.Update(d.Id(), u); err != nil {
return err
}
}
d.Partial(true)
err = assignUserRoles(d, m)
if err != nil {
return fmt.Errorf("failed assigning user roles. %s", err)
}
d.Partial(false)
return readUser(d, m)
}
func deleteUser(d *schema.ResourceData, m interface{}) error {
api := m.(*management.Management)
err := api.User.Delete(d.Id())
if err != nil {
if mErr, ok := err.(management.Error); ok {
if mErr.Status() == http.StatusNotFound {
d.SetId("")
return nil
}
}
}
return err
}
func buildUser(d *schema.ResourceData) (u *management.User, err error) {
u = new(management.User)
u.ID = String(d, "user_id", IsNewResource())
u.Connection = String(d, "connection_name")
u.Name = String(d, "name")
u.FamilyName = String(d, "family_name")
u.GivenName = String(d, "given_name")
u.Nickname = String(d, "nickname")
u.Username = String(d, "username", IsNewResource(), HasChange())
u.Email = String(d, "email", IsNewResource(), HasChange())
u.EmailVerified = Bool(d, "email_verified", IsNewResource(), HasChange())
u.VerifyEmail = Bool(d, "verify_email", IsNewResource(), HasChange())
u.PhoneNumber = String(d, "phone_number", IsNewResource(), HasChange())
u.PhoneVerified = Bool(d, "phone_verified", IsNewResource(), HasChange())
u.Password = String(d, "password", IsNewResource(), HasChange())
u.Blocked = Bool(d, "blocked")
u.Picture = String(d, "picture")
u.UserMetadata, err = JSON(d, "user_metadata")
if err != nil {
return nil, err
}
u.AppMetadata, err = JSON(d, "app_metadata")
if err != nil {
return nil, err
}
return u, nil
}
func validateUser(u *management.User) error {
var validation error
for _, fn := range []validateUserFunc{
validateNoUsernameAndPasswordSimultaneously(),
validateNoUsernameAndEmailVerifiedSimultaneously(),
validateNoPasswordAndEmailVerifiedSimultaneously(),
} {
if err := fn(u); err != nil {
validation = multierror.Append(validation, err)
}
}
return validation
}
type validateUserFunc func(*management.User) error
func validateNoUsernameAndPasswordSimultaneously() validateUserFunc {
return func(u *management.User) (err error) {
if u.Username != nil && u.Password != nil {
err = fmt.Errorf("Cannot update username and password simultaneously")
}
return
}
}
func validateNoUsernameAndEmailVerifiedSimultaneously() validateUserFunc {
return func(u *management.User) (err error) {
if u.Username != nil && u.EmailVerified != nil {
err = fmt.Errorf("Cannot update username and email_verified simultaneously")
}
return
}
}
func validateNoPasswordAndEmailVerifiedSimultaneously() validateUserFunc {
return func(u *management.User) (err error) {
if u.Password != nil && u.EmailVerified != nil {
err = fmt.Errorf("Cannot update password and email_verified simultaneously")
}
return
}
}
func assignUserRoles(d *schema.ResourceData, m interface{}) error {
add, rm := Diff(d, "roles")
var addRoles []*management.Role
for _, addRole := range add.List() {
addRoles = append(addRoles, &management.Role{
ID: auth0.String(addRole.(string)),
})
}
var rmRoles []*management.Role
for _, rmRole := range rm.List() {
rmRoles = append(rmRoles, &management.Role{
ID: auth0.String(rmRole.(string)),
})
}
api := m.(*management.Management)
if len(rmRoles) > 0 {
err := api.User.RemoveRoles(d.Id(), rmRoles)
if err != nil {
// Ignore 404 errors as the role may have been deleted prior to
// unassigning them from the user.
if mErr, ok := err.(management.Error); ok {
if mErr.Status() != http.StatusNotFound {
return err
}
} else {
return err
}
}
}
if len(addRoles) > 0 {
err := api.User.AssignRoles(d.Id(), addRoles)
if err != nil {
return err
}
}
d.SetPartial("roles")
return nil
}
func userHasChange(u *management.User) bool {
// hacky but we need to tell if an empty json is sent to the api.
return u.String() != "{}"
}