[MM-13840] Change eMail as a post-verification action (#10253)

* Change eMail as a post-verification action

* Fix broken test

* comment for special behavior, tests

* govet

* Check for already existent eMails when require email verification is turned on before accepting update
Этот коммит содержится в:
Daniel Schalla
2019-02-20 15:50:52 +01:00
коммит произвёл GitHub
родитель 1218e774ba
Коммит f046163a12
16 изменённых файлов: 190 добавлений и 48 удалений

Просмотреть файл

@@ -48,7 +48,7 @@ func (a *App) CreateBasicUser(client *model.Client4) *model.AppError {
if resp.Error != nil {
return resp.Error
}
store.Must(a.Srv.Store.User().VerifyEmail(ruser.Id))
store.Must(a.Srv.Store.User().VerifyEmail(ruser.Id, ruser.Email))
store.Must(a.Srv.Store.Team().SaveMember(&model.TeamMember{TeamId: basicteam.Id, UserId: ruser.Id}, *a.Config().TeamSettings.MaxUsersPerTeam))
}
return nil
@@ -83,7 +83,7 @@ func (cfg *AutoUserCreator) createRandomUser() (*model.User, bool) {
}
// We need to cheat to verify the user's email
store.Must(cfg.app.Srv.Store.User().VerifyEmail(ruser.Id))
store.Must(cfg.app.Srv.Store.User().VerifyEmail(ruser.Id, ruser.Email))
return ruser, true
}

Просмотреть файл

