0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 13:08:40 +08:00
discourse/spec/lib/theme_settings_validator_spec.rb
Martin Brennan 7e77ce4bd3
DEV: Add resolve_group_membership functionality to theme settings (#41360)
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;
```
2026-07-08 09:29:21 +10:00

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