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:
parent
b1df890951
commit
9defddb286
23 changed files with 547 additions and 603 deletions
|
@ -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))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue