mirror of
https://github.com/discourse/discourse.git
synced 2026-08-08 17:53:55 +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; ```
126 lines
2.5 KiB
Ruby
Vendored
126 lines
2.5 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
class ThemeSettingsSerializer < ApplicationSerializer
|
|
attributes :setting,
|
|
:humanized_name,
|
|
:type,
|
|
:default,
|
|
:value,
|
|
:description,
|
|
:valid_values,
|
|
:list_type,
|
|
:resolve_group_membership,
|
|
:textarea,
|
|
:json_schema,
|
|
:objects_schema
|
|
|
|
def setting
|
|
object.name
|
|
end
|
|
|
|
def humanized_name
|
|
SiteSetting.humanized_name(object.name)
|
|
end
|
|
|
|
def type
|
|
object.type_name
|
|
end
|
|
|
|
def default
|
|
object.default
|
|
end
|
|
|
|
def value
|
|
object.value
|
|
end
|
|
|
|
def description
|
|
description_regexp = /^theme_metadata\.settings\.#{setting}(\.description)?$/
|
|
|
|
locale_file_description =
|
|
object.theme.internal_translations.find { |t| t.key.match?(description_regexp) }&.value
|
|
|
|
resolved_description = locale_file_description || object.description
|
|
|
|
if resolved_description
|
|
catch(:exception) do
|
|
return I18n.interpolate(resolved_description, base_path: Discourse.base_path)
|
|
end
|
|
resolved_description
|
|
end
|
|
end
|
|
|
|
def valid_values
|
|
choices = object.choices
|
|
labels = choice_labels
|
|
|
|
choices.map do |choice|
|
|
label = labels[choice.to_s]
|
|
label ? { name: label, value: choice } : choice
|
|
end
|
|
end
|
|
|
|
def include_valid_values?
|
|
object.type == ThemeSetting.types[:enum]
|
|
end
|
|
|
|
def include_description?
|
|
description.present?
|
|
end
|
|
|
|
def list_type
|
|
object.list_type
|
|
end
|
|
|
|
def include_list_type?
|
|
object.type == ThemeSetting.types[:list]
|
|
end
|
|
|
|
def textarea
|
|
object.textarea
|
|
end
|
|
|
|
def include_textarea?
|
|
object.type == ThemeSetting.types[:string]
|
|
end
|
|
|
|
def objects_schema
|
|
object.schema
|
|
end
|
|
|
|
def include_objects_schema?
|
|
object.type == ThemeSetting.types[:objects]
|
|
end
|
|
|
|
def json_schema
|
|
object.json_schema
|
|
end
|
|
|
|
def include_json_schema?
|
|
object.type == ThemeSetting.types[:string] && object.json_schema.present?
|
|
end
|
|
|
|
def resolve_group_membership
|
|
object.resolve_group_membership?
|
|
end
|
|
|
|
def include_resolve_group_membership?
|
|
object.type == ThemeSetting.types[:list] && object.list_type == "group"
|
|
end
|
|
|
|
private
|
|
|
|
def choice_labels
|
|
labels = {}
|
|
key_prefix = "theme_metadata.settings.#{setting}.choices."
|
|
|
|
object.theme.internal_translations.each do |translation|
|
|
if translation.key.start_with?(key_prefix)
|
|
choice_key = translation.key.delete_prefix(key_prefix)
|
|
labels[choice_key] = translation.value
|
|
end
|
|
end
|
|
|
|
labels
|
|
end
|
|
end
|