]> source.dussan.org Git - gitea.git/commitdiff
Add option to change username to the admin panel (#14229)
author6543 <6543@obermui.de>
Sun, 10 Jan 2021 12:14:02 +0000 (13:14 +0100)
committerGitHub <noreply@github.com>
Sun, 10 Jan 2021 12:14:02 +0000 (14:14 +0200)
Co-authored-by: Bwko <bouwko@gmail.com>
Co-authored-by: techknowlogick <matti@mdranta.net>
Co-authored-by: zeripath <art27@cantab.net>
integrations/admin_user_test.go [new file with mode: 0644]
integrations/delete_user_test.go
models/user.go
modules/auth/admin.go
options/locale/locale_en-US.ini
routers/admin/users.go
routers/user/setting/profile.go
templates/admin/user/edit.tmpl
web_src/js/index.js

diff --git a/integrations/admin_user_test.go b/integrations/admin_user_test.go
new file mode 100644 (file)
index 0000000..f6b4590
--- /dev/null
@@ -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{})
+}
index 32032b3ddf45314cb69fa2cd9fd051ebdb9481ae..86be6f22ad78b4ba7126659419fb64dccf882864 100644 (file)
@@ -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)()
 
index 73178f256c1bbb66d3afbe1d652683b8f885eea0..d3f1b16c2e7f2b3951ed23061e0a89050fe142e2 100644 (file)
@@ -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)
        }
index 1f840251c7bca00f2915001f0655ba7b7d0f47bd..f2d02635517abe7743a8f7aa81dde7ace15b0964 100644 (file)
@@ -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)"`
index de4dcde3f0bb54d3d2c71f21966a4fb1abcb15f4..5f21c75f76d284bacaba9eef98604d35573c5b1d 100644 (file)
@@ -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.
index 1dc6d5bbe204e732dc5a71d74ef5c24ecaafc427..8a848b1f9ddcf38a421403e909def3d69d7bc00e 100644 (file)
@@ -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) {
index fe9ce098fef71e96a4fc7558e02a3666c7c4ed6e..c935b56230d8591e85d91bde531bcd02982392ca 100644 (file)
@@ -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
index 9edf337f04c08d16a59f587c3ae6c107de6da5c1..a07f3a105417bd4bd0e22bd9d4678c255df3f2b5 100644 (file)
@@ -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}}">
index 37c31538afb4aa9be580ba41de7b8b6a69739288..07d5b99e31167b022376136533a9057480b58fcc 100644 (file)
@@ -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();