]> source.dussan.org Git - gitea.git/commitdiff
Move EmailAddress & UserRedirect into models/user/ (#17607)
authorLunny Xiao <xiaolunwen@gmail.com>
Thu, 11 Nov 2021 07:03:30 +0000 (15:03 +0800)
committerGitHub <noreply@github.com>
Thu, 11 Nov 2021 07:03:30 +0000 (15:03 +0800)
* Move EmailAddress into models/user/

* Fix test

* rename user_mail to user_email

* Fix test

* Move UserRedirect into models/user/

* Fix lint & test

* Fix lint

* Fix lint

* remove nolint comment

* Fix lint

34 files changed:
models/error.go
models/gpg_key.go
models/gpg_key_commit_verification.go
models/org.go
models/ssh_key_principals.go
models/user.go
models/user/email_address.go [new file with mode: 0644]
models/user/email_address_test.go [new file with mode: 0644]
models/user/main_test.go [new file with mode: 0644]
models/user/redirect.go [new file with mode: 0644]
models/user/redirect_test.go [new file with mode: 0644]
models/user_email.go [new file with mode: 0644]
models/user_email_test.go [new file with mode: 0644]
models/user_mail.go [deleted file]
models/user_mail_test.go [deleted file]
models/user_redirect.go [deleted file]
models/user_redirect_test.go [deleted file]
models/user_test.go
modules/context/org.go
modules/convert/convert.go
routers/api/v1/admin/user.go
routers/api/v1/api.go
routers/api/v1/user/email.go
routers/api/v1/user/helper.go
routers/web/admin/emails.go
routers/web/admin/users.go
routers/web/repo/http.go
routers/web/user/auth.go
routers/web/user/profile.go
routers/web/user/setting/account.go
routers/web/user/setting/profile.go
services/auth/signin.go
services/auth/source/pam/source_authenticate.go
services/mailer/mail.go

index b365a67b73836cc4ef5df7e48705ffc982ec1241..db0fce0ce1e5ab120c694d0cbcc85c43dcb7d852 100644 (file)
@@ -123,21 +123,6 @@ func (err ErrUserNotExist) Error() string {
        return fmt.Sprintf("user does not exist [uid: %d, name: %s, keyid: %d]", err.UID, err.Name, err.KeyID)
 }
 
-// ErrUserRedirectNotExist represents a "UserRedirectNotExist" kind of error.
-type ErrUserRedirectNotExist struct {
-       Name string
-}
-
-// IsErrUserRedirectNotExist check if an error is an ErrUserRedirectNotExist.
-func IsErrUserRedirectNotExist(err error) bool {
-       _, ok := err.(ErrUserRedirectNotExist)
-       return ok
-}
-
-func (err ErrUserRedirectNotExist) Error() string {
-       return fmt.Sprintf("user redirect does not exist [name: %s]", err.Name)
-}
-
 // ErrUserProhibitLogin represents a "ErrUserProhibitLogin" kind of error.
 type ErrUserProhibitLogin struct {
        UID  int64
@@ -170,66 +155,6 @@ func (err ErrUserInactive) Error() string {
        return fmt.Sprintf("user is inactive [uid: %d, name: %s]", err.UID, err.Name)
 }
 
-// ErrEmailAlreadyUsed represents a "EmailAlreadyUsed" kind of error.
-type ErrEmailAlreadyUsed struct {
-       Email string
-}
-
-// IsErrEmailAlreadyUsed checks if an error is a ErrEmailAlreadyUsed.
-func IsErrEmailAlreadyUsed(err error) bool {
-       _, ok := err.(ErrEmailAlreadyUsed)
-       return ok
-}
-
-func (err ErrEmailAlreadyUsed) Error() string {
-       return fmt.Sprintf("e-mail already in use [email: %s]", err.Email)
-}
-
-// ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
-type ErrEmailInvalid struct {
-       Email string
-}
-
-// IsErrEmailInvalid checks if an error is an ErrEmailInvalid
-func IsErrEmailInvalid(err error) bool {
-       _, ok := err.(ErrEmailInvalid)
-       return ok
-}
-
-func (err ErrEmailInvalid) Error() string {
-       return fmt.Sprintf("e-mail invalid [email: %s]", err.Email)
-}
-
-// ErrEmailAddressNotExist email address not exist
-type ErrEmailAddressNotExist struct {
-       Email string
-}
-
-// IsErrEmailAddressNotExist checks if an error is an ErrEmailAddressNotExist
-func IsErrEmailAddressNotExist(err error) bool {
-       _, ok := err.(ErrEmailAddressNotExist)
-       return ok
-}
-
-func (err ErrEmailAddressNotExist) Error() string {
-       return fmt.Sprintf("Email address does not exist [email: %s]", err.Email)
-}
-
-// ErrPrimaryEmailCannotDelete primary email address cannot be deleted
-type ErrPrimaryEmailCannotDelete struct {
-       Email string
-}
-
-// IsErrPrimaryEmailCannotDelete checks if an error is an ErrPrimaryEmailCannotDelete
-func IsErrPrimaryEmailCannotDelete(err error) bool {
-       _, ok := err.(ErrPrimaryEmailCannotDelete)
-       return ok
-}
-
-func (err ErrPrimaryEmailCannotDelete) Error() string {
-       return fmt.Sprintf("Primary email address cannot be deleted [email: %s]", err.Email)
-}
-
 // ErrOpenIDAlreadyUsed represents a "OpenIDAlreadyUsed" kind of error.
 type ErrOpenIDAlreadyUsed struct {
        OpenID string
index a62ed61966eda16b5e56f5419d713536d25521a2..357dc2b964cbfb1ea9bfc66e7d403b76008fe534 100644 (file)
@@ -10,6 +10,7 @@ import (
        "time"
 
        "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/timeutil"
 
@@ -36,7 +37,7 @@ type GPGKey struct {
        ExpiredUnix       timeutil.TimeStamp
        AddedUnix         timeutil.TimeStamp
        SubsKey           []*GPGKey `xorm:"-"`
-       Emails            []*EmailAddress
+       Emails            []*user_model.EmailAddress
        Verified          bool `xorm:"NOT NULL DEFAULT false"`
        CanSign           bool
        CanEncryptComms   bool
@@ -148,12 +149,12 @@ func parseGPGKey(ownerID int64, e *openpgp.Entity, verified bool) (*GPGKey, erro
        }
 
        // Check emails
-       userEmails, err := GetEmailAddresses(ownerID)
+       userEmails, err := user_model.GetEmailAddresses(ownerID)
        if err != nil {
                return nil, err
        }
 
-       emails := make([]*EmailAddress, 0, len(e.Identities))
+       emails := make([]*user_model.EmailAddress, 0, len(e.Identities))
        for _, ident := range e.Identities {
                if ident.Revocation != nil {
                        continue
@@ -242,7 +243,7 @@ func DeleteGPGKey(doer *User, id int64) (err error) {
 
 func checkKeyEmails(email string, keys ...*GPGKey) (bool, string) {
        uid := int64(0)
-       var userEmails []*EmailAddress
+       var userEmails []*user_model.EmailAddress
        var user *User
        for _, key := range keys {
                for _, e := range key.Emails {
@@ -252,7 +253,7 @@ func checkKeyEmails(email string, keys ...*GPGKey) (bool, string) {
                }
                if key.Verified && key.OwnerID != 0 {
                        if uid != key.OwnerID {
-                               userEmails, _ = GetEmailAddresses(key.OwnerID)
+                               userEmails, _ = user_model.GetEmailAddresses(key.OwnerID)
                                uid = key.OwnerID
                                user = &User{ID: uid}
                                _, _ = GetUser(user)
index f508303a0965c2736873d528df111dd559f9d0f4..6da65d3697941b879bc8f1b0a3bd6adf8b13a4cc 100644 (file)
@@ -10,6 +10,7 @@ import (
        "strings"
 
        "code.gitea.io/gitea/models/db"
+       user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/git"
        "code.gitea.io/gitea/modules/log"
        "code.gitea.io/gitea/modules/setting"
@@ -167,7 +168,7 @@ func ParseCommitWithSignature(c *git.Commit) *CommitVerification {
                        }
                }
 
-               committerEmailAddresses, _ := GetEmailAddresses(committer.ID)
+               committerEmailAddresses, _ := user_model.GetEmailAddresses(committer.ID)
                activated := false
                for _, e := range committerEmailAddresses {
                        if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
index c6d2562f15e85407e0ff7fcba286513da3141202..1840cae9fa0d337f3c7f87c98e2a91ac9038ccbc 100644 (file)
@@ -11,6 +11,7 @@ import (
 
        "code.gitea.io/gitea/models/db"
        "code.gitea.io/gitea/models/unit"
+       user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/log"
        "code.gitea.io/gitea/modules/setting"
        "code.gitea.io/gitea/modules/storage"
@@ -163,25 +164,25 @@ func CreateOrganization(org, owner *User) (err error) {
        org.NumMembers = 1
        org.Type = UserTypeOrganization
 
-       sess := db.NewSession(db.DefaultContext)
-       defer sess.Close()
-       if err = sess.Begin(); err != nil {
+       ctx, committer, err := db.TxContext()
+       if err != nil {
                return err
        }
+       defer committer.Close()
 
-       if err = deleteUserRedirect(sess, org.Name); err != nil {
+       if err = user_model.DeleteUserRedirect(ctx, org.Name); err != nil {
                return err
        }
 
-       if _, err = sess.Insert(org); err != nil {
+       if err = db.Insert(ctx, org); err != nil {
                return fmt.Errorf("insert organization: %v", err)
        }
-       if err = org.generateRandomAvatar(sess); err != nil {
+       if err = org.generateRandomAvatar(db.GetEngine(ctx)); err != nil {
                return fmt.Errorf("generate random avatar: %v", err)
        }
 
        // Add initial creator to organization and owner team.
-       if _, err = sess.Insert(&OrgUser{
+       if err = db.Insert(ctx, &OrgUser{
                UID:   owner.ID,
                OrgID: org.ID,
        }); err != nil {
@@ -198,7 +199,7 @@ func CreateOrganization(org, owner *User) (err error) {
                IncludesAllRepositories: true,
                CanCreateOrgRepo:        true,
        }
-       if _, err = sess.Insert(t); err != nil {
+       if err = db.Insert(ctx, t); err != nil {
                return fmt.Errorf("insert owner team: %v", err)
        }
 
@@ -212,14 +213,11 @@ func CreateOrganization(org, owner *User) (err error) {
                })
        }
 
-       if _, err = sess.Insert(&units); err != nil {
-               if err := sess.Rollback(); err != nil {
-                       log.Error("CreateOrganization: sess.Rollback: %v", err)
-               }
+       if err = db.Insert(ctx, &units); err != nil {
                return err
        }
 
-       if _, err = sess.Insert(&TeamUser{
+       if err = db.Insert(ctx, &TeamUser{
                UID:    owner.ID,
                OrgID:  org.ID,
                TeamID: t.ID,
@@ -227,7 +225,7 @@ func CreateOrganization(org, owner *User) (err error) {
                return fmt.Errorf("insert team-user relation: %v", err)
        }
 
-       return sess.Commit()
+       return committer.Commit()
 }
 
 // GetOrgByName returns organization by given name.
index 44b2ee0bb4e60b64c5907617eafaaf2ecd9a08d1..50456b72dbe8034cd2f291ce29791bcdfc242df2 100644 (file)
@@ -10,6 +10,7 @@ import (
        "strings"
 
        "code.gitea.io/gitea/models/db"
+       user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/setting"
 )
 
@@ -88,7 +89,7 @@ func CheckPrincipalKeyString(user *User, content string) (_ string, err error) {
                case "anything":
                        return content, nil
                case "email":
-                       emails, err := GetEmailAddresses(user.ID)
+                       emails, err := user_model.GetEmailAddresses(user.ID)
                        if err != nil {
                                return "", err
                        }
index 13347a46b7e173a6d666683e50d34311625b8218..12035dbe42375c5d891ffb341d8fc2934690ff8a 100644 (file)
@@ -23,6 +23,7 @@ import (
        "code.gitea.io/gitea/models/db"
        "code.gitea.io/gitea/models/login"
        "code.gitea.io/gitea/models/unit"
+       user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/base"
        "code.gitea.io/gitea/modules/git"
        "code.gitea.io/gitea/modules/log"
@@ -77,9 +78,6 @@ const (
 )
 
 var (
-       // ErrEmailNotActivated e-mail address has not been activated error
-       ErrEmailNotActivated = errors.New("E-mail address has not been activated")
-
        // ErrUserNameIllegal user name contains illegal characters error
        ErrUserNameIllegal = errors.New("User name contains illegal characters")
 
@@ -890,14 +888,15 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
                u.Visibility = overwriteDefault[0].Visibility
        }
 
-       sess := db.NewSession(db.DefaultContext)
-       defer sess.Close()
-       if err = sess.Begin(); err != nil {
+       ctx, committer, err := db.TxContext()
+       if err != nil {
                return err
        }
+       defer committer.Close()
 
-       // validate data
+       sess := db.GetEngine(ctx)
 
+       // validate data
        if err := validateUser(u); err != nil {
                return err
        }
@@ -909,11 +908,13 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
                return ErrUserAlreadyExist{u.Name}
        }
 
-       isExist, err = isEmailUsed(sess, u.Email)
+       isExist, err = user_model.IsEmailUsed(ctx, u.Email)
        if err != nil {
                return err
        } else if isExist {
-               return ErrEmailAlreadyUsed{u.Email}
+               return user_model.ErrEmailAlreadyUsed{
+                       Email: u.Email,
+               }
        }
 
        // prepare for database
@@ -929,16 +930,16 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
 
        // save changes to database
 
-       if err = deleteUserRedirect(sess, u.Name); err != nil {
+       if err = user_model.DeleteUserRedirect(ctx, u.Name); err != nil {
                return err
        }
 
-       if _, err = sess.Insert(u); err != nil {
+       if err = db.Insert(ctx, u); err != nil {
                return err
        }
 
        // insert email address
-       if _, err := sess.Insert(&EmailAddress{
+       if err := db.Insert(ctx, &user_model.EmailAddress{
                UID:         u.ID,
                Email:       u.Email,
                LowerEmail:  strings.ToLower(u.Email),
@@ -948,7 +949,7 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
                return err
        }
 
-       return sess.Commit()
+       return committer.Commit()
 }
 
 func countUsers(e db.Engine) int64 {
@@ -998,7 +999,7 @@ func VerifyUserActiveCode(code string) (user *User) {
 }
 
 // VerifyActiveEmailCode verifies active email code when active account
-func VerifyActiveEmailCode(code, email string) *EmailAddress {
+func VerifyActiveEmailCode(code, email string) *user_model.EmailAddress {
        minutes := setting.Service.ActiveCodeLives
 
        if user := getVerifyUser(code); user != nil {
@@ -1007,7 +1008,7 @@ func VerifyActiveEmailCode(code, email string) *EmailAddress {
                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}
+                       emailAddress := &user_model.EmailAddress{UID: user.ID, Email: email}
                        if has, _ := db.GetEngine(db.DefaultContext).Get(emailAddress); has {
                                return emailAddress
                        }
@@ -1023,11 +1024,12 @@ func ChangeUserName(u *User, newUserName string) (err error) {
                return err
        }
 
-       sess := db.NewSession(db.DefaultContext)
-       defer sess.Close()
-       if err = sess.Begin(); err != nil {
+       ctx, committer, err := db.TxContext()
+       if err != nil {
                return err
        }
+       defer committer.Close()
+       sess := db.GetEngine(ctx)
 
        isExist, err := isUserExist(sess, 0, newUserName)
        if err != nil {
@@ -1045,11 +1047,11 @@ func ChangeUserName(u *User, newUserName string) (err error) {
                return fmt.Errorf("Rename user directory: %v", err)
        }
 
-       if err = newUserRedirect(sess, u.ID, oldUserName, newUserName); err != nil {
+       if err = user_model.NewUserRedirect(ctx, u.ID, oldUserName, newUserName); err != nil {
                return err
        }
 
-       if err = sess.Commit(); err != nil {
+       if err = committer.Commit(); err != nil {
                if err2 := util.Rename(UserPath(newUserName), UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) {
                        log.Critical("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2)
                        return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2)
@@ -1071,7 +1073,9 @@ func checkDupEmail(e db.Engine, u *User) error {
        if err != nil {
                return err
        } else if has {
-               return ErrEmailAlreadyUsed{u.Email}
+               return user_model.ErrEmailAlreadyUsed{
+                       Email: u.Email,
+               }
        }
        return nil
 }
@@ -1083,7 +1087,7 @@ func validateUser(u *User) error {
        }
 
        u.Email = strings.ToLower(u.Email)
-       return ValidateEmail(u.Email)
+       return user_model.ValidateEmail(u.Email)
 }
 
 func updateUser(e db.Engine, u *User) error {
@@ -1211,7 +1215,7 @@ func deleteUser(e db.Engine, u *User) error {
                &Follow{FollowID: u.ID},
                &Action{UserID: u.ID},
                &IssueUser{UID: u.ID},
-               &EmailAddress{UID: u.ID},
+               &user_model.EmailAddress{UID: u.ID},
                &UserOpenID{UID: u.ID},
                &Reaction{UserID: u.ID},
                &TeamUser{UID: u.ID},
@@ -1370,7 +1374,7 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) (err erro
 
        _, err = db.GetEngine(db.DefaultContext).
                Where("is_activated = ?", false).
-               Delete(new(EmailAddress))
+               Delete(new(user_model.EmailAddress))
        return err
 }
 
@@ -1576,7 +1580,7 @@ func GetUserByEmailContext(ctx context.Context, email string) (*User, error) {
        }
 
        // Otherwise, check in alternative list for activated email addresses
-       emailAddress := &EmailAddress{Email: email, IsActivated: true}
+       emailAddress := &user_model.EmailAddress{Email: email, IsActivated: true}
        has, err = db.GetEngine(ctx).Get(emailAddress)
        if err != nil {
                return nil, err
diff --git a/models/user/email_address.go b/models/user/email_address.go
new file mode 100644 (file)
index 0000000..74fb71d
--- /dev/null
@@ -0,0 +1,269 @@
+// Copyright 2016 The Gogs Authors. All rights reserved.
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+       "context"
+       "errors"
+       "fmt"
+       "net/mail"
+       "strings"
+
+       "code.gitea.io/gitea/models/db"
+       "code.gitea.io/gitea/modules/log"
+       "code.gitea.io/gitea/modules/setting"
+
+       "xorm.io/builder"
+)
+
+var (
+       // ErrEmailNotActivated e-mail address has not been activated error
+       ErrEmailNotActivated = errors.New("E-mail address has not been activated")
+)
+
+// ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
+type ErrEmailInvalid struct {
+       Email string
+}
+
+// IsErrEmailInvalid checks if an error is an ErrEmailInvalid
+func IsErrEmailInvalid(err error) bool {
+       _, ok := err.(ErrEmailInvalid)
+       return ok
+}
+
+func (err ErrEmailInvalid) Error() string {
+       return fmt.Sprintf("e-mail invalid [email: %s]", err.Email)
+}
+
+// ErrEmailAlreadyUsed represents a "EmailAlreadyUsed" kind of error.
+type ErrEmailAlreadyUsed struct {
+       Email string
+}
+
+// IsErrEmailAlreadyUsed checks if an error is a ErrEmailAlreadyUsed.
+func IsErrEmailAlreadyUsed(err error) bool {
+       _, ok := err.(ErrEmailAlreadyUsed)
+       return ok
+}
+
+func (err ErrEmailAlreadyUsed) Error() string {
+       return fmt.Sprintf("e-mail already in use [email: %s]", err.Email)
+}
+
+// ErrEmailAddressNotExist email address not exist
+type ErrEmailAddressNotExist struct {
+       Email string
+}
+
+// IsErrEmailAddressNotExist checks if an error is an ErrEmailAddressNotExist
+func IsErrEmailAddressNotExist(err error) bool {
+       _, ok := err.(ErrEmailAddressNotExist)
+       return ok
+}
+
+func (err ErrEmailAddressNotExist) Error() string {
+       return fmt.Sprintf("Email address does not exist [email: %s]", err.Email)
+}
+
+// ErrPrimaryEmailCannotDelete primary email address cannot be deleted
+type ErrPrimaryEmailCannotDelete struct {
+       Email string
+}
+
+// IsErrPrimaryEmailCannotDelete checks if an error is an ErrPrimaryEmailCannotDelete
+func IsErrPrimaryEmailCannotDelete(err error) bool {
+       _, ok := err.(ErrPrimaryEmailCannotDelete)
+       return ok
+}
+
+func (err ErrPrimaryEmailCannotDelete) Error() string {
+       return fmt.Sprintf("Primary email address cannot be deleted [email: %s]", err.Email)
+}
+
+// EmailAddress is the list of all email addresses of a user. It also contains the
+// primary email address which is saved in user table.
+type EmailAddress struct {
+       ID          int64  `xorm:"pk autoincr"`
+       UID         int64  `xorm:"INDEX NOT NULL"`
+       Email       string `xorm:"UNIQUE NOT NULL"`
+       LowerEmail  string `xorm:"UNIQUE NOT NULL"`
+       IsActivated bool
+       IsPrimary   bool `xorm:"DEFAULT(false) NOT NULL"`
+}
+
+func init() {
+       db.RegisterModel(new(EmailAddress))
+}
+
+// BeforeInsert will be invoked by XORM before inserting a record
+func (email *EmailAddress) BeforeInsert() {
+       if email.LowerEmail == "" {
+               email.LowerEmail = strings.ToLower(email.Email)
+       }
+}
+
+// ValidateEmail check if email is a allowed address
+func ValidateEmail(email string) error {
+       if len(email) == 0 {
+               return nil
+       }
+
+       if _, err := mail.ParseAddress(email); err != nil {
+               return ErrEmailInvalid{email}
+       }
+
+       // TODO: add an email allow/block list
+
+       return nil
+}
+
+// GetEmailAddresses returns all email addresses belongs to given user.
+func GetEmailAddresses(uid int64) ([]*EmailAddress, error) {
+       emails := make([]*EmailAddress, 0, 5)
+       if err := db.GetEngine(db.DefaultContext).
+               Where("uid=?", uid).
+               Asc("id").
+               Find(&emails); err != nil {
+               return nil, err
+       }
+       return emails, nil
+}
+
+// GetEmailAddressByID gets a user's email address by ID
+func GetEmailAddressByID(uid, id int64) (*EmailAddress, error) {
+       // User ID is required for security reasons
+       email := &EmailAddress{UID: uid}
+       if has, err := db.GetEngine(db.DefaultContext).ID(id).Get(email); err != nil {
+               return nil, err
+       } else if !has {
+               return nil, nil
+       }
+       return email, nil
+}
+
+// IsEmailActive check if email is activated with a different emailID
+func IsEmailActive(ctx context.Context, email string, excludeEmailID int64) (bool, error) {
+       if len(email) == 0 {
+               return true, nil
+       }
+
+       // Can't filter by boolean field unless it's explicit
+       cond := builder.NewCond()
+       cond = cond.And(builder.Eq{"lower_email": strings.ToLower(email)}, builder.Neq{"id": excludeEmailID})
+       if setting.Service.RegisterEmailConfirm {
+               // Inactive (unvalidated) addresses don't count as active if email validation is required
+               cond = cond.And(builder.Eq{"is_activated": true})
+       }
+
+       var em EmailAddress
+       if has, err := db.GetEngine(ctx).Where(cond).Get(&em); has || err != nil {
+               if has {
+                       log.Info("isEmailActive(%q, %d) found duplicate in email ID %d", email, excludeEmailID, em.ID)
+               }
+               return has, err
+       }
+
+       return false, nil
+}
+
+// IsEmailUsed returns true if the email has been used.
+func IsEmailUsed(ctx context.Context, email string) (bool, error) {
+       if len(email) == 0 {
+               return true, nil
+       }
+
+       return db.GetEngine(ctx).Where("lower_email=?", strings.ToLower(email)).Get(&EmailAddress{})
+}
+
+func addEmailAddress(ctx context.Context, email *EmailAddress) error {
+       email.Email = strings.TrimSpace(email.Email)
+       used, err := IsEmailUsed(ctx, email.Email)
+       if err != nil {
+               return err
+       } else if used {
+               return ErrEmailAlreadyUsed{email.Email}
+       }
+
+       if err = ValidateEmail(email.Email); err != nil {
+               return err
+       }
+
+       return db.Insert(ctx, email)
+}
+
+// AddEmailAddress adds an email address to given user.
+func AddEmailAddress(email *EmailAddress) error {
+       return addEmailAddress(db.DefaultContext, email)
+}
+
+// AddEmailAddresses adds an email address to given user.
+func AddEmailAddresses(emails []*EmailAddress) error {
+       if len(emails) == 0 {
+               return nil
+       }
+
+       // Check if any of them has been used
+       for i := range emails {
+               emails[i].Email = strings.TrimSpace(emails[i].Email)
+               used, err := IsEmailUsed(db.DefaultContext, emails[i].Email)
+               if err != nil {
+                       return err
+               } else if used {
+                       return ErrEmailAlreadyUsed{emails[i].Email}
+               }
+               if err = ValidateEmail(emails[i].Email); err != nil {
+                       return err
+               }
+       }
+
+       if err := db.Insert(db.DefaultContext, emails); err != nil {
+               return fmt.Errorf("Insert: %v", err)
+       }
+
+       return nil
+}
+
+// DeleteEmailAddress deletes an email address of given user.
+func DeleteEmailAddress(email *EmailAddress) (err error) {
+       if email.IsPrimary {
+               return ErrPrimaryEmailCannotDelete{Email: email.Email}
+       }
+
+       var deleted int64
+       // ask to check UID
+       address := EmailAddress{
+               UID: email.UID,
+       }
+       if email.ID > 0 {
+               deleted, err = db.GetEngine(db.DefaultContext).ID(email.ID).Delete(&address)
+       } else {
+               if email.Email != "" && email.LowerEmail == "" {
+                       email.LowerEmail = strings.ToLower(email.Email)
+               }
+               deleted, err = db.GetEngine(db.DefaultContext).
+                       Where("lower_email=?", email.LowerEmail).
+                       Delete(&address)
+       }
+
+       if err != nil {
+               return err
+       } else if deleted != 1 {
+               return ErrEmailAddressNotExist{Email: email.Email}
+       }
+       return nil
+}
+
+// DeleteEmailAddresses deletes multiple email addresses
+func DeleteEmailAddresses(emails []*EmailAddress) (err error) {
+       for i := range emails {
+               if err = DeleteEmailAddress(emails[i]); err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
diff --git a/models/user/email_address_test.go b/models/user/email_address_test.go
new file mode 100644 (file)
index 0000000..5ed0bf8
--- /dev/null
@@ -0,0 +1,131 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+       "testing"
+
+       "code.gitea.io/gitea/models/db"
+
+       "github.com/stretchr/testify/assert"
+)
+
+func TestGetEmailAddresses(t *testing.T) {
+       assert.NoError(t, db.PrepareTestDatabase())
+
+       emails, _ := GetEmailAddresses(int64(1))
+       if assert.Len(t, emails, 3) {
+               assert.True(t, emails[0].IsPrimary)
+               assert.True(t, emails[2].IsActivated)
+               assert.False(t, emails[2].IsPrimary)
+       }
+
+       emails, _ = GetEmailAddresses(int64(2))
+       if assert.Len(t, emails, 2) {
+               assert.True(t, emails[0].IsPrimary)
+               assert.True(t, emails[0].IsActivated)
+       }
+}
+
+func TestIsEmailUsed(t *testing.T) {
+       assert.NoError(t, db.PrepareTestDatabase())
+
+       isExist, _ := IsEmailUsed(db.DefaultContext, "")
+       assert.True(t, isExist)
+       isExist, _ = IsEmailUsed(db.DefaultContext, "user11@example.com")
+       assert.True(t, isExist)
+       isExist, _ = IsEmailUsed(db.DefaultContext, "user1234567890@example.com")
+       assert.False(t, isExist)
+}
+
+func TestAddEmailAddress(t *testing.T) {
+       assert.NoError(t, db.PrepareTestDatabase())
+
+       assert.NoError(t, AddEmailAddress(&EmailAddress{
+               Email:       "user1234567890@example.com",
+               LowerEmail:  "user1234567890@example.com",
+               IsPrimary:   true,
+               IsActivated: true,
+       }))
+
+       // ErrEmailAlreadyUsed
+       err := AddEmailAddress(&EmailAddress{
+               Email:      "user1234567890@example.com",
+               LowerEmail: "user1234567890@example.com",
+       })
+       assert.Error(t, err)
+       assert.True(t, IsErrEmailAlreadyUsed(err))
+}
+
+func TestAddEmailAddresses(t *testing.T) {
+       assert.NoError(t, db.PrepareTestDatabase())
+
+       // insert multiple email address
+       emails := make([]*EmailAddress, 2)
+       emails[0] = &EmailAddress{
+               Email:       "user1234@example.com",
+               LowerEmail:  "user1234@example.com",
+               IsActivated: true,
+       }
+       emails[1] = &EmailAddress{
+               Email:       "user5678@example.com",
+               LowerEmail:  "user5678@example.com",
+               IsActivated: true,
+       }
+       assert.NoError(t, AddEmailAddresses(emails))
+
+       // ErrEmailAlreadyUsed
+       err := AddEmailAddresses(emails)
+       assert.Error(t, err)
+       assert.True(t, IsErrEmailAlreadyUsed(err))
+}
+
+func TestDeleteEmailAddress(t *testing.T) {
+       assert.NoError(t, db.PrepareTestDatabase())
+
+       assert.NoError(t, DeleteEmailAddress(&EmailAddress{
+               UID:        int64(1),
+               ID:         int64(33),
+               Email:      "user1-2@example.com",
+               LowerEmail: "user1-2@example.com",
+       }))
+
+       assert.NoError(t, DeleteEmailAddress(&EmailAddress{
+               UID:        int64(1),
+               Email:      "user1-3@example.com",
+               LowerEmail: "user1-3@example.com",
+       }))
+
+       // Email address does not exist
+       err := DeleteEmailAddress(&EmailAddress{
+               UID:        int64(1),
+               Email:      "user1234567890@example.com",
+               LowerEmail: "user1234567890@example.com",
+       })
+       assert.Error(t, err)
+}
+
+func TestDeleteEmailAddresses(t *testing.T) {
+       assert.NoError(t, db.PrepareTestDatabase())
+
+       // delete multiple email address
+       emails := make([]*EmailAddress, 2)
+       emails[0] = &EmailAddress{
+               UID:        int64(2),
+               ID:         int64(3),
+               Email:      "user2@example.com",
+               LowerEmail: "user2@example.com",
+       }
+       emails[1] = &EmailAddress{
+               UID:        int64(2),
+               Email:      "user2-2@example.com",
+               LowerEmail: "user2-2@example.com",
+       }
+       assert.NoError(t, DeleteEmailAddresses(emails))
+
+       // ErrEmailAlreadyUsed
+       err := DeleteEmailAddresses(emails)
+       assert.Error(t, err)
+}
diff --git a/models/user/main_test.go b/models/user/main_test.go
new file mode 100644 (file)
index 0000000..2999c4c
--- /dev/null
@@ -0,0 +1,19 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+       "path/filepath"
+       "testing"
+
+       "code.gitea.io/gitea/models/db"
+)
+
+func TestMain(m *testing.M) {
+       db.MainTest(m, filepath.Join("..", ".."),
+               "email_address.yml",
+               "user_redirect.yml",
+       )
+}
diff --git a/models/user/redirect.go b/models/user/redirect.go
new file mode 100644 (file)
index 0000000..4937021
--- /dev/null
@@ -0,0 +1,79 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+       "context"
+       "fmt"
+       "strings"
+
+       "code.gitea.io/gitea/models/db"
+)
+
+// ErrUserRedirectNotExist represents a "UserRedirectNotExist" kind of error.
+type ErrUserRedirectNotExist struct {
+       Name string
+}
+
+// IsErrUserRedirectNotExist check if an error is an ErrUserRedirectNotExist.
+func IsErrUserRedirectNotExist(err error) bool {
+       _, ok := err.(ErrUserRedirectNotExist)
+       return ok
+}
+
+func (err ErrUserRedirectNotExist) Error() string {
+       return fmt.Sprintf("user redirect does not exist [name: %s]", err.Name)
+}
+
+// Redirect represents that a user name should be redirected to another
+type Redirect struct {
+       ID             int64  `xorm:"pk autoincr"`
+       LowerName      string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+       RedirectUserID int64  // userID to redirect to
+}
+
+// TableName provides the real table name
+func (Redirect) TableName() string {
+       return "user_redirect"
+}
+
+func init() {
+       db.RegisterModel(new(Redirect))
+}
+
+// LookupUserRedirect look up userID if a user has a redirect name
+func LookupUserRedirect(userName string) (int64, error) {
+       userName = strings.ToLower(userName)
+       redirect := &Redirect{LowerName: userName}
+       if has, err := db.GetEngine(db.DefaultContext).Get(redirect); err != nil {
+               return 0, err
+       } else if !has {
+               return 0, ErrUserRedirectNotExist{Name: userName}
+       }
+       return redirect.RedirectUserID, nil
+}
+
+// NewUserRedirect create a new user redirect
+func NewUserRedirect(ctx context.Context, ID int64, oldUserName, newUserName string) error {
+       oldUserName = strings.ToLower(oldUserName)
+       newUserName = strings.ToLower(newUserName)
+
+       if err := DeleteUserRedirect(ctx, newUserName); err != nil {
+               return err
+       }
+
+       return db.Insert(ctx, &Redirect{
+               LowerName:      oldUserName,
+               RedirectUserID: ID,
+       })
+}
+
+// DeleteUserRedirect delete any redirect from the specified user name to
+// anything else
+func DeleteUserRedirect(ctx context.Context, userName string) error {
+       userName = strings.ToLower(userName)
+       _, err := db.GetEngine(ctx).Delete(&Redirect{LowerName: userName})
+       return err
+}
diff --git a/models/user/redirect_test.go b/models/user/redirect_test.go
new file mode 100644 (file)
index 0000000..b33c42c
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+       "testing"
+
+       "code.gitea.io/gitea/models/db"
+       "github.com/stretchr/testify/assert"
+)
+
+func TestLookupUserRedirect(t *testing.T) {
+       assert.NoError(t, db.PrepareTestDatabase())
+
+       userID, err := LookupUserRedirect("olduser1")
+       assert.NoError(t, err)
+       assert.EqualValues(t, 1, userID)
+
+       _, err = LookupUserRedirect("doesnotexist")
+       assert.True(t, IsErrUserRedirectNotExist(err))
+}
diff --git a/models/user_email.go b/models/user_email.go
new file mode 100644 (file)
index 0000000..7de577b
--- /dev/null
@@ -0,0 +1,235 @@
+// 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/util"
+       "xorm.io/builder"
+)
+
+// ActivateEmail activates the email address to given user.
+func ActivateEmail(email *user_model.EmailAddress) error {
+       sess := db.NewSession(db.DefaultContext)
+       defer sess.Close()
+       if err := sess.Begin(); err != nil {
+               return err
+       }
+       if err := updateActivation(sess, email, true); err != nil {
+               return err
+       }
+       return sess.Commit()
+}
+
+func updateActivation(e db.Engine, email *user_model.EmailAddress, activate bool) error {
+       user, err := getUserByID(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 updateUserCols(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{}
+       has, err = db.GetEngine(db.DefaultContext).ID(email.UID).Get(user)
+       if err != nil {
+               return err
+       } else if !has {
+               return ErrUserNotExist{email.UID, "", 0}
+       }
+
+       sess := db.NewSession(db.DefaultContext)
+       defer sess.Close()
+       if err = sess.Begin(); err != nil {
+               return err
+       }
+
+       // 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 sess.Commit()
+}
+
+// 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(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{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 = updateUserCols(sess, &user, "is_active", "rands"); err != nil {
+                               return fmt.Errorf("unable to updateUserCols() for user ID: %d: %v", userID, err)
+                       }
+               }
+       }
+
+       return committer.Commit()
+}
diff --git a/models/user_email_test.go b/models/user_email_test.go
new file mode 100644 (file)
index 0000000..8c2bb48
--- /dev/null
@@ -0,0 +1,136 @@
+// 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"
+       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, db.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, IsErrUserNotExist(err))
+
+       email = &user_model.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, db.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, db.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, count, 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, count, 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)))
+}
diff --git a/models/user_mail.go b/models/user_mail.go
deleted file mode 100644 (file)
index caa9317..0000000
+++ /dev/null
@@ -1,427 +0,0 @@
-// Copyright 2016 The Gogs Authors. All rights reserved.
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package models
-
-import (
-       "fmt"
-       "net/mail"
-       "strings"
-
-       "code.gitea.io/gitea/models/db"
-       "code.gitea.io/gitea/modules/log"
-       "code.gitea.io/gitea/modules/setting"
-       "code.gitea.io/gitea/modules/util"
-
-       "xorm.io/builder"
-)
-
-// EmailAddress is the list of all email addresses of a user. It also contains the
-// primary email address which is saved in user table.
-type EmailAddress struct {
-       ID          int64  `xorm:"pk autoincr"`
-       UID         int64  `xorm:"INDEX NOT NULL"`
-       Email       string `xorm:"UNIQUE NOT NULL"`
-       LowerEmail  string `xorm:"UNIQUE NOT NULL"`
-       IsActivated bool
-       IsPrimary   bool `xorm:"DEFAULT(false) NOT NULL"`
-}
-
-func init() {
-       db.RegisterModel(new(EmailAddress))
-}
-
-// BeforeInsert will be invoked by XORM before inserting a record
-func (email *EmailAddress) BeforeInsert() {
-       if email.LowerEmail == "" {
-               email.LowerEmail = strings.ToLower(email.Email)
-       }
-}
-
-// ValidateEmail check if email is a allowed address
-func ValidateEmail(email string) error {
-       if len(email) == 0 {
-               return nil
-       }
-
-       if _, err := mail.ParseAddress(email); err != nil {
-               return ErrEmailInvalid{email}
-       }
-
-       // TODO: add an email allow/block list
-
-       return nil
-}
-
-// GetEmailAddresses returns all email addresses belongs to given user.
-func GetEmailAddresses(uid int64) ([]*EmailAddress, error) {
-       emails := make([]*EmailAddress, 0, 5)
-       if err := db.GetEngine(db.DefaultContext).
-               Where("uid=?", uid).
-               Asc("id").
-               Find(&emails); err != nil {
-               return nil, err
-       }
-       return emails, nil
-}
-
-// GetEmailAddressByID gets a user's email address by ID
-func GetEmailAddressByID(uid, id int64) (*EmailAddress, error) {
-       // User ID is required for security reasons
-       email := &EmailAddress{UID: uid}
-       if has, err := db.GetEngine(db.DefaultContext).ID(id).Get(email); err != nil {
-               return nil, err
-       } else if !has {
-               return nil, nil
-       }
-       return email, nil
-}
-
-// isEmailActive check if email is activated with a different emailID
-func isEmailActive(e db.Engine, email string, excludeEmailID int64) (bool, error) {
-       if len(email) == 0 {
-               return true, nil
-       }
-
-       // Can't filter by boolean field unless it's explicit
-       cond := builder.NewCond()
-       cond = cond.And(builder.Eq{"lower_email": strings.ToLower(email)}, builder.Neq{"id": excludeEmailID})
-       if setting.Service.RegisterEmailConfirm {
-               // Inactive (unvalidated) addresses don't count as active if email validation is required
-               cond = cond.And(builder.Eq{"is_activated": true})
-       }
-
-       var em EmailAddress
-       if has, err := e.Where(cond).Get(&em); has || err != nil {
-               if has {
-                       log.Info("isEmailActive(%q, %d) found duplicate in email ID %d", email, excludeEmailID, em.ID)
-               }
-               return has, err
-       }
-
-       return false, nil
-}
-
-func isEmailUsed(e db.Engine, email string) (bool, error) {
-       if len(email) == 0 {
-               return true, nil
-       }
-
-       return e.Where("lower_email=?", strings.ToLower(email)).Get(&EmailAddress{})
-}
-
-// IsEmailUsed returns true if the email has been used.
-func IsEmailUsed(email string) (bool, error) {
-       return isEmailUsed(db.GetEngine(db.DefaultContext), email)
-}
-
-func addEmailAddress(e db.Engine, email *EmailAddress) error {
-       email.Email = strings.TrimSpace(email.Email)
-       used, err := isEmailUsed(e, email.Email)
-       if err != nil {
-               return err
-       } else if used {
-               return ErrEmailAlreadyUsed{email.Email}
-       }
-
-       if err = ValidateEmail(email.Email); err != nil {
-               return err
-       }
-
-       _, err = e.Insert(email)
-       return err
-}
-
-// AddEmailAddress adds an email address to given user.
-func AddEmailAddress(email *EmailAddress) error {
-       return addEmailAddress(db.GetEngine(db.DefaultContext), email)
-}
-
-// AddEmailAddresses adds an email address to given user.
-func AddEmailAddresses(emails []*EmailAddress) error {
-       if len(emails) == 0 {
-               return nil
-       }
-
-       // Check if any of them has been used
-       for i := range emails {
-               emails[i].Email = strings.TrimSpace(emails[i].Email)
-               used, err := IsEmailUsed(emails[i].Email)
-               if err != nil {
-                       return err
-               } else if used {
-                       return ErrEmailAlreadyUsed{emails[i].Email}
-               }
-               if err = ValidateEmail(emails[i].Email); err != nil {
-                       return err
-               }
-       }
-
-       if _, err := db.GetEngine(db.DefaultContext).Insert(emails); err != nil {
-               return fmt.Errorf("Insert: %v", err)
-       }
-
-       return nil
-}
-
-// Activate activates the email address to given user.
-func (email *EmailAddress) Activate() error {
-       sess := db.NewSession(db.DefaultContext)
-       defer sess.Close()
-       if err := sess.Begin(); err != nil {
-               return err
-       }
-       if err := email.updateActivation(sess, true); err != nil {
-               return err
-       }
-       return sess.Commit()
-}
-
-func (email *EmailAddress) updateActivation(e db.Engine, activate bool) error {
-       user, err := getUserByID(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 updateUserCols(e, user, "rands")
-}
-
-// DeleteEmailAddress deletes an email address of given user.
-func DeleteEmailAddress(email *EmailAddress) (err error) {
-       if email.IsPrimary {
-               return ErrPrimaryEmailCannotDelete{Email: email.Email}
-       }
-
-       var deleted int64
-       // ask to check UID
-       address := EmailAddress{
-               UID: email.UID,
-       }
-       if email.ID > 0 {
-               deleted, err = db.GetEngine(db.DefaultContext).ID(email.ID).Delete(&address)
-       } else {
-               if email.Email != "" && email.LowerEmail == "" {
-                       email.LowerEmail = strings.ToLower(email.Email)
-               }
-               deleted, err = db.GetEngine(db.DefaultContext).
-                       Where("lower_email=?", email.LowerEmail).
-                       Delete(&address)
-       }
-
-       if err != nil {
-               return err
-       } else if deleted != 1 {
-               return ErrEmailAddressNotExist{Email: email.Email}
-       }
-       return nil
-}
-
-// DeleteEmailAddresses deletes multiple email addresses
-func DeleteEmailAddresses(emails []*EmailAddress) (err error) {
-       for i := range emails {
-               if err = DeleteEmailAddress(emails[i]); err != nil {
-                       return err
-               }
-       }
-
-       return nil
-}
-
-// 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{email.UID, "", 0}
-       }
-
-       sess := db.NewSession(db.DefaultContext)
-       defer sess.Close()
-       if err = sess.Begin(); err != nil {
-               return err
-       }
-
-       // 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 sess.Commit()
-}
-
-// 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) {
-       sess := db.NewSession(db.DefaultContext)
-       defer sess.Close()
-       if err = sess.Begin(); err != nil {
-               return err
-       }
-
-       // 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(sess, 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 = addr.updateActivation(sess, 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 = updateUserCols(sess, &user, "is_active", "rands"); err != nil {
-                               return fmt.Errorf("unable to updateUserCols() for user ID: %d: %v", userID, err)
-                       }
-               }
-       }
-
-       return sess.Commit()
-}
diff --git a/models/user_mail_test.go b/models/user_mail_test.go
deleted file mode 100644 (file)
index 384f28b..0000000
+++ /dev/null
@@ -1,253 +0,0 @@
-// Copyright 2017 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package models
-
-import (
-       "testing"
-
-       "code.gitea.io/gitea/models/db"
-       "code.gitea.io/gitea/modules/util"
-
-       "github.com/stretchr/testify/assert"
-)
-
-func TestGetEmailAddresses(t *testing.T) {
-       assert.NoError(t, db.PrepareTestDatabase())
-
-       emails, _ := GetEmailAddresses(int64(1))
-       if assert.Len(t, emails, 3) {
-               assert.True(t, emails[0].IsPrimary)
-               assert.True(t, emails[2].IsActivated)
-               assert.False(t, emails[2].IsPrimary)
-       }
-
-       emails, _ = GetEmailAddresses(int64(2))
-       if assert.Len(t, emails, 2) {
-               assert.True(t, emails[0].IsPrimary)
-               assert.True(t, emails[0].IsActivated)
-       }
-}
-
-func TestIsEmailUsed(t *testing.T) {
-       assert.NoError(t, db.PrepareTestDatabase())
-
-       isExist, _ := IsEmailUsed("")
-       assert.True(t, isExist)
-       isExist, _ = IsEmailUsed("user11@example.com")
-       assert.True(t, isExist)
-       isExist, _ = IsEmailUsed("user1234567890@example.com")
-       assert.False(t, isExist)
-}
-
-func TestAddEmailAddress(t *testing.T) {
-       assert.NoError(t, db.PrepareTestDatabase())
-
-       assert.NoError(t, AddEmailAddress(&EmailAddress{
-               Email:       "user1234567890@example.com",
-               LowerEmail:  "user1234567890@example.com",
-               IsPrimary:   true,
-               IsActivated: true,
-       }))
-
-       // ErrEmailAlreadyUsed
-       err := AddEmailAddress(&EmailAddress{
-               Email:      "user1234567890@example.com",
-               LowerEmail: "user1234567890@example.com",
-       })
-       assert.Error(t, err)
-       assert.True(t, IsErrEmailAlreadyUsed(err))
-}
-
-func TestAddEmailAddresses(t *testing.T) {
-       assert.NoError(t, db.PrepareTestDatabase())
-
-       // insert multiple email address
-       emails := make([]*EmailAddress, 2)
-       emails[0] = &EmailAddress{
-               Email:       "user1234@example.com",
-               LowerEmail:  "user1234@example.com",
-               IsActivated: true,
-       }
-       emails[1] = &EmailAddress{
-               Email:       "user5678@example.com",
-               LowerEmail:  "user5678@example.com",
-               IsActivated: true,
-       }
-       assert.NoError(t, AddEmailAddresses(emails))
-
-       // ErrEmailAlreadyUsed
-       err := AddEmailAddresses(emails)
-       assert.Error(t, err)
-       assert.True(t, IsErrEmailAlreadyUsed(err))
-}
-
-func TestDeleteEmailAddress(t *testing.T) {
-       assert.NoError(t, db.PrepareTestDatabase())
-
-       assert.NoError(t, DeleteEmailAddress(&EmailAddress{
-               UID:        int64(1),
-               ID:         int64(33),
-               Email:      "user1-2@example.com",
-               LowerEmail: "user1-2@example.com",
-       }))
-
-       assert.NoError(t, DeleteEmailAddress(&EmailAddress{
-               UID:        int64(1),
-               Email:      "user1-3@example.com",
-               LowerEmail: "user1-3@example.com",
-       }))
-
-       // Email address does not exist
-       err := DeleteEmailAddress(&EmailAddress{
-               UID:        int64(1),
-               Email:      "user1234567890@example.com",
-               LowerEmail: "user1234567890@example.com",
-       })
-       assert.Error(t, err)
-}
-
-func TestDeleteEmailAddresses(t *testing.T) {
-       assert.NoError(t, db.PrepareTestDatabase())
-
-       // delete multiple email address
-       emails := make([]*EmailAddress, 2)
-       emails[0] = &EmailAddress{
-               UID:        int64(2),
-               ID:         int64(3),
-               Email:      "user2@example.com",
-               LowerEmail: "user2@example.com",
-       }
-       emails[1] = &EmailAddress{
-               UID:        int64(2),
-               Email:      "user2-2@example.com",
-               LowerEmail: "user2-2@example.com",
-       }
-       assert.NoError(t, DeleteEmailAddresses(emails))
-
-       // ErrEmailAlreadyUsed
-       err := DeleteEmailAddresses(emails)
-       assert.Error(t, err)
-}
-
-func TestMakeEmailPrimary(t *testing.T) {
-       assert.NoError(t, db.PrepareTestDatabase())
-
-       email := &EmailAddress{
-               Email: "user567890@example.com",
-       }
-       err := MakeEmailPrimary(email)
-       assert.Error(t, err)
-       assert.EqualError(t, err, ErrEmailAddressNotExist{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, db.PrepareTestDatabase())
-
-       email := &EmailAddress{
-               ID:    int64(1),
-               UID:   int64(1),
-               Email: "user11@example.com",
-       }
-       assert.NoError(t, email.Activate())
-
-       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, db.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, count, 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, count, 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)))
-}
diff --git a/models/user_redirect.go b/models/user_redirect.go
deleted file mode 100644 (file)
index fdc7307..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package models
-
-import (
-       "strings"
-
-       "code.gitea.io/gitea/models/db"
-)
-
-// UserRedirect represents that a user name should be redirected to another
-type UserRedirect struct {
-       ID             int64  `xorm:"pk autoincr"`
-       LowerName      string `xorm:"UNIQUE(s) INDEX NOT NULL"`
-       RedirectUserID int64  // userID to redirect to
-}
-
-func init() {
-       db.RegisterModel(new(UserRedirect))
-}
-
-// LookupUserRedirect look up userID if a user has a redirect name
-func LookupUserRedirect(userName string) (int64, error) {
-       userName = strings.ToLower(userName)
-       redirect := &UserRedirect{LowerName: userName}
-       if has, err := db.GetEngine(db.DefaultContext).Get(redirect); err != nil {
-               return 0, err
-       } else if !has {
-               return 0, ErrUserRedirectNotExist{Name: userName}
-       }
-       return redirect.RedirectUserID, nil
-}
-
-// newUserRedirect create a new user redirect
-func newUserRedirect(e db.Engine, ID int64, oldUserName, newUserName string) error {
-       oldUserName = strings.ToLower(oldUserName)
-       newUserName = strings.ToLower(newUserName)
-
-       if err := deleteUserRedirect(e, newUserName); err != nil {
-               return err
-       }
-
-       if _, err := e.Insert(&UserRedirect{
-               LowerName:      oldUserName,
-               RedirectUserID: ID,
-       }); err != nil {
-               return err
-       }
-       return nil
-}
-
-// deleteUserRedirect delete any redirect from the specified user name to
-// anything else
-func deleteUserRedirect(e db.Engine, userName string) error {
-       userName = strings.ToLower(userName)
-       _, err := e.Delete(&UserRedirect{LowerName: userName})
-       return err
-}
diff --git a/models/user_redirect_test.go b/models/user_redirect_test.go
deleted file mode 100644 (file)
index 346bf98..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package models
-
-import (
-       "testing"
-
-       "code.gitea.io/gitea/models/db"
-       "github.com/stretchr/testify/assert"
-)
-
-func TestLookupUserRedirect(t *testing.T) {
-       assert.NoError(t, db.PrepareTestDatabase())
-
-       userID, err := LookupUserRedirect("olduser1")
-       assert.NoError(t, err)
-       assert.EqualValues(t, 1, userID)
-
-       _, err = LookupUserRedirect("doesnotexist")
-       assert.True(t, IsErrUserRedirectNotExist(err))
-}
-
-func TestNewUserRedirect(t *testing.T) {
-       // redirect to a completely new name
-       assert.NoError(t, db.PrepareTestDatabase())
-
-       user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
-       assert.NoError(t, newUserRedirect(db.GetEngine(db.DefaultContext), user.ID, user.Name, "newusername"))
-
-       db.AssertExistsAndLoadBean(t, &UserRedirect{
-               LowerName:      user.LowerName,
-               RedirectUserID: user.ID,
-       })
-       db.AssertExistsAndLoadBean(t, &UserRedirect{
-               LowerName:      "olduser1",
-               RedirectUserID: user.ID,
-       })
-}
-
-func TestNewUserRedirect2(t *testing.T) {
-       // redirect to previously used name
-       assert.NoError(t, db.PrepareTestDatabase())
-
-       user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
-       assert.NoError(t, newUserRedirect(db.GetEngine(db.DefaultContext), user.ID, user.Name, "olduser1"))
-
-       db.AssertExistsAndLoadBean(t, &UserRedirect{
-               LowerName:      user.LowerName,
-               RedirectUserID: user.ID,
-       })
-       db.AssertNotExistsBean(t, &UserRedirect{
-               LowerName:      "olduser1",
-               RedirectUserID: user.ID,
-       })
-}
-
-func TestNewUserRedirect3(t *testing.T) {
-       // redirect for a previously-unredirected user
-       assert.NoError(t, db.PrepareTestDatabase())
-
-       user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
-       assert.NoError(t, newUserRedirect(db.GetEngine(db.DefaultContext), user.ID, user.Name, "newusername"))
-
-       db.AssertExistsAndLoadBean(t, &UserRedirect{
-               LowerName:      user.LowerName,
-               RedirectUserID: user.ID,
-       })
-}
index 3f3536dafaa90045f318a8c19a3e900a553d08d4..58044d1e99f9b4cd274a50ebe16c2e09edf73698 100644 (file)
@@ -12,6 +12,7 @@ import (
 
        "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/setting"
        "code.gitea.io/gitea/modules/structs"
        "code.gitea.io/gitea/modules/util"
@@ -358,7 +359,7 @@ func TestCreateUserInvalidEmail(t *testing.T) {
 
        err := CreateUser(user)
        assert.Error(t, err)
-       assert.True(t, IsErrEmailInvalid(err))
+       assert.True(t, user_model.IsErrEmailInvalid(err))
 }
 
 func TestCreateUser_Issue5882(t *testing.T) {
@@ -511,3 +512,50 @@ func TestUpdateUser(t *testing.T) {
        user.Email = "no mail@mail.org"
        assert.Error(t, UpdateUser(user))
 }
+
+func TestNewUserRedirect(t *testing.T) {
+       // redirect to a completely new name
+       assert.NoError(t, db.PrepareTestDatabase())
+
+       user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
+       assert.NoError(t, user_model.NewUserRedirect(db.DefaultContext, user.ID, user.Name, "newusername"))
+
+       db.AssertExistsAndLoadBean(t, &user_model.Redirect{
+               LowerName:      user.LowerName,
+               RedirectUserID: user.ID,
+       })
+       db.AssertExistsAndLoadBean(t, &user_model.Redirect{
+               LowerName:      "olduser1",
+               RedirectUserID: user.ID,
+       })
+}
+
+func TestNewUserRedirect2(t *testing.T) {
+       // redirect to previously used name
+       assert.NoError(t, db.PrepareTestDatabase())
+
+       user := db.AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
+       assert.NoError(t, user_model.NewUserRedirect(db.DefaultContext, user.ID, user.Name, "olduser1"))
+
+       db.AssertExistsAndLoadBean(t, &user_model.Redirect{
+               LowerName:      user.LowerName,
+               RedirectUserID: user.ID,
+       })
+       db.AssertNotExistsBean(t, &user_model.Redirect{
+               LowerName:      "olduser1",
+               RedirectUserID: user.ID,
+       })
+}
+
+func TestNewUserRedirect3(t *testing.T) {
+       // redirect for a previously-unredirected user
+       assert.NoError(t, db.PrepareTestDatabase())
+
+       user := db.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
+       assert.NoError(t, user_model.NewUserRedirect(db.DefaultContext, user.ID, user.Name, "newusername"))
+
+       db.AssertExistsAndLoadBean(t, &user_model.Redirect{
+               LowerName:      user.LowerName,
+               RedirectUserID: user.ID,
+       })
+}
index fd67212a10c43e67ed3a9b07b8c8f9bc7344e500..d7257361fd82e94d2dbc502f299ee5bcc1c3c77c 100644 (file)
@@ -9,6 +9,7 @@ import (
        "strings"
 
        "code.gitea.io/gitea/models"
+       user_model "code.gitea.io/gitea/models/user"
 )
 
 // Organization contains organization context
@@ -51,10 +52,10 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
        ctx.Org.Organization, err = models.GetUserByName(orgName)
        if err != nil {
                if models.IsErrUserNotExist(err) {
-                       redirectUserID, err := models.LookupUserRedirect(orgName)
+                       redirectUserID, err := user_model.LookupUserRedirect(orgName)
                        if err == nil {
                                RedirectToUser(ctx, orgName, redirectUserID)
-                       } else if models.IsErrUserRedirectNotExist(err) {
+                       } else if user_model.IsErrUserRedirectNotExist(err) {
                                ctx.NotFound("GetUserByName", err)
                        } else {
                                ctx.ServerError("LookupUserRedirect", err)
index 7d67e2b5b13156818f4f1ac97352e76fce72b1d8..80d5ffff0593a569ae2dc43f87fde0fa6e45a615 100644 (file)
@@ -14,6 +14,7 @@ import (
        "code.gitea.io/gitea/models"
        "code.gitea.io/gitea/models/login"
        "code.gitea.io/gitea/models/unit"
+       user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/models/webhook"
        "code.gitea.io/gitea/modules/git"
        "code.gitea.io/gitea/modules/log"
@@ -23,7 +24,7 @@ import (
 )
 
 // ToEmail convert models.EmailAddress to api.Email
-func ToEmail(email *models.EmailAddress) *api.Email {
+func ToEmail(email *user_model.EmailAddress) *api.Email {
        return &api.Email{
                Email:    email.Email,
                Verified: email.IsActivated,
@@ -219,7 +220,7 @@ func ToGPGKey(key *models.GPGKey) *api.GPGKey {
 }
 
 // ToGPGKeyEmail convert models.EmailAddress to api.GPGKeyEmail
-func ToGPGKeyEmail(email *models.EmailAddress) *api.GPGKeyEmail {
+func ToGPGKeyEmail(email *user_model.EmailAddress) *api.GPGKeyEmail {
        return &api.GPGKeyEmail{
                Email:    email.Email,
                Verified: email.IsActivated,
index 2d585b60401ba26eaf9e06e4edab2ff25f3ff0ec..1e4a2851c216b14050f394e236cba7e5dbc3f0a1 100644 (file)
@@ -12,6 +12,7 @@ import (
 
        "code.gitea.io/gitea/models"
        "code.gitea.io/gitea/models/login"
+       user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/context"
        "code.gitea.io/gitea/modules/convert"
        "code.gitea.io/gitea/modules/log"
@@ -109,10 +110,10 @@ func CreateUser(ctx *context.APIContext) {
 
        if err := models.CreateUser(u, overwriteDefault); err != nil {
                if models.IsErrUserAlreadyExist(err) ||
-                       models.IsErrEmailAlreadyUsed(err) ||
+                       user_model.IsErrEmailAlreadyUsed(err) ||
                        models.IsErrNameReserved(err) ||
                        models.IsErrNameCharsNotAllowed(err) ||
-                       models.IsErrEmailInvalid(err) ||
+                       user_model.IsErrEmailInvalid(err) ||
                        models.IsErrNamePatternNotAllowed(err) {
                        ctx.Error(http.StatusUnprocessableEntity, "", err)
                } else {
@@ -245,7 +246,7 @@ func EditUser(ctx *context.APIContext) {
        }
 
        if err := models.UpdateUser(u); err != nil {
-               if models.IsErrEmailAlreadyUsed(err) || models.IsErrEmailInvalid(err) {
+               if user_model.IsErrEmailAlreadyUsed(err) || user_model.IsErrEmailInvalid(err) {
                        ctx.Error(http.StatusUnprocessableEntity, "", err)
                } else {
                        ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
index 8f852a96ce39bc5d953728ef4a1f6c5335316a4b..f312df23db1a6f59fa260f847797958913700a62 100644 (file)
@@ -71,6 +71,7 @@ import (
 
        "code.gitea.io/gitea/models"
        "code.gitea.io/gitea/models/unit"
+       user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/context"
        "code.gitea.io/gitea/modules/log"
        "code.gitea.io/gitea/modules/setting"
@@ -138,9 +139,9 @@ func repoAssignment() func(ctx *context.APIContext) {
                        owner, err = models.GetUserByName(userName)
                        if err != nil {
                                if models.IsErrUserNotExist(err) {
-                                       if redirectUserID, err := models.LookupUserRedirect(userName); err == nil {
+                                       if redirectUserID, err := user_model.LookupUserRedirect(userName); err == nil {
                                                context.RedirectToUser(ctx.Context, userName, redirectUserID)
-                                       } else if models.IsErrUserRedirectNotExist(err) {
+                                       } else if user_model.IsErrUserRedirectNotExist(err) {
                                                ctx.NotFound("GetUserByName", err)
                                        } else {
                                                ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
@@ -421,10 +422,10 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
                        ctx.Org.Organization, err = models.GetOrgByName(ctx.Params(":org"))
                        if err != nil {
                                if models.IsErrOrgNotExist(err) {
-                                       redirectUserID, err := models.LookupUserRedirect(ctx.Params(":org"))
+                                       redirectUserID, err := user_model.LookupUserRedirect(ctx.Params(":org"))
                                        if err == nil {
                                                context.RedirectToUser(ctx.Context, ctx.Params(":org"), redirectUserID)
-                                       } else if models.IsErrUserRedirectNotExist(err) {
+                                       } else if user_model.IsErrUserRedirectNotExist(err) {
                                                ctx.NotFound("GetOrgByName", err)
                                        } else {
                                                ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
index 9dd35f91b62b131a44226a08efe0c8db7d9118d1..6887c306cc16e666e81fc3b1c74d6ae846cceae9 100644 (file)
@@ -8,7 +8,7 @@ import (
        "fmt"
        "net/http"
 
-       "code.gitea.io/gitea/models"
+       user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/context"
        "code.gitea.io/gitea/modules/convert"
        "code.gitea.io/gitea/modules/setting"
@@ -28,7 +28,7 @@ func ListEmails(ctx *context.APIContext) {
        //   "200":
        //     "$ref": "#/responses/EmailList"
 
-       emails, err := models.GetEmailAddresses(ctx.User.ID)
+       emails, err := user_model.GetEmailAddresses(ctx.User.ID)
        if err != nil {
                ctx.Error(http.StatusInternalServerError, "GetEmailAddresses", err)
                return
@@ -68,20 +68,20 @@ func AddEmail(ctx *context.APIContext) {
                return
        }
 
-       emails := make([]*models.EmailAddress, len(form.Emails))
+       emails := make([]*user_model.EmailAddress, len(form.Emails))
        for i := range form.Emails {
-               emails[i] = &models.EmailAddress{
+               emails[i] = &user_model.EmailAddress{
                        UID:         ctx.User.ID,
                        Email:       form.Emails[i],
                        IsActivated: !setting.Service.RegisterEmailConfirm,
                }
        }
 
-       if err := models.AddEmailAddresses(emails); err != nil {
-               if models.IsErrEmailAlreadyUsed(err) {
-                       ctx.Error(http.StatusUnprocessableEntity, "", "Email address has been used: "+err.(models.ErrEmailAlreadyUsed).Email)
-               } else if models.IsErrEmailInvalid(err) {
-                       errMsg := fmt.Sprintf("Email address %s invalid", err.(models.ErrEmailInvalid).Email)
+       if err := user_model.AddEmailAddresses(emails); err != nil {
+               if user_model.IsErrEmailAlreadyUsed(err) {
+                       ctx.Error(http.StatusUnprocessableEntity, "", "Email address has been used: "+err.(user_model.ErrEmailAlreadyUsed).Email)
+               } else if user_model.IsErrEmailInvalid(err) {
+                       errMsg := fmt.Sprintf("Email address %s invalid", err.(user_model.ErrEmailInvalid).Email)
                        ctx.Error(http.StatusUnprocessableEntity, "", errMsg)
                } else {
                        ctx.Error(http.StatusInternalServerError, "AddEmailAddresses", err)
@@ -119,16 +119,16 @@ func DeleteEmail(ctx *context.APIContext) {
                return
        }
 
-       emails := make([]*models.EmailAddress, len(form.Emails))
+       emails := make([]*user_model.EmailAddress, len(form.Emails))
        for i := range form.Emails {
-               emails[i] = &models.EmailAddress{
+               emails[i] = &user_model.EmailAddress{
                        Email: form.Emails[i],
                        UID:   ctx.User.ID,
                }
        }
 
-       if err := models.DeleteEmailAddresses(emails); err != nil {
-               if models.IsErrEmailAddressNotExist(err) {
+       if err := user_model.DeleteEmailAddresses(emails); err != nil {
+               if user_model.IsErrEmailAddressNotExist(err) {
                        ctx.Error(http.StatusNotFound, "DeleteEmailAddresses", err)
                        return
                }
index a3500e0ee64870feda742759d1c67fe5db432c74..a7c2e61405423faba5f08acd53a980d41ef8969a 100644 (file)
@@ -8,6 +8,7 @@ import (
        "net/http"
 
        "code.gitea.io/gitea/models"
+       user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/context"
 )
 
@@ -17,7 +18,7 @@ func GetUserByParamsName(ctx *context.APIContext, name string) *models.User {
        user, err := models.GetUserByName(username)
        if err != nil {
                if models.IsErrUserNotExist(err) {
-                       if redirectUserID, err2 := models.LookupUserRedirect(username); err2 == nil {
+                       if redirectUserID, err2 := user_model.LookupUserRedirect(username); err2 == nil {
                                context.RedirectToUser(ctx.Context, username, redirectUserID)
                        } else {
                                ctx.NotFound("GetUserByName", err)
index 5cbe70020b703683cf168421cd9b43a08f7f4a69..4872ecb9206e14d30ad8347220a133f4e758df83 100644 (file)
@@ -11,6 +11,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/base"
        "code.gitea.io/gitea/modules/context"
        "code.gitea.io/gitea/modules/log"
@@ -128,7 +129,7 @@ func ActivateEmail(ctx *context.Context) {
 
        if err := models.ActivateUserEmail(uid, email, activate); err != nil {
                log.Error("ActivateUserEmail(%v,%v,%v): %v", uid, email, activate, err)
-               if models.IsErrEmailAlreadyUsed(err) {
+               if user_model.IsErrEmailAlreadyUsed(err) {
                        ctx.Flash.Error(ctx.Tr("admin.emails.duplicate_active"))
                } else {
                        ctx.Flash.Error(ctx.Tr("admin.emails.not_updated", err))
index 0041f3d07c27c504e3155f98f47eb5ba46cb5c5b..db7fe7b36f6eee2f91c4ebcd9060e98efd9a1b5d 100644 (file)
@@ -14,6 +14,7 @@ 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"
        "code.gitea.io/gitea/modules/base"
        "code.gitea.io/gitea/modules/context"
        "code.gitea.io/gitea/modules/log"
@@ -159,10 +160,10 @@ func NewUserPost(ctx *context.Context) {
                case models.IsErrUserAlreadyExist(err):
                        ctx.Data["Err_UserName"] = true
                        ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplUserNew, &form)
-               case models.IsErrEmailAlreadyUsed(err):
+               case user_model.IsErrEmailAlreadyUsed(err):
                        ctx.Data["Err_Email"] = true
                        ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserNew, &form)
-               case models.IsErrEmailInvalid(err):
+               case user_model.IsErrEmailInvalid(err):
                        ctx.Data["Err_Email"] = true
                        ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserNew, &form)
                case models.IsErrNameReserved(err):
@@ -351,10 +352,10 @@ func EditUserPost(ctx *context.Context) {
        }
 
        if err := models.UpdateUser(u); err != nil {
-               if models.IsErrEmailAlreadyUsed(err) {
+               if user_model.IsErrEmailAlreadyUsed(err) {
                        ctx.Data["Err_Email"] = true
                        ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserEdit, &form)
-               } else if models.IsErrEmailInvalid(err) {
+               } else if user_model.IsErrEmailInvalid(err) {
                        ctx.Data["Err_Email"] = true
                        ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserEdit, &form)
                } else {
index 7c5dcd42be373394289eca3e4a5039c84abeafd2..0626d08a7cf6bf033359ec9f882c159ba8d7e5dd 100644 (file)
@@ -23,6 +23,7 @@ import (
        "code.gitea.io/gitea/models"
        "code.gitea.io/gitea/models/login"
        "code.gitea.io/gitea/models/unit"
+       user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/context"
        "code.gitea.io/gitea/modules/git"
        "code.gitea.io/gitea/modules/log"
@@ -112,7 +113,7 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
        owner, err := models.GetUserByName(username)
        if err != nil {
                if models.IsErrUserNotExist(err) {
-                       if redirectUserID, err := models.LookupUserRedirect(username); err == nil {
+                       if redirectUserID, err := user_model.LookupUserRedirect(username); err == nil {
                                context.RedirectToUser(ctx, username, redirectUserID)
                        } else {
                                ctx.NotFound(fmt.Sprintf("User %s does not exist", username), nil)
index 55f304a7cb6258b2f0e8a94e2806e2e41bf6b668..ba37c8e3eda197064389782444b88c798f7f1a99 100644 (file)
@@ -15,6 +15,7 @@ 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"
        "code.gitea.io/gitea/modules/base"
        "code.gitea.io/gitea/modules/context"
        "code.gitea.io/gitea/modules/eventsource"
@@ -181,7 +182,7 @@ func SignInPost(ctx *context.Context) {
                if models.IsErrUserNotExist(err) {
                        ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form)
                        log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
-               } else if models.IsErrEmailAlreadyUsed(err) {
+               } else if user_model.IsErrEmailAlreadyUsed(err) {
                        ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSignIn, &form)
                        log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
                } else if models.IsErrUserProhibitLogin(err) {
@@ -1273,7 +1274,7 @@ func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form int
 // Optionally a template can be specified.
 func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User, gothUser *goth.User, allowLink bool) (ok bool) {
        if err := models.CreateUser(u); err != nil {
-               if allowLink && (models.IsErrUserAlreadyExist(err) || models.IsErrEmailAlreadyUsed(err)) {
+               if allowLink && (models.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) {
                        if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto {
                                var user *models.User
                                user = &models.User{Name: u.Name}
@@ -1307,10 +1308,10 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{
                case models.IsErrUserAlreadyExist(err):
                        ctx.Data["Err_UserName"] = true
                        ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tpl, form)
-               case models.IsErrEmailAlreadyUsed(err):
+               case user_model.IsErrEmailAlreadyUsed(err):
                        ctx.Data["Err_Email"] = true
                        ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, form)
-               case models.IsErrEmailInvalid(err):
+               case user_model.IsErrEmailInvalid(err):
                        ctx.Data["Err_Email"] = true
                        ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form)
                case models.IsErrNameReserved(err):
@@ -1499,7 +1500,7 @@ func ActivateEmail(ctx *context.Context) {
 
        // Verify code.
        if email := models.VerifyActiveEmailCode(code, emailStr); email != nil {
-               if err := email.Activate(); err != nil {
+               if err := models.ActivateEmail(email); err != nil {
                        ctx.ServerError("ActivateEmail", err)
                }
 
index d2a8d83faa1a52df91dbddf1d1712891fb2a9dab..f8fcbf65650705eaaccf61719ac51049496a0373 100644 (file)
@@ -13,6 +13,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/context"
        "code.gitea.io/gitea/modules/markup"
        "code.gitea.io/gitea/modules/markup/markdown"
@@ -27,7 +28,7 @@ func GetUserByName(ctx *context.Context, name string) *models.User {
        user, err := models.GetUserByName(name)
        if err != nil {
                if models.IsErrUserNotExist(err) {
-                       if redirectUserID, err := models.LookupUserRedirect(name); err == nil {
+                       if redirectUserID, err := user_model.LookupUserRedirect(name); err == nil {
                                context.RedirectToUser(ctx, name, redirectUserID)
                        } else {
                                ctx.NotFound("GetUserByName", err)
index 47014dc8143b221bb14cbec4df35787fe1cd6126..5ef1c3bdc840bbba10127ea7fd349a023b0eb760 100644 (file)
@@ -11,6 +11,7 @@ import (
        "time"
 
        "code.gitea.io/gitea/models"
+       user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/base"
        "code.gitea.io/gitea/modules/context"
        "code.gitea.io/gitea/modules/log"
@@ -91,7 +92,7 @@ func EmailPost(ctx *context.Context) {
 
        // Make emailaddress primary.
        if ctx.FormString("_method") == "PRIMARY" {
-               if err := models.MakeEmailPrimary(&models.EmailAddress{ID: ctx.FormInt64("id")}); err != nil {
+               if err := models.MakeEmailPrimary(&user_model.EmailAddress{ID: ctx.FormInt64("id")}); err != nil {
                        ctx.ServerError("MakeEmailPrimary", err)
                        return
                }
@@ -110,7 +111,7 @@ func EmailPost(ctx *context.Context) {
                }
 
                id := ctx.FormInt64("id")
-               email, err := models.GetEmailAddressByID(ctx.User.ID, id)
+               email, err := user_model.GetEmailAddressByID(ctx.User.ID, id)
                if err != nil {
                        log.Error("GetEmailAddressByID(%d,%d) error: %v", ctx.User.ID, id, err)
                        ctx.Redirect(setting.AppSubURL + "/user/settings/account")
@@ -174,18 +175,18 @@ func EmailPost(ctx *context.Context) {
                return
        }
 
-       email := &models.EmailAddress{
+       email := &user_model.EmailAddress{
                UID:         ctx.User.ID,
                Email:       form.Email,
                IsActivated: !setting.Service.RegisterEmailConfirm,
        }
-       if err := models.AddEmailAddress(email); err != nil {
-               if models.IsErrEmailAlreadyUsed(err) {
+       if err := user_model.AddEmailAddress(email); err != nil {
+               if user_model.IsErrEmailAlreadyUsed(err) {
                        loadAccountData(ctx)
 
                        ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSettingsAccount, &form)
                        return
-               } else if models.IsErrEmailInvalid(err) {
+               } else if user_model.IsErrEmailInvalid(err) {
                        loadAccountData(ctx)
 
                        ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSettingsAccount, &form)
@@ -212,7 +213,7 @@ func EmailPost(ctx *context.Context) {
 
 // DeleteEmail response for delete user's email
 func DeleteEmail(ctx *context.Context) {
-       if err := models.DeleteEmailAddress(&models.EmailAddress{ID: ctx.FormInt64("id"), UID: ctx.User.ID}); err != nil {
+       if err := user_model.DeleteEmailAddress(&user_model.EmailAddress{ID: ctx.FormInt64("id"), UID: ctx.User.ID}); err != nil {
                ctx.ServerError("DeleteEmail", err)
                return
        }
@@ -258,13 +259,13 @@ func DeleteAccount(ctx *context.Context) {
 }
 
 func loadAccountData(ctx *context.Context) {
-       emlist, err := models.GetEmailAddresses(ctx.User.ID)
+       emlist, err := user_model.GetEmailAddresses(ctx.User.ID)
        if err != nil {
                ctx.ServerError("GetEmailAddresses", err)
                return
        }
        type UserEmail struct {
-               models.EmailAddress
+               user_model.EmailAddress
                CanBePrimary bool
        }
        pendingActivation := ctx.Cache.IsExist("MailResendLimit_" + ctx.User.LowerName)
index d181ae1720db870199377822dd4d0a62886fd690..4a0900615099aea1608321c0b4eeb618bfa4c4a0 100644 (file)
@@ -16,6 +16,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/base"
        "code.gitea.io/gitea/modules/context"
        "code.gitea.io/gitea/modules/log"
@@ -60,7 +61,7 @@ func HandleUsernameChange(ctx *context.Context, user *models.User, newName strin
                        switch {
                        case models.IsErrUserAlreadyExist(err):
                                ctx.Flash.Error(ctx.Tr("form.username_been_taken"))
-                       case models.IsErrEmailAlreadyUsed(err):
+                       case user_model.IsErrEmailAlreadyUsed(err):
                                ctx.Flash.Error(ctx.Tr("form.email_been_used"))
                        case models.IsErrNameReserved(err):
                                ctx.Flash.Error(ctx.Tr("user.form.name_reserved", newName))
@@ -120,7 +121,7 @@ func ProfilePost(ctx *context.Context) {
        ctx.User.KeepActivityPrivate = form.KeepActivityPrivate
        ctx.User.Visibility = form.Visibility
        if err := models.UpdateUserSetting(ctx.User); err != nil {
-               if _, ok := err.(models.ErrEmailAlreadyUsed); ok {
+               if _, ok := err.(user_model.ErrEmailAlreadyUsed); ok {
                        ctx.Flash.Error(ctx.Tr("form.email_been_used"))
                        ctx.Redirect(setting.AppSubURL + "/user/settings")
                        return
index a7ad029456dfa522492fbe4369231b05bc7a1609..5477e8643ebc11204b5613793b02f210f81141e3 100644 (file)
@@ -10,6 +10,7 @@ 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"
        "code.gitea.io/gitea/modules/log"
 
        // Register the sources
@@ -32,7 +33,7 @@ func UserSignIn(username, password string) (*models.User, *login.Source, error)
                        return nil, nil, err
                }
                if cnt > 1 {
-                       return nil, nil, models.ErrEmailAlreadyUsed{
+                       return nil, nil, user_model.ErrEmailAlreadyUsed{
                                Email: user.Email,
                        }
                }
index cb5ffc2861f3349067e3ea3dbfafbcdd3b649656..6998241ad7214fa9f162fe16c5c7a86a9f445731 100644 (file)
@@ -10,6 +10,7 @@ import (
 
        "code.gitea.io/gitea/models"
        "code.gitea.io/gitea/models/login"
+       user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/auth/pam"
        "code.gitea.io/gitea/modules/setting"
        "code.gitea.io/gitea/services/mailer"
@@ -39,13 +40,13 @@ func (source *Source) Authenticate(user *models.User, userName, password string)
        if idx > -1 {
                username = pamLogin[:idx]
        }
-       if models.ValidateEmail(email) != nil {
+       if user_model.ValidateEmail(email) != nil {
                if source.EmailDomain != "" {
                        email = fmt.Sprintf("%s@%s", username, source.EmailDomain)
                } else {
                        email = fmt.Sprintf("%s@%s", username, setting.Service.NoReplyAddress)
                }
-               if models.ValidateEmail(email) != nil {
+               if user_model.ValidateEmail(email) != nil {
                        email = uuid.New().String() + "@localhost"
                }
        }
index bd8f059c5ebb829ef8306f4ae7a755c5e76ddf35..b579bf383a989b4f3934c3d124aedcc0130e5558 100644 (file)
@@ -16,6 +16,7 @@ import (
        texttmpl "text/template"
 
        "code.gitea.io/gitea/models"
+       user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/base"
        "code.gitea.io/gitea/modules/emoji"
        "code.gitea.io/gitea/modules/log"
@@ -112,7 +113,7 @@ func SendResetPasswordMail(u *models.User) {
 }
 
 // SendActivateEmailMail sends confirmation email to confirm new email address
-func SendActivateEmailMail(u *models.User, email *models.EmailAddress) {
+func SendActivateEmailMail(u *models.User, email *user_model.EmailAddress) {
        if setting.MailService == nil {
                // No mail service configured
                return