From 1bb5c09b5d973ac184922380b1e6379875ac5fa7 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Fri, 20 Nov 2020 02:56:42 +0100
Subject: [PATCH] API: Admin EditUser: Make FullName, Email, Website & Location
 optional (#13562)

* API: Admin EditUser: Make FullName, Email, Website & Location optional

* update swagger docs

* add Tests

Co-authored-by: Lauris BH <lauris@nix.lv>
---
 integrations/api_admin_test.go | 30 ++++++++++++++++++++++++++++++
 modules/structs/admin_user.go  | 31 ++++++++++++++++---------------
 routers/api/v1/admin/user.go   | 23 ++++++++++++++++++-----
 templates/swagger/v1_json.tmpl |  3 ++-
 4 files changed, 66 insertions(+), 21 deletions(-)

diff --git a/integrations/api_admin_test.go b/integrations/api_admin_test.go
index 80d6b52289..79fdc4a9f7 100644
--- a/integrations/api_admin_test.go
+++ b/integrations/api_admin_test.go
@@ -5,6 +5,7 @@
 package integrations
 
 import (
+	"encoding/json"
 	"fmt"
 	"net/http"
 	"testing"
@@ -163,3 +164,32 @@ func TestAPICreateUserInvalidEmail(t *testing.T) {
 	})
 	session.MakeRequest(t, req, http.StatusUnprocessableEntity)
 }
+
+func TestAPIEditUser(t *testing.T) {
+	defer prepareTestEnv(t)()
+	adminUsername := "user1"
+	session := loginUser(t, adminUsername)
+	token := getTokenForLoggedInUser(t, session)
+	urlStr := fmt.Sprintf("/api/v1/admin/users/%s?token=%s", "user2", token)
+
+	req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{
+		// required
+		"login_name": "user2",
+		"source_id":  "0",
+		// to change
+		"full_name": "Full Name User 2",
+	})
+	session.MakeRequest(t, req, http.StatusOK)
+
+	empty := ""
+	req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
+		LoginName: "user2",
+		SourceID:  0,
+		Email:     &empty,
+	})
+	resp := session.MakeRequest(t, req, http.StatusUnprocessableEntity)
+
+	errMap := make(map[string]interface{})
+	json.Unmarshal(resp.Body.Bytes(), &errMap)
+	assert.EqualValues(t, "email is not allowed to be empty string", errMap["message"].(string))
+}
diff --git a/modules/structs/admin_user.go b/modules/structs/admin_user.go
index 7a447e44f5..8cd4dc59e8 100644
--- a/modules/structs/admin_user.go
+++ b/modules/structs/admin_user.go
@@ -23,21 +23,22 @@ type CreateUserOption struct {
 
 // EditUserOption edit user options
 type EditUserOption struct {
-	SourceID  int64  `json:"source_id"`
-	LoginName string `json:"login_name"`
-	FullName  string `json:"full_name" binding:"MaxSize(100)"`
 	// required: true
+	SourceID int64 `json:"source_id"`
+	// required: true
+	LoginName string `json:"login_name" binding:"Required"`
 	// swagger:strfmt email
-	Email                   string `json:"email" binding:"Required;Email;MaxSize(254)"`
-	Password                string `json:"password" binding:"MaxSize(255)"`
-	MustChangePassword      *bool  `json:"must_change_password"`
-	Website                 string `json:"website" binding:"MaxSize(50)"`
-	Location                string `json:"location" binding:"MaxSize(50)"`
-	Active                  *bool  `json:"active"`
-	Admin                   *bool  `json:"admin"`
-	AllowGitHook            *bool  `json:"allow_git_hook"`
-	AllowImportLocal        *bool  `json:"allow_import_local"`
-	MaxRepoCreation         *int   `json:"max_repo_creation"`
-	ProhibitLogin           *bool  `json:"prohibit_login"`
-	AllowCreateOrganization *bool  `json:"allow_create_organization"`
+	Email                   *string `json:"email" binding:"MaxSize(254)"`
+	FullName                *string `json:"full_name" binding:"MaxSize(100)"`
+	Password                string  `json:"password" binding:"MaxSize(255)"`
+	MustChangePassword      *bool   `json:"must_change_password"`
+	Website                 *string `json:"website" binding:"MaxSize(50)"`
+	Location                *string `json:"location" binding:"MaxSize(50)"`
+	Active                  *bool   `json:"active"`
+	Admin                   *bool   `json:"admin"`
+	AllowGitHook            *bool   `json:"allow_git_hook"`
+	AllowImportLocal        *bool   `json:"allow_import_local"`
+	MaxRepoCreation         *int    `json:"max_repo_creation"`
+	ProhibitLogin           *bool   `json:"prohibit_login"`
+	AllowCreateOrganization *bool   `json:"allow_create_organization"`
 }
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index c4b52e4bd6..93b28c2175 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -155,7 +155,7 @@ func EditUser(ctx *context.APIContext, form api.EditUserOption) {
 		return
 	}
 
-	if len(form.Password) > 0 {
+	if len(form.Password) != 0 {
 		if !password.IsComplexEnough(form.Password) {
 			err := errors.New("PasswordComplexity")
 			ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
@@ -182,10 +182,23 @@ func EditUser(ctx *context.APIContext, form api.EditUserOption) {
 	}
 
 	u.LoginName = form.LoginName
-	u.FullName = form.FullName
-	u.Email = form.Email
-	u.Website = form.Website
-	u.Location = form.Location
+
+	if form.FullName != nil {
+		u.FullName = *form.FullName
+	}
+	if form.Email != nil {
+		u.Email = *form.Email
+		if len(u.Email) == 0 {
+			ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("email is not allowed to be empty string"))
+			return
+		}
+	}
+	if form.Website != nil {
+		u.Website = *form.Website
+	}
+	if form.Location != nil {
+		u.Location = *form.Location
+	}
 	if form.Active != nil {
 		u.IsActive = *form.Active
 	}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index e759a1558c..9d775da7d6 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -13097,7 +13097,8 @@
       "description": "EditUserOption edit user options",
       "type": "object",
       "required": [
-        "email"
+        "source_id",
+        "login_name"
       ],
       "properties": {
         "active": {