Auth: Add user entity functions and tests #98

This commit is contained in:
Timo Volkmann 2021-08-16 20:51:55 +02:00
parent 35869c6620
commit 19e9c7560e
2 changed files with 190 additions and 5 deletions

View file

@ -1,6 +1,7 @@
package entity
import (
"errors"
"fmt"
"time"
@ -137,12 +138,12 @@ func (m *User) Save() error {
}
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
func (m *User) BeforeCreate(scope *gorm.Scope) error {
func (m *User) BeforeCreate(tx *gorm.DB) error {
if rnd.IsUID(m.UserUID, 'u') {
return nil
}
return scope.SetColumn("UserUID", rnd.PPID('u'))
m.UserUID = rnd.PPID('u')
return nil
}
// FirstOrCreateUser returns an existing row, inserts a new row or nil in case of errors.
@ -191,6 +192,21 @@ func FindUserByUID(uid string) *User {
}
}
// DeleteUserByName deletes an existing user or returns error if not found.
func DeleteUserByName(userName string) error {
if userName == "" {
return fmt.Errorf("can not delete user from db: empty username")
}
user := FindUserByName(userName)
if err := Db().Where("user_name = ?", userName).Delete(&User{}).Error; user == nil || err != nil {
return fmt.Errorf("user %s not found", txt.Quote(userName))
}
if err := Db().Where("uid = ?", user.UserUID).Delete(&Password{}).Error; err != nil {
log.Debug(err)
}
return nil
}
// String returns an identifier that can be used in logs.
func (m *User) String() string {
if m.UserName != "" {
@ -321,3 +337,58 @@ func (m *User) Role() acl.Role {
return acl.RoleDefault
}
// Validate Makes sure username and email are unique and meet requirements. Returns error if any property is invalid
func (m *User) Validate() error {
if m.UserName == "" {
return errors.New("username must not be empty")
}
if len(m.UserName) < 4 {
return errors.New("username must be at least 4 characters")
}
var result = &User{}
var err error
if err = Db().Unscoped().Where("user_name = ?", m.UserName).First(result).Error; err == nil {
return errors.New("username already exists")
} else if err != gorm.ErrRecordNotFound {
return err
}
// stop here if no email is provided
if m.PrimaryEmail == "" {
return nil
}
if err = Db().Unscoped().Where("primary_email = ?", m.PrimaryEmail).First(result).Error; err == nil {
return errors.New("email already exists")
} else if err != gorm.ErrRecordNotFound {
return err
}
return nil
}
// CreateWithPassword Creates User with Password in db transaction.
func (m *User) CreateWithPassword(password string) error {
if len(password) < 4 {
return fmt.Errorf("new password for %s must be at least 4 characters", txt.Quote(m.UserName))
}
return Db().Transaction(func(tx *gorm.DB) error {
if err := tx.Create(m).Error; err != nil {
return err
}
pw := NewPassword(m.UserUID, password)
if err := tx.Create(&pw).Error; err != nil {
return err
}
log.Infof("created user %v with uid %v", txt.Quote(m.UserName), txt.Quote(m.UserUID))
return nil
})
}
// AllUsers Returns a list of all registered Users.
func AllUsers() []User {
var users []User
if err := Db().Find(&users).Error; err != nil {
log.Error(err)
return []User{}
}
return users
}

View file

@ -1,9 +1,8 @@
package entity
import (
"testing"
"github.com/photoprism/photoprism/internal/acl"
"testing"
"github.com/stretchr/testify/assert"
)
@ -376,3 +375,118 @@ func TestUser_Role(t *testing.T) {
assert.Equal(t, acl.Role("*"), p.Role())
})
}
func TestDeleteUserByName(t *testing.T) {
u := FirstOrCreateUser(&User{
ID: 877,
AddressID: 1,
UserName: "delete",
FullName: "Delete",
PrimaryEmail: "delete@example.com",
})
t.Run("delete empty username", func(t *testing.T) {
err := DeleteUserByName("")
assert.Error(t, err)
})
t.Run("delete fail", func(t *testing.T) {
err := DeleteUserByName("notmatching")
assert.Error(t, err)
})
t.Run("delete success", func(t *testing.T) {
err := DeleteUserByName(u.UserName)
assert.Nil(t, err)
})
}
func TestUser_Validate(t *testing.T) {
t.Run("valid", func(t *testing.T) {
u := &User{
ID: 878,
AddressID: 1,
UserName: "validate",
FullName: "Validate",
PrimaryEmail: "validate@example.com",
}
err := u.Validate()
assert.Nil(t, err)
})
t.Run("username empty", func(t *testing.T) {
u := &User{
ID: 878,
AddressID: 1,
UserName: "",
FullName: "Validate",
PrimaryEmail: "validate@example.com",
}
err := u.Validate()
assert.Error(t, err)
})
t.Run("username too short", func(t *testing.T) {
u := &User{
ID: 878,
AddressID: 1,
UserName: "val",
FullName: "Validate",
PrimaryEmail: "validate@example.com",
}
err := u.Validate()
assert.Error(t, err)
})
t.Run("username not unique", func(t *testing.T) {
u := FirstOrCreateUser(&User{
ID: 879,
AddressID: 1,
UserName: "notunique",
})
err := u.Validate()
assert.Error(t, err)
})
t.Run("email not unique", func(t *testing.T) {
u := FirstOrCreateUser(&User{
ID: 880,
AddressID: 1,
PrimaryEmail: "notunique@example.com",
})
err := u.Validate()
assert.Error(t, err)
})
}
func TestCreateWithPassword(t *testing.T) {
t.Run("valid", func(t *testing.T) {
u := &User{
ID: 881,
AddressID: 1,
UserName: "thomas",
FullName: "Thomas",
PrimaryEmail: "thomas@example.com",
}
err := u.CreateWithPassword("helloworld")
assert.Nil(t, err)
})
t.Run("password too short", func(t *testing.T) {
u := &User{
ID: 882,
AddressID: 1,
UserName: "thomas",
FullName: "Thomas",
PrimaryEmail: "thomas@example.com",
}
err := u.CreateWithPassword("hel")
assert.Error(t, err)
})
}
func TestAllUsers(t *testing.T) {
t.Run("list all", func(t *testing.T) {
users := AllUsers()
for _, user := range users {
log.Infof("user: %v, %s, %s, %s", user.ID, user.UserUID, user.UserName, user.FullName)
}
log.Infof("user count: %v", len(users))
assert.Greater(t, len(users), 3)
})
}