diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 9196180d8..f522b9da2 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1488,15 +1488,19 @@ LEVEL = Info ;; ;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled ;DEFAULT_EMAIL_NOTIFICATIONS = enabled -;; Disabled features for users, could be "deletion", "manage_ssh_keys","manage_gpg_keys" more features can be disabled in future +;; Disabled features for users could be "deletion", "manage_ssh_keys", "manage_gpg_keys", "manage_mfa", "manage_credentials" more features can be disabled in future ;; - deletion: a user cannot delete their own account ;; - manage_ssh_keys: a user cannot configure ssh keys ;; - manage_gpg_keys: a user cannot configure gpg keys +;; - manage_mfa: a user cannot configure mfa devices +;; - manage_credentials: a user cannot configure emails, passwords, or openid ;USER_DISABLED_FEATURES = -;; Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior. +;; Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be "deletion", "manage_ssh_keys", "manage_gpg_keys", "manage_mfa", "manage_credentials". This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior. ;; - deletion: a user cannot delete their own account ;; - manage_ssh_keys: a user cannot configure ssh keys ;; - manage_gpg_keys: a user cannot configure gpg keys +;; - manage_mfa: a user cannot configure mfa devices +;; - manage_credentials: a user cannot configure emails, passwords, or openid ;;EXTERNAL_USER_DISABLE_FEATURES = ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 0c15a866b..b38e53d4a 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -517,14 +517,18 @@ And the following unique queues: - `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled - `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations. -- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys` and more features can be added in future. +- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`, `manage_mfa`, `manage_credentials` and more features can be added in future. - `deletion`: User cannot delete their own account. - `manage_ssh_keys`: User cannot configure ssh keys. - `manage_gpg_keys`: User cannot configure gpg keys. -- `EXTERNAL_USER_DISABLE_FEATURES`: **_empty_**: Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior. + - `manage_mfa`: a User cannot configure mfa devices. + - `manage_credentials`: a user cannot configure emails, passwords, or openid +- `EXTERNAL_USER_DISABLE_FEATURES`: **_empty_**: Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`, `manage_mfa`, `manage_credentials`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior. - `deletion`: User cannot delete their own account. - `manage_ssh_keys`: User cannot configure ssh keys. - `manage_gpg_keys`: User cannot configure gpg keys. + - `manage_mfa`: a User cannot configure mfa devices. + - `manage_credentials`: a user cannot configure emails, passwords, or openid ## Security (`security`) diff --git a/models/user/user.go b/models/user/user.go index 5ac24f097..2a3c1833b 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -1263,12 +1263,14 @@ func GetOrderByName() string { return "name" } -// IsFeatureDisabledWithLoginType checks if a user feature is disabled, taking into account the login type of the +// IsFeatureDisabledWithLoginType checks if a user features are disabled, taking into account the login type of the // user if applicable -func IsFeatureDisabledWithLoginType(user *User, feature string) bool { +func IsFeatureDisabledWithLoginType(user *User, features ...string) bool { // NOTE: in the long run it may be better to check the ExternalLoginUser table rather than user.LoginType - return (user != nil && user.LoginType > auth.Plain && setting.Admin.ExternalUserDisableFeatures.Contains(feature)) || - setting.Admin.UserDisabledFeatures.Contains(feature) + if user != nil && user.LoginType > auth.Plain { + return setting.Admin.ExternalUserDisableFeatures.Contains(features...) + } + return setting.Admin.UserDisabledFeatures.Contains(features...) } // DisabledFeaturesWithLoginType returns the set of user features disabled, taking into account the login type diff --git a/modules/container/set.go b/modules/container/set.go index 15779983f..adb77dcac 100644 --- a/modules/container/set.go +++ b/modules/container/set.go @@ -3,6 +3,8 @@ package container +import "maps" + type Set[T comparable] map[T]struct{} // SetOf creates a set and adds the specified elements to it. @@ -29,11 +31,15 @@ func (s Set[T]) AddMultiple(values ...T) { } } -// Contains determines whether a set contains the specified element. +// Contains determines whether a set contains the specified elements. // Returns true if the set contains the specified element; otherwise, false. -func (s Set[T]) Contains(value T) bool { - _, has := s[value] - return has +func (s Set[T]) Contains(values ...T) bool { + ret := true + for _, value := range values { + _, has := s[value] + ret = ret && has + } + return ret } // Remove removes the specified element. @@ -54,3 +60,12 @@ func (s Set[T]) Values() []T { } return keys } + +// Union constructs a new set that is the union of the provided sets +func (s Set[T]) Union(sets ...Set[T]) Set[T] { + newSet := maps.Clone(s) + for i := range sets { + maps.Copy(newSet, sets[i]) + } + return newSet +} diff --git a/modules/setting/admin.go b/modules/setting/admin.go index 8aebc7615..ca4e9b1d5 100644 --- a/modules/setting/admin.go +++ b/modules/setting/admin.go @@ -20,11 +20,13 @@ func loadAdminFrom(rootCfg ConfigProvider) { Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false) Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled") Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...) - Admin.ExternalUserDisableFeatures = container.SetOf(sec.Key("EXTERNAL_USER_DISABLE_FEATURES").Strings(",")...) + Admin.ExternalUserDisableFeatures = container.SetOf(sec.Key("EXTERNAL_USER_DISABLE_FEATURES").Strings(",")...).Union(Admin.UserDisabledFeatures) } const ( - UserFeatureDeletion = "deletion" - UserFeatureManageSSHKeys = "manage_ssh_keys" - UserFeatureManageGPGKeys = "manage_gpg_keys" + UserFeatureDeletion = "deletion" + UserFeatureManageSSHKeys = "manage_ssh_keys" + UserFeatureManageGPGKeys = "manage_gpg_keys" + UserFeatureManageMFA = "manage_mfa" + UserFeatureManageCredentials = "manage_credentials" ) diff --git a/routers/web/repo/setting/secrets.go b/routers/web/repo/setting/secrets.go index d4d56bfc5..df1172934 100644 --- a/routers/web/repo/setting/secrets.go +++ b/routers/web/repo/setting/secrets.go @@ -7,6 +7,7 @@ import ( "errors" "net/http" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/setting" shared "code.gitea.io/gitea/routers/web/shared/secrets" @@ -74,6 +75,7 @@ func Secrets(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("actions.actions") ctx.Data["PageType"] = "secrets" ctx.Data["PageIsSharedSettingsSecrets"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) sCtx, err := getSecretsCtx(ctx) if err != nil { diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 8ea7548e5..563f39f0c 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -6,6 +6,7 @@ package setting import ( "errors" + "fmt" "net/http" "time" @@ -33,6 +34,11 @@ const ( // Account renders change user's password, user's email and user suicide page func Account(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials, setting.UserFeatureDeletion) && !setting.Service.EnableNotifyMail { + ctx.NotFound("Not Found", fmt.Errorf("account setting are not allowed to be changed")) + return + } + ctx.Data["Title"] = ctx.Tr("settings.account") ctx.Data["PageIsSettingsAccount"] = true ctx.Data["Email"] = ctx.Doer.Email @@ -45,9 +51,16 @@ func Account(ctx *context.Context) { // AccountPost response for change user's password func AccountPost(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { + ctx.NotFound("Not Found", fmt.Errorf("password setting is not allowed to be changed")) + return + } + form := web.GetForm(ctx).(*forms.ChangePasswordForm) ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsAccount"] = true + ctx.Data["Email"] = ctx.Doer.Email + ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail if ctx.HasError() { loadAccountData(ctx) @@ -89,9 +102,16 @@ func AccountPost(ctx *context.Context) { // EmailPost response for change user's email func EmailPost(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { + ctx.NotFound("Not Found", fmt.Errorf("emails are not allowed to be changed")) + return + } + form := web.GetForm(ctx).(*forms.AddEmailForm) ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsAccount"] = true + ctx.Data["Email"] = ctx.Doer.Email + ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail // Make email address primary. if ctx.FormString("_method") == "PRIMARY" { @@ -216,6 +236,10 @@ func EmailPost(ctx *context.Context) { // DeleteEmail response for delete user's email func DeleteEmail(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { + ctx.NotFound("Not Found", fmt.Errorf("emails are not allowed to be changed")) + return + } email, err := user_model.GetEmailAddressByID(ctx, ctx.Doer.ID, ctx.FormInt64("id")) if err != nil || email == nil { ctx.ServerError("GetEmailAddressByID", err) @@ -241,6 +265,8 @@ func DeleteAccount(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsAccount"] = true + ctx.Data["Email"] = ctx.Doer.Email + ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail if _, _, err := auth.UserSignIn(ctx, ctx.Doer.Name, ctx.FormString("password")); err != nil { switch { diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index e3822ca98..356c2ea5d 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -9,6 +9,7 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" @@ -24,6 +25,7 @@ const ( func Applications(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings.applications") ctx.Data["PageIsSettingsApplications"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) loadApplicationsData(ctx) @@ -35,6 +37,7 @@ func ApplicationsPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.NewAccessTokenForm) ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsApplications"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) if ctx.HasError() { loadApplicationsData(ctx) diff --git a/routers/web/user/setting/block.go b/routers/web/user/setting/block.go index 94fc380ce..d419fb321 100644 --- a/routers/web/user/setting/block.go +++ b/routers/web/user/setting/block.go @@ -6,6 +6,7 @@ package setting import ( "net/http" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/setting" shared_user "code.gitea.io/gitea/routers/web/shared/user" @@ -19,6 +20,7 @@ const ( func BlockedUsers(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("user.block.list") ctx.Data["PageIsSettingsBlockedUsers"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) shared_user.BlockedUsers(ctx, ctx.Doer) if ctx.Written() { diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index 9e969e045..c492715fb 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -25,11 +25,17 @@ const ( // Keys render user's SSH/GPG public keys page func Keys(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys, setting.UserFeatureManageGPGKeys) { + ctx.NotFound("Not Found", fmt.Errorf("keys setting is not allowed to be changed")) + return + } + ctx.Data["Title"] = ctx.Tr("settings.ssh_gpg_keys") ctx.Data["PageIsSettingsKeys"] = true ctx.Data["DisableSSH"] = setting.SSH.Disabled ctx.Data["BuiltinSSH"] = setting.SSH.StartBuiltinServer ctx.Data["AllowPrincipals"] = setting.SSH.AuthorizedPrincipalsEnabled + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) loadKeysData(ctx) @@ -44,6 +50,7 @@ func KeysPost(ctx *context.Context) { ctx.Data["DisableSSH"] = setting.SSH.Disabled ctx.Data["BuiltinSSH"] = setting.SSH.StartBuiltinServer ctx.Data["AllowPrincipals"] = setting.SSH.AuthorizedPrincipalsEnabled + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) if ctx.HasError() { loadKeysData(ctx) diff --git a/routers/web/user/setting/packages.go b/routers/web/user/setting/packages.go index 413265949..50521c11c 100644 --- a/routers/web/user/setting/packages.go +++ b/routers/web/user/setting/packages.go @@ -25,6 +25,7 @@ const ( func Packages(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("packages.title") ctx.Data["PageIsSettingsPackages"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) shared.SetPackagesContext(ctx, ctx.Doer) @@ -34,6 +35,7 @@ func Packages(ctx *context.Context) { func PackagesRuleAdd(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("packages.title") ctx.Data["PageIsSettingsPackages"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) shared.SetRuleAddContext(ctx) @@ -43,6 +45,7 @@ func PackagesRuleAdd(ctx *context.Context) { func PackagesRuleEdit(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("packages.title") ctx.Data["PageIsSettingsPackages"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) shared.SetRuleEditContext(ctx, ctx.Doer) @@ -52,6 +55,7 @@ func PackagesRuleEdit(ctx *context.Context) { func PackagesRuleAddPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsPackages"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) shared.PerformRuleAddPost( ctx, @@ -64,6 +68,7 @@ func PackagesRuleAddPost(ctx *context.Context) { func PackagesRuleEditPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("packages.title") ctx.Data["PageIsSettingsPackages"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) shared.PerformRuleEditPost( ctx, @@ -76,6 +81,7 @@ func PackagesRuleEditPost(ctx *context.Context) { func PackagesRulePreview(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("packages.title") ctx.Data["PageIsSettingsPackages"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) shared.SetRulePreviewContext(ctx, ctx.Doer) diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index e5ff8570c..554f6cd6c 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -48,6 +48,8 @@ func Profile(ctx *context.Context) { ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx) + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) + ctx.HTML(http.StatusOK, tplSettingsProfile) } @@ -57,6 +59,7 @@ func ProfilePost(ctx *context.Context) { ctx.Data["PageIsSettingsProfile"] = true ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx) + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) if ctx.HasError() { ctx.HTML(http.StatusOK, tplSettingsProfile) @@ -182,6 +185,7 @@ func DeleteAvatar(ctx *context.Context) { func Organization(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings.organization") ctx.Data["PageIsSettingsOrganization"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) opts := organization.FindOrgOptions{ ListOptions: db.ListOptions{ @@ -213,6 +217,7 @@ func Organization(ctx *context.Context) { func Repos(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings.repos") ctx.Data["PageIsSettingsRepos"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) ctx.Data["allowAdopt"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowAdoptionOfUnadoptedRepositories ctx.Data["allowDelete"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowDeleteOfUnadoptedRepositories @@ -326,6 +331,7 @@ func Appearance(ctx *context.Context) { allThemes = append([]string{setting.UI.DefaultTheme}, allThemes...) // move the default theme to the top } ctx.Data["AllThemes"] = allThemes + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) var hiddenCommentTypes *big.Int val, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes) diff --git a/routers/web/user/setting/security/2fa.go b/routers/web/user/setting/security/2fa.go index cd0910236..74ffe169d 100644 --- a/routers/web/user/setting/security/2fa.go +++ b/routers/web/user/setting/security/2fa.go @@ -13,6 +13,7 @@ import ( "strings" "code.gitea.io/gitea/models/auth" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" @@ -25,6 +26,11 @@ import ( // RegenerateScratchTwoFactor regenerates the user's 2FA scratch code. func RegenerateScratchTwoFactor(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { + ctx.Error(http.StatusNotFound) + return + } + ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsSecurity"] = true @@ -55,6 +61,11 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { // DisableTwoFactor deletes the user's 2FA settings. func DisableTwoFactor(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { + ctx.Error(http.StatusNotFound) + return + } + ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsSecurity"] = true @@ -142,6 +153,11 @@ func twofaGenerateSecretAndQr(ctx *context.Context) bool { // EnrollTwoFactor shows the page where the user can enroll into 2FA. func EnrollTwoFactor(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { + ctx.Error(http.StatusNotFound) + return + } + ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsSecurity"] = true @@ -167,6 +183,11 @@ func EnrollTwoFactor(ctx *context.Context) { // EnrollTwoFactorPost handles enrolling the user into 2FA. func EnrollTwoFactorPost(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { + ctx.Error(http.StatusNotFound) + return + } + form := web.GetForm(ctx).(*forms.TwoFactorAuthForm) ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsSecurity"] = true diff --git a/routers/web/user/setting/security/openid.go b/routers/web/user/setting/security/openid.go index 8f788e173..30eb6f63f 100644 --- a/routers/web/user/setting/security/openid.go +++ b/routers/web/user/setting/security/openid.go @@ -17,6 +17,11 @@ import ( // OpenIDPost response for change user's openid func OpenIDPost(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { + ctx.Error(http.StatusNotFound) + return + } + form := web.GetForm(ctx).(*forms.AddOpenIDForm) ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsSecurity"] = true @@ -105,6 +110,11 @@ func settingsOpenIDVerify(ctx *context.Context) { // DeleteOpenID response for delete user's openid func DeleteOpenID(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { + ctx.Error(http.StatusNotFound) + return + } + if err := user_model.DeleteUserOpenID(ctx, &user_model.UserOpenID{ID: ctx.FormInt64("id"), UID: ctx.Doer.ID}); err != nil { ctx.ServerError("DeleteUserOpenID", err) return @@ -117,6 +127,11 @@ func DeleteOpenID(ctx *context.Context) { // ToggleOpenIDVisibility response for toggle visibility of user's openid func ToggleOpenIDVisibility(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { + ctx.Error(http.StatusNotFound) + return + } + if err := user_model.ToggleUserOpenIDVisibility(ctx, ctx.FormInt64("id")); err != nil { ctx.ServerError("ToggleUserOpenIDVisibility", err) return diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go index 8d6859ab8..b44cb4dd4 100644 --- a/routers/web/user/setting/security/security.go +++ b/routers/web/user/setting/security/security.go @@ -25,6 +25,12 @@ const ( // Security render change user's password page and 2FA func Security(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, + setting.UserFeatureManageMFA, setting.UserFeatureManageCredentials) { + ctx.Error(http.StatusNotFound) + return + } + ctx.Data["Title"] = ctx.Tr("settings.security") ctx.Data["PageIsSettingsSecurity"] = true @@ -40,6 +46,11 @@ func Security(ctx *context.Context) { // DeleteAccountLink delete a single account link func DeleteAccountLink(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { + ctx.Error(http.StatusNotFound) + return + } + id := ctx.FormInt64("id") if id <= 0 { ctx.Flash.Error("Account link id is not given") @@ -145,4 +156,5 @@ func loadSecurityData(ctx *context.Context) { return } ctx.Data["OpenIDs"] = openid + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) } diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go index 1b8d0171f..aafc2f2a6 100644 --- a/routers/web/user/setting/security/webauthn.go +++ b/routers/web/user/setting/security/webauthn.go @@ -10,6 +10,7 @@ import ( "time" "code.gitea.io/gitea/models/auth" + user_model "code.gitea.io/gitea/models/user" wa "code.gitea.io/gitea/modules/auth/webauthn" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -23,6 +24,11 @@ import ( // WebAuthnRegister initializes the webauthn registration procedure func WebAuthnRegister(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { + ctx.Error(http.StatusNotFound) + return + } + form := web.GetForm(ctx).(*forms.WebauthnRegistrationForm) if form.Name == "" { // Set name to the hexadecimal of the current time @@ -64,6 +70,11 @@ func WebAuthnRegister(ctx *context.Context) { // WebauthnRegisterPost receives the response of the security key func WebauthnRegisterPost(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { + ctx.Error(http.StatusNotFound) + return + } + name, ok := ctx.Session.Get("webauthnName").(string) if !ok || name == "" { ctx.ServerError("Get webauthnName", errors.New("no webauthnName")) @@ -113,6 +124,11 @@ func WebauthnRegisterPost(ctx *context.Context) { // WebauthnDelete deletes an security key by id func WebauthnDelete(ctx *context.Context) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) { + ctx.Error(http.StatusNotFound) + return + } + form := web.GetForm(ctx).(*forms.WebauthnDeleteForm) if _, err := auth.DeleteCredential(ctx, form.ID, ctx.Doer.ID); err != nil { ctx.ServerError("GetWebAuthnCredentialByID", err) diff --git a/routers/web/user/setting/webhooks.go b/routers/web/user/setting/webhooks.go index 4423b6278..3732ca27c 100644 --- a/routers/web/user/setting/webhooks.go +++ b/routers/web/user/setting/webhooks.go @@ -7,6 +7,7 @@ import ( "net/http" "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/setting" @@ -24,6 +25,7 @@ func Webhooks(ctx *context.Context) { ctx.Data["BaseLink"] = setting.AppSubURL + "/user/settings/hooks" ctx.Data["BaseLinkNew"] = setting.AppSubURL + "/user/settings/hooks" ctx.Data["Description"] = ctx.Tr("settings.hooks.desc") + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{OwnerID: ctx.Doer.ID}) if err != nil { diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl index 2aaf8535d..a7b246406 100644 --- a/templates/user/settings/account.tmpl +++ b/templates/user/settings/account.tmpl @@ -4,7 +4,7 @@ {{ctx.Locale.Tr "settings.password"}}