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 }