Move user related model into models/user (#17781)
* Move user related model into models/user * Fix lint for windows * Fix windows lint * Fix windows lint * Move some tests in models * Merge
This commit is contained in:
parent
4e7ca946da
commit
a666829a37
345 changed files with 4230 additions and 3813 deletions
113
models/user/avatar.go
Normal file
113
models/user/avatar.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/avatars"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/avatar"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
)
|
||||
|
||||
// CustomAvatarRelativePath returns user custom avatar relative path.
|
||||
func (u *User) CustomAvatarRelativePath() string {
|
||||
return u.Avatar
|
||||
}
|
||||
|
||||
// GenerateRandomAvatar generates a random avatar for user.
|
||||
func GenerateRandomAvatar(u *User) error {
|
||||
return GenerateRandomAvatarCtx(db.DefaultContext, u)
|
||||
}
|
||||
|
||||
// GenerateRandomAvatarCtx generates a random avatar for user.
|
||||
func GenerateRandomAvatarCtx(ctx context.Context, u *User) error {
|
||||
seed := u.Email
|
||||
if len(seed) == 0 {
|
||||
seed = u.Name
|
||||
}
|
||||
|
||||
img, err := avatar.RandomImage([]byte(seed))
|
||||
if err != nil {
|
||||
return fmt.Errorf("RandomImage: %v", err)
|
||||
}
|
||||
|
||||
u.Avatar = avatars.HashEmail(seed)
|
||||
|
||||
// Don't share the images so that we can delete them easily
|
||||
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
|
||||
if err := png.Encode(w, img); err != nil {
|
||||
log.Error("Encode: %v", err)
|
||||
}
|
||||
return err
|
||||
}); err != nil {
|
||||
return fmt.Errorf("Failed to create dir %s: %v", u.CustomAvatarRelativePath(), err)
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("New random avatar created: %d", u.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
|
||||
func (u *User) AvatarLinkWithSize(size int) string {
|
||||
if u.ID == -1 {
|
||||
// ghost user
|
||||
return avatars.DefaultAvatarLink()
|
||||
}
|
||||
|
||||
useLocalAvatar := false
|
||||
autoGenerateAvatar := false
|
||||
|
||||
switch {
|
||||
case u.UseCustomAvatar:
|
||||
useLocalAvatar = true
|
||||
case setting.DisableGravatar, setting.OfflineMode:
|
||||
useLocalAvatar = true
|
||||
autoGenerateAvatar = true
|
||||
}
|
||||
|
||||
if useLocalAvatar {
|
||||
if u.Avatar == "" && autoGenerateAvatar {
|
||||
if err := GenerateRandomAvatar(u); err != nil {
|
||||
log.Error("GenerateRandomAvatar: %v", err)
|
||||
}
|
||||
}
|
||||
if u.Avatar == "" {
|
||||
return avatars.DefaultAvatarLink()
|
||||
}
|
||||
return avatars.GenerateUserAvatarImageLink(u.Avatar, size)
|
||||
}
|
||||
return avatars.GenerateEmailAvatarFastLink(u.AvatarEmail, size)
|
||||
}
|
||||
|
||||
// AvatarLink returns the full avatar link with http host
|
||||
func (u *User) AvatarLink() string {
|
||||
link := u.AvatarLinkWithSize(0)
|
||||
if !strings.HasPrefix(link, "//") && !strings.Contains(link, "://") {
|
||||
return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL+"/")
|
||||
}
|
||||
return link
|
||||
}
|
||||
|
||||
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data
|
||||
func (u *User) IsUploadAvatarChanged(data []byte) bool {
|
||||
if !u.UseCustomAvatar || len(u.Avatar) == 0 {
|
||||
return true
|
||||
}
|
||||
avatarID := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data)))))
|
||||
return u.Avatar != avatarID
|
||||
}
|
80
models/user/error.go
Normal file
80
models/user/error.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ____ ___
|
||||
// | | \______ ___________
|
||||
// | | / ___// __ \_ __ \
|
||||
// | | /\___ \\ ___/| | \/
|
||||
// |______//____ >\___ >__|
|
||||
// \/ \/
|
||||
|
||||
// ErrUserAlreadyExist represents a "user already exists" error.
|
||||
type ErrUserAlreadyExist struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// IsErrUserAlreadyExist checks if an error is a ErrUserAlreadyExists.
|
||||
func IsErrUserAlreadyExist(err error) bool {
|
||||
_, ok := err.(ErrUserAlreadyExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("user already exists [name: %s]", err.Name)
|
||||
}
|
||||
|
||||
// ErrUserNotExist represents a "UserNotExist" kind of error.
|
||||
type ErrUserNotExist struct {
|
||||
UID int64
|
||||
Name string
|
||||
KeyID int64
|
||||
}
|
||||
|
||||
// IsErrUserNotExist checks if an error is a ErrUserNotExist.
|
||||
func IsErrUserNotExist(err error) bool {
|
||||
_, ok := err.(ErrUserNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserNotExist) Error() string {
|
||||
return fmt.Sprintf("user does not exist [uid: %d, name: %s, keyid: %d]", err.UID, err.Name, err.KeyID)
|
||||
}
|
||||
|
||||
// ErrUserProhibitLogin represents a "ErrUserProhibitLogin" kind of error.
|
||||
type ErrUserProhibitLogin struct {
|
||||
UID int64
|
||||
Name string
|
||||
}
|
||||
|
||||
// IsErrUserProhibitLogin checks if an error is a ErrUserProhibitLogin
|
||||
func IsErrUserProhibitLogin(err error) bool {
|
||||
_, ok := err.(ErrUserProhibitLogin)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserProhibitLogin) Error() string {
|
||||
return fmt.Sprintf("user is not allowed login [uid: %d, name: %s]", err.UID, err.Name)
|
||||
}
|
||||
|
||||
// ErrUserInactive represents a "ErrUserInactive" kind of error.
|
||||
type ErrUserInactive struct {
|
||||
UID int64
|
||||
Name string
|
||||
}
|
||||
|
||||
// IsErrUserInactive checks if an error is a ErrUserInactive
|
||||
func IsErrUserInactive(err error) bool {
|
||||
_, ok := err.(ErrUserInactive)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserInactive) Error() string {
|
||||
return fmt.Sprintf("user is inactive [uid: %d, name: %s]", err.UID, err.Name)
|
||||
}
|
|
@ -17,5 +17,8 @@ func TestMain(m *testing.M) {
|
|||
"user_redirect.yml",
|
||||
"follow.yml",
|
||||
"user_open_id.yml",
|
||||
"two_factor.yml",
|
||||
"oauth2_application.yml",
|
||||
"user.yml",
|
||||
)
|
||||
}
|
||||
|
|
167
models/user/search.go
Normal file
167
models/user/search.go
Normal file
|
@ -0,0 +1,167 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// SearchUserOptions contains the options for searching
|
||||
type SearchUserOptions struct {
|
||||
db.ListOptions
|
||||
Keyword string
|
||||
Type UserType
|
||||
UID int64
|
||||
OrderBy db.SearchOrderBy
|
||||
Visible []structs.VisibleType
|
||||
Actor *User // The user doing the search
|
||||
SearchByEmail bool // Search by email as well as username/full name
|
||||
|
||||
IsActive util.OptionalBool
|
||||
IsAdmin util.OptionalBool
|
||||
IsRestricted util.OptionalBool
|
||||
IsTwoFactorEnabled util.OptionalBool
|
||||
IsProhibitLogin util.OptionalBool
|
||||
}
|
||||
|
||||
func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session {
|
||||
var cond builder.Cond = builder.Eq{"type": opts.Type}
|
||||
if len(opts.Keyword) > 0 {
|
||||
lowerKeyword := strings.ToLower(opts.Keyword)
|
||||
keywordCond := builder.Or(
|
||||
builder.Like{"lower_name", lowerKeyword},
|
||||
builder.Like{"LOWER(full_name)", lowerKeyword},
|
||||
)
|
||||
if opts.SearchByEmail {
|
||||
keywordCond = keywordCond.Or(builder.Like{"LOWER(email)", lowerKeyword})
|
||||
}
|
||||
|
||||
cond = cond.And(keywordCond)
|
||||
}
|
||||
|
||||
// If visibility filtered
|
||||
if len(opts.Visible) > 0 {
|
||||
cond = cond.And(builder.In("visibility", opts.Visible))
|
||||
}
|
||||
|
||||
if opts.Actor != nil {
|
||||
var exprCond builder.Cond = builder.Expr("org_user.org_id = `user`.id")
|
||||
|
||||
// If Admin - they see all users!
|
||||
if !opts.Actor.IsAdmin {
|
||||
// Force visibility for privacy
|
||||
var accessCond builder.Cond
|
||||
if !opts.Actor.IsRestricted {
|
||||
accessCond = builder.Or(
|
||||
builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID}, builder.Eq{"visibility": structs.VisibleTypePrivate}))),
|
||||
builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
|
||||
} else {
|
||||
// restricted users only see orgs they are a member of
|
||||
accessCond = builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID})))
|
||||
}
|
||||
// Don't forget about self
|
||||
accessCond = accessCond.Or(builder.Eq{"id": opts.Actor.ID})
|
||||
cond = cond.And(accessCond)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Force visibility for privacy
|
||||
// Not logged in - only public users
|
||||
cond = cond.And(builder.In("visibility", structs.VisibleTypePublic))
|
||||
}
|
||||
|
||||
if opts.UID > 0 {
|
||||
cond = cond.And(builder.Eq{"id": opts.UID})
|
||||
}
|
||||
|
||||
if !opts.IsActive.IsNone() {
|
||||
cond = cond.And(builder.Eq{"is_active": opts.IsActive.IsTrue()})
|
||||
}
|
||||
|
||||
if !opts.IsAdmin.IsNone() {
|
||||
cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.IsTrue()})
|
||||
}
|
||||
|
||||
if !opts.IsRestricted.IsNone() {
|
||||
cond = cond.And(builder.Eq{"is_restricted": opts.IsRestricted.IsTrue()})
|
||||
}
|
||||
|
||||
if !opts.IsProhibitLogin.IsNone() {
|
||||
cond = cond.And(builder.Eq{"prohibit_login": opts.IsProhibitLogin.IsTrue()})
|
||||
}
|
||||
|
||||
e := db.GetEngine(db.DefaultContext)
|
||||
if opts.IsTwoFactorEnabled.IsNone() {
|
||||
return e.Where(cond)
|
||||
}
|
||||
|
||||
// 2fa filter uses LEFT JOIN to check whether a user has a 2fa record
|
||||
// TODO: bad performance here, maybe there will be a column "is_2fa_enabled" in the future
|
||||
if opts.IsTwoFactorEnabled.IsTrue() {
|
||||
cond = cond.And(builder.Expr("two_factor.uid IS NOT NULL"))
|
||||
} else {
|
||||
cond = cond.And(builder.Expr("two_factor.uid IS NULL"))
|
||||
}
|
||||
|
||||
return e.Join("LEFT OUTER", "two_factor", "two_factor.uid = `user`.id").
|
||||
Where(cond)
|
||||
}
|
||||
|
||||
// SearchUsers takes options i.e. keyword and part of user name to search,
|
||||
// it returns results in given range and number of total results.
|
||||
func SearchUsers(opts *SearchUserOptions) (users []*User, _ int64, _ error) {
|
||||
sessCount := opts.toSearchQueryBase()
|
||||
defer sessCount.Close()
|
||||
count, err := sessCount.Count(new(User))
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("Count: %v", err)
|
||||
}
|
||||
|
||||
if len(opts.OrderBy) == 0 {
|
||||
opts.OrderBy = db.SearchOrderByAlphabetically
|
||||
}
|
||||
|
||||
sessQuery := opts.toSearchQueryBase().OrderBy(opts.OrderBy.String())
|
||||
defer sessQuery.Close()
|
||||
if opts.Page != 0 {
|
||||
sessQuery = db.SetSessionPagination(sessQuery, opts)
|
||||
}
|
||||
|
||||
// the sql may contain JOIN, so we must only select User related columns
|
||||
sessQuery = sessQuery.Select("`user`.*")
|
||||
users = make([]*User, 0, opts.PageSize)
|
||||
return users, count, sessQuery.Find(&users)
|
||||
}
|
||||
|
||||
// IterateUser iterate users
|
||||
func IterateUser(f func(user *User) error) error {
|
||||
var start int
|
||||
batchSize := setting.Database.IterateBufferSize
|
||||
for {
|
||||
users := make([]*User, 0, batchSize)
|
||||
if err := db.GetEngine(db.DefaultContext).Limit(batchSize, start).Find(&users); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return nil
|
||||
}
|
||||
start += len(users)
|
||||
|
||||
for _, user := range users {
|
||||
if err := f(user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1125
models/user/user.go
Normal file
1125
models/user/user.go
Normal file
File diff suppressed because it is too large
Load diff
355
models/user/user_test.go
Normal file
355
models/user/user_test.go
Normal file
|
@ -0,0 +1,355 @@
|
|||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/login"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOAuth2Application_LoadUser(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
app := unittest.AssertExistsAndLoadBean(t, &login.OAuth2Application{ID: 1}).(*login.OAuth2Application)
|
||||
user, err := GetUserByID(app.UID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, user)
|
||||
}
|
||||
|
||||
func TestGetUserEmailsByNames(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
// ignore none active user email
|
||||
assert.Equal(t, []string{"user8@example.com"}, GetUserEmailsByNames([]string{"user8", "user9"}))
|
||||
assert.Equal(t, []string{"user8@example.com", "user5@example.com"}, GetUserEmailsByNames([]string{"user8", "user5"}))
|
||||
|
||||
assert.Equal(t, []string{"user8@example.com"}, GetUserEmailsByNames([]string{"user8", "user7"}))
|
||||
}
|
||||
|
||||
func TestCanCreateOrganization(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
admin := unittest.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
|
||||
assert.True(t, admin.CanCreateOrganization())
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||
assert.True(t, user.CanCreateOrganization())
|
||||
// Disable user create organization permission.
|
||||
user.AllowCreateOrganization = false
|
||||
assert.False(t, user.CanCreateOrganization())
|
||||
|
||||
setting.Admin.DisableRegularOrgCreation = true
|
||||
user.AllowCreateOrganization = true
|
||||
assert.True(t, admin.CanCreateOrganization())
|
||||
assert.False(t, user.CanCreateOrganization())
|
||||
}
|
||||
|
||||
func TestSearchUsers(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
testSuccess := func(opts *SearchUserOptions, expectedUserOrOrgIDs []int64) {
|
||||
users, _, err := SearchUsers(opts)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, users, len(expectedUserOrOrgIDs), opts) {
|
||||
for i, expectedID := range expectedUserOrOrgIDs {
|
||||
assert.EqualValues(t, expectedID, users[i].ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test orgs
|
||||
testOrgSuccess := func(opts *SearchUserOptions, expectedOrgIDs []int64) {
|
||||
opts.Type = UserTypeOrganization
|
||||
testSuccess(opts, expectedOrgIDs)
|
||||
}
|
||||
|
||||
testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1, PageSize: 2}},
|
||||
[]int64{3, 6})
|
||||
|
||||
testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 2, PageSize: 2}},
|
||||
[]int64{7, 17})
|
||||
|
||||
testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 3, PageSize: 2}},
|
||||
[]int64{19, 25})
|
||||
|
||||
testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}},
|
||||
[]int64{26})
|
||||
|
||||
testOrgSuccess(&SearchUserOptions{ListOptions: db.ListOptions{Page: 5, PageSize: 2}},
|
||||
[]int64{})
|
||||
|
||||
// test users
|
||||
testUserSuccess := func(opts *SearchUserOptions, expectedUserIDs []int64) {
|
||||
opts.Type = UserTypeIndividual
|
||||
testSuccess(opts, expectedUserIDs)
|
||||
}
|
||||
|
||||
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}},
|
||||
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32})
|
||||
|
||||
testUserSuccess(&SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolFalse},
|
||||
[]int64{9})
|
||||
|
||||
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue},
|
||||
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 28, 29, 30, 32})
|
||||
|
||||
testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue},
|
||||
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
|
||||
|
||||
// order by name asc default
|
||||
testUserSuccess(&SearchUserOptions{Keyword: "user1", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue},
|
||||
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
|
||||
|
||||
testUserSuccess(&SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsAdmin: util.OptionalBoolTrue},
|
||||
[]int64{1})
|
||||
|
||||
testUserSuccess(&SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsRestricted: util.OptionalBoolTrue},
|
||||
[]int64{29, 30})
|
||||
|
||||
testUserSuccess(&SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: util.OptionalBoolTrue},
|
||||
[]int64{30})
|
||||
|
||||
testUserSuccess(&SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsTwoFactorEnabled: util.OptionalBoolTrue},
|
||||
[]int64{24})
|
||||
}
|
||||
|
||||
func TestEmailNotificationPreferences(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
for _, test := range []struct {
|
||||
expected string
|
||||
userID int64
|
||||
}{
|
||||
{EmailNotificationsEnabled, 1},
|
||||
{EmailNotificationsEnabled, 2},
|
||||
{EmailNotificationsOnMention, 3},
|
||||
{EmailNotificationsOnMention, 4},
|
||||
{EmailNotificationsEnabled, 5},
|
||||
{EmailNotificationsEnabled, 6},
|
||||
{EmailNotificationsDisabled, 7},
|
||||
{EmailNotificationsEnabled, 8},
|
||||
{EmailNotificationsOnMention, 9},
|
||||
} {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &User{ID: test.userID}).(*User)
|
||||
assert.Equal(t, test.expected, user.EmailNotifications())
|
||||
|
||||
// Try all possible settings
|
||||
assert.NoError(t, SetEmailNotifications(user, EmailNotificationsEnabled))
|
||||
assert.Equal(t, EmailNotificationsEnabled, user.EmailNotifications())
|
||||
|
||||
assert.NoError(t, SetEmailNotifications(user, EmailNotificationsOnMention))
|
||||
assert.Equal(t, EmailNotificationsOnMention, user.EmailNotifications())
|
||||
|
||||
assert.NoError(t, SetEmailNotifications(user, EmailNotificationsDisabled))
|
||||
assert.Equal(t, EmailNotificationsDisabled, user.EmailNotifications())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashPasswordDeterministic(t *testing.T) {
|
||||
b := make([]byte, 16)
|
||||
u := &User{}
|
||||
algos := []string{"argon2", "pbkdf2", "scrypt", "bcrypt"}
|
||||
for j := 0; j < len(algos); j++ {
|
||||
u.PasswdHashAlgo = algos[j]
|
||||
for i := 0; i < 50; i++ {
|
||||
// generate a random password
|
||||
rand.Read(b)
|
||||
pass := string(b)
|
||||
|
||||
// save the current password in the user - hash it and store the result
|
||||
u.SetPassword(pass)
|
||||
r1 := u.Passwd
|
||||
|
||||
// run again
|
||||
u.SetPassword(pass)
|
||||
r2 := u.Passwd
|
||||
|
||||
assert.NotEqual(t, r1, r2)
|
||||
assert.True(t, u.ValidatePassword(pass))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHashPassword(b *testing.B) {
|
||||
// BenchmarkHashPassword ensures that it takes a reasonable amount of time
|
||||
// to hash a password - in order to protect from brute-force attacks.
|
||||
pass := "password1337"
|
||||
u := &User{Passwd: pass}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
u.SetPassword(pass)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewGitSig(t *testing.T) {
|
||||
users := make([]*User, 0, 20)
|
||||
err := db.GetEngine(db.DefaultContext).Find(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, user := range users {
|
||||
sig := user.NewGitSig()
|
||||
assert.NotContains(t, sig.Name, "<")
|
||||
assert.NotContains(t, sig.Name, ">")
|
||||
assert.NotContains(t, sig.Name, "\n")
|
||||
assert.NotEqual(t, len(strings.TrimSpace(sig.Name)), 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisplayName(t *testing.T) {
|
||||
users := make([]*User, 0, 20)
|
||||
err := db.GetEngine(db.DefaultContext).Find(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, user := range users {
|
||||
displayName := user.DisplayName()
|
||||
assert.Equal(t, strings.TrimSpace(displayName), displayName)
|
||||
if len(strings.TrimSpace(user.FullName)) == 0 {
|
||||
assert.Equal(t, user.Name, displayName)
|
||||
}
|
||||
assert.NotEqual(t, len(strings.TrimSpace(displayName)), 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateUserInvalidEmail(t *testing.T) {
|
||||
user := &User{
|
||||
Name: "GiteaBot",
|
||||
Email: "GiteaBot@gitea.io\r\n",
|
||||
Passwd: ";p['////..-++']",
|
||||
IsAdmin: false,
|
||||
Theme: setting.UI.DefaultTheme,
|
||||
MustChangePassword: false,
|
||||
}
|
||||
|
||||
err := CreateUser(user)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrEmailInvalid(err))
|
||||
}
|
||||
|
||||
func TestGetUserIDsByNames(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
// ignore non existing
|
||||
IDs, err := GetUserIDsByNames([]string{"user1", "user2", "none_existing_user"}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []int64{1, 2}, IDs)
|
||||
|
||||
// ignore non existing
|
||||
IDs, err = GetUserIDsByNames([]string{"user1", "do_not_exist"}, false)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, []int64(nil), IDs)
|
||||
}
|
||||
|
||||
func TestGetMaileableUsersByIDs(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
results, err := GetMaileableUsersByIDs([]int64{1, 4}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, results, 1)
|
||||
if len(results) > 1 {
|
||||
assert.Equal(t, results[0].ID, 1)
|
||||
}
|
||||
|
||||
results, err = GetMaileableUsersByIDs([]int64{1, 4}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, results, 2)
|
||||
if len(results) > 2 {
|
||||
assert.Equal(t, results[0].ID, 1)
|
||||
assert.Equal(t, results[1].ID, 4)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateUser(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
user := unittest.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||
|
||||
user.KeepActivityPrivate = true
|
||||
assert.NoError(t, UpdateUser(user))
|
||||
user = unittest.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||
assert.True(t, user.KeepActivityPrivate)
|
||||
|
||||
setting.Service.AllowedUserVisibilityModesSlice = []bool{true, false, false}
|
||||
user.KeepActivityPrivate = false
|
||||
user.Visibility = structs.VisibleTypePrivate
|
||||
assert.Error(t, UpdateUser(user))
|
||||
user = unittest.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||
assert.True(t, user.KeepActivityPrivate)
|
||||
|
||||
user.Email = "no mail@mail.org"
|
||||
assert.Error(t, UpdateUser(user))
|
||||
}
|
||||
|
||||
func TestNewUserRedirect(t *testing.T) {
|
||||
// redirect to a completely new name
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
|
||||
assert.NoError(t, NewUserRedirect(db.DefaultContext, user.ID, user.Name, "newusername"))
|
||||
|
||||
unittest.AssertExistsAndLoadBean(t, &Redirect{
|
||||
LowerName: user.LowerName,
|
||||
RedirectUserID: user.ID,
|
||||
})
|
||||
unittest.AssertExistsAndLoadBean(t, &Redirect{
|
||||
LowerName: "olduser1",
|
||||
RedirectUserID: user.ID,
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewUserRedirect2(t *testing.T) {
|
||||
// redirect to previously used name
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
|
||||
assert.NoError(t, NewUserRedirect(db.DefaultContext, user.ID, user.Name, "olduser1"))
|
||||
|
||||
unittest.AssertExistsAndLoadBean(t, &Redirect{
|
||||
LowerName: user.LowerName,
|
||||
RedirectUserID: user.ID,
|
||||
})
|
||||
unittest.AssertNotExistsBean(t, &Redirect{
|
||||
LowerName: "olduser1",
|
||||
RedirectUserID: user.ID,
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewUserRedirect3(t *testing.T) {
|
||||
// redirect for a previously-unredirected user
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||
assert.NoError(t, NewUserRedirect(db.DefaultContext, user.ID, user.Name, "newusername"))
|
||||
|
||||
unittest.AssertExistsAndLoadBean(t, &Redirect{
|
||||
LowerName: user.LowerName,
|
||||
RedirectUserID: user.ID,
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetUserByOpenID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
_, err := GetUserByOpenID("https://unknown")
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, IsErrUserNotExist(err))
|
||||
}
|
||||
|
||||
user, err := GetUserByOpenID("https://user1.domain1.tld")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, int64(1), user.ID)
|
||||
}
|
||||
|
||||
user, err = GetUserByOpenID("https://domain1.tld/user2/")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, int64(2), user.ID)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue