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
This commit is contained in:
Azareal 2020-06-18 08:03:36 +10:00
parent 0254687d5b
commit f0c9544af6
4 changed files with 191 additions and 62 deletions

View File

@ -4,10 +4,8 @@ import (
"bytes" "bytes"
//"fmt" //"fmt"
//"log" //"log"
"encoding/json"
"io/ioutil"
"net/url" "net/url"
"os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv" "strconv"
@ -45,6 +43,7 @@ var attachOpen = []byte("<a class='attach'href=\"")
var attachClose = []byte("\"download>Attachment</a>") var attachClose = []byte("\"download>Attachment</a>")
var sidParam = []byte("?sid=") var sidParam = []byte("?sid=")
var stypeParam = []byte("&amp;stype=") var stypeParam = []byte("&amp;stype=")
/*var textShortOpen = []byte("<a class='attach'href=\"") /*var textShortOpen = []byte("<a class='attach'href=\"")
var textShortOpen2 = []byte("\">View</a> / <a class='attach'href=\"") var textShortOpen2 = []byte("\">View</a> / <a class='attach'href=\"")
var textShortClose = []byte("\"download>Download</a>")*/ var textShortClose = []byte("\"download>Download</a>")*/
@ -71,13 +70,8 @@ type emojiHolder struct {
} }
func InitEmoji() error { func InitEmoji() error {
data, err := ioutil.ReadFile("./config/emoji_default.json")
if err != nil {
return err
}
var emoji emojiHolder var emoji emojiHolder
err = json.Unmarshal(data, &emoji) err := unmarshalJsonFile("./config/emoji_default.json", &emoji)
if err != nil { if err != nil {
return err 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{} emoji = emojiHolder{}
err = json.Unmarshal(data, &emoji) err = unmarshalJsonFileIgnore404("./config/emoji.json", &emoji)
if err != nil { if err != nil {
return err return err
} }
@ -484,14 +471,14 @@ func (ps *ParseSettings) CopyPtr() *ParseSettings {
} }
func ParseMessage(msg string, sectionID int, sectionType string, settings *ParseSettings, user *User) string { 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 return msg
} }
// TODO: Write a test for this // TODO: Write a test for this
// TODO: We need a lot more hooks here. E.g. To add custom media types and handlers. // 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? // 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 { if settings == nil {
settings = DefaultParseSettings 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) vid, err := strconv.ParseInt(strings.TrimPrefix(path, "/watch/sm"), 10, 64)
if err == nil { if err == nil {
media.Type = ERawExternal media.Type = ERawExternal
media.Body = "<iframe class='postIframe'src='https://embed.nicovideo.jp/watch/sm"+strconv.FormatInt(vid, 10)+"?jsapi=1&amp;playerId=1'frameborder=0 allowfullscreen></iframe>" media.Body = "<iframe class='postIframe'src='https://embed.nicovideo.jp/watch/sm" + strconv.FormatInt(vid, 10) + "?jsapi=1&amp;playerId=1'frameborder=0 allowfullscreen></iframe>"
return media, true return media, true
} }
} }

View File

