Skip to content
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

Allow disable part user settings #20549

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Changes from all 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
@@ -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 =
10 changes: 10 additions & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
@@ -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.
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

- `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.
Comment on lines +1392 to +1397
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
- `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.
- `password`: Users cannot change their password from the website.
- `deletion`: Users cannot remove themselves from the website.
- `security`: Users cannot update their security settings from the website.
- `applications`: Users cannot create application themselves.
- `gpg_keys`: Users cannot manage gpg keys themselves.
- `organizations`: Users cannot manage their organizations themselves.


## Other (`other`)

- `SHOW_FOOTER_BRANDING`: **false**: Show Gitea branding in the footer.
1 change: 1 addition & 0 deletions modules/setting/setting.go
Original file line number Diff line number Diff line change
@@ -278,6 +278,7 @@ func loadCommonSettingsFrom(cfg ConfigProvider) {
loadMirrorFrom(cfg)
loadMarkupFrom(cfg)
loadOtherFrom(cfg)
loadUserFrom(cfg)
}

func loadRunModeFrom(rootCfg ConfigProvider) {
27 changes: 27 additions & 0 deletions modules/setting/user.go
Original file line number Diff line number Diff line change
@@ -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...)
}
17 changes: 17 additions & 0 deletions routers/web/user/setting/keys.go
Original file line number Diff line number Diff line change
@@ -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 {
27 changes: 18 additions & 9 deletions routers/web/web.go
Original file line number Diff line number Diff line change
@@ -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,18 +477,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)
@@ -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() {
2 changes: 1 addition & 1 deletion templates/base/head_navbar.tmpl
Original file line number Diff line number Diff line change
@@ -142,7 +142,7 @@
<span class="fitted">{{svg "octicon-repo-push"}}</span> {{.locale.Tr "new_migrate"}}
</a>
{{end}}
{{if .SignedUser.CanCreateOrganization}}
{{if and (.SignedUser.CanCreateOrganization) ($.UserModules.Enabled "organizations")}}
<a class="item" href="{{AppSubUrl}}/org/create">
<span class="fitted">{{svg "octicon-organization"}}</span> {{.locale.Tr "new_org"}}
</a>
30 changes: 16 additions & 14 deletions templates/org/member/members.tmpl
Original file line number Diff line number Diff line change
@@ -57,20 +57,22 @@
{{end}}
<div class="ui three wide column">
<div class="text right">
{{if eq $.SignedUser.ID .ID}}
<form>
<button class="ui red small button delete-button" data-modal-id="leave-organization"
data-url="{{$.OrgLink}}/members/action/leave" data-datauid="{{.ID}}"
data-name="{{.DisplayName}}"
data-data-organization-name="{{$.Org.DisplayName}}">{{$.locale.Tr "org.members.leave"}}</button>
</form>
{{else if $.IsOrganizationOwner}}
<form>
<button class="ui red small button delete-button" data-modal-id="remove-organization-member"
data-url="{{$.OrgLink}}/members/action/remove" data-datauid="{{.ID}}"
data-name="{{.DisplayName}}"
data-data-organization-name="{{$.Org.DisplayName}}">{{$.locale.Tr "org.members.remove"}}</button>
</form>
{{if ($.UserModules.Enabled "organizations")}}
{{if eq $.SignedUser.ID .ID}}
<form>
<button class="ui red small button delete-button" data-modal-id="leave-organization"
data-url="{{$.OrgLink}}/members/action/leave" data-datauid="{{.ID}}"
data-name="{{.DisplayName}}"
data-data-organization-name="{{$.Org.DisplayName}}">{{$.locale.Tr "org.members.leave"}}</button>
</form>
{{else if $.IsOrganizationOwner}}
<form>
<button class="ui red small button delete-button" data-modal-id="remove-organization-member"
data-url="{{$.OrgLink}}/members/action/remove" data-datauid="{{.ID}}"
data-name="{{.DisplayName}}"
data-data-organization-name="{{$.Org.DisplayName}}">{{$.locale.Tr "org.members.remove"}}</button>
</form>
{{end}}
{{end}}
</div>
</div>
26 changes: 14 additions & 12 deletions templates/org/team/sidebar.tmpl
Original file line number Diff line number Diff line change
@@ -2,18 +2,20 @@
<h4 class="ui top attached header">
<strong>{{.Team.Name}}</strong>
<div class="ui right">
{{if .Team.IsMember $.SignedUser.ID}}
<form>
<button class="ui red tiny button delete-button" data-modal-id="leave-team-sidebar"
data-url="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/action/leave" data-datauid="{{$.SignedUser.ID}}"
data-name="{{.Team.Name}}">{{$.locale.Tr "org.teams.leave"}}</button>
</form>
{{else if .IsOrganizationOwner}}
<form method="post" action="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/action/join">
{{$.CsrfTokenHtml}}
<input type="hidden" name="page" value="team"/>
<button type="submit" class="ui primary tiny button" name="uid" value="{{$.SignedUser.ID}}">{{$.locale.Tr "org.teams.join"}}</button>
</form>
{{if ($.UserModules.Enabled "organizations")}}
{{if .Team.IsMember $.SignedUser.ID}}
<form>
<button class="ui red tiny button delete-button" data-modal-id="leave-team-sidebar"
data-url="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/action/leave" data-datauid="{{$.SignedUser.ID}}"
data-name="{{.Team.Name}}">{{$.locale.Tr "org.teams.leave"}}</button>
</form>
{{else if .IsOrganizationOwner}}
<form method="post" action="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/action/join">
{{$.CsrfTokenHtml}}
<input type="hidden" name="page" value="team"/>
<button type="submit" class="ui primary tiny button" name="uid" value="{{$.SignedUser.ID}}">{{$.locale.Tr "org.teams.join"}}</button>
</form>
{{end}}
{{end}}
</div>
</h4>
24 changes: 13 additions & 11 deletions templates/org/team/teams.tmpl
Original file line number Diff line number Diff line change
@@ -16,17 +16,19 @@
<div class="ui top attached header">
<a class="text black" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.Name}}</strong></a>
<div class="ui right">
{{if .IsMember $.SignedUser.ID}}
<form>
<button class="ui red tiny button delete-button" data-modal-id="leave-team"
data-url="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/leave" data-datauid="{{$.SignedUser.ID}}"
data-name="{{.Name}}">{{$.locale.Tr "org.teams.leave"}}</button>
</form>
{{else if $.IsOrganizationOwner}}
<form method="post" action="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/join">
{{$.CsrfTokenHtml}}
<button type="submit" class="ui primary small button" name="uid" value="{{$.SignedUser.ID}}">{{$.locale.Tr "org.teams.join"}}</button>
</form>
{{if ($.UserModules.Enabled "organizations")}}
{{if .IsMember $.SignedUser.ID}}
<form>
<button class="ui red tiny button delete-button" data-modal-id="leave-team"
data-url="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/leave" data-datauid="{{$.SignedUser.ID}}"
data-name="{{.Name}}">{{$.locale.Tr "org.teams.leave"}}</button>
</form>
{{else if $.IsOrganizationOwner}}
<form method="post" action="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/join">
{{$.CsrfTokenHtml}}
<button type="submit" class="ui primary small button" name="uid" value="{{$.SignedUser.ID}}">{{$.locale.Tr "org.teams.join"}}</button>
</form>
{{end}}
{{end}}
</div>
</div>
90 changes: 43 additions & 47 deletions templates/user/settings/account.tmpl
Original file line number Diff line number Diff line change
@@ -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"}}
@@ -133,22 +135,16 @@
</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">
<button class="ui red button delete-button" data-modal-id="delete-account" data-type="form" data-form="#delete-form">
Loading