新增:更新用户接口

This commit is contained in:
yystopf 2022-12-22 14:28:33 +08:00
parent 34f7949c09
commit 7cadb49770
3 changed files with 243 additions and 0 deletions

View File

@ -0,0 +1,10 @@
package structs
import (
gitea_api "code.gitea.io/gitea/modules/structs"
)
type HatEditUserOption struct {
NewName string `json:"new_name" binding:"MaxSize(40)"`
*gitea_api.EditUserOption
}

214
routers/hat/admin/user.go Normal file
View File

@ -0,0 +1,214 @@
package admin
import (
"errors"
"fmt"
"net/http"
"strings"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
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"
"code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/agit"
container_service "code.gitea.io/gitea/services/packages/container"
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
)
func parseAuthSource(ctx *context.APIContext, u *user_model.User, sourceID int64, loginName string) {
if sourceID == 0 {
return
}
source, err := auth.GetSourceByID(sourceID)
if err != nil {
if auth.IsErrSourceNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else {
ctx.Error(http.StatusInternalServerError, "auth.GetSourceByID", err)
}
return
}
u.LoginType = source.Type
u.LoginSource = source.ID
u.LoginName = loginName
}
func EditUser(ctx *context.APIContext) {
form := web.GetForm(ctx).(*hat_api.HatEditUserOption)
parseAuthSource(ctx, ctx.ContextUser, form.SourceID, form.LoginName)
if ctx.Written() {
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.Data["Err_Password"] = true
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
}
if len(form.NewName) != 0 && ctx.ContextUser.Name != form.NewName {
if err := handleUsernameChange(ctx, ctx.ContextUser, form.NewName); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
return
}
ctx.ContextUser.Name = form.NewName
ctx.ContextUser.LowerName = strings.ToLower(form.NewName)
}
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"))
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 {
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)
} 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.ContextUser, ctx.Doer))
}
func handleUsernameChange(ctx *context.APIContext, user *user_model.User, newName string) error {
// Non-local users are not allowed to change their username.
if !user.IsLocal() {
ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user"))
return fmt.Errorf(ctx.Tr("form.username_change_not_local_user"))
}
// Check if user name has been changed
if user.LowerName != strings.ToLower(newName) {
if err := user_model.ChangeUserName(user, newName); err != nil {
switch {
case user_model.IsErrUserAlreadyExist(err):
ctx.Error(http.StatusInternalServerError, "ChangeUserName", ctx.Tr("form.username_been_taken"))
case user_model.IsErrEmailAlreadyUsed(err):
ctx.Error(http.StatusInternalServerError, "ChangeUserName", ctx.Tr("form.email_been_used"))
case db.IsErrNameReserved(err):
ctx.Error(http.StatusInternalServerError, "ChangeUserName", ctx.Tr("user.form.name_reserved", newName))
case db.IsErrNamePatternNotAllowed(err):
ctx.Error(http.StatusInternalServerError, "ChangeUserName", ctx.Tr("user.form.name_pattern_not_allowed", newName))
case db.IsErrNameCharsNotAllowed(err):
ctx.Error(http.StatusInternalServerError, "ChangeUserName", ctx.Tr("user.form.name_chars_not_allowed", newName))
default:
ctx.Error(http.StatusInternalServerError, "ChangeUserName", err)
}
return err
}
} else {
if err := repo_model.UpdateRepositoryOwnerNames(user.ID, newName); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
return err
}
}
// update all agit flow pull request header
err := agit.UserNameChanged(user, newName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "agit.UserNameChanged", err)
return err
}
if err := container_service.UpdateRepositoryNames(ctx, user, newName); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepositoryNames", err)
return err
}
log.Trace("User name changed: %s -> %s", user.Name, newName)
return nil
}

View File

@ -24,6 +24,7 @@ import (
"code.gitea.io/gitea/routers/api/v1/misc"
"code.gitea.io/gitea/services/auth"
context_service "code.gitea.io/gitea/services/context"
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/admin"
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/org"
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/repo"
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/user"
@ -127,6 +128,14 @@ func Routers(ctx gocontext.Context) *web.Route {
m.Group("/orgs/{org}", func() {
m.Combo("").Patch(reqToken(), reqOrgOwnership(), bind(hat_api.EditOrgOption{}), org.Edit)
}, orgAssignment(true))
m.Group("/admin", func() {
m.Group("/users", func() {
m.Group("/{username}", func() {
m.Combo("").Patch(bind(hat_api.HatEditUserOption{}), admin.EditUser)
}, context_service.UserAssignmentAPI())
})
}, reqToken(), reqSiteAdmin())
})
return m
@ -379,3 +388,13 @@ func reqToken() func(ctx *context.APIContext) {
ctx.Error(http.StatusUnauthorized, "reqToken", "token is required")
}
}
// reqSiteAdmin user should be the site admin
func reqSiteAdmin() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqSiteAdmin", "user should be the site admin")
return
}
}
}