mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	| @@ -239,7 +239,7 @@ func EditUser(ctx *context.APIContext) { | |||||||
| 		Location:                optional.FromPtr(form.Location), | 		Location:                optional.FromPtr(form.Location), | ||||||
| 		Description:             optional.FromPtr(form.Description), | 		Description:             optional.FromPtr(form.Description), | ||||||
| 		IsActive:                optional.FromPtr(form.Active), | 		IsActive:                optional.FromPtr(form.Active), | ||||||
| 		IsAdmin:                 optional.FromPtr(form.Admin), | 		IsAdmin:                 user_service.UpdateOptionFieldFromPtr(form.Admin), | ||||||
| 		Visibility:              optional.FromNonDefault(api.VisibilityModes[form.Visibility]), | 		Visibility:              optional.FromNonDefault(api.VisibilityModes[form.Visibility]), | ||||||
| 		AllowGitHook:            optional.FromPtr(form.AllowGitHook), | 		AllowGitHook:            optional.FromPtr(form.AllowGitHook), | ||||||
| 		AllowImportLocal:        optional.FromPtr(form.AllowImportLocal), | 		AllowImportLocal:        optional.FromPtr(form.AllowImportLocal), | ||||||
|   | |||||||
| @@ -432,7 +432,7 @@ func EditUserPost(ctx *context.Context) { | |||||||
| 		Website:                 optional.Some(form.Website), | 		Website:                 optional.Some(form.Website), | ||||||
| 		Location:                optional.Some(form.Location), | 		Location:                optional.Some(form.Location), | ||||||
| 		IsActive:                optional.Some(form.Active), | 		IsActive:                optional.Some(form.Active), | ||||||
| 		IsAdmin:                 optional.Some(form.Admin), | 		IsAdmin:                 user_service.UpdateOptionFieldFromValue(form.Admin), | ||||||
| 		AllowGitHook:            optional.Some(form.AllowGitHook), | 		AllowGitHook:            optional.Some(form.AllowGitHook), | ||||||
| 		AllowImportLocal:        optional.Some(form.AllowImportLocal), | 		AllowImportLocal:        optional.Some(form.AllowImportLocal), | ||||||
| 		MaxRepoCreation:         optional.Some(form.MaxRepoCreation), | 		MaxRepoCreation:         optional.Some(form.MaxRepoCreation), | ||||||
|   | |||||||
| @@ -613,7 +613,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth. | |||||||
| 	if user_model.CountUsers(ctx, nil) == 1 { | 	if user_model.CountUsers(ctx, nil) == 1 { | ||||||
| 		opts := &user_service.UpdateOptions{ | 		opts := &user_service.UpdateOptions{ | ||||||
| 			IsActive:     optional.Some(true), | 			IsActive:     optional.Some(true), | ||||||
| 			IsAdmin:      optional.Some(true), | 			IsAdmin:      user_service.UpdateOptionFieldFromValue(true), | ||||||
| 			SetLastLogin: true, | 			SetLastLogin: true, | ||||||
| 		} | 		} | ||||||
| 		if err := user_service.UpdateUser(ctx, u, opts); err != nil { | 		if err := user_service.UpdateUser(ctx, u, opts); err != nil { | ||||||
|   | |||||||
| @@ -193,8 +193,8 @@ func SignInOAuthCallback(ctx *context.Context) { | |||||||
| 			source := authSource.Cfg.(*oauth2.Source) | 			source := authSource.Cfg.(*oauth2.Source) | ||||||
|  |  | ||||||
| 			isAdmin, isRestricted := getUserAdminAndRestrictedFromGroupClaims(source, &gothUser) | 			isAdmin, isRestricted := getUserAdminAndRestrictedFromGroupClaims(source, &gothUser) | ||||||
| 			u.IsAdmin = isAdmin.ValueOrDefault(false) | 			u.IsAdmin = isAdmin.ValueOrDefault(user_service.UpdateOptionField[bool]{FieldValue: false}).FieldValue | ||||||
| 			u.IsRestricted = isRestricted.ValueOrDefault(false) | 			u.IsRestricted = isRestricted.ValueOrDefault(setting.Service.DefaultUserIsRestricted) | ||||||
|  |  | ||||||
| 			if !createAndHandleCreatedUser(ctx, templates.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) { | 			if !createAndHandleCreatedUser(ctx, templates.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) { | ||||||
| 				// error already handled | 				// error already handled | ||||||
| @@ -258,11 +258,11 @@ func getClaimedGroups(source *oauth2.Source, gothUser *goth.User) container.Set[ | |||||||
| 	return claimValueToStringSet(groupClaims) | 	return claimValueToStringSet(groupClaims) | ||||||
| } | } | ||||||
|  |  | ||||||
| func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *goth.User) (isAdmin, isRestricted optional.Option[bool]) { | func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *goth.User) (isAdmin optional.Option[user_service.UpdateOptionField[bool]], isRestricted optional.Option[bool]) { | ||||||
| 	groups := getClaimedGroups(source, gothUser) | 	groups := getClaimedGroups(source, gothUser) | ||||||
|  |  | ||||||
| 	if source.AdminGroup != "" { | 	if source.AdminGroup != "" { | ||||||
| 		isAdmin = optional.Some(groups.Contains(source.AdminGroup)) | 		isAdmin = user_service.UpdateOptionFieldFromSync(groups.Contains(source.AdminGroup)) | ||||||
| 	} | 	} | ||||||
| 	if source.RestrictedGroup != "" { | 	if source.RestrictedGroup != "" { | ||||||
| 		isRestricted = optional.Some(groups.Contains(source.RestrictedGroup)) | 		isRestricted = optional.Some(groups.Contains(source.RestrictedGroup)) | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u | |||||||
| 			opts := &user_service.UpdateOptions{} | 			opts := &user_service.UpdateOptions{} | ||||||
| 			if source.AdminFilter != "" && user.IsAdmin != sr.IsAdmin { | 			if source.AdminFilter != "" && user.IsAdmin != sr.IsAdmin { | ||||||
| 				// Change existing admin flag only if AdminFilter option is set | 				// Change existing admin flag only if AdminFilter option is set | ||||||
| 				opts.IsAdmin = optional.Some(sr.IsAdmin) | 				opts.IsAdmin = user_service.UpdateOptionFieldFromSync(sr.IsAdmin) | ||||||
| 			} | 			} | ||||||
| 			if !sr.IsAdmin && source.RestrictedFilter != "" && user.IsRestricted != sr.IsRestricted { | 			if !sr.IsAdmin && source.RestrictedFilter != "" && user.IsRestricted != sr.IsRestricted { | ||||||
| 				// Change existing restricted flag only if RestrictedFilter option is set | 				// Change existing restricted flag only if RestrictedFilter option is set | ||||||
|   | |||||||
| @@ -162,7 +162,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { | |||||||
| 					IsActive: optional.Some(true), | 					IsActive: optional.Some(true), | ||||||
| 				} | 				} | ||||||
| 				if source.AdminFilter != "" { | 				if source.AdminFilter != "" { | ||||||
| 					opts.IsAdmin = optional.Some(su.IsAdmin) | 					opts.IsAdmin = user_service.UpdateOptionFieldFromSync(su.IsAdmin) | ||||||
| 				} | 				} | ||||||
| 				// Change existing restricted flag only if RestrictedFilter option is set | 				// Change existing restricted flag only if RestrictedFilter option is set | ||||||
| 				if !su.IsAdmin && source.RestrictedFilter != "" { | 				if !su.IsAdmin && source.RestrictedFilter != "" { | ||||||
|   | |||||||
| @@ -15,6 +15,26 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/structs" | 	"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 { | type UpdateOptions struct { | ||||||
| 	KeepEmailPrivate             optional.Option[bool] | 	KeepEmailPrivate             optional.Option[bool] | ||||||
| 	FullName                     optional.Option[string] | 	FullName                     optional.Option[string] | ||||||
| @@ -32,7 +52,7 @@ type UpdateOptions struct { | |||||||
| 	DiffViewStyle                optional.Option[string] | 	DiffViewStyle                optional.Option[string] | ||||||
| 	AllowCreateOrganization      optional.Option[bool] | 	AllowCreateOrganization      optional.Option[bool] | ||||||
| 	IsActive                     optional.Option[bool] | 	IsActive                     optional.Option[bool] | ||||||
| 	IsAdmin                      optional.Option[bool] | 	IsAdmin                      optional.Option[UpdateOptionField[bool]] | ||||||
| 	EmailNotificationsPreference optional.Option[string] | 	EmailNotificationsPreference optional.Option[string] | ||||||
| 	SetLastLogin                 bool | 	SetLastLogin                 bool | ||||||
| 	RepoAdminChangeTeamAccess    optional.Option[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") | 		cols = append(cols, "is_restricted") | ||||||
| 	} | 	} | ||||||
| 	if opts.IsAdmin.Has() { | 	if opts.IsAdmin.Has() { | ||||||
| 		if !opts.IsAdmin.Value() && user_model.IsLastAdminUser(ctx, u) { | 		if opts.IsAdmin.Value().FieldValue /* true */ { | ||||||
| 			return user_model.ErrDeleteLastAdminUser{UID: u.ID} | 			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() { | 	if opts.Visibility.Has() { | ||||||
|   | |||||||
| @@ -22,7 +22,11 @@ func TestUpdateUser(t *testing.T) { | |||||||
| 	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) | 	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) | ||||||
|  |  | ||||||
| 	assert.Error(t, UpdateUser(db.DefaultContext, admin, &UpdateOptions{ | 	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}) | 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) | ||||||
| @@ -38,7 +42,7 @@ func TestUpdateUser(t *testing.T) { | |||||||
| 		MaxRepoCreation:              optional.Some(10), | 		MaxRepoCreation:              optional.Some(10), | ||||||
| 		IsRestricted:                 optional.Some(true), | 		IsRestricted:                 optional.Some(true), | ||||||
| 		IsActive:                     optional.Some(false), | 		IsActive:                     optional.Some(false), | ||||||
| 		IsAdmin:                      optional.Some(true), | 		IsAdmin:                      UpdateOptionFieldFromValue(true), | ||||||
| 		Visibility:                   optional.Some(structs.VisibleTypePrivate), | 		Visibility:                   optional.Some(structs.VisibleTypePrivate), | ||||||
| 		KeepActivityPrivate:          optional.Some(true), | 		KeepActivityPrivate:          optional.Some(true), | ||||||
| 		Language:                     optional.Some("lang"), | 		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.MaxRepoCreation.Value(), user.MaxRepoCreation) | ||||||
| 	assert.Equal(t, opts.IsRestricted.Value(), user.IsRestricted) | 	assert.Equal(t, opts.IsRestricted.Value(), user.IsRestricted) | ||||||
| 	assert.Equal(t, opts.IsActive.Value(), user.IsActive) | 	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.Visibility.Value(), user.Visibility) | ||||||
| 	assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate) | 	assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate) | ||||||
| 	assert.Equal(t, opts.Language.Value(), user.Language) | 	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.MaxRepoCreation.Value(), user.MaxRepoCreation) | ||||||
| 	assert.Equal(t, opts.IsRestricted.Value(), user.IsRestricted) | 	assert.Equal(t, opts.IsRestricted.Value(), user.IsRestricted) | ||||||
| 	assert.Equal(t, opts.IsActive.Value(), user.IsActive) | 	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.Visibility.Value(), user.Visibility) | ||||||
| 	assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate) | 	assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate) | ||||||
| 	assert.Equal(t, opts.Language.Value(), user.Language) | 	assert.Equal(t, opts.Language.Value(), user.Language) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user