diff --git a/pwlib/password_check.go b/pwlib/password_check.go index a82d14e..20ee7d4 100644 --- a/pwlib/password_check.go +++ b/pwlib/password_check.go @@ -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 @@ -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) } @@ -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 } @@ -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 @@ -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 } diff --git a/pwlib/password_check_test.go b/pwlib/password_check_test.go index b27e335..2fd6611 100644 --- a/pwlib/password_check_test.go +++ b/pwlib/password_check_test.go @@ -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") + } }) } } diff --git a/pwlib/password_generate.go b/pwlib/password_generate.go index f594bf5..00c2be6 100644 --- a/pwlib/password_generate.go +++ b/pwlib/password_generate.go @@ -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) @@ -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") diff --git a/pwlib/password_generate_test.go b/pwlib/password_generate_test.go index 24f1c52..b329538 100644 --- a/pwlib/password_generate_test.go +++ b/pwlib/password_generate_test.go @@ -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) diff --git a/pwlib/password_profiles.go b/pwlib/password_profiles.go index 43ffdf6..c5f6b0c 100644 --- a/pwlib/password_profiles.go +++ b/pwlib/password_profiles.go @@ -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 @@ -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}