Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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 @@ -821,3 +821,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
}
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
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
8 changes: 4 additions & 4 deletions routers/api/v1/admin/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,15 +505,15 @@ func RenameUser(ctx *context.APIContext) {
}

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

if strings.EqualFold(newName, ctx.ContextUser.Name) {
nameChanged := newName != ctx.ContextUser.Name
if !nameChanged {
// 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 {
switch {
case user_model.IsErrUserAlreadyExist(err):
ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("form.username_been_taken"))
Expand All @@ -528,5 +528,5 @@ func RenameUser(ctx *context.APIContext) {
}
return
}
ctx.Status(http.StatusNoContent)
ctx.Status(http.StatusOK)
}
2 changes: 1 addition & 1 deletion routers/web/admin/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,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 @@ -70,31 +69,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 @@ -189,7 +180,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
17 changes: 8 additions & 9 deletions routers/web/user/setting/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,14 @@ 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 {
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"))
return fmt.Errorf(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 +88,8 @@ 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.Flash.Error(ctx.Tr("form.email_been_used"))
ctx.Redirect(setting.AppSubURL + "/user/settings")
return
}
Expand Down
95 changes: 90 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,94 @@ 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")
}

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

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

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 {
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) {
return fmt.Errorf("rename user directory: %w", err)
}

if err = committer.Commit(); err != nil {
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