forked from forgejo/forgejo
Unify user update methods (#28733)
Fixes #28660 Fixes an admin api bug related to `user.LoginSource` Fixed `/user/emails` response not identical to GitHub api This PR unifies the user update methods. The goal is to keep the logic only at one place (having audit logs in mind). For example, do the password checks only in one method not everywhere a password is updated. After that PR is merged, the user creation should be next.
This commit is contained in:
parent
b4513f48ce
commit
f8b471ace1
42 changed files with 1383 additions and 1068 deletions
|
@ -8,7 +8,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
|
@ -18,6 +17,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/auth/password"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
@ -107,9 +107,8 @@ func CreateUser(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
pwned, err := password.IsPwned(ctx, form.Password)
|
||||
if pwned {
|
||||
if err != nil {
|
||||
if err := password.IsPwned(ctx, form.Password); err != nil {
|
||||
if password.IsErrIsPwnedRequest(err) {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
|
||||
|
@ -192,115 +191,65 @@ func EditUser(ctx *context.APIContext) {
|
|||
|
||||
form := web.GetForm(ctx).(*api.EditUserOption)
|
||||
|
||||
parseAuthSource(ctx, ctx.ContextUser, form.SourceID, form.LoginName)
|
||||
if ctx.Written() {
|
||||
authOpts := &user_service.UpdateAuthOptions{
|
||||
LoginSource: optional.FromNonDefault(form.SourceID),
|
||||
LoginName: optional.Some(form.LoginName),
|
||||
Password: optional.FromNonDefault(form.Password),
|
||||
MustChangePassword: optional.FromPtr(form.MustChangePassword),
|
||||
ProhibitLogin: optional.FromPtr(form.ProhibitLogin),
|
||||
}
|
||||
if err := user_service.UpdateAuth(ctx, ctx.ContextUser, authOpts); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, password.ErrMinLength):
|
||||
ctx.Error(http.StatusBadRequest, "PasswordTooShort", fmt.Errorf("password must be at least %d characters", setting.MinPasswordLength))
|
||||
case errors.Is(err, password.ErrComplexity):
|
||||
ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
|
||||
case errors.Is(err, password.ErrIsPwned), password.IsErrIsPwnedRequest(err):
|
||||
ctx.Error(http.StatusBadRequest, "PasswordIsPwned", err)
|
||||
default:
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateAuth", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Password) != 0 {
|
||||
if len(form.Password) < setting.MinPasswordLength {
|
||||
ctx.Error(http.StatusBadRequest, "PasswordTooShort", fmt.Errorf("password must be at least %d characters", setting.MinPasswordLength))
|
||||
return
|
||||
}
|
||||
if !password.IsComplexEnough(form.Password) {
|
||||
err := errors.New("PasswordComplexity")
|
||||
ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
|
||||
return
|
||||
}
|
||||
pwned, err := password.IsPwned(ctx, form.Password)
|
||||
if pwned {
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
|
||||
return
|
||||
}
|
||||
if ctx.ContextUser.Salt, err = user_model.GetUserSalt(); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
|
||||
return
|
||||
}
|
||||
if err = ctx.ContextUser.SetPassword(form.Password); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if form.MustChangePassword != nil {
|
||||
ctx.ContextUser.MustChangePassword = *form.MustChangePassword
|
||||
}
|
||||
|
||||
ctx.ContextUser.LoginName = form.LoginName
|
||||
|
||||
if form.FullName != nil {
|
||||
ctx.ContextUser.FullName = *form.FullName
|
||||
}
|
||||
var emailChanged bool
|
||||
if form.Email != nil {
|
||||
email := strings.TrimSpace(*form.Email)
|
||||
if len(email) == 0 {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("email is not allowed to be empty string"))
|
||||
if err := user_service.AddOrSetPrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil {
|
||||
switch {
|
||||
case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
|
||||
ctx.Error(http.StatusBadRequest, "EmailInvalid", err)
|
||||
case user_model.IsErrEmailAlreadyUsed(err):
|
||||
ctx.Error(http.StatusBadRequest, "EmailUsed", err)
|
||||
default:
|
||||
ctx.Error(http.StatusInternalServerError, "AddOrSetPrimaryEmailAddress", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := user_model.ValidateEmail(email); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
emailChanged = !strings.EqualFold(ctx.ContextUser.Email, email)
|
||||
ctx.ContextUser.Email = email
|
||||
}
|
||||
if form.Website != nil {
|
||||
ctx.ContextUser.Website = *form.Website
|
||||
}
|
||||
if form.Location != nil {
|
||||
ctx.ContextUser.Location = *form.Location
|
||||
}
|
||||
if form.Description != nil {
|
||||
ctx.ContextUser.Description = *form.Description
|
||||
}
|
||||
if form.Active != nil {
|
||||
ctx.ContextUser.IsActive = *form.Active
|
||||
}
|
||||
if len(form.Visibility) != 0 {
|
||||
ctx.ContextUser.Visibility = api.VisibilityModes[form.Visibility]
|
||||
}
|
||||
if form.Admin != nil {
|
||||
if !*form.Admin && user_model.IsLastAdminUser(ctx, ctx.ContextUser) {
|
||||
ctx.Error(http.StatusBadRequest, "LastAdmin", ctx.Tr("auth.last_admin"))
|
||||
return
|
||||
}
|
||||
ctx.ContextUser.IsAdmin = *form.Admin
|
||||
}
|
||||
if form.AllowGitHook != nil {
|
||||
ctx.ContextUser.AllowGitHook = *form.AllowGitHook
|
||||
}
|
||||
if form.AllowImportLocal != nil {
|
||||
ctx.ContextUser.AllowImportLocal = *form.AllowImportLocal
|
||||
}
|
||||
if form.MaxRepoCreation != nil {
|
||||
ctx.ContextUser.MaxRepoCreation = *form.MaxRepoCreation
|
||||
}
|
||||
if form.AllowCreateOrganization != nil {
|
||||
ctx.ContextUser.AllowCreateOrganization = *form.AllowCreateOrganization
|
||||
}
|
||||
if form.ProhibitLogin != nil {
|
||||
ctx.ContextUser.ProhibitLogin = *form.ProhibitLogin
|
||||
}
|
||||
if form.Restricted != nil {
|
||||
ctx.ContextUser.IsRestricted = *form.Restricted
|
||||
}
|
||||
|
||||
if err := user_model.UpdateUser(ctx, ctx.ContextUser, emailChanged); err != nil {
|
||||
if user_model.IsErrEmailAlreadyUsed(err) ||
|
||||
user_model.IsErrEmailCharIsNotSupported(err) ||
|
||||
user_model.IsErrEmailInvalid(err) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||
opts := &user_service.UpdateOptions{
|
||||
FullName: optional.FromPtr(form.FullName),
|
||||
Website: optional.FromPtr(form.Website),
|
||||
Location: optional.FromPtr(form.Location),
|
||||
Description: optional.FromPtr(form.Description),
|
||||
IsActive: optional.FromPtr(form.Active),
|
||||
IsAdmin: optional.FromPtr(form.Admin),
|
||||
Visibility: optional.FromNonDefault(api.VisibilityModes[form.Visibility]),
|
||||
AllowGitHook: optional.FromPtr(form.AllowGitHook),
|
||||
AllowImportLocal: optional.FromPtr(form.AllowImportLocal),
|
||||
MaxRepoCreation: optional.FromPtr(form.MaxRepoCreation),
|
||||
AllowCreateOrganization: optional.FromPtr(form.AllowCreateOrganization),
|
||||
IsRestricted: optional.FromPtr(form.Restricted),
|
||||
}
|
||||
|
||||
if err := user_service.UpdateUser(ctx, ctx.ContextUser, opts); err != nil {
|
||||
if models.IsErrDeleteLastAdminUser(err) {
|
||||
ctx.Error(http.StatusBadRequest, "LastAdmin", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, ctx.ContextUser.Name)
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToUser(ctx, ctx.ContextUser, ctx.Doer))
|
||||
|
@ -527,9 +476,6 @@ func RenameUser(ctx *context.APIContext) {
|
|||
// Check if user name has been changed
|
||||
if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil {
|
||||
switch {
|
||||
case user_model.IsErrUsernameNotChanged(err):
|
||||
// Noop as username is not changed
|
||||
ctx.Status(http.StatusNoContent)
|
||||
case user_model.IsErrUserAlreadyExist(err):
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("form.username_been_taken"))
|
||||
case db.IsErrNameReserved(err):
|
||||
|
@ -545,5 +491,5 @@ func RenameUser(ctx *context.APIContext) {
|
|||
}
|
||||
|
||||
log.Trace("User name changed: %s -> %s", oldName, newName)
|
||||
ctx.Status(http.StatusOK)
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue