Co-authored-by: Bwko <bouwko@gmail.com> Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: zeripath <art27@cantab.net>tags/v1.15.0-dev
@@ -0,0 +1,82 @@ | |||
// 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 integrations | |||
import ( | |||
"net/http" | |||
"strconv" | |||
"testing" | |||
"code.gitea.io/gitea/models" | |||
"github.com/stretchr/testify/assert" | |||
) | |||
func TestAdminViewUsers(t *testing.T) { | |||
prepareTestEnv(t) | |||
session := loginUser(t, "user1") | |||
req := NewRequest(t, "GET", "/admin/users") | |||
session.MakeRequest(t, req, http.StatusOK) | |||
session = loginUser(t, "user2") | |||
req = NewRequest(t, "GET", "/admin/users") | |||
session.MakeRequest(t, req, http.StatusForbidden) | |||
} | |||
func TestAdminViewUser(t *testing.T) { | |||
prepareTestEnv(t) | |||
session := loginUser(t, "user1") | |||
req := NewRequest(t, "GET", "/admin/users/1") | |||
session.MakeRequest(t, req, http.StatusOK) | |||
session = loginUser(t, "user2") | |||
req = NewRequest(t, "GET", "/admin/users/1") | |||
session.MakeRequest(t, req, http.StatusForbidden) | |||
} | |||
func TestAdminEditUser(t *testing.T) { | |||
prepareTestEnv(t) | |||
testSuccessfullEdit(t, models.User{ID: 2, Name: "newusername", LoginName: "otherlogin", Email: "new@e-mail.gitea"}) | |||
} | |||
func testSuccessfullEdit(t *testing.T, formData models.User) { | |||
makeRequest(t, formData, http.StatusFound) | |||
} | |||
func makeRequest(t *testing.T, formData models.User, headerCode int) { | |||
session := loginUser(t, "user1") | |||
csrf := GetCSRF(t, session, "/admin/users/"+strconv.Itoa(int(formData.ID))) | |||
req := NewRequestWithValues(t, "POST", "/admin/users/"+strconv.Itoa(int(formData.ID)), map[string]string{ | |||
"_csrf": csrf, | |||
"user_name": formData.Name, | |||
"login_name": formData.LoginName, | |||
"login_type": "0-0", | |||
"email": formData.Email, | |||
}) | |||
session.MakeRequest(t, req, headerCode) | |||
user := models.AssertExistsAndLoadBean(t, &models.User{ID: formData.ID}).(*models.User) | |||
assert.Equal(t, formData.Name, user.Name) | |||
assert.Equal(t, formData.LoginName, user.LoginName) | |||
assert.Equal(t, formData.Email, user.Email) | |||
} | |||
func TestAdminDeleteUser(t *testing.T) { | |||
defer prepareTestEnv(t)() | |||
session := loginUser(t, "user1") | |||
csrf := GetCSRF(t, session, "/admin/users/8") | |||
req := NewRequestWithValues(t, "POST", "/admin/users/8/delete", map[string]string{ | |||
"_csrf": csrf, | |||
}) | |||
session.MakeRequest(t, req, http.StatusOK) | |||
assertUserDeleted(t, 8) | |||
models.CheckConsistencyFor(t, &models.User{}) | |||
} |
@@ -24,21 +24,6 @@ func assertUserDeleted(t *testing.T, userID int64) { | |||
models.AssertNotExistsBean(t, &models.Star{UID: userID}) | |||
} | |||
func TestAdminDeleteUser(t *testing.T) { | |||
defer prepareTestEnv(t)() | |||
session := loginUser(t, "user1") | |||
csrf := GetCSRF(t, session, "/admin/users/8") | |||
req := NewRequestWithValues(t, "POST", "/admin/users/8/delete", map[string]string{ | |||
"_csrf": csrf, | |||
}) | |||
session.MakeRequest(t, req, http.StatusOK) | |||
assertUserDeleted(t, 8) | |||
models.CheckConsistencyFor(t, &models.User{}) | |||
} | |||
func TestUserDeleteAccount(t *testing.T) { | |||
defer prepareTestEnv(t)() | |||
@@ -913,19 +913,19 @@ func ChangeUserName(u *User, newUserName string) (err error) { | |||
return err | |||
} | |||
isExist, err := IsUserExist(0, newUserName) | |||
if err != nil { | |||
return err | |||
} else if isExist { | |||
return ErrUserAlreadyExist{newUserName} | |||
} | |||
sess := x.NewSession() | |||
defer sess.Close() | |||
if err = sess.Begin(); err != nil { | |||
return err | |||
} | |||
isExist, err := isUserExist(sess, 0, newUserName) | |||
if err != nil { | |||
return err | |||
} else if isExist { | |||
return ErrUserAlreadyExist{newUserName} | |||
} | |||
if _, err = sess.Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, u.Name); err != nil { | |||
return fmt.Errorf("Change repo owner name: %v", err) | |||
} |
@@ -28,6 +28,7 @@ func (f *AdminCreateUserForm) Validate(ctx *macaron.Context, errs binding.Errors | |||
// AdminEditUserForm form for admin to create user | |||
type AdminEditUserForm struct { | |||
LoginType string `binding:"Required"` | |||
UserName string `binding:"AlphaDashDot;MaxSize(40)"` | |||
LoginName string | |||
FullName string `binding:"MaxSize(100)"` | |||
Email string `binding:"Required;Email;MaxSize(254)"` |
@@ -359,6 +359,7 @@ password_not_match = The passwords do not match. | |||
lang_select_error = Select a language from the list. | |||
username_been_taken = The username is already taken. | |||
username_change_not_local_user = Non-local users are not allowed to change their username. | |||
repo_name_been_taken = The repository name is already used. | |||
repository_files_already_exist = Files already exist for this repository. Contact the system administrator. | |||
repository_files_already_exist.adopt = Files already exist for this repository and can only be Adopted. |
@@ -18,6 +18,7 @@ import ( | |||
"code.gitea.io/gitea/modules/password" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/routers" | |||
router_user_setting "code.gitea.io/gitea/routers/user/setting" | |||
"code.gitea.io/gitea/services/mailer" | |||
) | |||
@@ -269,6 +270,15 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) { | |||
u.HashPassword(form.Password) | |||
} | |||
if len(form.UserName) != 0 && u.Name != form.UserName { | |||
if err := router_user_setting.HandleUsernameChange(ctx, u, form.UserName); err != nil { | |||
ctx.Redirect(setting.AppSubURL + "/admin/users") | |||
return | |||
} | |||
u.Name = form.UserName | |||
u.LowerName = strings.ToLower(form.UserName) | |||
} | |||
if form.Reset2FA { | |||
tf, err := models.GetTwoFactorByUID(u.ID) | |||
if err != nil && !models.IsErrTwoFactorNotEnrolled(err) { |
@@ -38,42 +38,36 @@ func Profile(ctx *context.Context) { | |||
ctx.HTML(200, tplSettingsProfile) | |||
} | |||
func handleUsernameChange(ctx *context.Context, newName string) { | |||
// HandleUsernameChange handle username changes from user settings and admin interface | |||
func HandleUsernameChange(ctx *context.Context, user *models.User, newName string) error { | |||
// Non-local users are not allowed to change their username. | |||
if len(newName) == 0 || !ctx.User.IsLocal() { | |||
return | |||
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 ctx.User.LowerName != strings.ToLower(newName) { | |||
if err := models.ChangeUserName(ctx.User, newName); err != nil { | |||
if user.LowerName != strings.ToLower(newName) { | |||
if err := models.ChangeUserName(user, newName); err != nil { | |||
switch { | |||
case models.IsErrUserAlreadyExist(err): | |||
ctx.Flash.Error(ctx.Tr("form.username_been_taken")) | |||
ctx.Redirect(setting.AppSubURL + "/user/settings") | |||
case models.IsErrEmailAlreadyUsed(err): | |||
ctx.Flash.Error(ctx.Tr("form.email_been_used")) | |||
ctx.Redirect(setting.AppSubURL + "/user/settings") | |||
case models.IsErrNameReserved(err): | |||
ctx.Flash.Error(ctx.Tr("user.form.name_reserved", newName)) | |||
ctx.Redirect(setting.AppSubURL + "/user/settings") | |||
case models.IsErrNamePatternNotAllowed(err): | |||
ctx.Flash.Error(ctx.Tr("user.form.name_pattern_not_allowed", newName)) | |||
ctx.Redirect(setting.AppSubURL + "/user/settings") | |||
case models.IsErrNameCharsNotAllowed(err): | |||
ctx.Flash.Error(ctx.Tr("user.form.name_chars_not_allowed", newName)) | |||
ctx.Redirect(setting.AppSubURL + "/user/settings") | |||
default: | |||
ctx.ServerError("ChangeUserName", err) | |||
} | |||
return | |||
return err | |||
} | |||
log.Trace("User name changed: %s -> %s", ctx.User.Name, newName) | |||
log.Trace("User name changed: %s -> %s", user.Name, newName) | |||
} | |||
// In case it's just a case change | |||
ctx.User.Name = newName | |||
ctx.User.LowerName = strings.ToLower(newName) | |||
return nil | |||
} | |||
// ProfilePost response for change user's profile | |||
@@ -86,9 +80,13 @@ func ProfilePost(ctx *context.Context, form auth.UpdateProfileForm) { | |||
return | |||
} | |||
handleUsernameChange(ctx, form.Name) | |||
if ctx.Written() { | |||
return | |||
if len(form.Name) != 0 && ctx.User.Name != form.Name { | |||
if err := HandleUsernameChange(ctx, ctx.User, form.Name); err != nil { | |||
ctx.Redirect(setting.AppSubURL + "/user/settings") | |||
return | |||
} | |||
ctx.User.Name = form.Name | |||
ctx.User.LowerName = strings.ToLower(form.Name) | |||
} | |||
ctx.User.FullName = form.FullName |
@@ -9,9 +9,9 @@ | |||
<div class="ui attached segment"> | |||
<form class="ui form" action="{{.Link}}" method="post"> | |||
{{.CsrfTokenHtml}} | |||
<div class="inline field {{if .Err_UserName}}error{{end}}"> | |||
<div class="field {{if .Err_UserName}}error{{end}}"> | |||
<label for="user_name">{{.i18n.Tr "username"}}</label> | |||
<span>{{.User.Name}}</span> | |||
<input id="user_name" name="user_name" value="{{.User.Name}}" autofocus {{if not .User.IsLocal }}disabled{{end}}> | |||
</div> | |||
<!-- Types and name --> | |||
<div class="inline required field {{if .Err_LoginType}}error{{end}}"> |
@@ -1796,6 +1796,7 @@ function initAdmin() { | |||
if ($('.admin.new.user').length > 0 || $('.admin.edit.user').length > 0) { | |||
$('#login_type').on('change', function () { | |||
if ($(this).val().substring(0, 1) === '0') { | |||
$('#user_name').removeAttr('disabled'); | |||
$('#login_name').removeAttr('required'); | |||
$('.non-local').hide(); | |||
$('.local').show(); | |||
@@ -1805,6 +1806,7 @@ function initAdmin() { | |||
$('#password').attr('required', 'required'); | |||
} | |||
} else { | |||
$('#user_name').attr('disabled', 'disabled'); | |||
$('#login_name').attr('required', 'required'); | |||
$('.non-local').show(); | |||
$('.local').hide(); |