mirror of https://github.com/go-gitea/gitea
Move more model into models/user (#17826)
* Move more model into models/user * Remove unnecessary comment Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>pull/17501/head^2
parent
b1df890951
commit
9defddb286
@ -0,0 +1,69 @@ |
|||||||
|
// Copyright 2019 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" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db" |
||||||
|
"code.gitea.io/gitea/models/login" |
||||||
|
) |
||||||
|
|
||||||
|
// UserList is a list of user.
|
||||||
|
// This type provide valuable methods to retrieve information for a group of users efficiently.
|
||||||
|
type UserList []*User //revive:disable-line:exported
|
||||||
|
|
||||||
|
// GetUserIDs returns a slice of user's id
|
||||||
|
func (users UserList) GetUserIDs() []int64 { |
||||||
|
userIDs := make([]int64, len(users)) |
||||||
|
for _, user := range users { |
||||||
|
userIDs = append(userIDs, user.ID) // Considering that user id are unique in the list
|
||||||
|
} |
||||||
|
return userIDs |
||||||
|
} |
||||||
|
|
||||||
|
// GetTwoFaStatus return state of 2FA enrollement
|
||||||
|
func (users UserList) GetTwoFaStatus() map[int64]bool { |
||||||
|
results := make(map[int64]bool, len(users)) |
||||||
|
for _, user := range users { |
||||||
|
results[user.ID] = false // Set default to false
|
||||||
|
} |
||||||
|
tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext)) |
||||||
|
if err == nil { |
||||||
|
for _, token := range tokenMaps { |
||||||
|
results[token.UID] = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return results |
||||||
|
} |
||||||
|
|
||||||
|
func (users UserList) loadTwoFactorStatus(e db.Engine) (map[int64]*login.TwoFactor, error) { |
||||||
|
if len(users) == 0 { |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
|
||||||
|
userIDs := users.GetUserIDs() |
||||||
|
tokenMaps := make(map[int64]*login.TwoFactor, len(userIDs)) |
||||||
|
err := e. |
||||||
|
In("uid", userIDs). |
||||||
|
Find(&tokenMaps) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("find two factor: %v", err) |
||||||
|
} |
||||||
|
return tokenMaps, nil |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsersByIDs returns all resolved users from a list of Ids.
|
||||||
|
func GetUsersByIDs(ids []int64) (UserList, error) { |
||||||
|
ous := make([]*User, 0, len(ids)) |
||||||
|
if len(ids) == 0 { |
||||||
|
return ous, nil |
||||||
|
} |
||||||
|
err := db.GetEngine(db.DefaultContext).In("id", ids). |
||||||
|
Asc("name"). |
||||||
|
Find(&ous) |
||||||
|
return ous, err |
||||||
|
} |
@ -1,262 +0,0 @@ |
|||||||
// 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 models |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db" |
|
||||||
user_model "code.gitea.io/gitea/models/user" |
|
||||||
"code.gitea.io/gitea/modules/base" |
|
||||||
"code.gitea.io/gitea/modules/setting" |
|
||||||
"code.gitea.io/gitea/modules/util" |
|
||||||
|
|
||||||
"xorm.io/builder" |
|
||||||
) |
|
||||||
|
|
||||||
// ActivateEmail activates the email address to given user.
|
|
||||||
func ActivateEmail(email *user_model.EmailAddress) error { |
|
||||||
ctx, committer, err := db.TxContext() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
defer committer.Close() |
|
||||||
if err := updateActivation(db.GetEngine(ctx), email, true); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
return committer.Commit() |
|
||||||
} |
|
||||||
|
|
||||||
func updateActivation(e db.Engine, email *user_model.EmailAddress, activate bool) error { |
|
||||||
user, err := user_model.GetUserByIDEngine(e, email.UID) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
if user.Rands, err = user_model.GetUserSalt(); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
email.IsActivated = activate |
|
||||||
if _, err := e.ID(email.ID).Cols("is_activated").Update(email); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
return user_model.UpdateUserColsEngine(e, user, "rands") |
|
||||||
} |
|
||||||
|
|
||||||
// MakeEmailPrimary sets primary email address of given user.
|
|
||||||
func MakeEmailPrimary(email *user_model.EmailAddress) error { |
|
||||||
has, err := db.GetEngine(db.DefaultContext).Get(email) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} else if !has { |
|
||||||
return user_model.ErrEmailAddressNotExist{Email: email.Email} |
|
||||||
} |
|
||||||
|
|
||||||
if !email.IsActivated { |
|
||||||
return user_model.ErrEmailNotActivated |
|
||||||
} |
|
||||||
|
|
||||||
user := &user_model.User{} |
|
||||||
has, err = db.GetEngine(db.DefaultContext).ID(email.UID).Get(user) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} else if !has { |
|
||||||
return user_model.ErrUserNotExist{ |
|
||||||
UID: email.UID, |
|
||||||
Name: "", |
|
||||||
KeyID: 0, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
ctx, committer, err := db.TxContext() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
defer committer.Close() |
|
||||||
sess := db.GetEngine(ctx) |
|
||||||
|
|
||||||
// 1. Update user table
|
|
||||||
user.Email = email.Email |
|
||||||
if _, err = sess.ID(user.ID).Cols("email").Update(user); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// 2. Update old primary email
|
|
||||||
if _, err = sess.Where("uid=? AND is_primary=?", email.UID, true).Cols("is_primary").Update(&user_model.EmailAddress{ |
|
||||||
IsPrimary: false, |
|
||||||
}); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// 3. update new primary email
|
|
||||||
email.IsPrimary = true |
|
||||||
if _, err = sess.ID(email.ID).Cols("is_primary").Update(email); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
return committer.Commit() |
|
||||||
} |
|
||||||
|
|
||||||
// VerifyActiveEmailCode verifies active email code when active account
|
|
||||||
func VerifyActiveEmailCode(code, email string) *user_model.EmailAddress { |
|
||||||
minutes := setting.Service.ActiveCodeLives |
|
||||||
|
|
||||||
if user := user_model.GetVerifyUser(code); user != nil { |
|
||||||
// time limit code
|
|
||||||
prefix := code[:base.TimeLimitCodeLength] |
|
||||||
data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands) |
|
||||||
|
|
||||||
if base.VerifyTimeLimitCode(data, minutes, prefix) { |
|
||||||
emailAddress := &user_model.EmailAddress{UID: user.ID, Email: email} |
|
||||||
if has, _ := db.GetEngine(db.DefaultContext).Get(emailAddress); has { |
|
||||||
return emailAddress |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// SearchEmailOrderBy is used to sort the results from SearchEmails()
|
|
||||||
type SearchEmailOrderBy string |
|
||||||
|
|
||||||
func (s SearchEmailOrderBy) String() string { |
|
||||||
return string(s) |
|
||||||
} |
|
||||||
|
|
||||||
// Strings for sorting result
|
|
||||||
const ( |
|
||||||
SearchEmailOrderByEmail SearchEmailOrderBy = "email_address.lower_email ASC, email_address.is_primary DESC, email_address.id ASC" |
|
||||||
SearchEmailOrderByEmailReverse SearchEmailOrderBy = "email_address.lower_email DESC, email_address.is_primary ASC, email_address.id DESC" |
|
||||||
SearchEmailOrderByName SearchEmailOrderBy = "`user`.lower_name ASC, email_address.is_primary DESC, email_address.id ASC" |
|
||||||
SearchEmailOrderByNameReverse SearchEmailOrderBy = "`user`.lower_name DESC, email_address.is_primary ASC, email_address.id DESC" |
|
||||||
) |
|
||||||
|
|
||||||
// SearchEmailOptions are options to search e-mail addresses for the admin panel
|
|
||||||
type SearchEmailOptions struct { |
|
||||||
db.ListOptions |
|
||||||
Keyword string |
|
||||||
SortType SearchEmailOrderBy |
|
||||||
IsPrimary util.OptionalBool |
|
||||||
IsActivated util.OptionalBool |
|
||||||
} |
|
||||||
|
|
||||||
// SearchEmailResult is an e-mail address found in the user or email_address table
|
|
||||||
type SearchEmailResult struct { |
|
||||||
UID int64 |
|
||||||
Email string |
|
||||||
IsActivated bool |
|
||||||
IsPrimary bool |
|
||||||
// From User
|
|
||||||
Name string |
|
||||||
FullName string |
|
||||||
} |
|
||||||
|
|
||||||
// SearchEmails takes options i.e. keyword and part of email name to search,
|
|
||||||
// it returns results in given range and number of total results.
|
|
||||||
func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) { |
|
||||||
var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeIndividual} |
|
||||||
if len(opts.Keyword) > 0 { |
|
||||||
likeStr := "%" + strings.ToLower(opts.Keyword) + "%" |
|
||||||
cond = cond.And(builder.Or( |
|
||||||
builder.Like{"lower(`user`.full_name)", likeStr}, |
|
||||||
builder.Like{"`user`.lower_name", likeStr}, |
|
||||||
builder.Like{"email_address.lower_email", likeStr}, |
|
||||||
)) |
|
||||||
} |
|
||||||
|
|
||||||
switch { |
|
||||||
case opts.IsPrimary.IsTrue(): |
|
||||||
cond = cond.And(builder.Eq{"email_address.is_primary": true}) |
|
||||||
case opts.IsPrimary.IsFalse(): |
|
||||||
cond = cond.And(builder.Eq{"email_address.is_primary": false}) |
|
||||||
} |
|
||||||
|
|
||||||
switch { |
|
||||||
case opts.IsActivated.IsTrue(): |
|
||||||
cond = cond.And(builder.Eq{"email_address.is_activated": true}) |
|
||||||
case opts.IsActivated.IsFalse(): |
|
||||||
cond = cond.And(builder.Eq{"email_address.is_activated": false}) |
|
||||||
} |
|
||||||
|
|
||||||
count, err := db.GetEngine(db.DefaultContext).Join("INNER", "`user`", "`user`.ID = email_address.uid"). |
|
||||||
Where(cond).Count(new(user_model.EmailAddress)) |
|
||||||
if err != nil { |
|
||||||
return nil, 0, fmt.Errorf("Count: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
orderby := opts.SortType.String() |
|
||||||
if orderby == "" { |
|
||||||
orderby = SearchEmailOrderByEmail.String() |
|
||||||
} |
|
||||||
|
|
||||||
opts.SetDefaultValues() |
|
||||||
|
|
||||||
emails := make([]*SearchEmailResult, 0, opts.PageSize) |
|
||||||
err = db.GetEngine(db.DefaultContext).Table("email_address"). |
|
||||||
Select("email_address.*, `user`.name, `user`.full_name"). |
|
||||||
Join("INNER", "`user`", "`user`.ID = email_address.uid"). |
|
||||||
Where(cond). |
|
||||||
OrderBy(orderby). |
|
||||||
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize). |
|
||||||
Find(&emails) |
|
||||||
|
|
||||||
return emails, count, err |
|
||||||
} |
|
||||||
|
|
||||||
// ActivateUserEmail will change the activated state of an email address,
|
|
||||||
// either primary or secondary (all in the email_address table)
|
|
||||||
func ActivateUserEmail(userID int64, email string, activate bool) (err error) { |
|
||||||
ctx, committer, err := db.TxContext() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
defer committer.Close() |
|
||||||
sess := db.GetEngine(ctx) |
|
||||||
|
|
||||||
// Activate/deactivate a user's secondary email address
|
|
||||||
// First check if there's another user active with the same address
|
|
||||||
addr := user_model.EmailAddress{UID: userID, LowerEmail: strings.ToLower(email)} |
|
||||||
if has, err := sess.Get(&addr); err != nil { |
|
||||||
return err |
|
||||||
} else if !has { |
|
||||||
return fmt.Errorf("no such email: %d (%s)", userID, email) |
|
||||||
} |
|
||||||
if addr.IsActivated == activate { |
|
||||||
// Already in the desired state; no action
|
|
||||||
return nil |
|
||||||
} |
|
||||||
if activate { |
|
||||||
if used, err := user_model.IsEmailActive(ctx, email, addr.ID); err != nil { |
|
||||||
return fmt.Errorf("unable to check isEmailActive() for %s: %v", email, err) |
|
||||||
} else if used { |
|
||||||
return user_model.ErrEmailAlreadyUsed{Email: email} |
|
||||||
} |
|
||||||
} |
|
||||||
if err = updateActivation(sess, &addr, activate); err != nil { |
|
||||||
return fmt.Errorf("unable to updateActivation() for %d:%s: %w", addr.ID, addr.Email, err) |
|
||||||
} |
|
||||||
|
|
||||||
// Activate/deactivate a user's primary email address and account
|
|
||||||
if addr.IsPrimary { |
|
||||||
user := user_model.User{ID: userID, Email: email} |
|
||||||
if has, err := sess.Get(&user); err != nil { |
|
||||||
return err |
|
||||||
} else if !has { |
|
||||||
return fmt.Errorf("no user with ID: %d and Email: %s", userID, email) |
|
||||||
} |
|
||||||
// The user's activation state should be synchronized with the primary email
|
|
||||||
if user.IsActive != activate { |
|
||||||
user.IsActive = activate |
|
||||||
if user.Rands, err = user_model.GetUserSalt(); err != nil { |
|
||||||
return fmt.Errorf("unable to generate salt: %v", err) |
|
||||||
} |
|
||||||
if err = user_model.UpdateUserColsEngine(sess, &user, "is_active", "rands"); err != nil { |
|
||||||
return fmt.Errorf("unable to updateUserCols() for user ID: %d: %v", userID, err) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return committer.Commit() |
|
||||||
} |
|
@ -1,137 +0,0 @@ |
|||||||
// 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 models |
|
||||||
|
|
||||||
import ( |
|
||||||
"testing" |
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db" |
|
||||||
"code.gitea.io/gitea/models/unittest" |
|
||||||
user_model "code.gitea.io/gitea/models/user" |
|
||||||
"code.gitea.io/gitea/modules/util" |
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert" |
|
||||||
) |
|
||||||
|
|
||||||
func TestMakeEmailPrimary(t *testing.T) { |
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase()) |
|
||||||
|
|
||||||
email := &user_model.EmailAddress{ |
|
||||||
Email: "user567890@example.com", |
|
||||||
} |
|
||||||
err := MakeEmailPrimary(email) |
|
||||||
assert.Error(t, err) |
|
||||||
assert.EqualError(t, err, user_model.ErrEmailAddressNotExist{Email: email.Email}.Error()) |
|
||||||
|
|
||||||
email = &user_model.EmailAddress{ |
|
||||||
Email: "user11@example.com", |
|
||||||
} |
|
||||||
err = MakeEmailPrimary(email) |
|
||||||
assert.Error(t, err) |
|
||||||
assert.EqualError(t, err, user_model.ErrEmailNotActivated.Error()) |
|
||||||
|
|
||||||
email = &user_model.EmailAddress{ |
|
||||||
Email: "user9999999@example.com", |
|
||||||
} |
|
||||||
err = MakeEmailPrimary(email) |
|
||||||
assert.Error(t, err) |
|
||||||
assert.True(t, user_model.IsErrUserNotExist(err)) |
|
||||||
|
|
||||||
email = &user_model.EmailAddress{ |
|
||||||
Email: "user101@example.com", |
|
||||||
} |
|
||||||
err = MakeEmailPrimary(email) |
|
||||||
assert.NoError(t, err) |
|
||||||
|
|
||||||
user, _ := user_model.GetUserByID(int64(10)) |
|
||||||
assert.Equal(t, "user101@example.com", user.Email) |
|
||||||
} |
|
||||||
|
|
||||||
func TestActivate(t *testing.T) { |
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase()) |
|
||||||
|
|
||||||
email := &user_model.EmailAddress{ |
|
||||||
ID: int64(1), |
|
||||||
UID: int64(1), |
|
||||||
Email: "user11@example.com", |
|
||||||
} |
|
||||||
assert.NoError(t, ActivateEmail(email)) |
|
||||||
|
|
||||||
emails, _ := user_model.GetEmailAddresses(int64(1)) |
|
||||||
assert.Len(t, emails, 3) |
|
||||||
assert.True(t, emails[0].IsActivated) |
|
||||||
assert.True(t, emails[0].IsPrimary) |
|
||||||
assert.False(t, emails[1].IsPrimary) |
|
||||||
assert.True(t, emails[2].IsActivated) |
|
||||||
assert.False(t, emails[2].IsPrimary) |
|
||||||
} |
|
||||||
|
|
||||||
func TestListEmails(t *testing.T) { |
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase()) |
|
||||||
|
|
||||||
// Must find all users and their emails
|
|
||||||
opts := &SearchEmailOptions{ |
|
||||||
ListOptions: db.ListOptions{ |
|
||||||
PageSize: 10000, |
|
||||||
}, |
|
||||||
} |
|
||||||
emails, count, err := SearchEmails(opts) |
|
||||||
assert.NoError(t, err) |
|
||||||
assert.NotEqual(t, int64(0), count) |
|
||||||
assert.True(t, count > 5) |
|
||||||
|
|
||||||
contains := func(match func(s *SearchEmailResult) bool) bool { |
|
||||||
for _, v := range emails { |
|
||||||
if match(v) { |
|
||||||
return true |
|
||||||
} |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
assert.True(t, contains(func(s *SearchEmailResult) bool { return s.UID == 18 })) |
|
||||||
// 'user3' is an organization
|
|
||||||
assert.False(t, contains(func(s *SearchEmailResult) bool { return s.UID == 3 })) |
|
||||||
|
|
||||||
// Must find no records
|
|
||||||
opts = &SearchEmailOptions{Keyword: "NOTFOUND"} |
|
||||||
emails, count, err = SearchEmails(opts) |
|
||||||
assert.NoError(t, err) |
|
||||||
assert.Equal(t, int64(0), count) |
|
||||||
|
|
||||||
// Must find users 'user2', 'user28', etc.
|
|
||||||
opts = &SearchEmailOptions{Keyword: "user2"} |
|
||||||
emails, count, err = SearchEmails(opts) |
|
||||||
assert.NoError(t, err) |
|
||||||
assert.NotEqual(t, int64(0), count) |
|
||||||
assert.True(t, contains(func(s *SearchEmailResult) bool { return s.UID == 2 })) |
|
||||||
assert.True(t, contains(func(s *SearchEmailResult) bool { return s.UID == 27 })) |
|
||||||
|
|
||||||
// Must find only primary addresses (i.e. from the `user` table)
|
|
||||||
opts = &SearchEmailOptions{IsPrimary: util.OptionalBoolTrue} |
|
||||||
emails, _, err = SearchEmails(opts) |
|
||||||
assert.NoError(t, err) |
|
||||||
assert.True(t, contains(func(s *SearchEmailResult) bool { return s.IsPrimary })) |
|
||||||
assert.False(t, contains(func(s *SearchEmailResult) bool { return !s.IsPrimary })) |
|
||||||
|
|
||||||
// Must find only inactive addresses (i.e. not validated)
|
|
||||||
opts = &SearchEmailOptions{IsActivated: util.OptionalBoolFalse} |
|
||||||
emails, _, err = SearchEmails(opts) |
|
||||||
assert.NoError(t, err) |
|
||||||
assert.True(t, contains(func(s *SearchEmailResult) bool { return !s.IsActivated })) |
|
||||||
assert.False(t, contains(func(s *SearchEmailResult) bool { return s.IsActivated })) |
|
||||||
|
|
||||||
// Must find more than one page, but retrieve only one
|
|
||||||
opts = &SearchEmailOptions{ |
|
||||||
ListOptions: db.ListOptions{ |
|
||||||
PageSize: 5, |
|
||||||
Page: 1, |
|
||||||
}, |
|
||||||
} |
|
||||||
emails, count, err = SearchEmails(opts) |
|
||||||
assert.NoError(t, err) |
|
||||||
assert.Len(t, emails, 5) |
|
||||||
assert.Greater(t, count, int64(len(emails))) |
|
||||||
} |
|
Loading…
Reference in new issue