Skip to content

Allow disable part user settings #20549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2481,3 +2481,7 @@ ROUTER = console
;PROXY_URL =
;; Comma separated list of host names requiring proxy. Glob patterns (*) are accepted; use ** to match all hosts.
;PROXY_HOSTS =

;[user]
; Disabled modules from user settings, could be password, deletion, security, applications, gpg keys, organizations
;SETTING_DISABLED_MODULES =
4 changes: 4 additions & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,10 @@ PROXY_URL = socks://127.0.0.1:1080
PROXY_HOSTS = *.github.com
```

## 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `USER_SETTING_DISABLED_MODULES`:**** Disabled modules from user settings, could be a copmosite of `password`, `deletion`, `security`, `applications`, `gpg keys`, `organizations` with a comma.
- `USER_SETTING_DISABLED_MODULES`: **\<empty\>**: Disabled modules from user settings, could be a copmosite of `password`, `deletion`, `security`, `applications`, `gpg keys`, `organizations` with a comma.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind the spelling of copmosite


## Other (`other`)

- `SHOW_FOOTER_BRANDING`: **false**: Show Gitea branding in the footer.
Expand Down
2 changes: 2 additions & 0 deletions modules/setting/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,8 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
for _, emoji := range UI.CustomEmojis {
UI.CustomEmojisMap[emoji] = ":" + emoji + ":"
}

newUserSetting()
}

func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) {
Expand Down
33 changes: 33 additions & 0 deletions modules/setting/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package setting

import (
"strings"

"code.gitea.io/gitea/modules/log"
)

// userSetting represents user settings
type userSetting struct {
SettingDisabledModules []string
}

func (s *userSetting) Enabled(module string) bool {
for _, m := range s.SettingDisabledModules {
if strings.EqualFold(m, module) {
return false
}
}
return true
}

var User userSetting

func newUserSetting() {
sec := Cfg.Section("user")
if err := sec.MapTo(&User); err != nil {
log.Fatal("user setting mapping failed: %v", err)
}
}
17 changes: 17 additions & 0 deletions routers/web/user/setting/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package setting

import (
"fmt"
"net/http"

asymkey_model "code.gitea.io/gitea/models/asymkey"
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -216,6 +229,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 {
Expand Down
27 changes: 18 additions & 9 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,14 @@ func RegisterRoutes(m *web.Route) {
}
}

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.
Expand Down Expand Up @@ -404,15 +412,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)
Expand All @@ -439,18 +447,18 @@ 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)
m.Post("/{id}/regenerate_secret", user_setting.OAuthApplicationsRegenerateSecret)
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)
Expand All @@ -468,13 +476,14 @@ func RegisterRoutes(m *web.Route) {
})
})
}, packagesEnabled)
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)
}, reqSignIn, func(ctx *context.Context) {
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() {
Expand Down
119 changes: 65 additions & 54 deletions templates/user/settings/account.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,42 @@
{{template "user/settings/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}
<h4 class="ui top attached header">
{{.locale.Tr "settings.password"}}
</h4>
<div class="ui attached segment">
{{if or (.SignedUser.IsLocal) (.SignedUser.IsOAuth2)}}
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/user/settings/account" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
{{if .SignedUser.IsPasswordSet}}
<div class="required field {{if .Err_OldPassword}}error{{end}}">
<label for="old_password">{{.locale.Tr "settings.old_password"}}</label>
<input id="old_password" name="old_password" type="password" autocomplete="current-password" autofocus required>
</div>
{{end}}
<div class="required field {{if .Err_Password}}error{{end}}">
<label for="password">{{.locale.Tr "settings.new_password"}}</label>
<input id="password" name="password" type="password" autocomplete="new-password" required>
</div>
<div class="required field {{if .Err_Password}}error{{end}}">
<label for="retype">{{.locale.Tr "settings.retype_new_password"}}</label>
<input id="retype" name="retype" type="password" autocomplete="new-password" required>
</div>
{{if $.UserModules.Enabled "password"}}
<h4 class="ui top attached header">
{{.locale.Tr "settings.password"}}
</h4>
<div class="ui attached segment">
{{if or (.SignedUser.IsLocal) (.SignedUser.IsOAuth2)}}
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/user/settings/account" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
{{if .SignedUser.IsPasswordSet}}
<div class="required field {{if .Err_OldPassword}}error{{end}}">
<label for="old_password">{{.locale.Tr "settings.old_password"}}</label>
<input id="old_password" name="old_password" type="password" autocomplete="current-password" autofocus required>
</div>
{{end}}
<div class="required field {{if .Err_Password}}error{{end}}">
<label for="password">{{.locale.Tr "settings.new_password"}}</label>
<input id="password" name="password" type="password" autocomplete="new-password" required>
</div>
<div class="required field {{if .Err_Password}}error{{end}}">
<label for="retype">{{.locale.Tr "settings.retype_new_password"}}</label>
<input id="retype" name="retype" type="password" autocomplete="new-password" required>
</div>

<div class="field">
<button class="ui green button">{{$.locale.Tr "settings.change_password"}}</button>
<a href="{{AppSubUrl}}/user/forgot_password?email={{.Email}}">{{.locale.Tr "auth.forgot_password"}}</a>
<div class="field">
<button class="ui green button">{{$.locale.Tr "settings.change_password"}}</button>
<a href="{{AppSubUrl}}/user/forgot_password?email={{.Email}}">{{.locale.Tr "auth.forgot_password"}}</a>
</div>
</form>
{{else}}
<div class="ui info message">
<p class="text left">{{$.locale.Tr "settings.password_change_disabled"}}</p>
</div>
</form>
{{else}}
<div class="ui info message">
<p class="text left">{{$.locale.Tr "settings.password_change_disabled"}}</p>
{{end}}
</div>
{{end}}
</div>
{{end}}

<h4 class="ui top attached header">
{{.locale.Tr "settings.manage_emails"}}
Expand Down Expand Up @@ -133,31 +135,40 @@
</form>
</div>

<h4 class="ui top attached error header">
{{.locale.Tr "settings.delete_account"}}
</h4>
<div class="ui attached error segment">
<div class="ui red message">
<p class="text left">{{svg "octicon-alert"}} {{.locale.Tr "settings.delete_prompt" | Str2html}}</p>
{{if .UserDeleteWithComments}}
<p class="text left" style="font-weight: bold;">{{.locale.Tr "settings.delete_with_all_comments" .UserDeleteWithCommentsMaxTime | Str2html}}</p>
{{end}}
</div>
<form class="ui form ignore-dirty" id="delete-form" action="{{AppSubUrl}}/user/settings/account/delete" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<div class="required field {{if .Err_Password}}error{{end}}">
<label for="password-confirmation">{{.locale.Tr "password"}}</label>
<input id="password-confirmation" name="password" type="password" autocomplete="off" required>
{{if $.UserModules.Enabled "deletion"}}
<h4 class="ui top attached error header">
{{.locale.Tr "settings.delete_account"}}
</h4>
<div class="ui attached error segment">
<div class="ui red message">
<p class="text left">{{svg "octicon-alert"}} {{.locale.Tr "settings.delete_prompt" | Str2html}}</p>
{{if .UserDeleteWithComments}}
<p class="text left" style="font-weight: bold;">{{.locale.Tr "settings.delete_with_all_comments" .UserDeleteWithCommentsMaxTime | Str2html}}</p>
{{end}}
</div>
<div class="field">
<div class="ui red button delete-button" data-modal-id="delete-account" data-type="form" data-form="#delete-form">
{{.locale.Tr "settings.confirm_delete_account"}}
</div>
<a href="{{AppSubUrl}}/user/forgot_password?email={{.Email}}">{{.locale.Tr "auth.forgot_password"}}</a>
<form class="ui form ignore-dirty" id="delete-form" action="{{AppSubUrl}}/user/settings/account/delete" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<div class="required field {{if .Err_Password}}error{{end}}">
<label for="password-confirmation">{{.locale.Tr "password"}}</label>
<input id="password-confirmation" name="password" type="password" autocomplete="off" required>
</div>
</form>
</div>
<form class="ui form ignore-dirty" id="delete-form" action="{{AppSubUrl}}/user/settings/account/delete" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}}
<div class="required field {{if .Err_Password}}error{{end}}">
<label for="password-confirmation">{{.locale.Tr "password"}}</label>
<input id="password-confirmation" name="password" type="password" autocomplete="off" required>
</div>
<div class="field">
<div class="ui red button delete-button" data-modal-id="delete-account" data-type="form" data-form="#delete-form">
{{.locale.Tr "settings.confirm_delete_account"}}
</div>
<a href="{{AppSubUrl}}/user/forgot_password?email={{.Email}}">{{.locale.Tr "auth.forgot_password"}}</a>
</div>
</form>
</div>
{{end}}
</div>
</div>

Expand Down
4 changes: 3 additions & 1 deletion templates/user/settings/keys.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
{{template "base/alert" .}}
{{template "user/settings/keys_ssh" .}}
{{template "user/settings/keys_principal" .}}
{{template "user/settings/keys_gpg" .}}
{{if $.UserModules.Enabled "gpg keys"}}
{{template "user/settings/keys_gpg" .}}
{{end}}
</div>
</div>

Expand Down
24 changes: 15 additions & 9 deletions templates/user/settings/navbar.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
<a class="{{if .PageIsSettingsAppearance}}active {{end}}item" href="{{AppSubUrl}}/user/settings/appearance">
{{.locale.Tr "settings.appearance"}}
</a>
<a class="{{if .PageIsSettingsSecurity}}active {{end}}item" href="{{AppSubUrl}}/user/settings/security">
{{.locale.Tr "settings.security"}}
</a>
<a class="{{if .PageIsSettingsApplications}}active {{end}}item" href="{{AppSubUrl}}/user/settings/applications">
{{.locale.Tr "settings.applications"}}
</a>
{{if $.UserModules.Enabled "security"}}
<a class="{{if .PageIsSettingsSecurity}}active {{end}}item" href="{{AppSubUrl}}/user/settings/security">
{{.locale.Tr "settings.security"}}
</a>
{{end}}
{{if $.UserModules.Enabled "applications"}}
<a class="{{if .PageIsSettingsApplications}}active {{end}}item" href="{{AppSubUrl}}/user/settings/applications">
{{.locale.Tr "settings.applications"}}
</a>
{{end}}
<a class="{{if .PageIsSettingsKeys}}active {{end}}item" href="{{AppSubUrl}}/user/settings/keys">
{{.locale.Tr "settings.ssh_gpg_keys"}}
</a>
Expand All @@ -23,9 +27,11 @@
{{.locale.Tr "packages.title"}}
</a>
{{end}}
<a class="{{if .PageIsSettingsOrganization}}active {{end}}item" href="{{AppSubUrl}}/user/settings/organization">
{{.locale.Tr "settings.organization"}}
</a>
{{if $.UserModules.Enabled "organizations"}}
<a class="{{if .PageIsSettingsOrganization}}active {{end}}item" href="{{AppSubUrl}}/user/settings/organization">
{{.locale.Tr "settings.organization"}}
</a>
{{end}}
<a class="{{if .PageIsSettingsRepos}}active {{end}}item" href="{{AppSubUrl}}/user/settings/repos">
{{.locale.Tr "settings.repos"}}
</a>
Expand Down