From 96577478bb9f54298d6f2c9fab908fe7706b2b71 Mon Sep 17 00:00:00 2001 From: Maxim Timokhin Date: Sun, 5 Jun 2022 17:37:52 +0300 Subject: [PATCH] Add random password feature We need to manage user's channels and other resources using Terraform without knowledge of user's actual password (due to privacy issues). So the following feature is implemented. If password field in user resource definition is set to "-", the user is created with a random password and "reset password" email is sent to the user. --- internal/provider/resource_user.go | 19 +++++++++-- internal/provider/utils.go | 51 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/internal/provider/resource_user.go b/internal/provider/resource_user.go index 6382468..1a5ea9c 100644 --- a/internal/provider/resource_user.go +++ b/internal/provider/resource_user.go @@ -85,16 +85,25 @@ func resourceUser() *schema.Resource { func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { c := meta.(*model.Client4) + email := d.Get("email").(string) + password := d.Get("password").(string) + isRandomPassword := false + if password == "-" { + isRandomPassword = true + password = genPassword(16, 2, 2, 6) + } + user := &model.User{ Username: d.Get("username").(string), - Password: d.Get("password").(string), + Password: password, AuthService: d.Get("auth_service").(string), - Email: d.Get("email").(string), + Email: email, Nickname: d.Get("nickname").(string), FirstName: d.Get("first_name").(string), LastName: d.Get("last_name").(string), Locale: d.Get("locale").(string), } + if authData, ok := d.GetOk("auth_data"); ok { ad := authData.(string) user.AuthData = &ad @@ -107,6 +116,12 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf if err != nil { return diag.Errorf("cannot create user: %v", fmtErr(resp, err)) } + if isRandomPassword { + resp, err = c.SendPasswordResetEmail(email) + if err != nil { + return diag.Errorf("cannot reset password for user: %v", fmtErr(resp, err)) + } + } d.SetId(user.Id) diff --git a/internal/provider/utils.go b/internal/provider/utils.go index 69de105..fa37906 100644 --- a/internal/provider/utils.go +++ b/internal/provider/utils.go @@ -2,10 +2,15 @@ package provider import ( "fmt" + "math/rand" + "strings" + "time" "github.com/mattermost/mattermost-server/v6/model" ) +var random *rand.Rand + func fmtErr(resp *model.Response, err error) error { if resp == nil { return err @@ -22,3 +27,49 @@ func expandStringMap(m map[string]interface{}) map[string]string { return r } + +func init() { + random = rand.New(rand.NewSource(time.Now().UnixNano())) +} + +var passwordChars = struct { + Lower string + Upper string + Digits string + Specials string +}{ + "abcdefghijklmnopqrstuvwxyz", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "0123456789", + "!@#$%^&*()-+", +} + +func genPassword(size, numSpecials, numDigits, numUpper int) string { + var password strings.Builder + + for i := 0; i < numSpecials; i++ { + password.WriteString( + string(passwordChars.Specials[random.Intn(len(passwordChars.Specials))]), + ) + } + for i := 0; i < numDigits; i++ { + password.WriteString( + string(passwordChars.Digits[random.Intn(len(passwordChars.Digits))]), + ) + } + for i := 0; i < numUpper; i++ { + password.WriteString( + string(passwordChars.Upper[random.Intn(len(passwordChars.Upper))]), + ) + } + for i := 0; i < size-numSpecials-numDigits-numUpper; i++ { + password.WriteString( + string(passwordChars.Lower[random.Intn(len(passwordChars.Lower))]), + ) + } + runes := []rune(password.String()) + random.Shuffle(len(runes), func(i, j int) { + runes[i], runes[j] = runes[j], runes[i] + }) + return string(runes) +}