mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	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>
This commit is contained in:
		@@ -1624,46 +1624,6 @@ func (err ErrUploadNotExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  ___________         __                             .__    .____                 .__          ____ ___
 | 
			
		||||
//  \_   _____/__  ____/  |_  ___________  ____ _____  |  |   |    |    ____   ____ |__| ____   |    |   \______ ___________
 | 
			
		||||
//   |    __)_\  \/  /\   __\/ __ \_  __ \/    \\__  \ |  |   |    |   /  _ \ / ___\|  |/    \  |    |   /  ___// __ \_  __ \
 | 
			
		||||
//   |        \>    <  |  | \  ___/|  | \/   |  \/ __ \|  |__ |    |__(  <_> ) /_/  >  |   |  \ |    |  /\___ \\  ___/|  | \/
 | 
			
		||||
//  /_______  /__/\_ \ |__|  \___  >__|  |___|  (____  /____/ |_______ \____/\___  /|__|___|  / |______//____  >\___  >__|
 | 
			
		||||
//          \/      \/           \/           \/     \/               \/    /_____/         \/               \/     \/
 | 
			
		||||
 | 
			
		||||
// ErrExternalLoginUserAlreadyExist represents a "ExternalLoginUserAlreadyExist" kind of error.
 | 
			
		||||
type ErrExternalLoginUserAlreadyExist struct {
 | 
			
		||||
	ExternalID    string
 | 
			
		||||
	UserID        int64
 | 
			
		||||
	LoginSourceID int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsErrExternalLoginUserAlreadyExist checks if an error is a ExternalLoginUserAlreadyExist.
 | 
			
		||||
func IsErrExternalLoginUserAlreadyExist(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrExternalLoginUserAlreadyExist)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrExternalLoginUserAlreadyExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("external login user already exists [externalID: %s, userID: %d, loginSourceID: %d]", err.ExternalID, err.UserID, err.LoginSourceID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrExternalLoginUserNotExist represents a "ExternalLoginUserNotExist" kind of error.
 | 
			
		||||
type ErrExternalLoginUserNotExist struct {
 | 
			
		||||
	UserID        int64
 | 
			
		||||
	LoginSourceID int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsErrExternalLoginUserNotExist checks if an error is a ExternalLoginUserNotExist.
 | 
			
		||||
func IsErrExternalLoginUserNotExist(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrExternalLoginUserNotExist)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrExternalLoginUserNotExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// .___                            ________                                   .___                   .__
 | 
			
		||||
// |   | ______ ________ __   ____ \______ \   ____ ______   ____   ____    __| _/____   ____   ____ |__| ____   ______
 | 
			
		||||
// |   |/  ___//  ___/  |  \_/ __ \ |    |  \_/ __ \\____ \_/ __ \ /    \  / __ |/ __ \ /    \_/ ___\|  |/ __ \ /  ___/
 | 
			
		||||
 
 | 
			
		||||
@@ -245,3 +245,23 @@ func UpdateReviewsMigrationsByType(tp structs.GitServiceType, originalAuthorID s
 | 
			
		||||
		})
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateMigrationsByType updates all migrated repositories' posterid from gitServiceType to replace originalAuthorID to posterID
 | 
			
		||||
func UpdateMigrationsByType(tp structs.GitServiceType, externalUserID string, userID int64) error {
 | 
			
		||||
	if err := UpdateIssuesMigrationsByType(tp, externalUserID, userID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := UpdateCommentsMigrationsByType(tp, externalUserID, userID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := UpdateReleasesMigrationsByType(tp, externalUserID, userID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := UpdateReactionsMigrationsByType(tp, externalUserID, userID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return UpdateReviewsMigrationsByType(tp, externalUserID, userID)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,7 @@ func (org *Organization) LoadTeams() ([]*Team, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMembers returns all members of organization.
 | 
			
		||||
func (org *Organization) GetMembers() (UserList, map[int64]bool, error) {
 | 
			
		||||
func (org *Organization) GetMembers() (user_model.UserList, map[int64]bool, error) {
 | 
			
		||||
	return FindOrgMembers(&FindOrgMembersOpts{
 | 
			
		||||
		OrgID: org.ID,
 | 
			
		||||
	})
 | 
			
		||||
@@ -149,7 +149,7 @@ func CountOrgMembers(opts *FindOrgMembersOpts) (int64, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindOrgMembers loads organization members according conditions
 | 
			
		||||
func FindOrgMembers(opts *FindOrgMembersOpts) (UserList, map[int64]bool, error) {
 | 
			
		||||
func FindOrgMembers(opts *FindOrgMembersOpts) (user_model.UserList, map[int64]bool, error) {
 | 
			
		||||
	ous, err := GetOrgUsersByOrgID(opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
@@ -162,7 +162,7 @@ func FindOrgMembers(opts *FindOrgMembersOpts) (UserList, map[int64]bool, error)
 | 
			
		||||
		idsIsPublic[ou.UID] = ou.IsPublic
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	users, err := GetUsersByIDs(ids)
 | 
			
		||||
	users, err := user_model.GetUsersByIDs(ids)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -260,7 +260,7 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// ***** START: ExternalLoginUser *****
 | 
			
		||||
	if err = removeAllAccountLinks(e, u); err != nil {
 | 
			
		||||
	if err = user_model.RemoveAllAccountLinks(ctx, u); err != nil {
 | 
			
		||||
		return fmt.Errorf("ExternalLoginUser: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	// ***** END: ExternalLoginUser *****
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,10 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
@@ -275,3 +277,247 @@ func DeleteInactiveEmailAddresses(ctx context.Context) error {
 | 
			
		||||
		Delete(new(EmailAddress))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ActivateEmail activates the email address to given user.
 | 
			
		||||
func ActivateEmail(email *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 *EmailAddress, activate bool) error {
 | 
			
		||||
	user, err := GetUserByIDEngine(e, email.UID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if user.Rands, err = GetUserSalt(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	email.IsActivated = activate
 | 
			
		||||
	if _, err := e.ID(email.ID).Cols("is_activated").Update(email); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return UpdateUserColsEngine(e, user, "rands")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MakeEmailPrimary sets primary email address of given user.
 | 
			
		||||
func MakeEmailPrimary(email *EmailAddress) error {
 | 
			
		||||
	has, err := db.GetEngine(db.DefaultContext).Get(email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return ErrEmailAddressNotExist{Email: email.Email}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !email.IsActivated {
 | 
			
		||||
		return ErrEmailNotActivated
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user := &User{}
 | 
			
		||||
	has, err = db.GetEngine(db.DefaultContext).ID(email.UID).Get(user)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return 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(&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) *EmailAddress {
 | 
			
		||||
	minutes := setting.Service.ActiveCodeLives
 | 
			
		||||
 | 
			
		||||
	if user := 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 := &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`": 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(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 := 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 := IsEmailActive(ctx, email, addr.ID); err != nil {
 | 
			
		||||
			return fmt.Errorf("unable to check isEmailActive() for %s: %v", email, err)
 | 
			
		||||
		} else if used {
 | 
			
		||||
			return 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{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 = GetUserSalt(); err != nil {
 | 
			
		||||
				return fmt.Errorf("unable to generate salt: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			if err = UpdateUserColsEngine(sess, &user, "is_active", "rands"); err != nil {
 | 
			
		||||
				return fmt.Errorf("unable to updateUserCols() for user ID: %d: %v", userID, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return committer.Commit()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
@@ -130,3 +131,124 @@ func TestDeleteEmailAddresses(t *testing.T) {
 | 
			
		||||
	err := DeleteEmailAddresses(emails)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMakeEmailPrimary(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	email := &EmailAddress{
 | 
			
		||||
		Email: "user567890@example.com",
 | 
			
		||||
	}
 | 
			
		||||
	err := MakeEmailPrimary(email)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.EqualError(t, err, ErrEmailAddressNotExist{Email: email.Email}.Error())
 | 
			
		||||
 | 
			
		||||
	email = &EmailAddress{
 | 
			
		||||
		Email: "user11@example.com",
 | 
			
		||||
	}
 | 
			
		||||
	err = MakeEmailPrimary(email)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.EqualError(t, err, ErrEmailNotActivated.Error())
 | 
			
		||||
 | 
			
		||||
	email = &EmailAddress{
 | 
			
		||||
		Email: "user9999999@example.com",
 | 
			
		||||
	}
 | 
			
		||||
	err = MakeEmailPrimary(email)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.True(t, IsErrUserNotExist(err))
 | 
			
		||||
 | 
			
		||||
	email = &EmailAddress{
 | 
			
		||||
		Email: "user101@example.com",
 | 
			
		||||
	}
 | 
			
		||||
	err = MakeEmailPrimary(email)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	user, _ := GetUserByID(int64(10))
 | 
			
		||||
	assert.Equal(t, "user101@example.com", user.Email)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestActivate(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	email := &EmailAddress{
 | 
			
		||||
		ID:    int64(1),
 | 
			
		||||
		UID:   int64(1),
 | 
			
		||||
		Email: "user11@example.com",
 | 
			
		||||
	}
 | 
			
		||||
	assert.NoError(t, ActivateEmail(email))
 | 
			
		||||
 | 
			
		||||
	emails, _ := 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)))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,20 +2,53 @@
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package models
 | 
			
		||||
package user
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/login"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/structs"
 | 
			
		||||
 | 
			
		||||
	"github.com/markbates/goth"
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ErrExternalLoginUserAlreadyExist represents a "ExternalLoginUserAlreadyExist" kind of error.
 | 
			
		||||
type ErrExternalLoginUserAlreadyExist struct {
 | 
			
		||||
	ExternalID    string
 | 
			
		||||
	UserID        int64
 | 
			
		||||
	LoginSourceID int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsErrExternalLoginUserAlreadyExist checks if an error is a ExternalLoginUserAlreadyExist.
 | 
			
		||||
func IsErrExternalLoginUserAlreadyExist(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrExternalLoginUserAlreadyExist)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrExternalLoginUserAlreadyExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("external login user already exists [externalID: %s, userID: %d, loginSourceID: %d]", err.ExternalID, err.UserID, err.LoginSourceID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrExternalLoginUserNotExist represents a "ExternalLoginUserNotExist" kind of error.
 | 
			
		||||
type ErrExternalLoginUserNotExist struct {
 | 
			
		||||
	UserID        int64
 | 
			
		||||
	LoginSourceID int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsErrExternalLoginUserNotExist checks if an error is a ExternalLoginUserNotExist.
 | 
			
		||||
func IsErrExternalLoginUserNotExist(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrExternalLoginUserNotExist)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrExternalLoginUserNotExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExternalLoginUser makes the connecting between some existing user and additional external login sources
 | 
			
		||||
type ExternalLoginUser struct {
 | 
			
		||||
	ExternalID        string                 `xorm:"pk NOT NULL"`
 | 
			
		||||
@@ -47,7 +80,7 @@ func GetExternalLogin(externalLoginUser *ExternalLoginUser) (bool, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListAccountLinks returns a map with the ExternalLoginUser and its LoginSource
 | 
			
		||||
func ListAccountLinks(user *user_model.User) ([]*ExternalLoginUser, error) {
 | 
			
		||||
func ListAccountLinks(user *User) ([]*ExternalLoginUser, error) {
 | 
			
		||||
	externalAccounts := make([]*ExternalLoginUser, 0, 5)
 | 
			
		||||
	err := db.GetEngine(db.DefaultContext).Where("user_id=?", user.ID).
 | 
			
		||||
		Desc("login_source_id").
 | 
			
		||||
@@ -60,7 +93,7 @@ func ListAccountLinks(user *user_model.User) ([]*ExternalLoginUser, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LinkExternalToUser link the external user to the user
 | 
			
		||||
func LinkExternalToUser(user *user_model.User, externalLoginUser *ExternalLoginUser) error {
 | 
			
		||||
func LinkExternalToUser(user *User, externalLoginUser *ExternalLoginUser) error {
 | 
			
		||||
	has, err := db.GetEngine(db.DefaultContext).Where("external_id=? AND login_source_id=?", externalLoginUser.ExternalID, externalLoginUser.LoginSourceID).
 | 
			
		||||
		NoAutoCondition().
 | 
			
		||||
		Exist(externalLoginUser)
 | 
			
		||||
@@ -75,7 +108,7 @@ func LinkExternalToUser(user *user_model.User, externalLoginUser *ExternalLoginU
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoveAccountLink will remove all external login sources for the given user
 | 
			
		||||
func RemoveAccountLink(user *user_model.User, loginSourceID int64) (int64, error) {
 | 
			
		||||
func RemoveAccountLink(user *User, loginSourceID int64) (int64, error) {
 | 
			
		||||
	deleted, err := db.GetEngine(db.DefaultContext).Delete(&ExternalLoginUser{UserID: user.ID, LoginSourceID: loginSourceID})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return deleted, err
 | 
			
		||||
@@ -86,9 +119,9 @@ func RemoveAccountLink(user *user_model.User, loginSourceID int64) (int64, error
 | 
			
		||||
	return deleted, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// removeAllAccountLinks will remove all external login sources for the given user
 | 
			
		||||
func removeAllAccountLinks(e db.Engine, user *user_model.User) error {
 | 
			
		||||
	_, err := e.Delete(&ExternalLoginUser{UserID: user.ID})
 | 
			
		||||
// RemoveAllAccountLinks will remove all external login sources for the given user
 | 
			
		||||
func RemoveAllAccountLinks(ctx context.Context, user *User) error {
 | 
			
		||||
	_, err := db.GetEngine(ctx).Delete(&ExternalLoginUser{UserID: user.ID})
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -107,7 +140,7 @@ func GetUserIDByExternalUserID(provider, userID string) (int64, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateExternalUser updates external user's information
 | 
			
		||||
func UpdateExternalUser(user *user_model.User, gothUser goth.User) error {
 | 
			
		||||
func UpdateExternalUser(user *User, gothUser goth.User) error {
 | 
			
		||||
	loginSource, err := login.GetActiveOAuth2LoginSourceByName(gothUser.Provider)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -172,23 +205,3 @@ func FindExternalUsersByProvider(opts FindExternalUserOptions) ([]ExternalLoginU
 | 
			
		||||
	}
 | 
			
		||||
	return users, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateMigrationsByType updates all migrated repositories' posterid from gitServiceType to replace originalAuthorID to posterID
 | 
			
		||||
func UpdateMigrationsByType(tp structs.GitServiceType, externalUserID string, userID int64) error {
 | 
			
		||||
	if err := UpdateIssuesMigrationsByType(tp, externalUserID, userID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := UpdateCommentsMigrationsByType(tp, externalUserID, userID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := UpdateReleasesMigrationsByType(tp, externalUserID, userID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := UpdateReactionsMigrationsByType(tp, externalUserID, userID); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return UpdateReviewsMigrationsByType(tp, externalUserID, userID)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										69
									
								
								models/user/list.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								models/user/list.go
									
									
									
									
									
										Normal file
									
								
							@@ -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)))
 | 
			
		||||
}
 | 
			
		||||
@@ -8,30 +8,17 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/login"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UserList is a list of user.
 | 
			
		||||
// This type provide valuable methods to retrieve information for a group of users efficiently.
 | 
			
		||||
type UserList []*user_model.User
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsUserOrgOwner returns true if user is in the owner team of given organization.
 | 
			
		||||
func (users UserList) IsUserOrgOwner(orgID int64) map[int64]bool {
 | 
			
		||||
func IsUserOrgOwner(users user_model.UserList, orgID int64) map[int64]bool {
 | 
			
		||||
	results := make(map[int64]bool, len(users))
 | 
			
		||||
	for _, user := range users {
 | 
			
		||||
		results[user.ID] = false // Set default to false
 | 
			
		||||
	}
 | 
			
		||||
	ownerMaps, err := users.loadOrganizationOwners(db.GetEngine(db.DefaultContext), orgID)
 | 
			
		||||
	ownerMaps, err := loadOrganizationOwners(db.GetEngine(db.DefaultContext), users, orgID)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		for _, owner := range ownerMaps {
 | 
			
		||||
			results[owner.UID] = true
 | 
			
		||||
@@ -40,7 +27,7 @@ func (users UserList) IsUserOrgOwner(orgID int64) map[int64]bool {
 | 
			
		||||
	return results
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (users UserList) loadOrganizationOwners(e db.Engine, orgID int64) (map[int64]*TeamUser, error) {
 | 
			
		||||
func loadOrganizationOwners(e db.Engine, users user_model.UserList, orgID int64) (map[int64]*TeamUser, error) {
 | 
			
		||||
	if len(users) == 0 {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
@@ -53,7 +40,7 @@ func (users UserList) loadOrganizationOwners(e db.Engine, orgID int64) (map[int6
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	userIDs := users.getUserIDs()
 | 
			
		||||
	userIDs := users.GetUserIDs()
 | 
			
		||||
	ownerMaps := make(map[int64]*TeamUser)
 | 
			
		||||
	err = e.In("uid", userIDs).
 | 
			
		||||
		And("org_id=?", orgID).
 | 
			
		||||
@@ -64,47 +51,3 @@ func (users UserList) loadOrganizationOwners(e db.Engine, orgID int64) (map[int6
 | 
			
		||||
	}
 | 
			
		||||
	return ownerMaps, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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_model.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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -64,32 +64,5 @@ func testUserListIsUserOrgOwner(t *testing.T, orgID int64, expected map[int64]bo
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	members, _, err := org.GetMembers()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, expected, members.IsUserOrgOwner(orgID))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUserListIsTwoFaEnrolled(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	tt := []struct {
 | 
			
		||||
		orgid    int64
 | 
			
		||||
		expected map[int64]bool
 | 
			
		||||
	}{
 | 
			
		||||
		{3, map[int64]bool{2: false, 4: false, 28: false}},
 | 
			
		||||
		{6, map[int64]bool{5: false, 28: false}},
 | 
			
		||||
		{7, map[int64]bool{5: false}},
 | 
			
		||||
		{25, map[int64]bool{24: true}},
 | 
			
		||||
		{22, map[int64]bool{}},
 | 
			
		||||
	}
 | 
			
		||||
	for _, v := range tt {
 | 
			
		||||
		t.Run(fmt.Sprintf("IsTwoFaEnrolledOfOrdIg%d", v.orgid), func(t *testing.T) {
 | 
			
		||||
			testUserListIsTwoFaEnrolled(t, v.orgid, v.expected)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testUserListIsTwoFaEnrolled(t *testing.T, orgID int64, expected map[int64]bool) {
 | 
			
		||||
	org, err := GetOrgByID(orgID)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	members, _, err := org.GetMembers()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, expected, members.GetTwoFaStatus())
 | 
			
		||||
	assert.Equal(t, expected, IsUserOrgOwner(members, orgID))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -273,7 +273,7 @@ func GetIssueSubscribers(ctx *context.APIContext) {
 | 
			
		||||
		userIDs = append(userIDs, iw.UserID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	users, err := models.GetUsersByIDs(userIDs)
 | 
			
		||||
	users, err := user_model.GetUsersByIDs(userIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "GetUsersByIDs", err)
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@ import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
@@ -29,7 +28,7 @@ func Emails(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["PageIsAdmin"] = true
 | 
			
		||||
	ctx.Data["PageIsAdminEmails"] = true
 | 
			
		||||
 | 
			
		||||
	opts := &models.SearchEmailOptions{
 | 
			
		||||
	opts := &user_model.SearchEmailOptions{
 | 
			
		||||
		ListOptions: db.ListOptions{
 | 
			
		||||
			PageSize: setting.UI.Admin.UserPagingNum,
 | 
			
		||||
			Page:     ctx.FormInt("page"),
 | 
			
		||||
@@ -41,31 +40,31 @@ func Emails(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type ActiveEmail struct {
 | 
			
		||||
		models.SearchEmailResult
 | 
			
		||||
		user_model.SearchEmailResult
 | 
			
		||||
		CanChange bool
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		baseEmails []*models.SearchEmailResult
 | 
			
		||||
		baseEmails []*user_model.SearchEmailResult
 | 
			
		||||
		emails     []ActiveEmail
 | 
			
		||||
		count      int64
 | 
			
		||||
		err        error
 | 
			
		||||
		orderBy    models.SearchEmailOrderBy
 | 
			
		||||
		orderBy    user_model.SearchEmailOrderBy
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	ctx.Data["SortType"] = ctx.FormString("sort")
 | 
			
		||||
	switch ctx.FormString("sort") {
 | 
			
		||||
	case "email":
 | 
			
		||||
		orderBy = models.SearchEmailOrderByEmail
 | 
			
		||||
		orderBy = user_model.SearchEmailOrderByEmail
 | 
			
		||||
	case "reverseemail":
 | 
			
		||||
		orderBy = models.SearchEmailOrderByEmailReverse
 | 
			
		||||
		orderBy = user_model.SearchEmailOrderByEmailReverse
 | 
			
		||||
	case "username":
 | 
			
		||||
		orderBy = models.SearchEmailOrderByName
 | 
			
		||||
		orderBy = user_model.SearchEmailOrderByName
 | 
			
		||||
	case "reverseusername":
 | 
			
		||||
		orderBy = models.SearchEmailOrderByNameReverse
 | 
			
		||||
		orderBy = user_model.SearchEmailOrderByNameReverse
 | 
			
		||||
	default:
 | 
			
		||||
		ctx.Data["SortType"] = "email"
 | 
			
		||||
		orderBy = models.SearchEmailOrderByEmail
 | 
			
		||||
		orderBy = user_model.SearchEmailOrderByEmail
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	opts.Keyword = ctx.FormTrim("q")
 | 
			
		||||
@@ -78,7 +77,7 @@ func Emails(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) {
 | 
			
		||||
		baseEmails, count, err = models.SearchEmails(opts)
 | 
			
		||||
		baseEmails, count, err = user_model.SearchEmails(opts)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.ServerError("SearchEmails", err)
 | 
			
		||||
			return
 | 
			
		||||
@@ -127,7 +126,7 @@ func ActivateEmail(ctx *context.Context) {
 | 
			
		||||
 | 
			
		||||
	log.Info("Changing activation for User ID: %d, email: %s, primary: %v to %v", uid, email, primary, activate)
 | 
			
		||||
 | 
			
		||||
	if err := models.ActivateUserEmail(uid, email, activate); err != nil {
 | 
			
		||||
	if err := user_model.ActivateUserEmail(uid, email, activate); err != nil {
 | 
			
		||||
		log.Error("ActivateUserEmail(%v,%v,%v): %v", uid, email, activate, err)
 | 
			
		||||
		if user_model.IsErrEmailAlreadyUsed(err) {
 | 
			
		||||
			ctx.Flash.Error(ctx.Tr("admin.emails.duplicate_active"))
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
@@ -79,7 +78,7 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions,
 | 
			
		||||
	ctx.Data["Keyword"] = opts.Keyword
 | 
			
		||||
	ctx.Data["Total"] = count
 | 
			
		||||
	ctx.Data["Users"] = users
 | 
			
		||||
	ctx.Data["UsersTwoFaStatus"] = models.UserList(users).GetTwoFaStatus()
 | 
			
		||||
	ctx.Data["UsersTwoFaStatus"] = user_model.UserList(users).GetTwoFaStatus()
 | 
			
		||||
	ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail
 | 
			
		||||
	ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ func Members(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["Page"] = pager
 | 
			
		||||
	ctx.Data["Members"] = members
 | 
			
		||||
	ctx.Data["MembersIsPublicMember"] = membersIsPublic
 | 
			
		||||
	ctx.Data["MembersIsUserOrgOwner"] = members.IsUserOrgOwner(org.ID)
 | 
			
		||||
	ctx.Data["MembersIsUserOrgOwner"] = models.IsUserOrgOwner(members, org.ID)
 | 
			
		||||
	ctx.Data["MembersTwoFaStatus"] = members.GetTwoFaStatus()
 | 
			
		||||
 | 
			
		||||
	ctx.HTML(http.StatusOK, tplMembers)
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/login"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
@@ -781,7 +780,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *login.Source, u *user_mode
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// update external user information
 | 
			
		||||
		if err := models.UpdateExternalUser(u, gothUser); err != nil {
 | 
			
		||||
		if err := user_model.UpdateExternalUser(u, gothUser); err != nil {
 | 
			
		||||
			log.Error("UpdateExternalUser failed: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -844,11 +843,11 @@ func oAuth2UserLoginCallback(loginSource *login.Source, request *http.Request, r
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// search in external linked users
 | 
			
		||||
	externalLoginUser := &models.ExternalLoginUser{
 | 
			
		||||
	externalLoginUser := &user_model.ExternalLoginUser{
 | 
			
		||||
		ExternalID:    gothUser.UserID,
 | 
			
		||||
		LoginSourceID: loginSource.ID,
 | 
			
		||||
	}
 | 
			
		||||
	hasUser, err = models.GetExternalLogin(externalLoginUser)
 | 
			
		||||
	hasUser, err = user_model.GetExternalLogin(externalLoginUser)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, goth.User{}, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -1355,7 +1354,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
 | 
			
		||||
 | 
			
		||||
	// update external user information
 | 
			
		||||
	if gothUser != nil {
 | 
			
		||||
		if err := models.UpdateExternalUser(u, *gothUser); err != nil {
 | 
			
		||||
		if err := user_model.UpdateExternalUser(u, *gothUser); err != nil {
 | 
			
		||||
			log.Error("UpdateExternalUser failed: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -1477,7 +1476,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := models.ActivateUserEmail(user.ID, user.Email, true); err != nil {
 | 
			
		||||
	if err := user_model.ActivateUserEmail(user.ID, user.Email, true); err != nil {
 | 
			
		||||
		log.Error("Unable to activate email for user: %-v with email: %s: %v", user, user.Email, err)
 | 
			
		||||
		ctx.ServerError("ActivateUserEmail", err)
 | 
			
		||||
		return
 | 
			
		||||
@@ -1505,8 +1504,8 @@ func ActivateEmail(ctx *context.Context) {
 | 
			
		||||
	emailStr := ctx.FormString("email")
 | 
			
		||||
 | 
			
		||||
	// Verify code.
 | 
			
		||||
	if email := models.VerifyActiveEmailCode(code, emailStr); email != nil {
 | 
			
		||||
		if err := models.ActivateEmail(email); err != nil {
 | 
			
		||||
	if email := user_model.VerifyActiveEmailCode(code, emailStr); email != nil {
 | 
			
		||||
		if err := user_model.ActivateEmail(email); err != nil {
 | 
			
		||||
			ctx.ServerError("ActivateEmail", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@ func EmailPost(ctx *context.Context) {
 | 
			
		||||
 | 
			
		||||
	// Make emailaddress primary.
 | 
			
		||||
	if ctx.FormString("_method") == "PRIMARY" {
 | 
			
		||||
		if err := models.MakeEmailPrimary(&user_model.EmailAddress{ID: ctx.FormInt64("id")}); err != nil {
 | 
			
		||||
		if err := user_model.MakeEmailPrimary(&user_model.EmailAddress{ID: ctx.FormInt64("id")}); err != nil {
 | 
			
		||||
			ctx.ServerError("MakeEmailPrimary", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ func DeleteAccountLink(ctx *context.Context) {
 | 
			
		||||
	if id <= 0 {
 | 
			
		||||
		ctx.Flash.Error("Account link id is not given")
 | 
			
		||||
	} else {
 | 
			
		||||
		if _, err := models.RemoveAccountLink(ctx.User, id); err != nil {
 | 
			
		||||
		if _, err := user_model.RemoveAccountLink(ctx.User, id); err != nil {
 | 
			
		||||
			ctx.Flash.Error("RemoveAccountLink: " + err.Error())
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success"))
 | 
			
		||||
@@ -76,7 +76,7 @@ func loadSecurityData(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["Tokens"] = tokens
 | 
			
		||||
 | 
			
		||||
	accountLinks, err := models.ListAccountLinks(ctx.User)
 | 
			
		||||
	accountLinks, err := user_model.ListAccountLinks(ctx.User)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("ListAccountLinks", err)
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@
 | 
			
		||||
package auth
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/login"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
@@ -22,7 +21,7 @@ func DeleteLoginSource(source *login.Source) error {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	count, err = db.GetEngine(db.DefaultContext).Count(&models.ExternalLoginUser{LoginSourceID: source.ID})
 | 
			
		||||
	count, err = db.GetEngine(db.DefaultContext).Count(&user_model.ExternalLoginUser{LoginSourceID: source.ID})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	} else if count > 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ func LinkAccountToUser(user *user_model.User, gothUser goth.User) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	externalLoginUser := &models.ExternalLoginUser{
 | 
			
		||||
	externalLoginUser := &user_model.ExternalLoginUser{
 | 
			
		||||
		ExternalID:        gothUser.UserID,
 | 
			
		||||
		UserID:            user.ID,
 | 
			
		||||
		LoginSourceID:     loginSource.ID,
 | 
			
		||||
@@ -42,7 +42,7 @@ func LinkAccountToUser(user *user_model.User, gothUser goth.User) error {
 | 
			
		||||
		ExpiresAt:         gothUser.ExpiresAt,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := models.LinkExternalToUser(user, externalLoginUser); err != nil {
 | 
			
		||||
	if err := user_model.LinkExternalToUser(user, externalLoginUser); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -260,7 +260,7 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
 | 
			
		||||
		tp := g.gitServiceType.Name()
 | 
			
		||||
		if !ok && tp != "" {
 | 
			
		||||
			var err error
 | 
			
		||||
			userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", release.PublisherID))
 | 
			
		||||
			userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", release.PublisherID))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("GetUserIDByExternalUserID: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
@@ -400,7 +400,7 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
 | 
			
		||||
		tp := g.gitServiceType.Name()
 | 
			
		||||
		if !ok && tp != "" {
 | 
			
		||||
			var err error
 | 
			
		||||
			userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", issue.PosterID))
 | 
			
		||||
			userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", issue.PosterID))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("GetUserIDByExternalUserID: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
@@ -425,7 +425,7 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
 | 
			
		||||
			userid, ok := g.userMap[reaction.UserID]
 | 
			
		||||
			if !ok && tp != "" {
 | 
			
		||||
				var err error
 | 
			
		||||
				userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
 | 
			
		||||
				userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Error("GetUserIDByExternalUserID: %v", err)
 | 
			
		||||
				}
 | 
			
		||||
@@ -483,7 +483,7 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
 | 
			
		||||
		tp := g.gitServiceType.Name()
 | 
			
		||||
		if !ok && tp != "" {
 | 
			
		||||
			var err error
 | 
			
		||||
			userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", comment.PosterID))
 | 
			
		||||
			userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", comment.PosterID))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("GetUserIDByExternalUserID: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
@@ -520,7 +520,7 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
 | 
			
		||||
			userid, ok := g.userMap[reaction.UserID]
 | 
			
		||||
			if !ok && tp != "" {
 | 
			
		||||
				var err error
 | 
			
		||||
				userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
 | 
			
		||||
				userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Error("GetUserIDByExternalUserID: %v", err)
 | 
			
		||||
				}
 | 
			
		||||
@@ -564,7 +564,7 @@ func (g *GiteaLocalUploader) CreatePullRequests(prs ...*base.PullRequest) error
 | 
			
		||||
		tp := g.gitServiceType.Name()
 | 
			
		||||
		if !ok && tp != "" {
 | 
			
		||||
			var err error
 | 
			
		||||
			userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", pr.PosterID))
 | 
			
		||||
			userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", pr.PosterID))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("GetUserIDByExternalUserID: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
@@ -744,7 +744,7 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR
 | 
			
		||||
	userid, ok := g.userMap[pr.PosterID]
 | 
			
		||||
	if !ok && tp != "" {
 | 
			
		||||
		var err error
 | 
			
		||||
		userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", pr.PosterID))
 | 
			
		||||
		userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", pr.PosterID))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("GetUserIDByExternalUserID: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
@@ -766,7 +766,7 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR
 | 
			
		||||
		userid, ok := g.userMap[reaction.UserID]
 | 
			
		||||
		if !ok && tp != "" {
 | 
			
		||||
			var err error
 | 
			
		||||
			userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
 | 
			
		||||
			userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("GetUserIDByExternalUserID: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
@@ -850,7 +850,7 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
 | 
			
		||||
		tp := g.gitServiceType.Name()
 | 
			
		||||
		if !ok && tp != "" {
 | 
			
		||||
			var err error
 | 
			
		||||
			userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", review.ReviewerID))
 | 
			
		||||
			userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", review.ReviewerID))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("GetUserIDByExternalUserID: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/structs"
 | 
			
		||||
)
 | 
			
		||||
@@ -45,7 +46,7 @@ func updateMigrationPosterIDByGitService(ctx context.Context, tp structs.GitServ
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		users, err := models.FindExternalUsersByProvider(models.FindExternalUserOptions{
 | 
			
		||||
		users, err := user_model.FindExternalUsersByProvider(user_model.FindExternalUserOptions{
 | 
			
		||||
			Provider: provider,
 | 
			
		||||
			Start:    start,
 | 
			
		||||
			Limit:    batchSize,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user