@ -10,9 +10,11 @@ import (
"crypto/rand" "crypto/rand"
"encoding/base32" "encoding/base32"
"encoding/base64" "encoding/base64"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"html" "html"
"io/ioutil"
"math" "math"
"os" "os"
"strconv" "strconv"
@ -161,11 +163,12 @@ func RelativeTimeBytes(t time.Time, lang int) []byte {
} }
*/ */
var pMs = 1000; var pMs = 1000
var pSec = pMs * 1000; var pSec = pMs * 1000
var pMin = pSec * 60; var pMin = pSec * 60
var pHour = pMin * 60; var pHour = pMin * 60
var pDay = pHour * 24; var pDay = pHour * 24
func ConvertPerfUnit(quan float64) (out float64, unit string) { func ConvertPerfUnit(quan float64) (out float64, unit string) {
f := func() (float64, string) { f := func() (float64, string) {
switch { switch {
@ -186,7 +189,6 @@ func ConvertPerfUnit(quan float64) (out float64, unit string) {
return math.Ceil(out), unit return math.Ceil(out), unit
} }
// TODO: Write a test for this // TODO: Write a test for this
func ConvertByteUnit(bytes float64) (float64, string) { func ConvertByteUnit(bytes float64) (float64, string) {
switch { switch {
@ -341,7 +343,7 @@ func NameToSlug(name string) (slug string) {
func HasSuspiciousEmail(email string) bool { func HasSuspiciousEmail(email string) bool {
lowEmail := strings.ToLower(email) lowEmail := strings.ToLower(email)
// TODO: Use a more flexible blacklist, perhaps with a similar mechanism to the HTML tag registration system in PreparseMessage() // 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 return true
} }
@ -361,36 +363,113 @@ func HasSuspiciousEmail(email string) bool {
return dotCount > 7 || shortBits > 2 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 // TODO: Write a test for this
func WeakPassword(password, username, email string) error { func WeakPassword(password, username, email string) error {
lowPassword := strings.ToLower(password) lowPassword := strings.ToLower(password)
switch { switch {
case password == "": case password == "":
return errors.New("You didn't put in a password.") return ErrWeakPasswordNone
case len(password) < 8: case len(password) < 8:
return errors.New("Your password needs to be at-least eight characters long") return ErrWeakPasswordShort
case strings.Contains(lowPassword, strings.ToLower(username)) && len(username) > 3: case len(username) > 3 && strings.Contains(lowPassword, strings.ToLower(username)):
return errors.New("You can't use your username in your password.") return ErrWeakPasswordNameInPass
case email != "" && strings.Contains(lowPassword, strings.ToLower(email)): case len(email) > 2 && strings.Contains(lowPassword, strings.ToLower(email)):
return errors.New("You can't use your email in your password.") return ErrWeakPasswordEmailInPass
} }
_, ok := weakPassLit[lowPassword]
if ok {
return ErrWeakPasswordCommon
}
for _, passBit := range weakPassStrings { for _, passBit := range weakPassStrings {
if strings.Contains(lowPassword, passBit) { if strings.Contains(lowPassword, passBit) {
s := "You may not have " return ErrWeakPasswordContains
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")
} }
} }
@ -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 { if upper == 0 {
return errors.New("You don't have any uppercase characters in your password") return ErrWeakPasswordNoUpper
} }
if lower == 0 { if lower == 0 {
return errors.New("You don't have any lowercase characters in your password") return ErrWeakPasswordNoLower
} }
if len(password) < 18 { if len(password) < 18 {
if numbers == 0 {
return ErrWeakPasswordNoNumbers
}
if (len(password) / 2) > len(charMap) { 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) { } else if (len(password) / 3) > len(charMap) {
// Be a little lenient on the number of unique characters for long passwords // 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 return nil
} }
@ -441,11 +520,11 @@ func WeakPassword(password, username, email string) error {
// TODO: Write a test for this // TODO: Write a test for this
func CanonEmail(email string) string { func CanonEmail(email string) string {
email = strings.ToLower(email) email = strings.ToLower(email)
// Gmail emails are equivalent without the dots // Gmail emails are equivalent without the dots
espl := strings.Split(email, "@") espl := strings.Split(email, "@")
if len(espl) >= 2 && espl[1] == "gmail.com" { 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 return email

View File

@ -159,6 +159,9 @@ func storeInit() (err error) {
if err = c.InitEmoji(); err != nil { if err = c.InitEmoji(); err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
if err = c.InitWeakPasswords(); err != nil {
return errors.WithStack(err)
}
log.Print("Loading the static files.") log.Print("Loading the static files.")
if err = c.Themes.LoadStaticFiles(); err != nil { if err = c.Themes.LoadStaticFiles(); err != nil {

View File

@ -2,7 +2,6 @@ package main
import ( import (
"bytes" "bytes"
"strings"
"database/sql" "database/sql"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -10,12 +9,14 @@ import (
"os" "os"
"runtime/debug" "runtime/debug"
"strconv" "strconv"
"strings"
"testing" "testing"
"time" "time"
c "github.com/Azareal/Gosora/common" c "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/common/gauth" "github.com/Azareal/Gosora/common/gauth"
"github.com/Azareal/Gosora/common/phrases" "github.com/Azareal/Gosora/common/phrases"
"github.com/pkg/errors"
) )
func miscinit(t *testing.T) { func miscinit(t *testing.T) {
@ -2002,26 +2003,85 @@ func TestWidgets(t *testing.T) {
func TestUtils(t *testing.T) { func TestUtils(t *testing.T) {
email := "test@example.com" email := "test@example.com"
cemail := c.CanonEmail(email) 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" email = "test.test@example.com"
cemail = c.CanonEmail(email) 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" email = "test.test@gmail.com"
eemail := "testtest@gmail.com" eemail := "testtest@gmail.com"
cemail = c.CanonEmail(email) 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" email = "TEST.test@gmail.com"
eemail = "testtest@gmail.com" eemail = "testtest@gmail.com"
cemail = c.CanonEmail(email) 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" email = "TEST.test@example.com"
lowEmail := strings.ToLower(email) lowEmail := strings.ToLower(email)
cemail = c.CanonEmail(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 // TODO: More utils.go tests
} }