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

Refactor rename user and rename organization #24052

Merged
merged 22 commits into from
May 21, 2023
Merged
Show file tree
Hide file tree
Changes from 16 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
8 changes: 8 additions & 0 deletions models/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -826,3 +826,11 @@ func FixNullArchivedRepository(ctx context.Context) (int64, error) {
IsArchived: false,
})
}

// UpdateRepositoryOwnerName updates the owner name of all repositories owned by the user
func UpdateRepositoryOwnerName(ctx context.Context, oldUserName, newUserName string) error {
if _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, oldUserName); err != nil {
return fmt.Errorf("change repo owner name: %w", err)
}
return nil
}
lunny marked this conversation as resolved.
Show resolved Hide resolved
25 changes: 17 additions & 8 deletions models/user/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,6 @@ import (
"code.gitea.io/gitea/modules/util"
)

// ____ ___
// | | \______ ___________
// | | / ___// __ \_ __ \
// | | /\___ \\ ___/| | \/
// |______//____ >\___ >__|
// \/ \/

// ErrUserAlreadyExist represents a "user already exists" error.
type ErrUserAlreadyExist struct {
Name string
Expand Down Expand Up @@ -95,7 +88,23 @@ func (err ErrUserInactive) Error() string {
return fmt.Sprintf("user is inactive [uid: %d, name: %s]", err.UID, err.Name)
}

// Unwrap unwraps this error as a ErrPermission error
// Unwrap unwraps this error as a ErrUserInactive error
lunny marked this conversation as resolved.
Show resolved Hide resolved
func (err ErrUserInactive) Unwrap() error {
return util.ErrPermissionDenied
}

// ErrUserIsNotLocal represents a "ErrUserIsNotLocal" kind of error.
type ErrUserIsNotLocal struct {
UID int64
Name string
}

func (err ErrUserIsNotLocal) Error() string {
return fmt.Sprintf("user is not local type [uid: %d, name: %s]", err.UID, err.Name)
}

// IsErrUserIsNotLocal
func IsErrUserIsNotLocal(err error) bool {
_, ok := err.(ErrUserIsNotLocal)
return ok
}
45 changes: 0 additions & 45 deletions models/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"encoding/hex"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"time"
Expand Down Expand Up @@ -756,50 +755,6 @@ func VerifyUserActiveCode(code string) (user *User) {
return nil
}

// ChangeUserName changes all corresponding setting from old user name to new one.
func ChangeUserName(ctx context.Context, u *User, newUserName string) (err error) {
oldUserName := u.Name
if err = IsUsableUsername(newUserName); err != nil {
return err
}

ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()

isExist, err := IsUserExist(ctx, 0, newUserName)
if err != nil {
return err
} else if isExist {
return ErrUserAlreadyExist{newUserName}
}

if _, err = db.GetEngine(ctx).Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, oldUserName); err != nil {
return fmt.Errorf("Change repo owner name: %w", err)
}

// Do not fail if directory does not exist
if err = util.Rename(UserPath(oldUserName), UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("Rename user directory: %w", err)
}

if err = NewUserRedirect(ctx, u.ID, oldUserName, newUserName); err != nil {
return err
}

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)
}
return err
}

return nil
}

