summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Bezold <andrew.bezold@gmail.com>2021-01-24 10:23:05 -0500
committerGitHub <noreply@github.com>2021-01-24 16:23:05 +0100
commitbc05ddc0ebd6fdc826ef2beec99304bac60ddd8a (patch)
tree03d52e07feedd37e169a04980f9efbb8bd1aca65
parent4f608ad31f538cb45411ff9a2238db812b5ca414 (diff)
downloadgitea-bc05ddc0ebd6fdc826ef2beec99304bac60ddd8a.tar.gz
gitea-bc05ddc0ebd6fdc826ef2beec99304bac60ddd8a.zip
Redirect on changed user and org name (#11649)
* Add redirect for user * Add redirect for orgs * Add user redirect test * Appease linter * Add comment to DeleteUserRedirect function * Fix locale changes * Fix GetUserByParams * Fix orgAssignment * Remove debug logging * Add redirect prompt * Dont Export DeleteUserRedirect & only use it within a session * Unexport newUserRedirect * cleanup * Fix & Dedub API code * Format Template * Add Migration & rm dublicat * Refactor: unexport newRepoRedirect() & rm dedub del exec * if this fails we'll need to re-rename the user directory Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
-rw-r--r--models/error.go15
-rw-r--r--models/fixtures/user_redirect.yml4
-rw-r--r--models/migrations/migrations.go2
-rw-r--r--models/migrations/v167.go24
-rw-r--r--models/models.go1
-rw-r--r--models/org.go4
-rw-r--r--models/repo.go11
-rw-r--r--models/repo_redirect.go8
-rw-r--r--models/repo_redirect_test.go6
-rw-r--r--models/user.go23
-rw-r--r--models/user_redirect.go52
-rw-r--r--models/user_redirect_test.go69
-rw-r--r--modules/context/context.go20
-rw-r--r--modules/context/org.go9
-rw-r--r--modules/context/repo.go15
-rw-r--r--options/locale/locale_en-US.ini2
-rw-r--r--routers/api/v1/api.go17
-rw-r--r--routers/api/v1/user/helper.go36
-rw-r--r--routers/api/v1/user/key.go19
-rw-r--r--routers/api/v1/user/user.go19
-rw-r--r--routers/repo/http.go11
-rw-r--r--routers/user/profile.go6
-rw-r--r--templates/org/settings/options.tmpl5
-rw-r--r--templates/user/settings/profile.tmpl5
-rw-r--r--web_src/js/index.js6
25 files changed, 325 insertions, 64 deletions
diff --git a/models/error.go b/models/error.go
index 7f1eda1b14..fc161ed806 100644
--- a/models/error.go
+++ b/models/error.go
@@ -146,6 +146,21 @@ func (err ErrUserNotExist) Error() string {
return fmt.Sprintf("user does not exist [uid: %d, name: %s, keyid: %d]", err.UID, err.Name, err.KeyID)
}
+// ErrUserRedirectNotExist represents a "UserRedirectNotExist" kind of error.
+type ErrUserRedirectNotExist struct {
+ Name string
+}
+
+// IsErrUserRedirectNotExist check if an error is an ErrUserRedirectNotExist.
+func IsErrUserRedirectNotExist(err error) bool {
+ _, ok := err.(ErrUserRedirectNotExist)
+ return ok
+}
+
+func (err ErrUserRedirectNotExist) Error() string {
+ return fmt.Sprintf("user redirect does not exist [name: %s]", err.Name)
+}
+
// ErrUserProhibitLogin represents a "ErrUserProhibitLogin" kind of error.
type ErrUserProhibitLogin struct {
UID int64
diff --git a/models/fixtures/user_redirect.yml b/models/fixtures/user_redirect.yml
new file mode 100644
index 0000000000..8ff7993398
--- /dev/null
+++ b/models/fixtures/user_redirect.yml
@@ -0,0 +1,4 @@
+-
+ id: 1
+ lower_name: olduser1
+ redirect_user_id: 1
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index f62bba2a71..3227f6f754 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -279,6 +279,8 @@ var migrations = []Migration{
NewMigration("Convert hook task type from char(16) to varchar(16) and trim the column", convertHookTaskTypeToVarcharAndTrim),
// v166 -> v167
NewMigration("Where Password is Valid with Empty String delete it", recalculateUserEmptyPWD),
+ // v167 -> v168
+ NewMigration("Add user redirect", addUserRedirect),
}
// GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v167.go b/models/migrations/v167.go
new file mode 100644
index 0000000000..fd91f226ab
--- /dev/null
+++ b/models/migrations/v167.go
@@ -0,0 +1,24 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func addUserRedirect(x *xorm.Engine) (err error) {
+ type UserRedirect struct {
+ ID int64 `xorm:"pk autoincr"`
+ LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ RedirectUserID int64
+ }
+
+ if err := x.Sync2(new(UserRedirect)); err != nil {
+ return fmt.Errorf("Sync2: %v", err)
+ }
+ return nil
+}
diff --git a/models/models.go b/models/models.go
index 2ace1ea6dd..1bfe75b7fc 100644
--- a/models/models.go
+++ b/models/models.go
@@ -128,6 +128,7 @@ func init() {
new(Task),
new(LanguageStat),
new(EmailHash),
+ new(UserRedirect),
new(Project),
new(ProjectBoard),
new(ProjectIssue),
diff --git a/models/org.go b/models/org.go
index f45c9af7a7..ee867eec88 100644
--- a/models/org.go
+++ b/models/org.go
@@ -171,6 +171,10 @@ func CreateOrganization(org, owner *User) (err error) {
return err
}
+ if err = deleteUserRedirect(sess, org.Name); err != nil {
+ return err
+ }
+
if _, err = sess.Insert(org); err != nil {
return fmt.Errorf("insert organization: %v", err)
}
diff --git a/models/repo.go b/models/repo.go
index b11671e1fc..62d64fbee9 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -1312,8 +1312,8 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
return fmt.Errorf("delete repo redirect: %v", err)
}
- if err := NewRepoRedirect(DBContext{sess}, oldOwner.ID, repo.ID, repo.Name, repo.Name); err != nil {
- return fmt.Errorf("NewRepoRedirect: %v", err)
+ if err := newRepoRedirect(sess, oldOwner.ID, repo.ID, repo.Name, repo.Name); err != nil {
+ return fmt.Errorf("newRepoRedirect: %v", err)
}
return sess.Commit()
@@ -1361,12 +1361,7 @@ func ChangeRepositoryName(doer *User, repo *Repository, newRepoName string) (err
return fmt.Errorf("sess.Begin: %v", err)
}
- // If there was previously a redirect at this location, remove it.
- if err = deleteRepoRedirect(sess, repo.OwnerID, newRepoName); err != nil {
- return fmt.Errorf("delete repo redirect: %v", err)
- }
-
- if err := NewRepoRedirect(DBContext{sess}, repo.Owner.ID, repo.ID, oldRepoName, newRepoName); err != nil {
+ if err := newRepoRedirect(sess, repo.Owner.ID, repo.ID, oldRepoName, newRepoName); err != nil {
return err
}
diff --git a/models/repo_redirect.go b/models/repo_redirect.go
index 182b6b41a2..afbfeb499e 100644
--- a/models/repo_redirect.go
+++ b/models/repo_redirect.go
@@ -28,16 +28,16 @@ func LookupRepoRedirect(ownerID int64, repoName string) (int64, error) {
return redirect.RedirectRepoID, nil
}
-// NewRepoRedirect create a new repo redirect
-func NewRepoRedirect(ctx DBContext, ownerID, repoID int64, oldRepoName, newRepoName string) error {
+// newRepoRedirect create a new repo redirect
+func newRepoRedirect(e Engine, ownerID, repoID int64, oldRepoName, newRepoName string) error {
oldRepoName = strings.ToLower(oldRepoName)
newRepoName = strings.ToLower(newRepoName)
- if err := deleteRepoRedirect(ctx.e, ownerID, newRepoName); err != nil {
+ if err := deleteRepoRedirect(e, ownerID, newRepoName); err != nil {
return err
}
- if _, err := ctx.e.Insert(&RepoRedirect{
+ if _, err := e.Insert(&RepoRedirect{
OwnerID: ownerID,
LowerName: oldRepoName,
RedirectRepoID: repoID,
diff --git a/models/repo_redirect_test.go b/models/repo_redirect_test.go
index 44ec2b4e94..4c3184a0fd 100644
--- a/models/repo_redirect_test.go
+++ b/models/repo_redirect_test.go
@@ -26,7 +26,7 @@ func TestNewRepoRedirect(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
- assert.NoError(t, NewRepoRedirect(DefaultDBContext(), repo.OwnerID, repo.ID, repo.Name, "newreponame"))
+ assert.NoError(t, newRepoRedirect(x, repo.OwnerID, repo.ID, repo.Name, "newreponame"))
AssertExistsAndLoadBean(t, &RepoRedirect{
OwnerID: repo.OwnerID,
@@ -45,7 +45,7 @@ func TestNewRepoRedirect2(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
- assert.NoError(t, NewRepoRedirect(DefaultDBContext(), repo.OwnerID, repo.ID, repo.Name, "oldrepo1"))
+ assert.NoError(t, newRepoRedirect(x, repo.OwnerID, repo.ID, repo.Name, "oldrepo1"))
AssertExistsAndLoadBean(t, &RepoRedirect{
OwnerID: repo.OwnerID,
@@ -64,7 +64,7 @@ func TestNewRepoRedirect3(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
repo := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository)
- assert.NoError(t, NewRepoRedirect(DefaultDBContext(), repo.OwnerID, repo.ID, repo.Name, "newreponame"))
+ assert.NoError(t, newRepoRedirect(x, repo.OwnerID, repo.ID, repo.Name, "newreponame"))
AssertExistsAndLoadBean(t, &RepoRedirect{
OwnerID: repo.OwnerID,
diff --git a/models/user.go b/models/user.go
index 746608aaa4..8147c9f626 100644
--- a/models/user.go
+++ b/models/user.go
@@ -863,6 +863,10 @@ func CreateUser(u *User) (err error) {
return ErrUserAlreadyExist{u.Name}
}
+ if err = deleteUserRedirect(sess, u.Name); err != nil {
+ return err
+ }
+
u.Email = strings.ToLower(u.Email)
isExist, err = sess.
Where("email=?", u.Email).
@@ -973,6 +977,7 @@ func VerifyActiveEmailCode(code, email string) *EmailAddress {
// ChangeUserName changes all corresponding setting from old user name to new one.
func ChangeUserName(u *User, newUserName string) (err error) {
+ oldUserName := u.Name
if err = IsUsableUsername(newUserName); err != nil {
return err
}
@@ -990,16 +995,28 @@ func ChangeUserName(u *User, newUserName string) (err error) {
return ErrUserAlreadyExist{newUserName}
}
- if _, err = sess.Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, u.Name); err != nil {
+ if _, err = sess.Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, oldUserName); err != nil {
return fmt.Errorf("Change repo owner name: %v", err)
}
// Do not fail if directory does not exist
- if err = os.Rename(UserPath(u.Name), UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
+ if err = os.Rename(UserPath(oldUserName), UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("Rename user directory: %v", err)
}
- return sess.Commit()
+ if err = newUserRedirect(sess, u.ID, oldUserName, newUserName); err != nil {
+ return err
+ }
+
+ if err = sess.Commit(); err != nil {
+ if err2 := os.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
diff --git a/models/user_redirect.go b/models/user_redirect.go
new file mode 100644
index 0000000000..1da8b44088
--- /dev/null
+++ b/models/user_redirect.go
@@ -0,0 +1,52 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import "strings"
+
+// UserRedirect represents that a user name should be redirected to another
+type UserRedirect struct {
+ ID int64 `xorm:"pk autoincr"`
+ LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+ RedirectUserID int64 // userID to redirect to
+}
+
+// LookupUserRedirect look up userID if a user has a redirect name
+func LookupUserRedirect(userName string) (int64, error) {
+ userName = strings.ToLower(userName)
+ redirect := &UserRedirect{LowerName: userName}
+ if has, err := x.Get(redirect); err != nil {
+ return 0, err
+ } else if !has {
+ return 0, ErrUserRedirectNotExist{Name: userName}
+ }
+ return redirect.RedirectUserID, nil
+}
+
+// newUserRedirect create a new user redirect
+func newUserRedirect(e Engine, ID int64, oldUserName, newUserName string) error {
+ oldUserName = strings.ToLower(oldUserName)
+ newUserName = strings.ToLower(newUserName)
+
+ if err := deleteUserRedirect(e, newUserName); err != nil {
+ return err
+ }
+
+ if _, err := e.Insert(&UserRedirect{
+ LowerName: oldUserName,
+ RedirectUserID: ID,
+ }); err != nil {
+ return err
+ }
+ return nil
+}
+
+// deleteUserRedirect delete any redirect from the specified user name to
+// anything else
+func deleteUserRedirect(e Engine, userName string) error {
+ userName = strings.ToLower(userName)
+ _, err := e.Delete(&UserRedirect{LowerName: userName})
+ return err
+}
diff --git a/models/user_redirect_test.go b/models/user_redirect_test.go
new file mode 100644
index 0000000000..791c920bcf
--- /dev/null
+++ b/models/user_redirect_test.go
@@ -0,0 +1,69 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestLookupUserRedirect(t *testing.T) {
+ assert.NoError(t, PrepareTestDatabase())
+
+ userID, err := LookupUserRedirect("olduser1")
+ assert.NoError(t, err)
+ assert.EqualValues(t, 1, userID)
+
+ _, err = LookupUserRedirect("doesnotexist")
+ assert.True(t, IsErrUserRedirectNotExist(err))
+}
+
+func TestNewUserRedirect(t *testing.T) {
+ // redirect to a completely new name
+ assert.NoError(t, PrepareTestDatabase())
+
+ user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
+ assert.NoError(t, newUserRedirect(x, user.ID, user.Name, "newusername"))
+
+ AssertExistsAndLoadBean(t, &UserRedirect{
+ LowerName: user.LowerName,
+ RedirectUserID: user.ID,
+ })
+ AssertExistsAndLoadBean(t, &UserRedirect{
+ LowerName: "olduser1",
+ RedirectUserID: user.ID,
+ })
+}
+
+func TestNewUserRedirect2(t *testing.T) {
+ // redirect to previously used name
+ assert.NoError(t, PrepareTestDatabase())
+
+ user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
+ assert.NoError(t, newUserRedirect(x, user.ID, user.Name, "olduser1"))
+
+ AssertExistsAndLoadBean(t, &UserRedirect{
+ LowerName: user.LowerName,
+ RedirectUserID: user.ID,
+ })
+ AssertNotExistsBean(t, &UserRedirect{
+ LowerName: "olduser1",
+ RedirectUserID: user.ID,
+ })
+}
+
+func TestNewUserRedirect3(t *testing.T) {
+ // redirect for a previously-unredirected user
+ assert.NoError(t, PrepareTestDatabase())
+
+ user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
+ assert.NoError(t, newUserRedirect(x, user.ID, user.Name, "newusername"))
+
+ AssertExistsAndLoadBean(t, &UserRedirect{
+ LowerName: user.LowerName,
+ RedirectUserID: user.ID,
+ })
+}
diff --git a/modules/context/context.go b/modules/context/context.go
index 1ee31e0ebb..e4121649ae 100644
--- a/modules/context/context.go
+++ b/modules/context/context.go
@@ -90,6 +90,26 @@ func (ctx *Context) IsUserRepoReaderAny() bool {
return ctx.Repo.HasAccess()
}
+// RedirectToUser redirect to a differently-named user
+func RedirectToUser(ctx *Context, userName string, redirectUserID int64) {
+ user, err := models.GetUserByID(redirectUserID)
+ if err != nil {
+ ctx.ServerError("GetUserByID", err)
+ return
+ }
+
+ redirectPath := strings.Replace(
+ ctx.Req.URL.Path,
+ userName,
+ user.Name,
+ 1,
+ )
+ if ctx.Req.URL.RawQuery != "" {
+ redirectPath += "?" + ctx.Req.URL.RawQuery
+ }
+ ctx.Redirect(path.Join(setting.AppSubURL, redirectPath))
+}
+
// HasAPIError returns true if error occurs in form validation.
func (ctx *Context) HasAPIError() bool {
hasErr, ok := ctx.Data["HasError"]
diff --git a/modules/context/org.go b/modules/context/org.go
index 9b87fba9fd..f61a39c666 100644
--- a/modules/context/org.go
+++ b/modules/context/org.go
@@ -54,7 +54,14 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
ctx.Org.Organization, err = models.GetUserByName(orgName)
if err != nil {
if models.IsErrUserNotExist(err) {
- ctx.NotFound("GetUserByName", err)
+ redirectUserID, err := models.LookupUserRedirect(orgName)
+ if err == nil {
+ RedirectToUser(ctx, orgName, redirectUserID)
+ } else if models.IsErrUserRedirectNotExist(err) {
+ ctx.NotFound("GetUserByName", err)
+ } else {
+ ctx.ServerError("LookupUserRedirect", err)
+ }
} else {
ctx.ServerError("GetUserByName", err)
}
diff --git a/modules/context/repo.go b/modules/context/repo.go
index 2aee6caca4..63cb02dc06 100644
--- a/modules/context/repo.go
+++ b/modules/context/repo.go
@@ -411,11 +411,18 @@ func RepoAssignment() macaron.Handler {
owner, err = models.GetUserByName(userName)
if err != nil {
if models.IsErrUserNotExist(err) {
- if ctx.Query("go-get") == "1" {
- EarlyResponseForGoGetMeta(ctx)
- return
+ redirectUserID, err := models.LookupUserRedirect(userName)
+ if err == nil {
+ RedirectToUser(ctx, userName, redirectUserID)
+ } else if models.IsErrUserRedirectNotExist(err) {
+ if ctx.Query("go-get") == "1" {
+ EarlyResponseForGoGetMeta(ctx)
+ return
+ }
+ ctx.NotFound("GetUserByName", nil)
+ } else {
+ ctx.ServerError("LookupUserRedirect", err)
}
- ctx.NotFound("GetUserByName", nil)
} else {
ctx.ServerError("GetUserByName", err)
}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 770670ec1f..6c9604bf8b 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -450,6 +450,7 @@ update_language_not_found = Language '%s' is not available.
update_profile_success = Your profile has been updated.
change_username = Your username has been changed.
change_username_prompt = Note: username changes also change your account URL.
+change_username_redirect_prompt = The old username will redirect until it is claimed.
continue = Continue
cancel = Cancel
language = Language
@@ -1941,6 +1942,7 @@ settings.visibility.private_shortname = Private
settings.update_settings = Update Settings
settings.update_setting_success = Organization settings have been updated.
settings.change_orgname_prompt = Note: changing the organization name also changes the organization's URL.
+settings.change_orgname_redirect_prompt = The old name will redirect until it is claimed.
settings.update_avatar_success = The organization's avatar has been updated.
settings.delete = Delete Organization
settings.delete_account = Delete This Organization
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 02ad8ab360..876f48ca5c 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -134,7 +134,13 @@ func repoAssignment() macaron.Handler {
owner, err = models.GetUserByName(userName)
if err != nil {
if models.IsErrUserNotExist(err) {
- ctx.NotFound()
+ if redirectUserID, err := models.LookupUserRedirect(userName); err == nil {
+ context.RedirectToUser(ctx.Context, userName, redirectUserID)
+ } else if models.IsErrUserRedirectNotExist(err) {
+ ctx.NotFound("GetUserByName", err)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
+ }
} else {
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
}
@@ -393,7 +399,14 @@ func orgAssignment(args ...bool) macaron.Handler {
ctx.Org.Organization, err = models.GetOrgByName(ctx.Params(":org"))
if err != nil {
if models.IsErrOrgNotExist(err) {
- ctx.NotFound()
+ redirectUserID, err := models.LookupUserRedirect(ctx.Params(":org"))
+ if err == nil {
+ context.RedirectToUser(ctx.Context, ctx.Params(":org"), redirectUserID)
+ } else if models.IsErrUserRedirectNotExist(err) {
+ ctx.NotFound("GetOrgByName", err)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
+ }
} else {
ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
}
diff --git a/routers/api/v1/user/helper.go b/routers/api/v1/user/helper.go
new file mode 100644
index 0000000000..fcdac257ed
--- /dev/null
+++ b/routers/api/v1/user/helper.go
@@ -0,0 +1,36 @@
+// Copyright 2021 The Gitea Authors.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
+)
+
+// GetUserByParamsName get user by name
+func GetUserByParamsName(ctx *context.APIContext, name string) *models.User {
+ username := ctx.Params(name)
+ user, err := models.GetUserByName(username)
+ if err != nil {
+ if models.IsErrUserNotExist(err) {
+ if redirectUserID, err := models.LookupUserRedirect(username); err == nil {
+ context.RedirectToUser(ctx.Context, username, redirectUserID)
+ } else {
+ ctx.NotFound("GetUserByName", err)
+ }
+ } else {
+ ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
+ }
+ return nil
+ }
+ return user
+}
+
+// GetUserByParams returns user whose name is presented in URL (":username").
+func GetUserByParams(ctx *context.APIContext) *models.User {
+ return GetUserByParamsName(ctx, ":username")
+}
diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go
index 8069660653..fa16df1836 100644
--- a/routers/api/v1/user/key.go
+++ b/routers/api/v1/user/key.go
@@ -39,25 +39,6 @@ func appendPrivateInformation(apiKey *api.PublicKey, key *models.PublicKey, defa
return apiKey, nil
}
-// GetUserByParamsName get user by name
-func GetUserByParamsName(ctx *context.APIContext, name string) *models.User {
- user, err := models.GetUserByName(ctx.Params(name))
- if err != nil {
- if models.IsErrUserNotExist(err) {
- ctx.NotFound()
- } else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
- }
- return nil
- }
- return user
-}
-
-// GetUserByParams returns user whose name is presented in URL paramenter.
-func GetUserByParams(ctx *context.APIContext) *models.User {
- return GetUserByParamsName(ctx, ":username")
-}
-
func composePublicKeysAPILink() string {
return setting.AppURL + "api/v1/user/keys/"
}
diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go
index b860219e62..ecc149fe52 100644
--- a/routers/api/v1/user/user.go
+++ b/routers/api/v1/user/user.go
@@ -107,13 +107,8 @@ func GetInfo(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- u, err := models.GetUserByName(ctx.Params(":username"))
- if err != nil {
- if models.IsErrUserNotExist(err) {
- ctx.NotFound()
- } else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
- }
+ u := GetUserByParams(ctx)
+ if ctx.Written() {
return
}
@@ -153,14 +148,8 @@ func GetUserHeatmapData(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- // Get the user to throw an error if it does not exist
- user, err := models.GetUserByName(ctx.Params(":username"))
- if err != nil {
- if models.IsErrUserNotExist(err) {
- ctx.Status(http.StatusNotFound)
- } else {
- ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
- }
+ user := GetUserByParams(ctx)
+ if ctx.Written() {
return
}
diff --git a/routers/repo/http.go b/routers/repo/http.go
index d4464ec62e..3de45698e8 100644
--- a/routers/repo/http.go
+++ b/routers/repo/http.go
@@ -102,8 +102,15 @@ func HTTP(ctx *context.Context) {
owner, err := models.GetUserByName(username)
if err != nil {
- log.Error("Attempted access of unknown user from %s", ctx.RemoteAddr())
- ctx.NotFoundOrServerError("GetUserByName", models.IsErrUserNotExist, err)
+ if models.IsErrUserNotExist(err) {
+ if redirectUserID, err := models.LookupUserRedirect(username); err == nil {
+ context.RedirectToUser(ctx, username, redirectUserID)
+ } else {
+ ctx.NotFound("GetUserByName", err)
+ }
+ } else {
+ ctx.ServerError("GetUserByName", err)
+ }
return
}
if !owner.IsOrganization() && !owner.IsActive {
diff --git a/routers/user/profile.go b/routers/user/profile.go
index bd5b359272..e19407baa7 100644
--- a/routers/user/profile.go
+++ b/routers/user/profile.go
@@ -23,7 +23,11 @@ func GetUserByName(ctx *context.Context, name string) *models.User {
user, err := models.GetUserByName(name)
if err != nil {
if models.IsErrUserNotExist(err) {
- ctx.NotFound("GetUserByName", nil)
+ if redirectUserID, err := models.LookupUserRedirect(name); err == nil {
+ context.RedirectToUser(ctx, name, redirectUserID)
+ } else {
+ ctx.NotFound("GetUserByName", err)
+ }
} else {
ctx.ServerError("GetUserByName", err)
}
diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl
index 348f8cb533..09bf5e0caa 100644
--- a/templates/org/settings/options.tmpl
+++ b/templates/org/settings/options.tmpl
@@ -13,7 +13,10 @@
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="required field {{if .Err_Name}}error{{end}}">
- <label for="org_name">{{.i18n.Tr "org.org_name_holder"}}<span class="text red hide" id="org-name-change-prompt"> {{.i18n.Tr "org.settings.change_orgname_prompt"}}</span></label>
+ <label for="org_name">{{.i18n.Tr "org.org_name_holder"}}
+ <span class="text red hide" id="org-name-change-prompt"> {{.i18n.Tr "org.settings.change_orgname_prompt"}}</span>
+ <span class="text red hide" id="org-name-change-redirect-prompt"> {{.i18n.Tr "org.settings.change_orgname_redirect_prompt"}}</span>
+ </label>
<input id="org_name" name="name" value="{{.Org.Name}}" data-org-name="{{.Org.Name}}" autofocus required>
</div>
<div class="field {{if .Err_FullName}}error{{end}}">
diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl
index 7ec8d2a15f..ee3cc58904 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -11,7 +11,10 @@
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="required field {{if .Err_Name}}error{{end}}">
- <label for="username">{{.i18n.Tr "username"}}<span class="text red hide" id="name-change-prompt"> {{.i18n.Tr "settings.change_username_prompt"}}</span></label>
+ <label for="username">{{.i18n.Tr "username"}}
+ <span class="text red hide" id="name-change-prompt"> {{.i18n.Tr "settings.change_username_prompt"}}</span>
+ <span class="text red hide" id="name-change-redirect-prompt"> {{.i18n.Tr "settings.change_username_redirect_prompt"}}</span>
+ </label>
<input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if not .SignedUser.IsLocal}}disabled{{end}}>
{{if not .SignedUser.IsLocal}}
<p class="help text blue">{{$.i18n.Tr "settings.password_username_disabled"}}</p>
diff --git a/web_src/js/index.js b/web_src/js/index.js
index 16bb412f09..f5f4841410 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -1736,10 +1736,13 @@ function initOrganization() {
if ($('.organization.settings.options').length > 0) {
$('#org_name').on('keyup', function () {
const $prompt = $('#org-name-change-prompt');
+ const $prompt_redirect = $('#org-name-change-redirect-prompt');
if ($(this).val().toString().toLowerCase() !== $(this).data('org-name').toString().toLowerCase()) {
$prompt.show();
+ $prompt_redirect.show();
} else {
$prompt.hide();
+ $prompt_redirect.hide();
}
});
}
@@ -1755,10 +1758,13 @@ function initUserSettings() {
if ($('.user.settings.profile').length > 0) {
$('#username').on('keyup', function () {
const $prompt = $('#name-change-prompt');
+ const $prompt_redirect = $('#name-change-redirect-prompt');
if ($(this).val().toString().toLowerCase() !== $(this).data('name').toString().toLowerCase()) {
$prompt.show();
+ $prompt_redirect.show();
} else {
$prompt.hide();
+ $prompt_redirect.hide();
}
});
}