Make password strength configurable

This commit is contained in:
Jannis Mattheis 2018-02-23 19:14:14 +01:00
parent 8cffa66531
commit cda4127551
11 changed files with 31 additions and 30 deletions

View File

@ -22,6 +22,7 @@ type UserDatabase interface {
// The UserAPI provides handlers for managing users.
type UserAPI struct {
DB UserDatabase
PasswordStrength int
}
// GetUsers returns all the users
@ -49,7 +50,7 @@ func (a *UserAPI) CreateUser(ctx *gin.Context) {
if len(user.Pass) == 0 {
ctx.AbortWithError(400, errors.New("password may not be empty"))
} else {
internal := toInternal(&user, []byte{})
internal := a.toInternal(&user, []byte{})
if a.DB.GetUserByName(internal.Name) == nil {
a.DB.CreateUser(internal)
ctx.JSON(200, toExternal(internal))
@ -95,7 +96,7 @@ func (a *UserAPI) ChangePassword(ctx *gin.Context) {
pw := userPassword{}
if err := ctx.Bind(&pw); err == nil {
user := a.DB.GetUserByID(auth.GetUserID(ctx))
user.Pass = auth.CreatePassword(pw.Pass)
user.Pass = auth.CreatePassword(pw.Pass, a.PasswordStrength)
a.DB.UpdateUser(user)
}
}
@ -106,7 +107,7 @@ func (a *UserAPI) UpdateUserByID(ctx *gin.Context) {
var user *model.UserExternal
if err := ctx.Bind(&user); err == nil {
if oldUser := a.DB.GetUserByID(id); oldUser != nil {
internal := toInternal(user, oldUser.Pass)
internal := a.toInternal(user, oldUser.Pass)
internal.ID = id
a.DB.UpdateUser(internal)
ctx.JSON(200, toExternal(internal))
@ -124,13 +125,13 @@ func toUInt(id string) (uint, error) {
return uint(parsed), err
}
func toInternal(response *model.UserExternal, pw []byte) *model.User {
func (a *UserAPI) toInternal(response *model.UserExternal, pw []byte) *model.User {
user := &model.User{
Name: response.Name,
Admin: response.Admin,
}
if response.Pass != "" {
user.Pass = auth.CreatePassword(response.Pass)
user.Pass = auth.CreatePassword(response.Pass, a.PasswordStrength)
} else {
user.Pass = pw
}

View File

@ -119,7 +119,7 @@ func (s *UserSuite) Test_DeleteUserByID() {
func (s *UserSuite) Test_CreateUser() {
pwByte := []byte{1, 2, 3}
patch := monkey.Patch(auth.CreatePassword, func(pw string) []byte {
patch := monkey.Patch(auth.CreatePassword, func(pw string, strength int) []byte {
if pw == "mylittlepony" {
return pwByte
}
@ -160,7 +160,7 @@ func (s *UserSuite) Test_CreateUser_NoName() {
func (s *UserSuite) Test_CreateUser_NameAlreadyExists() {
pwByte := []byte{1, 2, 3}
monkey.Patch(auth.CreatePassword, func(pw string) []byte { return pwByte })
monkey.Patch(auth.CreatePassword, func(pw string, strength int) []byte { return pwByte })
s.db.On("GetUserByName", "tom").Return(&model.User{ID: 3, Name: "tom"})
@ -215,7 +215,7 @@ func (s *UserSuite) Test_UpdateUserByID_UpdateNotPassword() {
func (s *UserSuite) Test_UpdateUserByID_UpdatePassword() {
pwByte := []byte{1, 2, 3}
patch := monkey.Patch(auth.CreatePassword, func(pw string) []byte { return pwByte })
patch := monkey.Patch(auth.CreatePassword, func(pw string, strength int) []byte { return pwByte })
defer patch.Unpatch()
s.db.On("GetUserByID", uint(2)).Return(normalUser)
@ -236,7 +236,7 @@ func (s *UserSuite) Test_UpdateUserByID_UpdatePassword() {
func (s *UserSuite) Test_UpdatePassword() {
pwByte := []byte{1, 2, 3}
createPasswordPatch := monkey.Patch(auth.CreatePassword, func(pw string) []byte { return pwByte })
createPasswordPatch := monkey.Patch(auth.CreatePassword, func(pw string, strength int) []byte { return pwByte })
defer createPasswordPatch.Unpatch()
patchUser := monkey.Patch(auth.GetUserID, func(*gin.Context) uint { return 1 })
defer patchUser.Unpatch()

4
app.go
View File

@ -31,14 +31,14 @@ func main() {
fmt.Println("Starting Gotify version", vInfo.Version+"@"+BuildDate)
rand.Seed(time.Now().UnixNano())
conf := config.Get()
db, err := database.New(conf.Database.Dialect, conf.Database.Connection, conf.DefaultUser.Name, conf.DefaultUser.Pass)
db, err := database.New(conf.Database.Dialect, conf.Database.Connection, conf.DefaultUser.Name, conf.DefaultUser.Pass, conf.PassStrength)
if err != nil {
panic(err)
}
defer db.Close()
gin.SetMode(gin.ReleaseMode)
engine, closeable := router.Create(db, vInfo)
engine, closeable := router.Create(db, vInfo, conf)
defer closeable()
runner.Run(engine, conf)

View File

@ -39,8 +39,8 @@ func (s *AuthenticationSuite) SetupSuite() {
s.DB.On("GetUserByID", uint(1)).Return(&model.User{ID: 1, Name: "irrelevant", Admin: false})
s.DB.On("GetUserByID", uint(2)).Return(&model.User{ID: 2, Name: "irrelevant", Admin: true})
s.DB.On("GetUserByName", "existing").Return(&model.User{Name: "existing", Pass: CreatePassword("pw")})
s.DB.On("GetUserByName", "admin").Return(&model.User{Name: "admin", Pass: CreatePassword("pw"), Admin: true})
s.DB.On("GetUserByName", "existing").Return(&model.User{Name: "existing", Pass: CreatePassword("pw", 5)})
s.DB.On("GetUserByName", "admin").Return(&model.User{Name: "admin", Pass: CreatePassword("pw", 5), Admin: true})
s.DB.On("GetUserByName", mock.Anything).Return(nil)
}

View File

@ -2,10 +2,8 @@ package auth
import "golang.org/x/crypto/bcrypt"
var strength = 13
// CreatePassword returns a hashed version of the given password.
func CreatePassword(pw string) []byte {
func CreatePassword(pw string, strength int) []byte {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(pw), strength)
if err != nil {
panic(err)

View File

@ -9,16 +9,15 @@ import (
)
func TestPasswordSuccess(t *testing.T) {
password := CreatePassword("secret")
password := CreatePassword("secret", 5)
assert.Equal(t, true, ComparePassword(password, []byte("secret")))
}
func TestPasswordFailure(t *testing.T) {
password := CreatePassword("secret")
password := CreatePassword("secret", 5)
assert.Equal(t, false, ComparePassword(password, []byte("secretx")))
}
func TestBCryptFailure(t *testing.T) {
strength = 12312 // invalid value
assert.Panics(t, func() { CreatePassword("secret") })
assert.Panics(t, func() { CreatePassword("secret", 12312) })
}

View File

@ -28,6 +28,7 @@ type Configuration struct {
Name string `default:"admin"`
Pass string `default:"admin"`
}
PassStrength int `default:"10"`
}
// Get returns the configuration extracted from env variables or config file.

View File

@ -10,7 +10,7 @@ import (
)
// New creates a new wrapper for the gorm database framework.
func New(dialect, connection, defaultUser, defaultPass string) (*GormDatabase, error) {
func New(dialect, connection, defaultUser, defaultPass string, strength int) (*GormDatabase, error) {
db, err := gorm.Open(dialect, connection)
if err != nil {
return nil, err
@ -25,7 +25,7 @@ func New(dialect, connection, defaultUser, defaultPass string) (*GormDatabase, e
if !db.HasTable(new(model.User)) && !db.HasTable(new(model.Message)) &&
!db.HasTable(new(model.Client)) && !db.HasTable(new(model.Application)) {
db.AutoMigrate(new(model.User), new(model.Application), new(model.Message), new(model.Client))
db.Create(&model.User{Name: defaultUser, Pass: auth.CreatePassword(defaultPass), Admin: true})
db.Create(&model.User{Name: defaultUser, Pass: auth.CreatePassword(defaultPass, strength), Admin: true})
}
return &GormDatabase{DB: db}, nil

View File

@ -18,7 +18,7 @@ type DatabaseSuite struct {
}
func (s *DatabaseSuite) BeforeTest(suiteName, testName string) {
db, err := New("sqlite3", "testdb.db", "defaultUser", "defaultPass")
db, err := New("sqlite3", "testdb.db", "defaultUser", "defaultPass", 5)
assert.Nil(s.T(), err)
s.db = db
}
@ -29,6 +29,6 @@ func (s *DatabaseSuite) AfterTest(suiteName, testName string) {
}
func TestInvalidDialect(t *testing.T) {
_, err := New("asdf", "testdb.db", "defaultUser", "defaultPass")
_, err := New("asdf", "testdb.db", "defaultUser", "defaultPass", 5)
assert.NotNil(t, err)
}

View File

@ -12,18 +12,19 @@ import (
"net/http"
"github.com/gotify/server/config"
"github.com/gotify/server/docs"
"github.com/gotify/server/stream"
"github.com/gotify/server/model"
"github.com/gotify/server/stream"
)
// Create creates the gin engine with all routes.
func Create(db *database.GormDatabase, vInfo *model.VersionInfo) (*gin.Engine, func()) {
func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Configuration) (*gin.Engine, func()) {
streamHandler := stream.New(200*time.Second, 15*time.Second)
authentication := auth.Auth{DB: db}
messageHandler := api.MessageAPI{Notifier: streamHandler, DB: db}
tokenHandler := api.TokenAPI{DB: db}
userHandler := api.UserAPI{DB: db}
userHandler := api.UserAPI{DB: db, PasswordStrength: conf.PassStrength}
g := gin.New()

View File

@ -15,6 +15,7 @@ import (
"github.com/gotify/server/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/gotify/server/config"
)
var (
@ -36,9 +37,9 @@ type IntegrationSuite struct {
func (s *IntegrationSuite) BeforeTest(string, string) {
gin.SetMode(gin.TestMode)
var err error
s.db, err = database.New("sqlite3", "itest.db", "admin", "pw")
s.db, err = database.New("sqlite3", "itest.db", "admin", "pw", 5)
assert.Nil(s.T(), err)
g, closable := Create(s.db, &model.VersionInfo{Version:"1.0.0", BuildDate:"2018-02-20-17:30:47", Branch:"master", Commit:"asdasds"})
g, closable := Create(s.db, &model.VersionInfo{Version:"1.0.0", BuildDate:"2018-02-20-17:30:47", Branch:"master", Commit:"asdasds"}, &config.Configuration{PassStrength:5})
s.closable = closable
s.server = httptest.NewServer(g)
}