diff --git a/model/user.go b/model/user.go index dd4e2ba8ca..c3f16d56ac 100644 --- a/model/user.go +++ b/model/user.go @@ -14,6 +14,7 @@ import ( "regexp" "sort" "strings" + "sync" "time" "unicode/utf8" @@ -890,7 +891,22 @@ func UsersWithGroupsAndCountFromJson(data io.Reader) *UsersWithGroupsAndCount { return uwg } -var passwordRandomSource = rand.NewSource(time.Now().Unix()) +type lockedRand struct { + mu sync.Mutex + rn *rand.Rand +} + +func (r *lockedRand) Intn(n int) int { + r.mu.Lock() + m := r.rn.Intn(n) + r.mu.Unlock() + return m +} + +var passwordRandom = lockedRand{ + rn: rand.New(rand.NewSource(time.Now().Unix())), +} + var passwordSpecialChars = "!$%^&*(),." var passwordNumbers = "0123456789" var passwordUpperCaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -898,16 +914,14 @@ var passwordLowerCaseLetters = "abcdefghijklmnopqrstuvwxyz" var passwordAllChars = passwordSpecialChars + passwordNumbers + passwordUpperCaseLetters + passwordLowerCaseLetters func GeneratePassword(minimumLength int) string { - r := rand.New(passwordRandomSource) - // Make sure we are guaranteed at least one of each type to meet any possible password complexity requirements. - password := string([]rune(passwordUpperCaseLetters)[r.Intn(len(passwordUpperCaseLetters))]) + - string([]rune(passwordNumbers)[r.Intn(len(passwordNumbers))]) + - string([]rune(passwordLowerCaseLetters)[r.Intn(len(passwordLowerCaseLetters))]) + - string([]rune(passwordSpecialChars)[r.Intn(len(passwordSpecialChars))]) + password := string([]rune(passwordUpperCaseLetters)[passwordRandom.Intn(len(passwordUpperCaseLetters))]) + + string([]rune(passwordNumbers)[passwordRandom.Intn(len(passwordNumbers))]) + + string([]rune(passwordLowerCaseLetters)[passwordRandom.Intn(len(passwordLowerCaseLetters))]) + + string([]rune(passwordSpecialChars)[passwordRandom.Intn(len(passwordSpecialChars))]) for len(password) < minimumLength { - i := r.Intn(len(passwordAllChars)) + i := passwordRandom.Intn(len(passwordAllChars)) password = password + string([]rune(passwordAllChars)[i]) } diff --git a/model/user_test.go b/model/user_test.go index 3f8340cf65..ee5d1a516c 100644 --- a/model/user_test.go +++ b/model/user_test.go @@ -353,7 +353,9 @@ func TestUserSlice(t *testing.T) { } func TestGeneratePassword(t *testing.T) { - passwordRandomSource = rand.NewSource(12345) + passwordRandom = lockedRand{ + rn: rand.New(rand.NewSource(12345)), + } t.Run("Should be the minimum length or 4, whichever is less", func(t *testing.T) { password1 := GeneratePassword(5) @@ -372,4 +374,10 @@ func TestGeneratePassword(t *testing.T) { assert.Contains(t, []rune(passwordLowerCaseLetters), []rune(password)[2]) assert.Contains(t, []rune(passwordSpecialChars), []rune(password)[3]) }) + + t.Run("Should not fail on concurrent calls", func(t *testing.T) { + for i := 0; i < 10; i++ { + go GeneratePassword(10) + } + }) }