Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix database inconsistent when admin change user email #17549

Merged
merged 10 commits into from
Nov 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions models/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -796,18 +796,48 @@ func validateUser(u *User) error {
return ValidateEmail(u.Email)
}

func updateUser(e db.Engine, u *User) error {
func updateUser(ctx context.Context, u *User, changePrimaryEmail bool) error {
if err := validateUser(u); err != nil {
return err
}

e := db.GetEngine(ctx)

if changePrimaryEmail {
var emailAddress EmailAddress
has, err := e.Where("lower_email=?", strings.ToLower(u.Email)).Get(&emailAddress)
if err != nil {
return err
}
if !has {
// 1. Update old primary email
if _, err = e.Where("uid=? AND is_primary=?", u.ID, true).Cols("is_primary").Update(&EmailAddress{
IsPrimary: false,
}); err != nil {
return err
}

emailAddress.Email = u.Email
emailAddress.UID = u.ID
emailAddress.IsActivated = true
emailAddress.IsPrimary = true
if _, err := e.Insert(&emailAddress); err != nil {
return err
}
} else if _, err := e.ID(emailAddress).Cols("is_primary").Update(&EmailAddress{
IsPrimary: true,
}); err != nil {
return err
}
}

_, err := e.ID(u.ID).AllCols().Update(u)
return err
}

// UpdateUser updates user's information.
func UpdateUser(u *User) error {
return updateUser(db.GetEngine(db.DefaultContext), u)
func UpdateUser(u *User, emailChanged bool) error {
return updateUser(db.DefaultContext, u, emailChanged)
}

// UpdateUserCols update user according special columns
Expand Down Expand Up @@ -836,14 +866,13 @@ func UpdateUserSetting(u *User) (err error) {
return err
}
defer committer.Close()
sess := db.GetEngine(ctx)

if !u.IsOrganization() {
if err = checkDupEmail(sess, u); err != nil {
if err = checkDupEmail(db.GetEngine(ctx), u); err != nil {
return err
}
}
if err = updateUser(sess, u); err != nil {
if err = updateUser(ctx, u, false); err != nil {
return err
}
return committer.Commit()
Expand Down
6 changes: 3 additions & 3 deletions models/user/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,19 +273,19 @@ func TestUpdateUser(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)

user.KeepActivityPrivate = true
assert.NoError(t, UpdateUser(user))
assert.NoError(t, UpdateUser(user, false))
user = unittest.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
assert.True(t, user.KeepActivityPrivate)

setting.Service.AllowedUserVisibilityModesSlice = []bool{true, false, false}
user.KeepActivityPrivate = false
user.Visibility = structs.VisibleTypePrivate
assert.Error(t, UpdateUser(user))
assert.Error(t, UpdateUser(user, false))
user = unittest.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
assert.True(t, user.KeepActivityPrivate)

user.Email = "no mail@mail.org"
assert.Error(t, UpdateUser(user))
assert.Error(t, UpdateUser(user, true))
}

func TestNewUserRedirect(t *testing.T) {
Expand Down
16 changes: 13 additions & 3 deletions routers/api/v1/admin/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"net/http"
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
Expand Down Expand Up @@ -203,12 +204,21 @@ func EditUser(ctx *context.APIContext) {
if form.FullName != nil {
u.FullName = *form.FullName
}
var emailChanged bool
if form.Email != nil {
u.Email = *form.Email
if len(u.Email) == 0 {
email := strings.TrimSpace(*form.Email)
if len(email) == 0 {
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("email is not allowed to be empty string"))
return
}

if err := user_model.ValidateEmail(email); err != nil {
ctx.InternalServerError(err)
return
}

emailChanged = !strings.EqualFold(u.Email, email)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We assume ValidateEmail will remove all invisible characters.

u.Email = email
}
if form.Website != nil {
u.Website = *form.Website
Expand Down Expand Up @@ -247,7 +257,7 @@ func EditUser(ctx *context.APIContext) {
u.IsRestricted = *form.Restricted
}

if err := user_model.UpdateUser(u); err != nil {
if err := user_model.UpdateUser(u, emailChanged); err != nil {
if user_model.IsErrEmailAlreadyUsed(err) || user_model.IsErrEmailInvalid(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else {
Expand Down
2 changes: 1 addition & 1 deletion routers/api/v1/user/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func UpdateUserSettings(ctx *context.APIContext) {
ctx.User.KeepActivityPrivate = *form.HideActivity
}

if err := user_model.UpdateUser(ctx.User); err != nil {
if err := user_model.UpdateUser(ctx.User, false); err != nil {
ctx.InternalServerError(err)
return
}
Expand Down
10 changes: 9 additions & 1 deletion routers/web/admin/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,13 @@ func EditUserPost(ctx *context.Context) {
ctx.RenderWithErr(errMsg, tplUserNew, &form)
return
}

if err := user_model.ValidateEmail(form.Email); err != nil {
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_error"), tplUserNew, &form)
return
}

if u.Salt, err = user_model.GetUserSalt(); err != nil {
ctx.ServerError("UpdateUser", err)
return
Expand Down Expand Up @@ -332,6 +339,7 @@ func EditUserPost(ctx *context.Context) {

u.LoginName = form.LoginName
u.FullName = form.FullName
emailChanged := !strings.EqualFold(u.Email, form.Email)
lunny marked this conversation as resolved.
Show resolved Hide resolved
u.Email = form.Email
u.Website = form.Website
u.Location = form.Location
Expand All @@ -352,7 +360,7 @@ func EditUserPost(ctx *context.Context) {
u.ProhibitLogin = form.ProhibitLogin
}

if err := user_model.UpdateUser(u); err != nil {
if err := user_model.UpdateUser(u, emailChanged); err != nil {
if user_model.IsErrEmailAlreadyUsed(err) {
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserEdit, &form)
Expand Down
2 changes: 1 addition & 1 deletion routers/web/org/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func SettingsPost(ctx *context.Context) {
visibilityChanged := form.Visibility != org.Visibility
org.Visibility = form.Visibility

if err := user_model.UpdateUser(org.AsUser()); err != nil {
if err := user_model.UpdateUser(org.AsUser(), false); err != nil {
ctx.ServerError("UpdateUser", err)
return
}
Expand Down