Do not require login_name & source_id for /admin/user/{username}

When editing a user via the API, do not require setting `login_name` or
`source_id`: for local accounts, these do not matter. However, when
editing a non-local account, require *both*, as before.

Fixes #1861.

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
This commit is contained in:
Gergely Nagy 2024-04-17 01:25:20 +02:00
parent 787bc6ed94
commit d07c8c821c
No known key found for this signature in database
5 changed files with 56 additions and 33 deletions

View file

@ -30,10 +30,8 @@ type CreateUserOption struct {
// EditUserOption edit user options // EditUserOption edit user options
type EditUserOption struct { type EditUserOption struct {
// required: true SourceID *int64 `json:"source_id"`
SourceID int64 `json:"source_id"` LoginName *string `json:"login_name"`
// required: true
LoginName string `json:"login_name" binding:"Required"`
// swagger:strfmt email // swagger:strfmt email
Email *string `json:"email" binding:"MaxSize(254)"` Email *string `json:"email" binding:"MaxSize(254)"`
FullName *string `json:"full_name" binding:"MaxSize(100)"` FullName *string `json:"full_name" binding:"MaxSize(100)"`

View file

@ -192,9 +192,17 @@ func EditUser(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.EditUserOption) form := web.GetForm(ctx).(*api.EditUserOption)
// If either LoginSource or LoginName is given, the other must be present too.
if form.SourceID != nil || form.LoginName != nil {
if form.SourceID == nil || form.LoginName == nil {
ctx.Error(http.StatusUnprocessableEntity, "LoginSourceAndLoginName", fmt.Errorf("source_id and login_name must be specified together"))
return
}
}
authOpts := &user_service.UpdateAuthOptions{ authOpts := &user_service.UpdateAuthOptions{
LoginSource: optional.FromNonDefault(form.SourceID), LoginSource: optional.FromPtr(form.SourceID),
LoginName: optional.Some(form.LoginName), LoginName: optional.FromPtr(form.LoginName),
Password: optional.FromNonDefault(form.Password), Password: optional.FromNonDefault(form.Password),
MustChangePassword: optional.FromPtr(form.MustChangePassword), MustChangePassword: optional.FromPtr(form.MustChangePassword),
ProhibitLogin: optional.FromPtr(form.ProhibitLogin), ProhibitLogin: optional.FromPtr(form.ProhibitLogin),

View file

@ -20984,10 +20984,6 @@
"EditUserOption": { "EditUserOption": {
"description": "EditUserOption edit user options", "description": "EditUserOption edit user options",
"type": "object", "type": "object",
"required": [
"source_id",
"login_name"
],
"properties": { "properties": {
"active": { "active": {
"type": "boolean", "type": "boolean",

View file

@ -196,18 +196,12 @@ func TestAPIEditUser(t *testing.T) {
urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2") urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2")
req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{
// required
"login_name": "user2",
"source_id": "0",
// to change
"full_name": "Full Name User 2", "full_name": "Full Name User 2",
}).AddTokenAuth(token) }).AddTokenAuth(token)
MakeRequest(t, req, http.StatusOK) MakeRequest(t, req, http.StatusOK)
empty := "" empty := ""
req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
LoginName: "user2",
SourceID: 0,
Email: &empty, Email: &empty,
}).AddTokenAuth(token) }).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusBadRequest) resp := MakeRequest(t, req, http.StatusBadRequest)
@ -220,10 +214,6 @@ func TestAPIEditUser(t *testing.T) {
assert.False(t, user2.IsRestricted) assert.False(t, user2.IsRestricted)
bTrue := true bTrue := true
req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
// required
LoginName: "user2",
SourceID: 0,
// to change
Restricted: &bTrue, Restricted: &bTrue,
}).AddTokenAuth(token) }).AddTokenAuth(token)
MakeRequest(t, req, http.StatusOK) MakeRequest(t, req, http.StatusOK)
@ -231,6 +221,45 @@ func TestAPIEditUser(t *testing.T) {
assert.True(t, user2.IsRestricted) assert.True(t, user2.IsRestricted)
} }
func TestAPIEditUserWithLoginName(t *testing.T) {
defer tests.PrepareTestEnv(t)()
adminUsername := "user1"
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2")
loginName := "user2"
loginSource := int64(0)
t.Run("login_name only", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
LoginName: &loginName,
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusUnprocessableEntity)
})
t.Run("source_id only", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
SourceID: &loginSource,
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusUnprocessableEntity)
})
t.Run("login_name & source_id", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
LoginName: &loginName,
SourceID: &loginSource,
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusOK)
})
}
func TestAPICreateRepoForUser(t *testing.T) { func TestAPICreateRepoForUser(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
adminUsername := "user1" adminUsername := "user1"
@ -375,8 +404,6 @@ func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) {
newEmail := "user2@example1.com" newEmail := "user2@example1.com"
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
LoginName: "user2",
SourceID: 0,
Email: &newEmail, Email: &newEmail,
}).AddTokenAuth(token) }).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
@ -384,8 +411,6 @@ func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) {
originalEmail := "user2@example.com" originalEmail := "user2@example.com"
req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
LoginName: "user2",
SourceID: 0,
Email: &originalEmail, Email: &originalEmail,
}).AddTokenAuth(token) }).AddTokenAuth(token)
MakeRequest(t, req, http.StatusOK) MakeRequest(t, req, http.StatusOK)

View file

@ -495,8 +495,6 @@ func TestUserPronouns(t *testing.T) {
// Set the pronouns for user2 // Set the pronouns for user2
pronouns := "she/her" pronouns := "she/her"
req := NewRequestWithJSON(t, "PATCH", "/api/v1/admin/users/user2", &api.EditUserOption{ req := NewRequestWithJSON(t, "PATCH", "/api/v1/admin/users/user2", &api.EditUserOption{
LoginName: "user2",
SourceID: 0,
Pronouns: &pronouns, Pronouns: &pronouns,
}).AddTokenAuth(adminToken) }).AddTokenAuth(adminToken)
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
@ -596,8 +594,6 @@ func TestUserPronouns(t *testing.T) {
// Set the pronouns to Unspecified (an empty string) via the API // Set the pronouns to Unspecified (an empty string) via the API
pronouns := "" pronouns := ""
req := NewRequestWithJSON(t, "PATCH", "/api/v1/admin/users/user2", &api.EditUserOption{ req := NewRequestWithJSON(t, "PATCH", "/api/v1/admin/users/user2", &api.EditUserOption{
LoginName: "user2",
SourceID: 0,
Pronouns: &pronouns, Pronouns: &pronouns,
}).AddTokenAuth(adminToken) }).AddTokenAuth(adminToken)
MakeRequest(t, req, http.StatusOK) MakeRequest(t, req, http.StatusOK)