// checkDupEmail checks whether there are the same email with the user
func checkDupEmail(ctx context.Context, u *User) error {
u.Email = strings.ToLower(u.Email)
Expand Down
7 changes: 3 additions & 4 deletions routers/api/v1/admin/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,15 +505,14 @@ func RenameUser(ctx *context.APIContext) {
}

newName := web.GetForm(ctx).(*api.RenameUserOption).NewName

if strings.EqualFold(newName, ctx.ContextUser.Name) {
if newName == ctx.ContextUser.Name {
// Noop as username is not changed
ctx.Status(http.StatusNoContent)
return
}

// Check if user name has been changed
if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil {
if err := user_service.RenameUser(ctx, ctx.ContextUser, newName, strings.EqualFold(newName, ctx.ContextUser.Name)); err != nil {
lunny marked this conversation as resolved.
Show resolved Hide resolved
switch {
case user_model.IsErrUserAlreadyExist(err):
ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("form.username_been_taken"))
Expand All @@ -528,5 +527,5 @@ func RenameUser(ctx *context.APIContext) {
}
return
}
ctx.Status(http.StatusNoContent)
ctx.Status(http.StatusOK)
lunny marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 1 addition & 1 deletion routers/web/admin/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ func EditUserPost(ctx *context.Context) {
}

if len(form.UserName) != 0 && u.Name != form.UserName {
if err := user_setting.HandleUsernameChange(ctx, u, form.UserName); err != nil {
if err := user_setting.HandleUsernameChange(ctx, u, form.UserName, u.LowerName == strings.ToLower(form.UserName)); err != nil {
if ctx.Written() {
return
}
Expand Down
39 changes: 15 additions & 24 deletions routers/web/org/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ import (
"code.gitea.io/gitea/modules/web"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/org"
container_service "code.gitea.io/gitea/services/packages/container"
org_service "code.gitea.io/gitea/services/org"
repo_service "code.gitea.io/gitea/services/repository"
user_service "code.gitea.io/gitea/services/user"
)
Expand Down Expand Up @@ -66,31 +65,23 @@ func SettingsPost(ctx *context.Context) {
nameChanged := org.Name != form.Name

// Check if organization name has been changed.
if org.LowerName != strings.ToLower(form.Name) {
isExist, err := user_model.IsUserExist(ctx, org.ID, form.Name)
if err != nil {
ctx.ServerError("IsUserExist", err)
return
} else if isExist {
if nameChanged {
err := org_service.RenameOrganization(ctx, org, form.Name, org.LowerName == strings.ToLower(form.Name))
switch {
case user_model.IsErrUserAlreadyExist(err):
ctx.Data["OrgName"] = true
ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSettingsOptions, &form)
return
} else if err = user_model.ChangeUserName(ctx, org.AsUser(), form.Name); err != nil {
switch {
case db.IsErrNameReserved(err):
ctx.Data["OrgName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form)
case db.IsErrNamePatternNotAllowed(err):
ctx.Data["OrgName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
default:
ctx.ServerError("ChangeUserName", err)
}
case db.IsErrNameReserved(err):
ctx.Data["OrgName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form)
return
}

if err := container_service.UpdateRepositoryNames(ctx, org.AsUser(), form.Name); err != nil {
ctx.ServerError("UpdateRepositoryNames", err)
case db.IsErrNamePatternNotAllowed(err):
ctx.Data["OrgName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
return
case err != nil:
ctx.ServerError("org_service.RenameOrganization", err)
return
}

Expand Down Expand Up @@ -185,7 +176,7 @@ func SettingsDelete(ctx *context.Context) {
return
}

if err := org.DeleteOrganization(ctx.Org.Organization); err != nil {
if err := org_service.DeleteOrganization(ctx.Org.Organization); err != nil {
if models.IsErrUserOwnRepos(err) {
ctx.Flash.Error(ctx.Tr("form.org_still_own_repo"))
ctx.Redirect(ctx.Org.OrgLink + "/settings/delete")
Expand Down
15 changes: 6 additions & 9 deletions routers/web/user/setting/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,13 @@ func Profile(ctx *context.Context) {
}

// HandleUsernameChange handle username changes from user settings and admin interface
func HandleUsernameChange(ctx *context.Context, 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"))
}

func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName string, onlyCapitalization bool) error {
// rename user
if err := user_service.RenameUser(ctx, user, newName); err != nil {
if err := user_service.RenameUser(ctx, user, newName, onlyCapitalization); err != nil {
lunny marked this conversation as resolved.
Show resolved Hide resolved
switch {
// Non-local users are not allowed to change their username.
case user_model.IsErrUserIsNotLocal(err):
ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user"))
case user_model.IsErrUserAlreadyExist(err):
ctx.Flash.Error(ctx.Tr("form.username_been_taken"))
case user_model.IsErrEmailAlreadyUsed(err):
Expand Down Expand Up @@ -90,7 +87,7 @@ func ProfilePost(ctx *context.Context) {

if len(form.Name) != 0 && ctx.Doer.Name != form.Name {
log.Debug("Changing name for %s to %s", ctx.Doer.Name, form.Name)
if err := HandleUsernameChange(ctx, ctx.Doer, form.Name); err != nil {
if err := HandleUsernameChange(ctx, ctx.Doer, form.Name, ctx.Doer.LowerName == strings.ToLower(form.Name)); err != nil {
ctx.Redirect(setting.AppSubURL + "/user/settings")
return
}
Expand Down
101 changes: 96 additions & 5 deletions services/org/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,26 @@
package org

import (
"context"
"fmt"
"os"
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
org_model "code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/agit"
container_service "code.gitea.io/gitea/services/packages/container"
)

// DeleteOrganization completely and permanently deletes everything of organization.
func DeleteOrganization(org *organization.Organization) error {
func DeleteOrganization(org *org_model.Organization) error {
ctx, commiter, err := db.TxContext(db.DefaultContext)
if err != nil {
return err
Expand All @@ -39,7 +45,7 @@ func DeleteOrganization(org *organization.Organization) error {
return models.ErrUserOwnPackages{UID: org.ID}
}

if err := organization.DeleteOrganization(ctx, org); err != nil {
if err := org_model.DeleteOrganization(ctx, org); err != nil {
return fmt.Errorf("DeleteOrganization: %w", err)
}

Expand All @@ -53,15 +59,100 @@ func DeleteOrganization(org *organization.Organization) error {
path := user_model.UserPath(org.Name)

if err := util.RemoveAll(path); err != nil {
return fmt.Errorf("Failed to RemoveAll %s: %w", path, err)
return fmt.Errorf("failed to RemoveAll %s: %w", path, err)
}

if len(org.Avatar) > 0 {
avatarPath := org.CustomAvatarRelativePath()
if err := storage.Avatars.Delete(avatarPath); err != nil {
return fmt.Errorf("Failed to remove %s: %w", avatarPath, err)
return fmt.Errorf("failed to remove %s: %w", avatarPath, err)
}
}

return nil
}

// RenameOrganization renames an organization.
func RenameOrganization(ctx context.Context, org *org_model.Organization, newName string, onlyCapitalization bool) error {
if !org.AsUser().IsOrganization() {
return fmt.Errorf("cannot rename user")
}
lunny marked this conversation as resolved.
Show resolved Hide resolved

if err := user_model.IsUsableUsername(newName); err != nil {
return err
}

ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
lunny marked this conversation as resolved.
Show resolved Hide resolved

oldName := org.Name

if onlyCapitalization {
org.Name = newName
if err := user_model.UpdateUserCols(ctx, org.AsUser(), "name"); err != nil {
org.Name = oldName
return err
}
if err := committer.Commit(); err != nil {
org.Name = oldName
return err
}
return nil
}

isExist, err := user_model.IsUserExist(ctx, org.ID, newName)
if err != nil {
return err
}
if isExist {
return user_model.ErrUserAlreadyExist{
Name: newName,
}
}
Copy link
Contributor

@wxiaoguang wxiaoguang May 7, 2023

Choose a reason for hiding this comment

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

It looks like onlyCapitalization only affects isExist and util.Rename ? If yes, could it be like this?

	if isExist && lower_case_not_match {
		return user_model.ErrUserAlreadyExist{
			Name: newName,
		}
	}

Then remove the onlyCapitalization argument?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't understand what's your meaning. I have updated this PR.


if err = repo_model.UpdateRepositoryOwnerName(ctx, oldName, newName); err != nil {
return err
}

if err = user_model.NewUserRedirect(ctx, org.ID, oldName, newName); err != nil {
return err
}

if err := agit.UserNameChanged(ctx, org.AsUser(), newName); err != nil {
return err
}
if err := container_service.UpdateRepositoryNames(ctx, org.AsUser(), newName); err != nil {
return err
}

org.Name = newName
org.LowerName = strings.ToLower(newName)
if err := user_model.UpdateUserCols(ctx, org.AsUser(), "name", "lower_name"); err != nil {
org.Name = oldName
org.LowerName = strings.ToLower(oldName)
return err
}

// Do not fail if directory does not exist
if err = util.Rename(user_model.UserPath(oldName), user_model.UserPath(newName)); err != nil && !os.IsNotExist(err) {
org.Name = oldName
org.LowerName = strings.ToLower(oldName)
return fmt.Errorf("rename user directory: %w", err)
lunny marked this conversation as resolved.
Show resolved Hide resolved
}

if err = committer.Commit(); err != nil {
org.Name = oldName
org.LowerName = strings.ToLower(oldName)
if err2 := util.Rename(user_model.UserPath(newName), user_model.UserPath(oldName)); 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", oldName, newName, err, err2)
return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldName, newName, err, err2)
}
return err
}

log.Trace("Org name changed: %s -> %s", oldName, newName)
return nil
}
Loading