mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
Currently, theme settings with `type: list` and `list_type: group` require client-side permission checks, but `currentUser.groups` only includes visible groups, not all groups the user belongs to. This makes permission checks unreliable and can leak hidden group membership. To address this, this commit adds an opt-in `resolve_group_membership: true` option that replaces the group ID list with a user_in_SETTING_NAME boolean resolved server-side via `guardian.in_any_groups?`. The original group list is removed from the frontend payload to prevent leaking group IDs. Since theme settings are cached per-theme (not per-user), the resolution happens after the cache lookup during per-request serialization in `ApplicationLayoutPreloader#activated_themes_json`. Example YAML: ```yaml copy_button_allowed_groups: type: list list_type: group resolve_group_membership: true default: "1|3" ``` Frontend usage: ```javascript if (!settings.user_in_copy_button_allowed_groups) return; ```
86 lines
2.4 KiB
Ruby
Vendored
86 lines
2.4 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe ThemeSettingsValidator do
|
|
describe ".validate_value" do
|
|
it "does not throw an error when an integer value is given with type `string`" do
|
|
errors = described_class.validate_value(1, ThemeSetting.types[:string], {})
|
|
|
|
expect(errors).to eq([])
|
|
end
|
|
|
|
it "returns the right error messages when value is invalid for type `objects`" do
|
|
errors =
|
|
described_class.validate_value(
|
|
[{ name: "something" }],
|
|
ThemeSetting.types[:objects],
|
|
{
|
|
schema: {
|
|
name: "test",
|
|
properties: {
|
|
name: {
|
|
type: "string",
|
|
validations: {
|
|
max_length: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
expect(errors).to contain_exactly(
|
|
"The property at JSON Pointer '/0/name' must be at most 1 character long.",
|
|
)
|
|
end
|
|
end
|
|
|
|
describe ".validate_resolve_group_membership" do
|
|
it "returns error when resolve_group_membership is true on non-list setting" do
|
|
errors =
|
|
described_class.validate_value(
|
|
"test",
|
|
ThemeSetting.types[:string],
|
|
{ resolve_group_membership: true },
|
|
)
|
|
|
|
expect(errors).to contain_exactly(
|
|
I18n.t("themes.settings_errors.resolve_group_membership_requires_list"),
|
|
)
|
|
end
|
|
|
|
it "returns error when resolve_group_membership is true without list_type group" do
|
|
errors =
|
|
described_class.validate_value(
|
|
"a|b|c",
|
|
ThemeSetting.types[:list],
|
|
{ resolve_group_membership: true, list_type: "compact" },
|
|
)
|
|
|
|
expect(errors).to contain_exactly(
|
|
I18n.t("themes.settings_errors.resolve_group_membership_requires_group_list"),
|
|
)
|
|
end
|
|
|
|
it "returns no errors when resolve_group_membership is true with list_type group" do
|
|
errors =
|
|
described_class.validate_value(
|
|
"1|2|3",
|
|
ThemeSetting.types[:list],
|
|
{ resolve_group_membership: true, list_type: "group" },
|
|
)
|
|
|
|
expect(errors).to eq([])
|
|
end
|
|
|
|
it "returns no errors when resolve_group_membership is false" do
|
|
errors =
|
|
described_class.validate_value(
|
|
"test",
|
|
ThemeSetting.types[:string],
|
|
{ resolve_group_membership: false },
|
|
)
|
|
|
|
expect(errors).to eq([])
|
|
end
|
|
end
|
|
end
|