diff --git a/models/error.go b/models/error.go index b365a67b7..db0fce0ce 100644 --- a/models/error.go +++ b/models/error.go @@ -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 diff --git a/models/gpg_key.go b/models/gpg_key.go index a62ed6196..357dc2b96 100644 --- a/models/gpg_key.go +++ b/models/gpg_key.go @@ -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) diff --git a/models/gpg_key_commit_verification.go b/models/gpg_key_commit_verification.go index f508303a0..6da65d369 100644 --- a/models/gpg_key_commit_verification.go +++ b/models/gpg_key_commit_verification.go @@ -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) { diff --git a/models/org.go b/models/org.go index c6d2562f1..1840cae9f 100644 --- a/models/org.go +++ b/models/org.go @@ -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. diff --git a/models/ssh_key_principals.go b/models/ssh_key_principals.go index 44b2ee0bb..50456b72d 100644 --- a/models/ssh_key_principals.go +++ b/models/ssh_key_principals.go @@ -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 } diff --git a/models/user.go b/models/user.go index 13347a46b..12035dbe4 100644 --- a/models/user.go +++ b/models/user.go @@ -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 index 000000000..74fb71d45 --- /dev/null +++ b/models/user/email_address.go @@ -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 index 000000000..5ed0bf888 --- /dev/null +++ b/models/user/email_address_test.go @@ -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 index 000000000..2999c4c81 --- /dev/null +++ b/models/user/main_test.go @@ -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 index 000000000..49370218d --- /dev/null +++ b/models/user/redirect.go @@ -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 index 000000000..b33c42cf3 --- /dev/null +++ b/models/user/redirect_test.go @@ -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_mail.go b/models/user_email.go similarity index 50% rename from models/user_mail.go rename to models/user_email.go index caa931788..7de577bc2 100644 --- a/models/user_mail.go +++ b/models/user_email.go @@ -1,5 +1,4 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Copyright 2020 The Gitea Authors. All rights reserved. +// 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. @@ -7,179 +6,28 @@ 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" + user_model "code.gitea.io/gitea/models/user" "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 { +// 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 := email.updateActivation(sess, true); err != nil { + if err := updateActivation(sess, email, true); err != nil { return err } return sess.Commit() } -func (email *EmailAddress) updateActivation(e db.Engine, activate bool) error { +func updateActivation(e db.Engine, email *user_model.EmailAddress, activate bool) error { user, err := getUserByID(e, email.UID) if err != nil { return err @@ -194,58 +42,17 @@ func (email *EmailAddress) updateActivation(e db.Engine, activate bool) error { 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 { +func MakeEmailPrimary(email *user_model.EmailAddress) error { has, err := db.GetEngine(db.DefaultContext).Get(email) if err != nil { return err } else if !has { - return ErrEmailAddressNotExist{Email: email.Email} + return user_model.ErrEmailAddressNotExist{Email: email.Email} } if !email.IsActivated { - return ErrEmailNotActivated + return user_model.ErrEmailNotActivated } user := &User{} @@ -269,7 +76,7 @@ func MakeEmailPrimary(email *EmailAddress) error { } // 2. Update old primary email - if _, err = sess.Where("uid=? AND is_primary=?", email.UID, true).Cols("is_primary").Update(&EmailAddress{ + if _, err = sess.Where("uid=? AND is_primary=?", email.UID, true).Cols("is_primary").Update(&user_model.EmailAddress{ IsPrimary: false, }); err != nil { return err @@ -347,7 +154,7 @@ func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) } count, err := db.GetEngine(db.DefaultContext).Join("INNER", "`user`", "`user`.ID = email_address.uid"). - Where(cond).Count(new(EmailAddress)) + Where(cond).Count(new(user_model.EmailAddress)) if err != nil { return nil, 0, fmt.Errorf("Count: %v", err) } @@ -374,15 +181,16 @@ func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) // 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 { + ctx, committer, err := db.TxContext() + if err != nil { return err } + defer committer.Close() + sess := db.GetEngine(ctx) // Activate/deactivate a user's secondary email address // First check if there's another user active with the same address - addr := EmailAddress{UID: userID, LowerEmail: strings.ToLower(email)} + addr := user_model.EmailAddress{UID: userID, LowerEmail: strings.ToLower(email)} if has, err := sess.Get(&addr); err != nil { return err } else if !has { @@ -393,13 +201,13 @@ func ActivateUserEmail(userID int64, email string, activate bool) (err error) { return nil } if activate { - if used, err := isEmailActive(sess, email, addr.ID); err != nil { + 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 ErrEmailAlreadyUsed{Email: email} + return user_model.ErrEmailAlreadyUsed{Email: email} } } - if err = addr.updateActivation(sess, activate); err != nil { + if err = updateActivation(sess, &addr, activate); err != nil { return fmt.Errorf("unable to updateActivation() for %d:%s: %w", addr.ID, addr.Email, err) } @@ -423,5 +231,5 @@ func ActivateUserEmail(userID int64, email string, activate bool) (err error) { } } - return sess.Commit() + return committer.Commit() } diff --git a/models/user_mail_test.go b/models/user_email_test.go similarity index 50% rename from models/user_mail_test.go rename to models/user_email_test.go index 384f28b7b..8c2bb48b9 100644 --- a/models/user_mail_test.go +++ b/models/user_email_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. +// 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. @@ -8,154 +8,37 @@ 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 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 := &user_model.EmailAddress{ Email: "user567890@example.com", } err := MakeEmailPrimary(email) assert.Error(t, err) - assert.EqualError(t, err, ErrEmailAddressNotExist{email.Email}.Error()) + assert.EqualError(t, err, user_model.ErrEmailAddressNotExist{Email: email.Email}.Error()) - email = &EmailAddress{ + email = &user_model.EmailAddress{ Email: "user11@example.com", } err = MakeEmailPrimary(email) assert.Error(t, err) - assert.EqualError(t, err, ErrEmailNotActivated.Error()) + assert.EqualError(t, err, user_model.ErrEmailNotActivated.Error()) - email = &EmailAddress{ + email = &user_model.EmailAddress{ Email: "user9999999@example.com", } err = MakeEmailPrimary(email) assert.Error(t, err) assert.True(t, IsErrUserNotExist(err)) - email = &EmailAddress{ + email = &user_model.EmailAddress{ Email: "user101@example.com", } err = MakeEmailPrimary(email) @@ -168,14 +51,14 @@ func TestMakeEmailPrimary(t *testing.T) { func TestActivate(t *testing.T) { assert.NoError(t, db.PrepareTestDatabase()) - email := &EmailAddress{ + email := &user_model.EmailAddress{ ID: int64(1), UID: int64(1), Email: "user11@example.com", } - assert.NoError(t, email.Activate()) + assert.NoError(t, ActivateEmail(email)) - emails, _ := GetEmailAddresses(int64(1)) + emails, _ := user_model.GetEmailAddresses(int64(1)) assert.Len(t, emails, 3) assert.True(t, emails[0].IsActivated) assert.True(t, emails[0].IsPrimary) diff --git a/models/user_redirect.go b/models/user_redirect.go deleted file mode 100644 index fdc730775..000000000 --- a/models/user_redirect.go +++ /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 index 346bf98b8..000000000 --- a/models/user_redirect_test.go +++ /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, - }) -} diff --git a/models/user_test.go b/models/user_test.go index 3f3536daf..58044d1e9 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -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, + }) +} diff --git a/modules/context/org.go b/modules/context/org.go index fd67212a1..d7257361f 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -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) diff --git a/modules/convert/convert.go b/modules/convert/convert.go index 7d67e2b5b..80d5ffff0 100644 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -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, diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 2d585b604..1e4a2851c 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -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) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 8f852a96c..f312df23d 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -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) diff --git a/routers/api/v1/user/email.go b/routers/api/v1/user/email.go index 9dd35f91b..6887c306c 100644 --- a/routers/api/v1/user/email.go +++ b/routers/api/v1/user/email.go @@ -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 } diff --git a/routers/api/v1/user/helper.go b/routers/api/v1/user/helper.go index a3500e0ee..a7c2e6140 100644 --- a/routers/api/v1/user/helper.go +++ b/routers/api/v1/user/helper.go @@ -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) diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go index 5cbe70020..4872ecb92 100644 --- a/routers/web/admin/emails.go +++ b/routers/web/admin/emails.go @@ -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)) diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 0041f3d07..db7fe7b36 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -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 { diff --git a/routers/web/repo/http.go b/routers/web/repo/http.go index 7c5dcd42b..0626d08a7 100644 --- a/routers/web/repo/http.go +++ b/routers/web/repo/http.go @@ -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) diff --git a/routers/web/user/auth.go b/routers/web/user/auth.go index 55f304a7c..ba37c8e3e 100644 --- a/routers/web/user/auth.go +++ b/routers/web/user/auth.go @@ -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) } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index d2a8d83fa..f8fcbf656 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -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) diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 47014dc81..5ef1c3bdc 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -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) diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index d181ae172..4a0900615 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -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 diff --git a/services/auth/signin.go b/services/auth/signin.go index a7ad02945..5477e8643 100644 --- a/services/auth/signin.go +++ b/services/auth/signin.go @@ -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, } } diff --git a/services/auth/source/pam/source_authenticate.go b/services/auth/source/pam/source_authenticate.go index cb5ffc286..6998241ad 100644 --- a/services/auth/source/pam/source_authenticate.go +++ b/services/auth/source/pam/source_authenticate.go @@ -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" } } diff --git a/services/mailer/mail.go b/services/mailer/mail.go index bd8f059c5..b579bf383 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -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