1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 10:18:38 +00:00

Fix last admin check when syncing users (#34649) (#34673)

Backport #34649 by @wxiaoguang

Fix #34358

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Giteabot
2025-06-10 06:18:44 +08:00
committed by GitHub
parent b947bc4363
commit a3b25436f2
8 changed files with 49 additions and 20 deletions

View File

@@ -15,6 +15,26 @@ import (
"code.gitea.io/gitea/modules/structs"
)
type UpdateOptionField[T any] struct {
FieldValue T
FromSync bool
}
func UpdateOptionFieldFromValue[T any](value T) optional.Option[UpdateOptionField[T]] {
return optional.Some(UpdateOptionField[T]{FieldValue: value})
}
func UpdateOptionFieldFromSync[T any](value T) optional.Option[UpdateOptionField[T]] {
return optional.Some(UpdateOptionField[T]{FieldValue: value, FromSync: true})
}
func UpdateOptionFieldFromPtr[T any](value *T) optional.Option[UpdateOptionField[T]] {
if value == nil {
return optional.None[UpdateOptionField[T]]()
}
return UpdateOptionFieldFromValue(*value)
}
type UpdateOptions struct {
KeepEmailPrivate optional.Option[bool]
FullName optional.Option[string]
@@ -32,7 +52,7 @@ type UpdateOptions struct {
DiffViewStyle optional.Option[string]
AllowCreateOrganization optional.Option[bool]
IsActive optional.Option[bool]
IsAdmin optional.Option[bool]
IsAdmin optional.Option[UpdateOptionField[bool]]
EmailNotificationsPreference optional.Option[string]
SetLastLogin bool
RepoAdminChangeTeamAccess optional.Option[bool]
@@ -111,13 +131,18 @@ func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) er
cols = append(cols, "is_restricted")
}
if opts.IsAdmin.Has() {
if !opts.IsAdmin.Value() && user_model.IsLastAdminUser(ctx, u) {
return user_model.ErrDeleteLastAdminUser{UID: u.ID}
if opts.IsAdmin.Value().FieldValue /* true */ {
u.IsAdmin = opts.IsAdmin.Value().FieldValue // set IsAdmin=true
cols = append(cols, "is_admin")
} else if !user_model.IsLastAdminUser(ctx, u) /* not the last admin */ {
u.IsAdmin = opts.IsAdmin.Value().FieldValue // it's safe to change it from false to true (not the last admin)
cols = append(cols, "is_admin")
} else /* IsAdmin=false but this is the last admin user */ { //nolint
if !opts.IsAdmin.Value().FromSync {
return user_model.ErrDeleteLastAdminUser{UID: u.ID}
}
// else: syncing from external-source, this user is the last admin, so skip the "IsAdmin=false" change
}
u.IsAdmin = opts.IsAdmin.Value()
cols = append(cols, "is_admin")
}
if opts.Visibility.Has() {

View File

@@ -22,7 +22,11 @@ func TestUpdateUser(t *testing.T) {
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
assert.Error(t, UpdateUser(db.DefaultContext, admin, &UpdateOptions{
IsAdmin: optional.Some(false),
IsAdmin: UpdateOptionFieldFromValue(false),
}))
assert.NoError(t, UpdateUser(db.DefaultContext, admin, &UpdateOptions{
IsAdmin: UpdateOptionFieldFromSync(false),
}))
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28})
@@ -38,7 +42,7 @@ func TestUpdateUser(t *testing.T) {
MaxRepoCreation: optional.Some(10),
IsRestricted: optional.Some(true),
IsActive: optional.Some(false),
IsAdmin: optional.Some(true),
IsAdmin: UpdateOptionFieldFromValue(true),
Visibility: optional.Some(structs.VisibleTypePrivate),
KeepActivityPrivate: optional.Some(true),
Language: optional.Some("lang"),
@@ -60,7 +64,7 @@ func TestUpdateUser(t *testing.T) {
assert.Equal(t, opts.MaxRepoCreation.Value(), user.MaxRepoCreation)
assert.Equal(t, opts.IsRestricted.Value(), user.IsRestricted)
assert.Equal(t, opts.IsActive.Value(), user.IsActive)
assert.Equal(t, opts.IsAdmin.Value(), user.IsAdmin)
assert.Equal(t, opts.IsAdmin.Value().FieldValue, user.IsAdmin)
assert.Equal(t, opts.Visibility.Value(), user.Visibility)
assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate)
assert.Equal(t, opts.Language.Value(), user.Language)
@@ -80,7 +84,7 @@ func TestUpdateUser(t *testing.T) {
assert.Equal(t, opts.MaxRepoCreation.Value(), user.MaxRepoCreation)
assert.Equal(t, opts.IsRestricted.Value(), user.IsRestricted)
assert.Equal(t, opts.IsActive.Value(), user.IsActive)
assert.Equal(t, opts.IsAdmin.Value(), user.IsAdmin)
assert.Equal(t, opts.IsAdmin.Value().FieldValue, user.IsAdmin)
assert.Equal(t, opts.Visibility.Value(), user.Visibility)
assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate)
assert.Equal(t, opts.Language.Value(), user.Language)