diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 131fb3401eec2..4abd3128d9514 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2611,3 +2611,7 @@ ROUTER = console ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; storage type ;STORAGE_TYPE = local + +;[user] +; Disabled modules from user settings, could be password, deletion, security, applications, gpg_keys, organizations +;SETTING_DISABLED_MODULES = diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index aca591787e91b..a9aba13a43b30 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -1386,6 +1386,16 @@ steps: although Github don't support this form. +## User (`user`) + +- `USER_SETTING_DISABLED_MODULES`:**** Disabled modules from user settings, could be a copmosite of `password`, `deletion`, `security`, `applications`, `gpg keys`, `organizations` with a comma. + - `password`: User cannot change his password from the website. + - `deletion`: User cannot remove himself from the website. + - `security`: User cannot update his security settings from the website. + - `applications`: User cannot create application himself. + - `gpg_keys`: User cannot manage gpg keys himself. + - `organizations`: User cannot manage his organizations himself. + ## Other (`other`) - `SHOW_FOOTER_BRANDING`: **false**: Show Gitea branding in the footer. diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 4d7a7caab8de3..e38795aa48632 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -278,6 +278,7 @@ func loadCommonSettingsFrom(cfg ConfigProvider) { loadMirrorFrom(cfg) loadMarkupFrom(cfg) loadOtherFrom(cfg) + loadUserFrom(cfg) } func loadRunModeFrom(rootCfg ConfigProvider) { diff --git a/modules/setting/user.go b/modules/setting/user.go new file mode 100644 index 0000000000000..ccb7a2b070739 --- /dev/null +++ b/modules/setting/user.go @@ -0,0 +1,27 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "strings" + + "code.gitea.io/gitea/modules/container" +) + +// userSetting represents user settings +type userSetting struct { + content container.Set[string] +} + +func (s *userSetting) Enabled(module string) bool { + return !s.content.Contains(strings.ToLower(module)) +} + +var User userSetting + +func loadUserFrom(rootCfg ConfigProvider) { + sec := rootCfg.Section("user") + values := sec.Key("SETTING_DISABLED_MODULES").Strings(",") + User.content = container.SetOf(values...) +} diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index 6debf95bbce06..6f092e32110a7 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -5,6 +5,7 @@ package setting import ( + "fmt" "net/http" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -19,6 +20,13 @@ import ( const ( tplSettingsKeys base.TplName = "user/settings/keys" + + UserPasswordKey = "password" + UserGPGKeysKey = "gpg_keys" + UserDeletionKey = "deletion" + UserSecurityKey = "security" + UserApplicationKey = "applications" + UserOrganizations = "organizations" ) // Keys render user's SSH/GPG public keys page @@ -77,6 +85,11 @@ func KeysPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "gpg": + if !setting.User.Enabled(UserGPGKeysKey) { + ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting are not allowed")) + return + } + token := asymkey_model.VerificationToken(ctx.Doer, 1) lastToken := asymkey_model.VerificationToken(ctx.Doer, 0) @@ -224,6 +237,10 @@ func KeysPost(ctx *context.Context) { func DeleteKey(ctx *context.Context) { switch ctx.FormString("type") { case "gpg": + if !setting.User.Enabled(UserGPGKeysKey) { + ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting are not allowed")) + return + } if err := asymkey_model.DeleteGPGKey(ctx.Doer, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteGPGKey: " + err.Error()) } else { diff --git a/routers/web/web.go b/routers/web/web.go index 292268dc8055a..f782af59978af 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -344,6 +344,14 @@ func RegisterRoutes(m *web.Route) { m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost) } + userSettingModuleEnabled := func(module string) func(ctx *context.Context) { + return func(ctx *context.Context) { + if !setting.User.Enabled(module) { + ctx.Error(http.StatusNotFound) + } + } + } + // FIXME: not all routes need go through same middleware. // Especially some AJAX requests, we can reduce middleware number to improve performance. // Routers. @@ -434,15 +442,15 @@ func RegisterRoutes(m *web.Route) { m.Group("/user/settings", func() { m.Get("", user_setting.Profile) m.Post("", web.Bind(forms.UpdateProfileForm{}), user_setting.ProfilePost) - m.Get("/change_password", auth.MustChangePassword) - m.Post("/change_password", web.Bind(forms.MustChangePasswordForm{}), auth.MustChangePasswordPost) + m.Get("/change_password", userSettingModuleEnabled(user_setting.UserPasswordKey), auth.MustChangePassword) + m.Post("/change_password", userSettingModuleEnabled(user_setting.UserPasswordKey), web.Bind(forms.MustChangePasswordForm{}), auth.MustChangePasswordPost) m.Post("/avatar", web.Bind(forms.AvatarForm{}), user_setting.AvatarPost) m.Post("/avatar/delete", user_setting.DeleteAvatar) m.Group("/account", func() { m.Combo("").Get(user_setting.Account).Post(web.Bind(forms.ChangePasswordForm{}), user_setting.AccountPost) m.Post("/email", web.Bind(forms.AddEmailForm{}), user_setting.EmailPost) m.Post("/email/delete", user_setting.DeleteEmail) - m.Post("/delete", user_setting.DeleteAccount) + m.Post("/delete", userSettingModuleEnabled(user_setting.UserDeletionKey), user_setting.DeleteAccount) }) m.Group("/appearance", func() { m.Get("", user_setting.Appearance) @@ -469,7 +477,7 @@ func RegisterRoutes(m *web.Route) { m.Post("/toggle_visibility", security.ToggleOpenIDVisibility) }, openIDSignInEnabled) m.Post("/account_link", linkAccountEnabled, security.DeleteAccountLink) - }) + }, userSettingModuleEnabled(user_setting.UserSecurityKey)) m.Group("/applications/oauth2", func() { m.Get("/{id}", user_setting.OAuth2ApplicationShow) m.Post("/{id}", web.Bind(forms.EditOAuth2ApplicationForm{}), user_setting.OAuthApplicationsEdit) @@ -477,10 +485,10 @@ func RegisterRoutes(m *web.Route) { m.Post("", web.Bind(forms.EditOAuth2ApplicationForm{}), user_setting.OAuthApplicationsPost) m.Post("/{id}/delete", user_setting.DeleteOAuth2Application) m.Post("/{id}/revoke/{grantId}", user_setting.RevokeOAuth2Grant) - }) - m.Combo("/applications").Get(user_setting.Applications). - Post(web.Bind(forms.NewAccessTokenForm{}), user_setting.ApplicationsPost) - m.Post("/applications/delete", user_setting.DeleteApplication) + }, userSettingModuleEnabled(user_setting.UserApplicationKey)) + m.Combo("/applications").Get(userSettingModuleEnabled(user_setting.UserApplicationKey), user_setting.Applications). + Post(userSettingModuleEnabled(user_setting.UserApplicationKey), web.Bind(forms.NewAccessTokenForm{}), user_setting.ApplicationsPost) + m.Post("/applications/delete", userSettingModuleEnabled(user_setting.UserApplicationKey), user_setting.DeleteApplication) m.Combo("/keys").Get(user_setting.Keys). Post(web.Bind(forms.AddKeyForm{}), user_setting.KeysPost) m.Post("/keys/delete", user_setting.DeleteKey) @@ -508,7 +516,7 @@ func RegisterRoutes(m *web.Route) { m.Post("", web.Bind(forms.AddSecretForm{}), user_setting.SecretsPost) m.Post("/delete", user_setting.SecretsDelete) }) - m.Get("/organization", user_setting.Organization) + m.Get("/organization", userSettingModuleEnabled(user_setting.UserOrganizations), user_setting.Organization) m.Get("/repos", user_setting.Repos) m.Post("/repos/unadopted", user_setting.AdoptOrDeleteRepository) @@ -528,6 +536,7 @@ func RegisterRoutes(m *web.Route) { ctx.Data["PageIsUserSettings"] = true ctx.Data["AllThemes"] = setting.UI.Themes ctx.Data["EnablePackages"] = setting.Packages.Enabled + ctx.Data["UserModules"] = &setting.User }) m.Group("/user", func() { diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index 24b4e18fbff2e..4ac47dfacdfbe 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -142,7 +142,7 @@ {{svg "octicon-repo-push"}} {{.locale.Tr "new_migrate"}} {{end}} - {{if .SignedUser.CanCreateOrganization}} + {{if and (.SignedUser.CanCreateOrganization) ($.UserModules.Enabled "organizations")}} {{svg "octicon-organization"}} {{.locale.Tr "new_org"}} diff --git a/templates/org/member/members.tmpl b/templates/org/member/members.tmpl index 0eae60fbfc490..d95b4aa8cb6fe 100644 --- a/templates/org/member/members.tmpl +++ b/templates/org/member/members.tmpl @@ -57,20 +57,22 @@ {{end}}