* 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>tags/v1.15.0-dev
@@ -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) | 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. | // ErrUserProhibitLogin represents a "ErrUserProhibitLogin" kind of error. | ||||
type ErrUserProhibitLogin struct { | type ErrUserProhibitLogin struct { | ||||
UID int64 | UID int64 | ||||
@@ -0,0 +1,4 @@ | |||||
- | |||||
id: 1 | |||||
lower_name: olduser1 | |||||
redirect_user_id: 1 |
@@ -279,6 +279,8 @@ var migrations = []Migration{ | |||||
NewMigration("Convert hook task type from char(16) to varchar(16) and trim the column", convertHookTaskTypeToVarcharAndTrim), | NewMigration("Convert hook task type from char(16) to varchar(16) and trim the column", convertHookTaskTypeToVarcharAndTrim), | ||||
// v166 -> v167 | // v166 -> v167 | ||||
NewMigration("Where Password is Valid with Empty String delete it", recalculateUserEmptyPWD), | NewMigration("Where Password is Valid with Empty String delete it", recalculateUserEmptyPWD), | ||||
// v167 -> v168 | |||||
NewMigration("Add user redirect", addUserRedirect), | |||||
} | } | ||||
// GetCurrentDBVersion returns the current db version | // GetCurrentDBVersion returns the current db version | ||||
@@ -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 | |||||
} |
@@ -128,6 +128,7 @@ func init() { | |||||
new(Task), | new(Task), | ||||
new(LanguageStat), | new(LanguageStat), | ||||
new(EmailHash), | new(EmailHash), | ||||
new(UserRedirect), | |||||
new(Project), | new(Project), | ||||
new(ProjectBoard), | new(ProjectBoard), | ||||
new(ProjectIssue), | new(ProjectIssue), | ||||
@@ -171,6 +171,10 @@ func CreateOrganization(org, owner *User) (err error) { | |||||
return err | return err | ||||
} | } | ||||
if err = deleteUserRedirect(sess, org.Name); err != nil { | |||||
return err | |||||
} | |||||
if _, err = sess.Insert(org); err != nil { | if _, err = sess.Insert(org); err != nil { | ||||
return fmt.Errorf("insert organization: %v", err) | return fmt.Errorf("insert organization: %v", err) | ||||
} | } | ||||
@@ -1312,8 +1312,8 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error | |||||
return fmt.Errorf("delete repo redirect: %v", err) | 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() | return sess.Commit() | ||||
@@ -1361,12 +1361,7 @@ func ChangeRepositoryName(doer *User, repo *Repository, newRepoName string) (err | |||||
return fmt.Errorf("sess.Begin: %v", 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 | return err | ||||
} | } | ||||
@@ -28,16 +28,16 @@ func LookupRepoRedirect(ownerID int64, repoName string) (int64, error) { | |||||
return redirect.RedirectRepoID, nil | 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) | oldRepoName = strings.ToLower(oldRepoName) | ||||
newRepoName = strings.ToLower(newRepoName) | newRepoName = strings.ToLower(newRepoName) | ||||
if err := deleteRepoRedirect(ctx.e, ownerID, newRepoName); err != nil { | |||||
if err := deleteRepoRedirect(e, ownerID, newRepoName); err != nil { | |||||
return err | return err | ||||
} | } | ||||
if _, err := ctx.e.Insert(&RepoRedirect{ | |||||
if _, err := e.Insert(&RepoRedirect{ | |||||
OwnerID: ownerID, | OwnerID: ownerID, | ||||
LowerName: oldRepoName, | LowerName: oldRepoName, | ||||
RedirectRepoID: repoID, | RedirectRepoID: repoID, | ||||
@@ -26,7 +26,7 @@ func TestNewRepoRedirect(t *testing.T) { | |||||
assert.NoError(t, PrepareTestDatabase()) | assert.NoError(t, PrepareTestDatabase()) | ||||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | 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{ | AssertExistsAndLoadBean(t, &RepoRedirect{ | ||||
OwnerID: repo.OwnerID, | OwnerID: repo.OwnerID, | ||||
@@ -45,7 +45,7 @@ func TestNewRepoRedirect2(t *testing.T) { | |||||
assert.NoError(t, PrepareTestDatabase()) | assert.NoError(t, PrepareTestDatabase()) | ||||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | 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{ | AssertExistsAndLoadBean(t, &RepoRedirect{ | ||||
OwnerID: repo.OwnerID, | OwnerID: repo.OwnerID, | ||||
@@ -64,7 +64,7 @@ func TestNewRepoRedirect3(t *testing.T) { | |||||
assert.NoError(t, PrepareTestDatabase()) | assert.NoError(t, PrepareTestDatabase()) | ||||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) | 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{ | AssertExistsAndLoadBean(t, &RepoRedirect{ | ||||
OwnerID: repo.OwnerID, | OwnerID: repo.OwnerID, | ||||
@@ -863,6 +863,10 @@ func CreateUser(u *User) (err error) { | |||||
return ErrUserAlreadyExist{u.Name} | return ErrUserAlreadyExist{u.Name} | ||||
} | } | ||||
if err = deleteUserRedirect(sess, u.Name); err != nil { | |||||
return err | |||||
} | |||||
u.Email = strings.ToLower(u.Email) | u.Email = strings.ToLower(u.Email) | ||||
isExist, err = sess. | isExist, err = sess. | ||||
Where("email=?", u.Email). | 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. | // ChangeUserName changes all corresponding setting from old user name to new one. | ||||
func ChangeUserName(u *User, newUserName string) (err error) { | func ChangeUserName(u *User, newUserName string) (err error) { | ||||
oldUserName := u.Name | |||||
if err = IsUsableUsername(newUserName); err != nil { | if err = IsUsableUsername(newUserName); err != nil { | ||||
return err | return err | ||||
} | } | ||||
@@ -990,16 +995,28 @@ func ChangeUserName(u *User, newUserName string) (err error) { | |||||
return ErrUserAlreadyExist{newUserName} | 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) | return fmt.Errorf("Change repo owner name: %v", err) | ||||
} | } | ||||
// Do not fail if directory does not exist | // 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 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 | // checkDupEmail checks whether there are the same email with the user | ||||
@@ -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 | |||||
} |
@@ -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, | |||||
}) | |||||
} |
@@ -90,6 +90,26 @@ func (ctx *Context) IsUserRepoReaderAny() bool { | |||||
return ctx.Repo.HasAccess() | 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. | // HasAPIError returns true if error occurs in form validation. | ||||
func (ctx *Context) HasAPIError() bool { | func (ctx *Context) HasAPIError() bool { | ||||
hasErr, ok := ctx.Data["HasError"] | hasErr, ok := ctx.Data["HasError"] | ||||
@@ -54,7 +54,14 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { | |||||
ctx.Org.Organization, err = models.GetUserByName(orgName) | ctx.Org.Organization, err = models.GetUserByName(orgName) | ||||
if err != nil { | if err != nil { | ||||
if models.IsErrUserNotExist(err) { | 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 { | } else { | ||||
ctx.ServerError("GetUserByName", err) | ctx.ServerError("GetUserByName", err) | ||||
} | } | ||||
@@ -411,11 +411,18 @@ func RepoAssignment() macaron.Handler { | |||||
owner, err = models.GetUserByName(userName) | owner, err = models.GetUserByName(userName) | ||||
if err != nil { | if err != nil { | ||||
if models.IsErrUserNotExist(err) { | 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 { | } else { | ||||
ctx.ServerError("GetUserByName", err) | ctx.ServerError("GetUserByName", err) | ||||
} | } | ||||
@@ -450,6 +450,7 @@ update_language_not_found = Language '%s' is not available. | |||||
update_profile_success = Your profile has been updated. | update_profile_success = Your profile has been updated. | ||||
change_username = Your username has been changed. | change_username = Your username has been changed. | ||||
change_username_prompt = Note: username changes also change your account URL. | 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 | continue = Continue | ||||
cancel = Cancel | cancel = Cancel | ||||
language = Language | language = Language | ||||
@@ -1941,6 +1942,7 @@ settings.visibility.private_shortname = Private | |||||
settings.update_settings = Update Settings | settings.update_settings = Update Settings | ||||
settings.update_setting_success = Organization settings have been updated. | 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_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.update_avatar_success = The organization's avatar has been updated. | ||||
settings.delete = Delete Organization | settings.delete = Delete Organization | ||||
settings.delete_account = Delete This Organization | settings.delete_account = Delete This Organization | ||||
@@ -134,7 +134,13 @@ func repoAssignment() macaron.Handler { | |||||
owner, err = models.GetUserByName(userName) | owner, err = models.GetUserByName(userName) | ||||
if err != nil { | if err != nil { | ||||
if models.IsErrUserNotExist(err) { | 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 { | } else { | ||||
ctx.Error(http.StatusInternalServerError, "GetUserByName", err) | 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")) | ctx.Org.Organization, err = models.GetOrgByName(ctx.Params(":org")) | ||||
if err != nil { | if err != nil { | ||||
if models.IsErrOrgNotExist(err) { | 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 { | } else { | ||||
ctx.Error(http.StatusInternalServerError, "GetOrgByName", err) | ctx.Error(http.StatusInternalServerError, "GetOrgByName", err) | ||||
} | } | ||||
@@ -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") | |||||
} |
@@ -39,25 +39,6 @@ func appendPrivateInformation(apiKey *api.PublicKey, key *models.PublicKey, defa | |||||
return apiKey, nil | 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 { | func composePublicKeysAPILink() string { | ||||
return setting.AppURL + "api/v1/user/keys/" | return setting.AppURL + "api/v1/user/keys/" | ||||
} | } | ||||
@@ -107,13 +107,8 @@ func GetInfo(ctx *context.APIContext) { | |||||
// "404": | // "404": | ||||
// "$ref": "#/responses/notFound" | // "$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 | return | ||||
} | } | ||||
@@ -153,14 +148,8 @@ func GetUserHeatmapData(ctx *context.APIContext) { | |||||
// "404": | // "404": | ||||
// "$ref": "#/responses/notFound" | // "$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 | return | ||||
} | } | ||||
@@ -102,8 +102,15 @@ func HTTP(ctx *context.Context) { | |||||
owner, err := models.GetUserByName(username) | owner, err := models.GetUserByName(username) | ||||
if err != nil { | 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 | return | ||||
} | } | ||||
if !owner.IsOrganization() && !owner.IsActive { | if !owner.IsOrganization() && !owner.IsActive { | ||||
@@ -23,7 +23,11 @@ func GetUserByName(ctx *context.Context, name string) *models.User { | |||||
user, err := models.GetUserByName(name) | user, err := models.GetUserByName(name) | ||||
if err != nil { | if err != nil { | ||||
if models.IsErrUserNotExist(err) { | 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 { | } else { | ||||
ctx.ServerError("GetUserByName", err) | ctx.ServerError("GetUserByName", err) | ||||
} | } | ||||
@@ -13,7 +13,10 @@ | |||||
<form class="ui form" action="{{.Link}}" method="post"> | <form class="ui form" action="{{.Link}}" method="post"> | ||||
{{.CsrfTokenHtml}} | {{.CsrfTokenHtml}} | ||||
<div class="required field {{if .Err_Name}}error{{end}}"> | <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> | <input id="org_name" name="name" value="{{.Org.Name}}" data-org-name="{{.Org.Name}}" autofocus required> | ||||
</div> | </div> | ||||
<div class="field {{if .Err_FullName}}error{{end}}"> | <div class="field {{if .Err_FullName}}error{{end}}"> | ||||
@@ -11,7 +11,10 @@ | |||||
<form class="ui form" action="{{.Link}}" method="post"> | <form class="ui form" action="{{.Link}}" method="post"> | ||||
{{.CsrfTokenHtml}} | {{.CsrfTokenHtml}} | ||||
<div class="required field {{if .Err_Name}}error{{end}}"> | <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}}> | <input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if not .SignedUser.IsLocal}}disabled{{end}}> | ||||
{{if not .SignedUser.IsLocal}} | {{if not .SignedUser.IsLocal}} | ||||
<p class="help text blue">{{$.i18n.Tr "settings.password_username_disabled"}}</p> | <p class="help text blue">{{$.i18n.Tr "settings.password_username_disabled"}}</p> | ||||
@@ -1736,10 +1736,13 @@ function initOrganization() { | |||||
if ($('.organization.settings.options').length > 0) { | if ($('.organization.settings.options').length > 0) { | ||||
$('#org_name').on('keyup', function () { | $('#org_name').on('keyup', function () { | ||||
const $prompt = $('#org-name-change-prompt'); | 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()) { | if ($(this).val().toString().toLowerCase() !== $(this).data('org-name').toString().toLowerCase()) { | ||||
$prompt.show(); | $prompt.show(); | ||||
$prompt_redirect.show(); | |||||
} else { | } else { | ||||
$prompt.hide(); | $prompt.hide(); | ||||
$prompt_redirect.hide(); | |||||
} | } | ||||
}); | }); | ||||
} | } | ||||
@@ -1755,10 +1758,13 @@ function initUserSettings() { | |||||
if ($('.user.settings.profile').length > 0) { | if ($('.user.settings.profile').length > 0) { | ||||
$('#username').on('keyup', function () { | $('#username').on('keyup', function () { | ||||
const $prompt = $('#name-change-prompt'); | const $prompt = $('#name-change-prompt'); | ||||
const $prompt_redirect = $('#name-change-redirect-prompt'); | |||||
if ($(this).val().toString().toLowerCase() !== $(this).data('name').toString().toLowerCase()) { | if ($(this).val().toString().toLowerCase() !== $(this).data('name').toString().toLowerCase()) { | ||||
$prompt.show(); | $prompt.show(); | ||||
$prompt_redirect.show(); | |||||
} else { | } else { | ||||
$prompt.hide(); | $prompt.hide(); | ||||
$prompt_redirect.hide(); | |||||
} | } | ||||
}); | }); | ||||
} | } | ||||