From f0c9544af668c9bb97759bb3e1fbc004d1e4f938 Mon Sep 17 00:00:00 2001 From: Azareal Date: Thu, 18 Jun 2020 08:03:36 +1000 Subject: [PATCH] make it easier to define weak password definitions add tests for weak passwords make it easier to check values of errors returned from weak password loosen weak password requirements for long passwords reduce boilerplate when loading json files only check for email in passwords when it is longer than two chars more canonemail tests --- common/parser.go | 27 +++------ common/utils.go | 149 ++++++++++++++++++++++++++++++++++++----------- main.go | 3 + misc_test.go | 74 ++++++++++++++++++++--- 4 files changed, 191 insertions(+), 62 deletions(-) diff --git a/common/parser.go b/common/parser.go index d94a8e27..37827e45 100644 --- a/common/parser.go +++ b/common/parser.go @@ -4,10 +4,8 @@ import ( "bytes" //"fmt" //"log" - "encoding/json" - "io/ioutil" + "net/url" - "os" "path/filepath" "regexp" "strconv" @@ -45,6 +43,7 @@ var attachOpen = []byte("Attachment") var sidParam = []byte("?sid=") var stypeParam = []byte("&stype=") + /*var textShortOpen = []byte("View / Download")*/ @@ -71,13 +70,8 @@ type emojiHolder struct { } func InitEmoji() error { - data, err := ioutil.ReadFile("./config/emoji_default.json") - if err != nil { - return err - } - var emoji emojiHolder - err = json.Unmarshal(data, &emoji) + err := unmarshalJsonFile("./config/emoji_default.json", &emoji) if err != nil { return err } @@ -89,15 +83,8 @@ func InitEmoji() error { } } - data, err = ioutil.ReadFile("./config/emoji.json") - if err == os.ErrPermission || err == os.ErrClosed { - return err - } else if err != nil { - return nil - } - emoji = emojiHolder{} - err = json.Unmarshal(data, &emoji) + err = unmarshalJsonFileIgnore404("./config/emoji.json", &emoji) if err != nil { return err } @@ -484,14 +471,14 @@ func (ps *ParseSettings) CopyPtr() *ParseSettings { } func ParseMessage(msg string, sectionID int, sectionType string, settings *ParseSettings, user *User) string { - msg, _ = ParseMessage2(msg,sectionID,sectionType,settings,user) + msg, _ = ParseMessage2(msg, sectionID, sectionType, settings, user) return msg } // TODO: Write a test for this // TODO: We need a lot more hooks here. E.g. To add custom media types and handlers. // TODO: Use templates to reduce the amount of boilerplate? -func ParseMessage2(msg string, sectionID int, sectionType string, settings *ParseSettings, user *User) (string,bool) { +func ParseMessage2(msg string, sectionID int, sectionType string, settings *ParseSettings, user *User) (string, bool) { if settings == nil { settings = DefaultParseSettings } @@ -1095,7 +1082,7 @@ func parseMediaString(data string, settings *ParseSettings) (media MediaEmbed, o vid, err := strconv.ParseInt(strings.TrimPrefix(path, "/watch/sm"), 10, 64) if err == nil { media.Type = ERawExternal - media.Body = "" + media.Body = "" return media, true } } diff --git a/common/utils.go b/common/utils.go index 49be76c6..db48df58 100644 --- a/common/utils.go +++ b/common/utils.go @@ -10,9 +10,11 @@ import ( "crypto/rand" "encoding/base32" "encoding/base64" + "encoding/json" "errors" "fmt" "html" + "io/ioutil" "math" "os" "strconv" @@ -161,11 +163,12 @@ func RelativeTimeBytes(t time.Time, lang int) []byte { } */ -var pMs = 1000; -var pSec = pMs * 1000; -var pMin = pSec * 60; -var pHour = pMin * 60; -var pDay = pHour * 24; +var pMs = 1000 +var pSec = pMs * 1000 +var pMin = pSec * 60 +var pHour = pMin * 60 +var pDay = pHour * 24 + func ConvertPerfUnit(quan float64) (out float64, unit string) { f := func() (float64, string) { switch { @@ -186,7 +189,6 @@ func ConvertPerfUnit(quan float64) (out float64, unit string) { return math.Ceil(out), unit } - // TODO: Write a test for this func ConvertByteUnit(bytes float64) (float64, string) { switch { @@ -341,7 +343,7 @@ func NameToSlug(name string) (slug string) { func HasSuspiciousEmail(email string) bool { lowEmail := strings.ToLower(email) // TODO: Use a more flexible blacklist, perhaps with a similar mechanism to the HTML tag registration system in PreparseMessage() - if !strings.Contains(lowEmail,"@") || strings.Contains(lowEmail, "casino") || strings.Contains(lowEmail, "viagra") || strings.Contains(lowEmail, "pharma") || strings.Contains(lowEmail, "pill") { + if !strings.Contains(lowEmail, "@") || strings.Contains(lowEmail, "casino") || strings.Contains(lowEmail, "viagra") || strings.Contains(lowEmail, "pharma") || strings.Contains(lowEmail, "pill") { return true } @@ -361,36 +363,113 @@ func HasSuspiciousEmail(email string) bool { return dotCount > 7 || shortBits > 2 } -var weakPassStrings = []string{"test", "123", "6969", "password", "qwerty", "fuck", "love"} +func unmarshalJsonFile(name string, in interface{}) error { + data, err := ioutil.ReadFile(name) + if err != nil { + return err + } + return json.Unmarshal(data, in) +} + +func unmarshalJsonFileIgnore404(name string, in interface{}) error { + data, err := ioutil.ReadFile(name) + if err == os.ErrPermission || err == os.ErrClosed { + return err + } else if err != nil { + return nil + } + return json.Unmarshal(data, in) +} + +type weakpassHolder struct { + Contains []string `json:"contains"` + Literal []string `json:"literal"` +} + +func InitWeakPasswords() error { + var weakpass weakpassHolder + err := unmarshalJsonFile("./config/weakpass_default.json", &weakpass) + if err != nil { + return err + } + + wcon := make(map[string]struct{}) + for _, item := range weakpass.Contains { + wcon[item] = struct{}{} + } + for _, item := range weakpass.Literal { + weakPassLit[item] = struct{}{} + } + + weakpass = weakpassHolder{} + err = unmarshalJsonFileIgnore404("./config/weakpass.json", &weakpass) + if err != nil { + return err + } + + for _, item := range weakpass.Contains { + wcon[item] = struct{}{} + } + for _, item := range weakpass.Literal { + weakPassLit[item] = struct{}{} + } + weakPassStrings = make([]string, len(wcon)) + var i int + for pattern, _ := range wcon { + weakPassStrings[i] = pattern + i++ + } + + s := "You may not have " + for i, passBit := range weakPassStrings { + if i > 0 { + if i == len(weakPassStrings)-1 { + s += " or " + } else { + s += ", " + } + } + s += "'" + passBit + "'" + } + ErrWeakPasswordContains = errors.New(s + " in your password") + + return nil +} + +var weakPassStrings []string +var weakPassLit = make(map[string]struct{}) +var ErrWeakPasswordNone = errors.New("You didn't put in a password.") +var ErrWeakPasswordShort = errors.New("Your password needs to be at-least eight characters long") +var ErrWeakPasswordNameInPass = errors.New("You can't use your name in your password.") +var ErrWeakPasswordEmailInPass = errors.New("You can't use your email in your password.") +var ErrWeakPasswordCommon = errors.New("You may not use a password that is in common use") +var ErrWeakPasswordNoNumbers = errors.New("You don't have any numbers in your password") +var ErrWeakPasswordNoUpper = errors.New("You don't have any uppercase characters in your password") +var ErrWeakPasswordNoLower = errors.New("You don't have any lowercase characters in your password") +var ErrWeakPasswordUniqueChars = errors.New("You don't have enough unique characters in your password") +var ErrWeakPasswordContains error // TODO: Write a test for this func WeakPassword(password, username, email string) error { lowPassword := strings.ToLower(password) switch { case password == "": - return errors.New("You didn't put in a password.") + return ErrWeakPasswordNone case len(password) < 8: - return errors.New("Your password needs to be at-least eight characters long") - case strings.Contains(lowPassword, strings.ToLower(username)) && len(username) > 3: - return errors.New("You can't use your username in your password.") - case email != "" && strings.Contains(lowPassword, strings.ToLower(email)): - return errors.New("You can't use your email in your password.") + return ErrWeakPasswordShort + case len(username) > 3 && strings.Contains(lowPassword, strings.ToLower(username)): + return ErrWeakPasswordNameInPass + case len(email) > 2 && strings.Contains(lowPassword, strings.ToLower(email)): + return ErrWeakPasswordEmailInPass } + _, ok := weakPassLit[lowPassword] + if ok { + return ErrWeakPasswordCommon + } for _, passBit := range weakPassStrings { if strings.Contains(lowPassword, passBit) { - s := "You may not have " - for i, passBit := range weakPassStrings { - if i > 0 { - if i == len(weakPassStrings)-1 { - s += " or " - } else { - s += ", " - } - } - s += "'" + passBit + "'" - } - return errors.New(s + " in your password") + return ErrWeakPasswordContains } } @@ -418,22 +497,22 @@ func WeakPassword(password, username, email string) error { } } - if numbers == 0 { - return errors.New("You don't have any numbers in your password") - } if upper == 0 { - return errors.New("You don't have any uppercase characters in your password") + return ErrWeakPasswordNoUpper } if lower == 0 { - return errors.New("You don't have any lowercase characters in your password") + return ErrWeakPasswordNoLower } if len(password) < 18 { + if numbers == 0 { + return ErrWeakPasswordNoNumbers + } if (len(password) / 2) > len(charMap) { - return errors.New("You don't have enough unique characters in your password") + return ErrWeakPasswordUniqueChars } } else if (len(password) / 3) > len(charMap) { // Be a little lenient on the number of unique characters for long passwords - return errors.New("You don't have enough unique characters in your password") + return ErrWeakPasswordUniqueChars } return nil } @@ -441,11 +520,11 @@ func WeakPassword(password, username, email string) error { // TODO: Write a test for this func CanonEmail(email string) string { email = strings.ToLower(email) - + // Gmail emails are equivalent without the dots espl := strings.Split(email, "@") if len(espl) >= 2 && espl[1] == "gmail.com" { - return strings.Replace(espl[0],".","",-1) + "@" + espl[1] + return strings.Replace(espl[0], ".", "", -1) + "@" + espl[1] } return email diff --git a/main.go b/main.go index 9e509b55..1c406a2c 100644 --- a/main.go +++ b/main.go @@ -159,6 +159,9 @@ func storeInit() (err error) { if err = c.InitEmoji(); err != nil { return errors.WithStack(err) } + if err = c.InitWeakPasswords(); err != nil { + return errors.WithStack(err) + } log.Print("Loading the static files.") if err = c.Themes.LoadStaticFiles(); err != nil { diff --git a/misc_test.go b/misc_test.go index af3807a1..952bb1aa 100644 --- a/misc_test.go +++ b/misc_test.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "strings" "database/sql" "fmt" "io/ioutil" @@ -10,12 +9,14 @@ import ( "os" "runtime/debug" "strconv" + "strings" "testing" "time" c "github.com/Azareal/Gosora/common" "github.com/Azareal/Gosora/common/gauth" "github.com/Azareal/Gosora/common/phrases" + "github.com/pkg/errors" ) func miscinit(t *testing.T) { @@ -2002,26 +2003,85 @@ func TestWidgets(t *testing.T) { func TestUtils(t *testing.T) { email := "test@example.com" cemail := c.CanonEmail(email) - expect(t,cemail==email,fmt.Sprintf("%s should be %s", cemail, email)) + expect(t, cemail == email, fmt.Sprintf("%s should be %s", cemail, email)) email = "test.test@example.com" cemail = c.CanonEmail(email) - expect(t,cemail==email,fmt.Sprintf("%s should be %s", cemail, email)) + expect(t, cemail == email, fmt.Sprintf("%s should be %s", cemail, email)) email = "test.test@gmail.com" eemail := "testtest@gmail.com" cemail = c.CanonEmail(email) - expect(t,cemail==eemail,fmt.Sprintf("%s should be %s", cemail, eemail)) + expect(t, cemail == eemail, fmt.Sprintf("%s should be %s", cemail, eemail)) email = "TEST.test@gmail.com" eemail = "testtest@gmail.com" cemail = c.CanonEmail(email) - expect(t,cemail==eemail,fmt.Sprintf("%s should be %s", cemail, eemail)) + expect(t, cemail == eemail, fmt.Sprintf("%s should be %s", cemail, eemail)) + + email = "test.TEST.test@gmail.com" + eemail = "testtesttest@gmail.com" + cemail = c.CanonEmail(email) + expect(t, cemail == eemail, fmt.Sprintf("%s should be %s", cemail, eemail)) + + email = "test..TEST.test@gmail.com" + eemail = "testtesttest@gmail.com" + cemail = c.CanonEmail(email) + expect(t, cemail == eemail, fmt.Sprintf("%s should be %s", cemail, eemail)) email = "TEST.test@example.com" lowEmail := strings.ToLower(email) cemail = c.CanonEmail(email) - expect(t,cemail==lowEmail,fmt.Sprintf("%s should be %s", cemail, lowEmail)) - + expect(t, cemail == lowEmail, fmt.Sprintf("%s should be %s", cemail, lowEmail)) + + email = "test.TEST.test@example.com" + lowEmail = strings.ToLower(email) + cemail = c.CanonEmail(email) + expect(t, cemail == lowEmail, fmt.Sprintf("%s should be %s", cemail, lowEmail)) + + /*weakPass := func(password, username, email string) func(error,string,...interface{}) { + err := c.WeakPassword(password, username, email) + return func(expectErr error, m string, p ...interface{}) { + m = fmt.Sprintf("pass=%s, user=%s, email=%s ", password, username, email) + m + expect(t, err == expectErr, fmt.Sprintf(m,p...)) + } + }*/ + nilErrStr := func(e error) error { + if e == nil { + e = errors.New("nil") + } + return e + } + weakPass := func(password, username, email string) func(error) { + err := c.WeakPassword(password, username, email) + e := nilErrStr(err) + m := fmt.Sprintf("pass=%s, user=%s, email=%s ", password, username, email) + return func(expectErr error) { + ee := nilErrStr(expectErr) + expect(t, err == expectErr, m+fmt.Sprintf("err should be '%s' not '%s'", ee, e)) + } + } + + //weakPass("test", "test", "test@example.com")(c.ErrWeakPasswordContains,"err should be ErrWeakPasswordContains not '%s'") + weakPass("", "draw", "test@example.com")(c.ErrWeakPasswordNone) + weakPass("test", "draw", "test@example.com")(c.ErrWeakPasswordShort) + weakPass("testtest", "draw", "test@example.com")(c.ErrWeakPasswordContains) + weakPass("testdraw", "draw", "test@example.com")(c.ErrWeakPasswordNameInPass) + weakPass("test@example.com", "draw", "test@example.com")(c.ErrWeakPasswordEmailInPass) + weakPass("meet@example.com2", "draw", "")(c.ErrWeakPasswordNoUpper) + weakPass("Meet@example.com2", "draw", "")(nil) + weakPass("test2", "draw", "test@example.com")(c.ErrWeakPasswordShort) + weakPass("test22222222", "draw", "test@example.com")(c.ErrWeakPasswordContains) + weakPass("superman", "draw", "test@example.com")(c.ErrWeakPasswordCommon) + weakPass("K\\@<^s}1", "draw", "test@example.com")(nil) + weakPass("K\\@<^s}r", "draw", "test@example.com")(c.ErrWeakPasswordNoNumbers) + weakPass("k\\@<^s}1", "draw", "test@example.com")(c.ErrWeakPasswordNoUpper) + weakPass("aaaaaaaa", "draw", "test@example.com")(c.ErrWeakPasswordNoUpper) + weakPass("aA1aA1aA1", "draw", "test@example.com")(c.ErrWeakPasswordUniqueChars) + weakPass("abababab", "draw", "test@example.com")(c.ErrWeakPasswordNoUpper) + weakPass("11111111111111111111", "draw", "test@example.com")(c.ErrWeakPasswordNoUpper) + weakPass("aaaaaaaaaaAAAAAAAAAA", "draw", "test@example.com")(c.ErrWeakPasswordUniqueChars) + weakPass("-:u/nMxb,A!n=B;H\\sjM", "draw", "test@example.com")(nil) + // TODO: More utils.go tests }