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
Show file tree
Hide file tree
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
Expand Up @@ -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
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions modules/setting/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ func loadCommonSettingsFrom(cfg ConfigProvider) {
loadMirrorFrom(cfg)
loadMarkupFrom(cfg)
loadOtherFrom(cfg)
loadUserFrom(cfg)
}

func loadRunModeFrom(rootCfg ConfigProvider) {
Expand Down
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
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 @@ -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 {
Expand Down
27 changes: 18 additions & 9 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
lunny marked this conversation as resolved.
Show resolved Hide resolved
m.Get("/repos", user_setting.Repos)
m.Post("/repos/unadopted", user_setting.AdoptOrDeleteRepository)

Expand All @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion templates/base/head_navbar.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
30 changes: 16 additions & 14 deletions templates/org/member/members.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
26 changes: 14 additions & 12 deletions templates/org/team/sidebar.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
24 changes: 13 additions & 11 deletions templates/org/team/teams.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
90 changes: 43 additions & 47 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,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">
Expand Down
Loading