@@ -180,7 +180,7 @@ func (a *App) SendWelcomeEmail(userId string, email string, verified bool, local
}
if !verified {
token, err := a.CreateVerifyEmailToken(userId)
token, err := a.CreateVerifyEmailToken(userId, email)
if err != nil {
return err
}

Просмотреть файл

@@ -477,7 +477,7 @@ func (a *App) ImportUser(data *UserImportData, dryRun bool) *model.AppError {
}
if emailVerified {
if hasUserEmailVerifiedChanged {
if err := a.VerifyUserEmail(user.Id); err != nil {
if err := a.VerifyUserEmail(user.Id, user.Email); err != nil {
return err
}
}

Просмотреть файл

@@ -746,7 +746,7 @@ func (a *App) OldImportUser(team *model.Team, user *model.User) *model.User {
}
ruser := result.Data.(*model.User)
if cresult := <-a.Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil {
if cresult := <-a.Srv.Store.User().VerifyEmail(ruser.Id, ruser.Email); cresult.Err != nil {
mlog.Error(fmt.Sprintf("Failed to set email verified err=%v", cresult.Err))
}

Просмотреть файл

@@ -6,6 +6,7 @@ package app
import (
"bytes"
b64 "encoding/base64"
"encoding/json"
"fmt"
"hash/fnv"
"image"
@@ -243,7 +244,7 @@ func (a *App) createUser(user *model.User) (*model.User, *model.AppError) {
ruser := result.Data.(*model.User)
if user.EmailVerified {
if err := a.VerifyUserEmail(ruser.Id); err != nil {
if err := a.VerifyUserEmail(ruser.Id, user.Email); err != nil {
mlog.Error(fmt.Sprintf("Failed to set email verified err=%v", err))
}
}
@@ -996,34 +997,49 @@ func (a *App) sendUpdatedUserEvent(user model.User) {
}
func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User, *model.AppError) {
result := <-a.Srv.Store.User().Get(user.Id)
if result.Err != nil {
return nil, result.Err
}
prev := result.Data.(*model.User)
if !CheckUserDomain(user, *a.Config().TeamSettings.RestrictCreationToDomains) {
result := <-a.Srv.Store.User().Get(user.Id)
if result.Err != nil {
return nil, result.Err
}
prev := result.Data.(*model.User)
if !prev.IsLDAPUser() && !prev.IsSAMLUser() && user.Email != prev.Email {
return nil, model.NewAppError("UpdateUser", "api.user.create_user.accepted_domain.app_error", nil, "", http.StatusBadRequest)
}
}
result := <-a.Srv.Store.User().Update(user, false)
// Don't set new eMail on user account if email verification is required, this will be done as a post-verification action
// to avoid users being able to set non-controlled eMails as their account email
newEmail := ""
if *a.Config().EmailSettings.RequireEmailVerification && prev.Email != user.Email {
newEmail = user.Email
_, err := a.GetUserByEmail(newEmail)
if err == nil {
return nil, model.NewAppError("UpdateUser", "store.sql_user.update.email_taken.app_error", nil, "user_id="+user.Id, http.StatusBadRequest)
}
user.Email = prev.Email
}
result = <-a.Srv.Store.User().Update(user, false)
if result.Err != nil {
return nil, result.Err
}
rusers := result.Data.([2]*model.User)
if sendNotifications {
if rusers[0].Email != rusers[1].Email {
a.Srv.Go(func() {
if err := a.SendEmailChangeEmail(rusers[1].Email, rusers[0].Email, rusers[0].Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
})
if rusers[0].Email != rusers[1].Email || newEmail != "" {
if *a.Config().EmailSettings.RequireEmailVerification {
a.Srv.Go(func() {
if err := a.SendEmailVerification(rusers[0]); err != nil {
if err := a.SendEmailVerification(rusers[0], newEmail); err != nil {
mlog.Error(err.Error())
}
})
} else {
a.Srv.Go(func() {
if err := a.SendEmailChangeEmail(rusers[1].Email, rusers[0].Email, rusers[0].Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
})
@@ -1366,16 +1382,16 @@ func (a *App) PermanentDeleteAllUsers() *model.AppError {
return nil
}
func (a *App) SendEmailVerification(user *model.User) *model.AppError {
token, err := a.CreateVerifyEmailToken(user.Id)
func (a *App) SendEmailVerification(user *model.User, newEmail string) *model.AppError {
token, err := a.CreateVerifyEmailToken(user.Id, newEmail)
if err != nil {
return err
}
if _, err := a.GetStatus(user.Id); err != nil {
return a.SendVerifyEmail(user.Email, user.Locale, a.GetSiteURL(), token.Token)
return a.SendVerifyEmail(newEmail, user.Locale, a.GetSiteURL(), token.Token)
}
return a.SendEmailChangeVerifyEmail(user.Email, user.Locale, a.GetSiteURL(), token.Token)
return a.SendEmailChangeVerifyEmail(newEmail, user.Locale, a.GetSiteURL(), token.Token)
}
func (a *App) VerifyEmailFromToken(userSuppliedTokenString string) *model.AppError {
@@ -1384,11 +1400,36 @@ func (a *App) VerifyEmailFromToken(userSuppliedTokenString string) *model.AppErr
return err
}
if model.GetMillis()-token.CreateAt >= PASSWORD_RECOVER_EXPIRY_TIME {
return model.NewAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "", http.StatusBadRequest)
return model.NewAppError("VerifyEmailFromToken", "api.user.verify_email.link_expired.app_error", nil, "", http.StatusBadRequest)
}
if err := a.VerifyUserEmail(token.Extra); err != nil {
tokenData := struct {
UserId string
Email string
}{}
err2 := json.Unmarshal([]byte(token.Extra), &tokenData)
if err2 != nil {
return model.NewAppError("VerifyEmailFromToken", "api.user.verify_email.token_parse.error", nil, "", http.StatusInternalServerError)
}
user, err := a.GetUser(tokenData.UserId)
if err != nil {
return err
}
if err := a.VerifyUserEmail(tokenData.UserId, tokenData.Email); err != nil {
return err
}
if user.Email != tokenData.Email {
a.Srv.Go(func() {
if err := a.SendEmailChangeEmail(user.Email, tokenData.Email, user.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
})
}
if err := a.DeleteToken(token); err != nil {
mlog.Error(err.Error())
}
@@ -1396,8 +1437,21 @@ func (a *App) VerifyEmailFromToken(userSuppliedTokenString string) *model.AppErr
return nil
}
func (a *App) CreateVerifyEmailToken(userId string) (*model.Token, *model.AppError) {
token := model.NewToken(TOKEN_TYPE_VERIFY_EMAIL, userId)
func (a *App) CreateVerifyEmailToken(userId string, newEmail string) (*model.Token, *model.AppError) {
tokenExtra := struct {
UserId string
Email string
}{
userId,
newEmail,
}
jsonData, err := json.Marshal(tokenExtra)
if err != nil {
return nil, model.NewAppError("CreateVerifyEmailToken", "api.user.create_email_token.error", nil, "", http.StatusInternalServerError)
}
token := model.NewToken(TOKEN_TYPE_VERIFY_EMAIL, string(jsonData))
if result := <-a.Srv.Store.Token().Save(token); result.Err != nil {
return nil, result.Err
@@ -1429,8 +1483,22 @@ func (a *App) GetTotalUsersStats() (*model.UsersStats, *model.AppError) {
return stats, nil
}
func (a *App) VerifyUserEmail(userId string) *model.AppError {
return (<-a.Srv.Store.User().VerifyEmail(userId)).Err
func (a *App) VerifyUserEmail(userId, email string) *model.AppError {
err := (<-a.Srv.Store.User().VerifyEmail(userId, email)).Err
if err != nil {
return err
}
user, err := a.GetUser(userId)
if err != nil {
return err
}
a.sendUpdatedUserEvent(*user)
return nil
}
func (a *App) SearchUsers(props *model.UserSearch, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {

Просмотреть файл

@@ -286,6 +286,66 @@ func TestUpdateOAuthUserAttrs(t *testing.T) {
})
}
func TestUpdateUserEmail(t *testing.T) {
th := Setup(t)
defer th.TearDown()
user := th.CreateUser()
t.Run("RequireVerification", func(t *testing.T){
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.EmailSettings.RequireEmailVerification = true
})
currentEmail := user.Email
newEmail := th.MakeEmail()
user.Email = newEmail
user2, err := th.App.UpdateUser(user, false)
assert.Nil(t, err)
assert.Equal(t, currentEmail, user2.Email)
assert.True(t, user2.EmailVerified)
token, err := th.App.CreateVerifyEmailToken(user2.Id, newEmail)
assert.Nil(t, err)
err = th.App.VerifyEmailFromToken(token.Token)
assert.Nil(t, err)
user2, err = th.App.GetUser(user2.Id)
assert.Nil(t, err)
assert.Equal(t, newEmail, user2.Email)
assert.True(t, user2.EmailVerified)
})
t.Run("RequireVerificationAlreadyUsedEmail", func(t *testing.T){
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.EmailSettings.RequireEmailVerification = true
})
user2 := th.CreateUser()
newEmail := user2.Email
user.Email = newEmail
user3, err := th.App.UpdateUser(user, false)
assert.NotNil(t, err)
assert.Nil(t, user3)
})
t.Run("NoVerification", func(t *testing.T){
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.EmailSettings.RequireEmailVerification = false
})
newEmail := th.MakeEmail()
user.Email = newEmail
user2, err := th.App.UpdateUser(user, false)
assert.Nil(t, err)
assert.Equal(t, newEmail, user2.Email)
})
}
func getUserFromDB(a *App, id string, t *testing.T) *model.User {
user, err := a.GetUser(id)
if err != nil {