forked from mirrors/forgejo
feat: add manage_password to user disable features (#10541)
Forgejo supports disabling features for users with the configuration options `USER_DISABLED_FEATURES` and `EXTERNAL_USER_DISABLE_FEATURES`. Add `manage_password` that prevents users from configuring passwords. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10541 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: hwipl <hwipl@noreply.codeberg.org> Co-committed-by: hwipl <hwipl@noreply.codeberg.org>
This commit is contained in:
parent
66d83702a3
commit
c9f81315d6
6 changed files with 152 additions and 11 deletions
|
|
@ -1561,15 +1561,17 @@ LEVEL = Info
|
|||
;DEFAULT_EMAIL_NOTIFICATIONS = enabled
|
||||
;; Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false
|
||||
;SEND_NOTIFICATION_EMAIL_ON_NEW_USER = false
|
||||
;; 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_password" 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_password: a user cannot configure their password
|
||||
;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_password`. 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_password: a user cannot configure their password
|
||||
;;EXTERNAL_USER_DISABLE_FEATURES =
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ func loadAdminFrom(rootCfg ConfigProvider) {
|
|||
}
|
||||
|
||||
const (
|
||||
UserFeatureDeletion = "deletion"
|
||||
UserFeatureManageSSHKeys = "manage_ssh_keys"
|
||||
UserFeatureManageGPGKeys = "manage_gpg_keys"
|
||||
UserFeatureDeletion = "deletion"
|
||||
UserFeatureManageSSHKeys = "manage_ssh_keys"
|
||||
UserFeatureManageGPGKeys = "manage_gpg_keys"
|
||||
UserFeatureManagePassword = "manage_password"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,11 @@ func Account(ctx *context.Context) {
|
|||
|
||||
// AccountPost response for change user's password
|
||||
func AccountPost(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManagePassword) {
|
||||
ctx.NotFound("Not Found", errors.New("password is not allowed to be changed"))
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*forms.ChangePasswordForm)
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsAccount"] = true
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
{{ctx.Locale.Tr "settings.change_password"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
{{if or (.SignedUser.IsLocal) (.SignedUser.IsOAuth2)}}
|
||||
{{if and (not ($.UserDisabledFeatures.Contains "manage_password")) (or (.SignedUser.IsLocal) (.SignedUser.IsOAuth2))}}
|
||||
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/user/settings/account" method="post">
|
||||
{{template "base/disable_form_autofill"}}
|
||||
{{if .SignedUser.IsPasswordSet}}
|
||||
|
|
|
|||
|
|
@ -708,16 +708,23 @@ func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile
|
|||
func GetHTMLTitle(t testing.TB, session *TestSession, urlStr string) string {
|
||||
t.Helper()
|
||||
|
||||
doc := getHTMLDoc(t, session, urlStr, http.StatusOK)
|
||||
return doc.Find("head title").Text()
|
||||
}
|
||||
|
||||
// getHTMLDoc gets HTMLDoc from url with expected status. Use status
|
||||
// NoExpectedStatus to ignore status.
|
||||
func getHTMLDoc(t testing.TB, session *TestSession, urlStr string, expectedStatus int) *HTMLDoc {
|
||||
t.Helper()
|
||||
|
||||
req := NewRequest(t, "GET", urlStr)
|
||||
var resp *httptest.ResponseRecorder
|
||||
if session == nil {
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
resp = MakeRequest(t, req, expectedStatus)
|
||||
} else {
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
resp = session.MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
return doc.Find("head title").Text()
|
||||
return NewHTMLParser(t, resp.Body)
|
||||
}
|
||||
|
||||
func SortMailerMessages(msgs []*mailer.Message) {
|
||||
|
|
|
|||
126
tests/integration/user_settings_test.go
Normal file
126
tests/integration/user_settings_test.go
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/modules/container"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/test"
|
||||
"forgejo.org/tests"
|
||||
)
|
||||
|
||||
// TestUserSettingsAccount tests the contents of a user's account settings
|
||||
// with(out) disabled user features.
|
||||
func TestUserSettingsAccount(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
t.Run("all features enabled", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
doc := getHTMLDoc(t, loginUser(t, "user2"), "/user/settings/account", http.StatusOK)
|
||||
doc.AssertElement(t, "#password", true)
|
||||
doc.AssertElement(t, "#email", true)
|
||||
doc.AssertElement(t, "#delete-form", true)
|
||||
})
|
||||
|
||||
t.Run("password disabled", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
disabled := container.SetOf(setting.UserFeatureManagePassword)
|
||||
defer test.MockVariableValue(&setting.Admin.UserDisabledFeatures, disabled)()
|
||||
defer test.MockVariableValue(&setting.Admin.ExternalUserDisableFeatures, disabled)()
|
||||
|
||||
doc := getHTMLDoc(t, loginUser(t, "user2"), "/user/settings/account", http.StatusOK)
|
||||
doc.AssertElement(t, "#password", false)
|
||||
doc.AssertElement(t, "#email", true)
|
||||
doc.AssertElement(t, "#delete-form", true)
|
||||
})
|
||||
|
||||
t.Run("deletion disabled", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
disabled := container.SetOf(setting.UserFeatureDeletion)
|
||||
defer test.MockVariableValue(&setting.Admin.UserDisabledFeatures, disabled)()
|
||||
defer test.MockVariableValue(&setting.Admin.ExternalUserDisableFeatures, disabled)()
|
||||
|
||||
doc := getHTMLDoc(t, loginUser(t, "user2"), "/user/settings/account", http.StatusOK)
|
||||
doc.AssertElement(t, "#password", true)
|
||||
doc.AssertElement(t, "#email", true)
|
||||
doc.AssertElement(t, "#delete-form", false)
|
||||
})
|
||||
|
||||
t.Run("deletion, password disabled", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
disabled := container.SetOf(
|
||||
setting.UserFeatureDeletion,
|
||||
setting.UserFeatureManagePassword,
|
||||
)
|
||||
defer test.MockVariableValue(&setting.Admin.UserDisabledFeatures, disabled)()
|
||||
defer test.MockVariableValue(&setting.Admin.ExternalUserDisableFeatures, disabled)()
|
||||
|
||||
doc := getHTMLDoc(t, loginUser(t, "user2"), "/user/settings/account", http.StatusOK)
|
||||
doc.AssertElement(t, "#password", false)
|
||||
doc.AssertElement(t, "#email", true)
|
||||
doc.AssertElement(t, "#delete-form", false)
|
||||
})
|
||||
}
|
||||
|
||||
// TestUserSettingsUpdatePassword tests updating a user's password with(out)
|
||||
// disabled user features.
|
||||
func TestUserSettingsUpdatePassword(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
t.Run("password enabled", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
// changing password should work
|
||||
session := loginUser(t, "user2")
|
||||
req := NewRequestWithValues(t, "POST", "/user/settings/account", map[string]string{
|
||||
"old_password": "password",
|
||||
"password": "password",
|
||||
"retype": "password",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
})
|
||||
|
||||
t.Run("password disabled", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
disabled := container.SetOf(setting.UserFeatureManagePassword)
|
||||
defer test.MockVariableValue(&setting.Admin.UserDisabledFeatures, disabled)()
|
||||
defer test.MockVariableValue(&setting.Admin.ExternalUserDisableFeatures, disabled)()
|
||||
|
||||
// changing password should not work
|
||||
session := loginUser(t, "user2")
|
||||
req := NewRequestWithValues(t, "POST", "/user/settings/account", map[string]string{
|
||||
"old_password": "password",
|
||||
"password": "password",
|
||||
"retype": "password",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
// TestUserSettingsDelete tests deleting a user with(out) disabled user
|
||||
// features.
|
||||
func TestUserSettingsDelete(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
t.Run("deletion disabled", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
disabled := container.SetOf(setting.UserFeatureDeletion)
|
||||
defer test.MockVariableValue(&setting.Admin.UserDisabledFeatures, disabled)()
|
||||
defer test.MockVariableValue(&setting.Admin.ExternalUserDisableFeatures, disabled)()
|
||||
|
||||
// deleting user should not work
|
||||
session := loginUser(t, "user2")
|
||||
req := NewRequest(t, "POST", "/user/settings/account/delete")
|
||||
session.MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue