Skip to content

Commit 6b3b6f1

Browse files
6543Bwkotechknowlogickzeripath
authored
Add option to change username to the admin panel (#14229)
Co-authored-by: Bwko <[email protected]> Co-authored-by: techknowlogick <[email protected]> Co-authored-by: zeripath <[email protected]>
1 parent d989247 commit 6b3b6f1

File tree

9 files changed

+122
-43
lines changed

9 files changed

+122
-43
lines changed

integrations/admin_user_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package integrations
6+
7+
import (
8+
"net/http"
9+
"strconv"
10+
"testing"
11+
12+
"code.gitea.io/gitea/models"
13+
14+
"github.com/stretchr/testify/assert"
15+
)
16+
17+
func TestAdminViewUsers(t *testing.T) {
18+
prepareTestEnv(t)
19+
20+
session := loginUser(t, "user1")
21+
req := NewRequest(t, "GET", "/admin/users")
22+
session.MakeRequest(t, req, http.StatusOK)
23+
24+
session = loginUser(t, "user2")
25+
req = NewRequest(t, "GET", "/admin/users")
26+
session.MakeRequest(t, req, http.StatusForbidden)
27+
}
28+
29+
func TestAdminViewUser(t *testing.T) {
30+
prepareTestEnv(t)
31+
32+
session := loginUser(t, "user1")
33+
req := NewRequest(t, "GET", "/admin/users/1")
34+
session.MakeRequest(t, req, http.StatusOK)
35+
36+
session = loginUser(t, "user2")
37+
req = NewRequest(t, "GET", "/admin/users/1")
38+
session.MakeRequest(t, req, http.StatusForbidden)
39+
}
40+
41+
func TestAdminEditUser(t *testing.T) {
42+
prepareTestEnv(t)
43+
44+
testSuccessfullEdit(t, models.User{ID: 2, Name: "newusername", LoginName: "otherlogin", Email: "[email protected]"})
45+
}
46+
47+
func testSuccessfullEdit(t *testing.T, formData models.User) {
48+
makeRequest(t, formData, http.StatusFound)
49+
}
50+
51+
func makeRequest(t *testing.T, formData models.User, headerCode int) {
52+
session := loginUser(t, "user1")
53+
csrf := GetCSRF(t, session, "/admin/users/"+strconv.Itoa(int(formData.ID)))
54+
req := NewRequestWithValues(t, "POST", "/admin/users/"+strconv.Itoa(int(formData.ID)), map[string]string{
55+
"_csrf": csrf,
56+
"user_name": formData.Name,
57+
"login_name": formData.LoginName,
58+
"login_type": "0-0",
59+
"email": formData.Email,
60+
})
61+
62+
session.MakeRequest(t, req, headerCode)
63+
user := models.AssertExistsAndLoadBean(t, &models.User{ID: formData.ID}).(*models.User)
64+
assert.Equal(t, formData.Name, user.Name)
65+
assert.Equal(t, formData.LoginName, user.LoginName)
66+
assert.Equal(t, formData.Email, user.Email)
67+
}
68+
69+
func TestAdminDeleteUser(t *testing.T) {
70+
defer prepareTestEnv(t)()
71+
72+
session := loginUser(t, "user1")
73+
74+
csrf := GetCSRF(t, session, "/admin/users/8")
75+
req := NewRequestWithValues(t, "POST", "/admin/users/8/delete", map[string]string{
76+
"_csrf": csrf,
77+
})
78+
session.MakeRequest(t, req, http.StatusOK)
79+
80+
assertUserDeleted(t, 8)
81+
models.CheckConsistencyFor(t, &models.User{})
82+
}

integrations/delete_user_test.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,6 @@ func assertUserDeleted(t *testing.T, userID int64) {
2424
models.AssertNotExistsBean(t, &models.Star{UID: userID})
2525
}
2626

27-
func TestAdminDeleteUser(t *testing.T) {
28-
defer prepareTestEnv(t)()
29-
30-
session := loginUser(t, "user1")
31-
32-
csrf := GetCSRF(t, session, "/admin/users/8")
33-
req := NewRequestWithValues(t, "POST", "/admin/users/8/delete", map[string]string{
34-
"_csrf": csrf,
35-
})
36-
session.MakeRequest(t, req, http.StatusOK)
37-
38-
assertUserDeleted(t, 8)
39-
models.CheckConsistencyFor(t, &models.User{})
40-
}
41-
4227
func TestUserDeleteAccount(t *testing.T) {
4328
defer prepareTestEnv(t)()
4429

models/user.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -913,19 +913,19 @@ func ChangeUserName(u *User, newUserName string) (err error) {
913913
return err
914914
}
915915

916-
isExist, err := IsUserExist(0, newUserName)
917-
if err != nil {
918-
return err
919-
} else if isExist {
920-
return ErrUserAlreadyExist{newUserName}
921-
}
922-
923916
sess := x.NewSession()
924917
defer sess.Close()
925918
if err = sess.Begin(); err != nil {
926919
return err
927920
}
928921

922+
isExist, err := isUserExist(sess, 0, newUserName)
923+
if err != nil {
924+
return err
925+
} else if isExist {
926+
return ErrUserAlreadyExist{newUserName}
927+
}
928+
929929
if _, err = sess.Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, u.Name); err != nil {
930930
return fmt.Errorf("Change repo owner name: %v", err)
931931
}

modules/auth/admin.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func (f *AdminCreateUserForm) Validate(ctx *macaron.Context, errs binding.Errors
2828
// AdminEditUserForm form for admin to create user
2929
type AdminEditUserForm struct {
3030
LoginType string `binding:"Required"`
31+
UserName string `binding:"AlphaDashDot;MaxSize(40)"`
3132
LoginName string
3233
FullName string `binding:"MaxSize(100)"`
3334
Email string `binding:"Required;Email;MaxSize(254)"`

options/locale/locale_en-US.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ password_not_match = The passwords do not match.
359359
lang_select_error = Select a language from the list.
360360
361361
username_been_taken = The username is already taken.
362+
username_change_not_local_user = Non-local users are not allowed to change their username.
362363
repo_name_been_taken = The repository name is already used.
363364
repository_files_already_exist = Files already exist for this repository. Contact the system administrator.
364365
repository_files_already_exist.adopt = Files already exist for this repository and can only be Adopted.

routers/admin/users.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"code.gitea.io/gitea/modules/password"
1919
"code.gitea.io/gitea/modules/setting"
2020
"code.gitea.io/gitea/routers"
21+
router_user_setting "code.gitea.io/gitea/routers/user/setting"
2122
"code.gitea.io/gitea/services/mailer"
2223
)
2324

@@ -269,6 +270,15 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) {
269270
u.HashPassword(form.Password)
270271
}
271272

273+
if len(form.UserName) != 0 && u.Name != form.UserName {
274+
if err := router_user_setting.HandleUsernameChange(ctx, u, form.UserName); err != nil {
275+
ctx.Redirect(setting.AppSubURL + "/admin/users")
276+
return
277+
}
278+
u.Name = form.UserName
279+
u.LowerName = strings.ToLower(form.UserName)
280+
}
281+
272282
if form.Reset2FA {
273283
tf, err := models.GetTwoFactorByUID(u.ID)
274284
if err != nil && !models.IsErrTwoFactorNotEnrolled(err) {

routers/user/setting/profile.go

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,42 +38,36 @@ func Profile(ctx *context.Context) {
3838
ctx.HTML(200, tplSettingsProfile)
3939
}
4040

41-
func handleUsernameChange(ctx *context.Context, newName string) {
41+
// HandleUsernameChange handle username changes from user settings and admin interface
42+
func HandleUsernameChange(ctx *context.Context, user *models.User, newName string) error {
4243
// Non-local users are not allowed to change their username.
43-
if len(newName) == 0 || !ctx.User.IsLocal() {
44-
return
44+
if !user.IsLocal() {
45+
ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user"))
46+
return fmt.Errorf(ctx.Tr("form.username_change_not_local_user"))
4547
}
4648

4749
// Check if user name has been changed
48-
if ctx.User.LowerName != strings.ToLower(newName) {
49-
if err := models.ChangeUserName(ctx.User, newName); err != nil {
50+
if user.LowerName != strings.ToLower(newName) {
51+
if err := models.ChangeUserName(user, newName); err != nil {
5052
switch {
5153
case models.IsErrUserAlreadyExist(err):
5254
ctx.Flash.Error(ctx.Tr("form.username_been_taken"))
53-
ctx.Redirect(setting.AppSubURL + "/user/settings")
5455
case models.IsErrEmailAlreadyUsed(err):
5556
ctx.Flash.Error(ctx.Tr("form.email_been_used"))
56-
ctx.Redirect(setting.AppSubURL + "/user/settings")
5757
case models.IsErrNameReserved(err):
5858
ctx.Flash.Error(ctx.Tr("user.form.name_reserved", newName))
59-
ctx.Redirect(setting.AppSubURL + "/user/settings")
6059
case models.IsErrNamePatternNotAllowed(err):
6160
ctx.Flash.Error(ctx.Tr("user.form.name_pattern_not_allowed", newName))
62-
ctx.Redirect(setting.AppSubURL + "/user/settings")
6361
case models.IsErrNameCharsNotAllowed(err):
6462
ctx.Flash.Error(ctx.Tr("user.form.name_chars_not_allowed", newName))
65-
ctx.Redirect(setting.AppSubURL + "/user/settings")
6663
default:
6764
ctx.ServerError("ChangeUserName", err)
6865
}
69-
return
66+
return err
7067
}
71-
log.Trace("User name changed: %s -> %s", ctx.User.Name, newName)
68+
log.Trace("User name changed: %s -> %s", user.Name, newName)
7269
}
73-
74-
// In case it's just a case change
75-
ctx.User.Name = newName
76-
ctx.User.LowerName = strings.ToLower(newName)
70+
return nil
7771
}
7872

7973
// ProfilePost response for change user's profile
@@ -86,9 +80,13 @@ func ProfilePost(ctx *context.Context, form auth.UpdateProfileForm) {
8680
return
8781
}
8882

89-
handleUsernameChange(ctx, form.Name)
90-
if ctx.Written() {
91-
return
83+
if len(form.Name) != 0 && ctx.User.Name != form.Name {
84+
if err := HandleUsernameChange(ctx, ctx.User, form.Name); err != nil {
85+
ctx.Redirect(setting.AppSubURL + "/user/settings")
86+
return
87+
}
88+
ctx.User.Name = form.Name
89+
ctx.User.LowerName = strings.ToLower(form.Name)
9290
}
9391

9492
ctx.User.FullName = form.FullName

templates/admin/user/edit.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
<div class="ui attached segment">
1010
<form class="ui form" action="{{.Link}}" method="post">
1111
{{.CsrfTokenHtml}}
12-
<div class="inline field {{if .Err_UserName}}error{{end}}">
12+
<div class="field {{if .Err_UserName}}error{{end}}">
1313
<label for="user_name">{{.i18n.Tr "username"}}</label>
14-
<span>{{.User.Name}}</span>
14+
<input id="user_name" name="user_name" value="{{.User.Name}}" autofocus {{if not .User.IsLocal }}disabled{{end}}>
1515
</div>
1616
<!-- Types and name -->
1717
<div class="inline required field {{if .Err_LoginType}}error{{end}}">

web_src/js/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1796,6 +1796,7 @@ function initAdmin() {
17961796
if ($('.admin.new.user').length > 0 || $('.admin.edit.user').length > 0) {
17971797
$('#login_type').on('change', function () {
17981798
if ($(this).val().substring(0, 1) === '0') {
1799+
$('#user_name').removeAttr('disabled');
17991800
$('#login_name').removeAttr('required');
18001801
$('.non-local').hide();
18011802
$('.local').show();
@@ -1805,6 +1806,7 @@ function initAdmin() {
18051806
$('#password').attr('required', 'required');
18061807
}
18071808
} else {
1809+
$('#user_name').attr('disabled', 'disabled');
18081810
$('#login_name').attr('required', 'required');
18091811
$('.non-local').show();
18101812
$('.local').hide();

0 commit comments

Comments
 (0)