2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2025-10-03 17:21:20 +08:00

FEATURE: Add site setting to prevent mods from changing trust levels

This commit is contained in:
OsamaSayegh 2025-10-03 02:22:32 +03:00
parent 2b7835d02a
commit 6128a0736d
No known key found for this signature in database
GPG key ID: 060E5AC82223685F
9 changed files with 106 additions and 28 deletions

View file

@ -2,7 +2,7 @@ import { fn, hash } from "@ember/helper";
import { LinkTo } from "@ember/routing"; import { LinkTo } from "@ember/routing";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import RouteTemplate from "ember-route-template"; import RouteTemplate from "ember-route-template";
import { and, gt } from "truth-helpers"; import { and, gt, not } from "truth-helpers";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner"; import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import DButton from "discourse/components/d-button"; import DButton from "discourse/components/d-button";
import PluginOutlet from "discourse/components/plugin-outlet"; import PluginOutlet from "discourse/components/plugin-outlet";
@ -476,10 +476,14 @@ export default RouteTemplate(
<div class="field">{{i18n "trust_level"}}</div> <div class="field">{{i18n "trust_level"}}</div>
<div class="value"> <div class="value">
<ComboBox <ComboBox
class="change-trust-level-dropdown"
@content={{@controller.site.trustLevels}} @content={{@controller.site.trustLevels}}
@nameProperty="detailedName" @nameProperty="detailedName"
@value={{@controller.model.trustLevel.id}} @value={{@controller.model.trustLevel.id}}
@onChange={{fn (mut @controller.model.trust_level)}} @onChange={{fn (mut @controller.model.trust_level)}}
@options={{hash
disabled=(not @controller.model.can_change_trust_level)
}}
/> />


{{#if @controller.model.dirty}} {{#if @controller.model.dirty}}
@ -498,6 +502,7 @@ export default RouteTemplate(
{{/if}} {{/if}}
</div> </div>
<div class="controls"> <div class="controls">
{{#if @controller.model.can_change_trust_level}}
{{#if @controller.model.canLockTrustLevel}} {{#if @controller.model.canLockTrustLevel}}
{{#if @controller.hasLockedTrustLevel}} {{#if @controller.hasLockedTrustLevel}}
{{icon "lock" title="admin.user.trust_level_locked_tip"}} {{icon "lock" title="admin.user.trust_level_locked_tip"}}
@ -524,6 +529,7 @@ export default RouteTemplate(
{{i18n "admin.user.trust_level_3_requirements"}} {{i18n "admin.user.trust_level_3_requirements"}}
</LinkTo> </LinkTo>
{{/if}} {{/if}}
{{/if}}
</div> </div>
</div> </div>



View file

@ -7,6 +7,7 @@ class AdminUserSerializer < AdminUserListSerializer
:can_activate, :can_activate,
:can_deactivate, :can_deactivate,
:can_approve, :can_approve,
:can_change_trust_level,
:ip_address, :ip_address,
:registration_ip_address, :registration_ip_address,
:include_ip :include_ip
@ -33,6 +34,10 @@ class AdminUserSerializer < AdminUserListSerializer
scope.can_deactivate?(object) scope.can_deactivate?(object)
end end


def can_change_trust_level
scope.can_change_trust_level?(object)
end

def ip_address def ip_address
object.ip_address.try(:to_s) object.ip_address.try(:to_s)
end end

View file

@ -1931,6 +1931,7 @@ en:
top_page_default_timeframe: "Default top page time period for anonymous users (automatically adjusts for logged in users based on their last visit)." top_page_default_timeframe: "Default top page time period for anonymous users (automatically adjusts for logged in users based on their last visit)."
moderators_view_emails: "Allow moderators to view user email addresses." moderators_view_emails: "Allow moderators to view user email addresses."
moderators_view_ips: "Allow moderators to view user ip addresses." moderators_view_ips: "Allow moderators to view user ip addresses."
moderators_change_trust_levels: "Allow moderators to change user trust levels."
prioritize_username_in_ux: "Show username first on user page, user card and posts (when disabled name is shown first)" prioritize_username_in_ux: "Show username first on user page, user card and posts (when disabled name is shown first)"
enable_rich_text_paste: "Enable automatic HTML to Markdown conversion when pasting text into the composer." enable_rich_text_paste: "Enable automatic HTML to Markdown conversion when pasting text into the composer."
send_old_credential_reminder_days: "Remind about old credentials after days" send_old_credential_reminder_days: "Remind about old credentials after days"

View file

@ -2588,6 +2588,8 @@ security:
moderators_view_ips: moderators_view_ips:
default: true default: true
client: true client: true
moderators_change_trust_levels:
default: true
non_crawler_user_agents: non_crawler_user_agents:
hidden: true hidden: true
default: "trident|webkit|gecko|chrome|safari|msie|opera|goanna|discourse" default: "trident|webkit|gecko|chrome|safari|msie|opera|goanna|discourse"

View file

@ -397,7 +397,7 @@ class Guardian
end end


def can_change_trust_level?(user) def can_change_trust_level?(user)
user && is_staff? user && (is_admin? || (is_moderator? && SiteSetting.moderators_change_trust_levels))
end end


# Support sites that have to approve users # Support sites that have to approve users

View file

@ -1038,17 +1038,43 @@ RSpec.describe Admin::UsersController do


before { sign_in(admin) } before { sign_in(admin) }


context "when moderators_change_trust_levels setting is enabled" do
before { SiteSetting.moderators_change_trust_levels = true }

include_examples "trust level updates possible" include_examples "trust level updates possible"
end end


context "when moderators_change_trust_levels setting is disabled" do
before { SiteSetting.moderators_change_trust_levels = false }

include_examples "trust level updates possible"
end
end

context "when logged in as a moderator" do context "when logged in as a moderator" do
let(:acting_user) { moderator } let(:acting_user) { moderator }


before { sign_in(moderator) } before { sign_in(moderator) }


context "when moderators_change_trust_levels setting is enabled" do
before { SiteSetting.moderators_change_trust_levels = true }

include_examples "trust level updates possible" include_examples "trust level updates possible"
end end


context "when moderators_change_trust_levels setting is disabled" do
before { SiteSetting.moderators_change_trust_levels = false }

it "prevents updates to trust level with a 422 response" do
another_user.update(trust_level: TrustLevel[1])
put "/admin/users/#{another_user.id}/trust_level.json", params: { level: TrustLevel[0] }

expect(response.status).to eq(422)
expect(another_user.reload.trust_level).to eq(TrustLevel[1])
end
end
end

context "when logged in as a non-staff user" do context "when logged in as a non-staff user" do
before { sign_in(user) } before { sign_in(user) }



View file

@ -80,6 +80,9 @@
"can_deactivate": { "can_deactivate": {
"type": "boolean" "type": "boolean"
}, },
"can_change_trust_level": {
"type": "boolean"
},
"ip_address": { "ip_address": {
"type": "string" "type": "string"
}, },

View file

@ -125,4 +125,30 @@ describe "Admin User Page", type: :system do
end end
end end
end end

context "when logged in as a moderator" do
fab!(:current_user, :moderator)

context "when visiting a regular user's page" do
fab!(:user)

before { admin_user_page.visit(user) }

context "when moderators_change_trust_levels setting is enabled" do
before { SiteSetting.moderators_change_trust_levels = true }

it "the dropdown to change trust level is enabled" do
expect(admin_user_page).to have_change_trust_level_dropdown_enabled
end
end

context "when moderators_change_trust_levels setting is disabled" do
before { SiteSetting.moderators_change_trust_levels = false }

it "the dropdown to change trust level is disabled" do
expect(admin_user_page).to have_change_trust_level_dropdown_disabled
end
end
end
end
end end

View file

@ -23,6 +23,15 @@ module PageObjects
has_no_css?(".btn-danger.silence-user") has_no_css?(".btn-danger.silence-user")
end end


def has_change_trust_level_dropdown_enabled?
has_css?(".change-trust-level-dropdown") &&
has_no_css?(".change-trust-level-dropdown.is-disabled")
end

def has_change_trust_level_dropdown_disabled?
has_css?(".change-trust-level-dropdown.is-disabled")
end

def click_suspend_button def click_suspend_button
find(".btn-danger.suspend-user").click find(".btn-danger.suspend-user").click
end end