Skip to content

Commit

Permalink
pwlib: updates and fixes for password generator and check
Browse files Browse the repository at this point in the history
  • Loading branch information
Tommi2Day committed Apr 20, 2023
1 parent ab14d6c commit fd37919
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 48 deletions.
50 changes: 31 additions & 19 deletions pwlib/password_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ import (
log "github.com/sirupsen/logrus"
)

// SilentCheck skip log messages while checking
var SilentCheck = false

// SetSpecialChars change default charset for checks
func SetSpecialChars(specialChars string) {
all := UpperChar + LowerChar + Digits + specialChars
charset = PasswordCharset{UpperChar, LowerChar, Digits, specialChars, all}
}

// DoPasswordCheck Checks a password to given criteria
func DoPasswordCheck(password string, length int, upper int, lower int, numeric int, special int, firstCharCheck bool, allowedChars string) bool {
// var ls = true
Expand All @@ -21,42 +30,45 @@ func DoPasswordCheck(password string, length int, upper int, lower int, numeric
// allowed chars
possible := allowedChars
if allowedChars == "" {
possible = AllChars
possible = charset.AllChars
}

// do checks
ls, err := checkLength(password, length)
debugLog("length", err)
logError("length", err)
if upper > 0 {
ucs, err = checkClass(password, upper, UpperChar)
debugLog("uppercase", err)
ucs, err = checkClass(password, upper, charset.UpperChar)
logError("uppercase", err)
}
if lower > 0 {
lcs, err = checkClass(password, lower, LowerChar)
debugLog("lowercase", err)
lcs, err = checkClass(password, lower, charset.LowerChar)
logError("lowercase", err)
}
if numeric > 0 {
ncs, err = checkClass(password, numeric, Digits)
debugLog("numeric", err)
ncs, err = checkClass(password, numeric, charset.Digits)
logError("numeric", err)
}
if special > 0 {
sps, err = checkClass(password, special, SpecialChar)
debugLog("special", err)
sps, err = checkClass(password, special, charset.SpecialChar)
logError("special", err)
}
cs, err := checkChars(password, possible)
debugLog("allowed chars", err)
logError("allowed chars", err)
if firstCharCheck {
fcs, err = checkFirstChar(password, UpperChar+LowerChar)
debugLog("first character", err)
fcs, err = checkFirstChar(password, charset.UpperChar+charset.LowerChar)
logError("first character", err)
}

// final state
return ls && ucs && lcs && ncs && sps && cs && fcs
}

func debugLog(name string, err error) {
func logError(name string, err error) {
if SilentCheck {
return
}
if err != nil {
log.Debugf("%s check failed,", err.Error())
log.Errorf("%s check failed: %s", name, err.Error())
} else {
log.Debugf("%s check passed", name)
}
Expand All @@ -75,7 +87,7 @@ func checkClass(
cnt += strings.Count(password, char)
}
if cnt < should {
return false, fmt.Errorf("at least %d chars from %s", should, chars)
return false, fmt.Errorf("at least %d chars out of '%s' expected", should, chars)
}
return true, nil
}
Expand All @@ -85,14 +97,14 @@ func checkChars(
chars string,
) (bool, error) {
if len(password) == 0 {
return false, fmt.Errorf("%s check failed, password empty", "character")
return false, fmt.Errorf("password empty")
}
data := []rune(password)
for i := 0; i < len(data); i++ {
r := data[i]
idx := strings.IndexRune(chars, r)
if idx == -1 {
return false, fmt.Errorf("%s check failed, only %s allowed", "character", chars)
return false, fmt.Errorf("only %s allowed", chars)
}
}
return true, nil
Expand All @@ -101,7 +113,7 @@ func checkChars(
func checkLength(password string, minlen int) (bool, error) {
length := len(password)
if length < minlen {
return false, fmt.Errorf("length check failed, at least %d chars expected, have %d", minlen, length)
return false, fmt.Errorf("at least %d chars expected, have %d", minlen, length)
}
return true, nil
}
Expand Down
49 changes: 44 additions & 5 deletions pwlib/password_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,118 @@ package pwlib

import (
"testing"

"github.com/stretchr/testify/assert"
)

// nolint gocyclo
// TechProfile profile settings for technical users
var TechProfile = PasswordProfile{
Length: 12,
Upper: 1,
Lower: 1,
Digits: 1,
Special: 1,
Firstchar: true,
}

// UserProfile profile settings for personal users
var UserProfile = PasswordProfile{
Length: 10,
Upper: 1,
Lower: 1,
Digits: 1,
Special: 0,
Firstchar: true,
}

func TestDoPasswordCheck(t *testing.T) {
tests := []struct {
name string
pass string
valid bool
profile PasswordProfile
special string
}{
{
"NoCharacterAtAll",
"",
false,
TechProfile,
"",
},
{
"JustEmptyStringAndWhitespace",
" \n\t\r\v\f xxxx",
false,
TechProfile,
"",
},
{
"MixtureOfEmptyStringAndWhitespace",
"U u\n1\t?\r1\v2\f34",
false,
TechProfile,
"",
},
{
"MissingUpperCaseString",
"uu1?1234aaaa",
false,
TechProfile,
"",
},
{
"MissingLowerCaseString",
"UU1?1234AAAA",
false,
TechProfile,
"",
},
{
"MissingNumber",
"Uua?aaaaxxxx",
false,
TechProfile,
"",
},
{
"MissingSymbol",
"Uu101234aaaa",
false,
TechProfile,
"",
},
{
"LessThanRequiredMinimumLength",
"Uu1?123",
false,
TechProfile,
"",
},
{
"ValidPassword",
"Uu1?1234aaaa",
true,
TechProfile,
"",
},
{
"InvalidSpecial",
"Uu1?1234aaaa",
false,
TechProfile,
"x!=@",
},
}

for _, c := range tests {
t.Run(c.name, func(t *testing.T) {
profile := c.profile
actual := DoPasswordCheck(c.pass, profile.Length, profile.Upper, profile.Lower, profile.Digits, profile.Special, profile.Firstchar, "")
assert.Equalf(t, c.valid, actual, "Check %s not as expected", c.name)
special := c.special
if special != "" {
SetSpecialChars(special)
}
if c.valid != DoPasswordCheck(c.pass, profile.Length, profile.Upper, profile.Lower, profile.Digits, profile.Special, profile.Firstchar, charset.AllChars) {
t.Fatal("invalid password")
}
})
}
}
12 changes: 8 additions & 4 deletions pwlib/password_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,22 @@ func GenPassword(length int, upper int, lower int, numeric int, special int, fir
var allowedChars = ""
// define allowed charset based on parameters
if upper > 0 {
allowedChars += UpperChar
allowedChars += charset.UpperChar
}
if lower > 0 {
allowedChars += LowerChar
allowedChars += charset.LowerChar
}
if numeric > 0 {
allowedChars += Digits
allowedChars += charset.Digits
}
if special > 0 {
allowedChars += SpecialChar
allowedChars += charset.SpecialChar
}
charset.AllChars = allowedChars

newPassword := ""
// skip password check logging when used to generate
SilentCheck = true
// max 50 tries to generate a valid password
for c := 0; c < 50; c++ {
newPassword = generateRandomString(length, allowedChars)
Expand All @@ -53,6 +56,7 @@ func GenPassword(length int, upper int, lower int, numeric int, special int, fir
newPassword = ""
log.Debugf("generate retry %d", c)
}
SilentCheck = false

if !ok {
err = fmt.Errorf("unable to create required Password")
Expand Down
2 changes: 1 addition & 1 deletion pwlib/password_generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func TestGenPassword(t *testing.T) {
UpperChar + Digits,
},
}

SetSpecialChars(SpecialChar)
for _, c := range tests {
t.Run(c.name, func(t *testing.T) {
newPassword, err := GenPassword(c.length, c.uc, c.lc, c.num, c.sp, c.first)
Expand Down
34 changes: 15 additions & 19 deletions pwlib/password_profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ const (
AllChars = UpperChar + LowerChar + Digits + SpecialChar
)

// PasswordCharset defines the allowed characters to choose
type PasswordCharset struct {
// UpperChar allowed charsets upper
UpperChar string
// LowerChar allowed charsets lower
LowerChar string
// Digits allowed charsets digits
Digits string
// SpecialChar allowed charsets special
SpecialChar string
// AllChars allowed charsets combined
AllChars string
}

// PasswordProfile struct for password profile
type PasswordProfile struct {
Length int
Expand All @@ -23,22 +37,4 @@ type PasswordProfile struct {
Firstchar bool
}

// TechProfile profile settings for technical users
var TechProfile = PasswordProfile{
Length: 12,
Upper: 1,
Lower: 1,
Digits: 1,
Special: 1,
Firstchar: true,
}

// UserProfile profile settings for personal users
var UserProfile = PasswordProfile{
Length: 10,
Upper: 1,
Lower: 1,
Digits: 1,
Special: 0,
Firstchar: true,
}
var charset = PasswordCharset{UpperChar, LowerChar, Digits, SpecialChar, UpperChar + LowerChar + Digits + SpecialChar}

0 comments on commit fd37919

Please sign in to comment.