0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-04 10:39:43 +08:00
discourse/lib/theme_settings_validator.rb
Martin Brennan bee1be8599
DEV: Add resolve_group_membership for theme object settings of group type (#41756)
Followup 7e77ce4bd3

We need to automatically resolve group membership into a boolean
for theme object type settings which are of the group type, similar
to what we did in the original commit above for group list type
settings.

This behaves in the same way -- for an object setting schema like this:

```
menu_sections:
  type: objects
  default:
    - name: section 1
      groups:
        - 1
        - 3
  schema:
    name: menu section
    properties:
      name:
        type: string
      groups:
        type: groups
        resolve_group_membership: true
```

We replace `groups` with a boolean `user_in_groups` (groups is just
the property name, it could be foo_bar etc.) and then you can
do this on the client:

```
for (const section of settings.menu_sections) {
  if (section.user_in_groups) {
    // User is in at least one selected group for this section.
  }
}
```

Rather than inspecting the `currentUser.groups`, which only includes
visible groups, not all groups the user is a member of. This allows
for more accurate permission checks for theme settings that are group
based.

Also c.f.
https://meta.discourse.org/t/granular-group-based-permissions-for-anonymous-and-logged-in-users/402273/18?u=martin
2026-07-17 10:19:29 +10:00

130 lines
3.6 KiB
Ruby
Vendored

# frozen_string_literal: true
# Service class that holds helper methods that can be used to validate theme settings.
class ThemeSettingsValidator
class << self
def is_value_present?(value)
!value.nil?
end
def is_valid_value_type?(value, type)
case type
when types[:integer]
value.is_a?(Integer)
when types[:float]
value.is_a?(Integer) || value.is_a?(Float)
when types[:bool]
value.is_a?(TrueClass) || value.is_a?(FalseClass)
when types[:list]
value.is_a?(String)
when types[:objects]
value.is_a?(Array) && value.all? { |v| v.is_a?(Hash) }
else
true
end
end
def validate_value(value, type, opts)
errors = []
errors.concat(validate_resolve_group_membership(opts, type))
case type
when types[:enum]
if opts[:choices].exclude?(value) && opts[:choices].map(&:to_s).exclude?(value)
errors << I18n.t(
"themes.settings_errors.enum_value_not_valid",
choices: opts[:choices].join(", "),
)
end
when types[:integer], types[:float]
validate_value_in_range!(
value,
min: opts[:min],
max: opts[:max],
errors:,
translation_prefix: "number",
)
when types[:string]
validate_value_in_range!(
value.to_s.length,
min: opts[:min],
max: opts[:max],
errors:,
translation_prefix: "string",
)
when types[:objects]
errors.concat(
SchemaSettingsObjectValidator.validate_objects(schema: opts[:schema], objects: value),
)
end
errors
end
def validate_resolve_group_membership(opts, type)
errors = []
if opts[:resolve_group_membership]
if type != types[:list]
errors << I18n.t("themes.settings_errors.resolve_group_membership_requires_list")
elsif opts[:list_type] != "group"
errors << I18n.t("themes.settings_errors.resolve_group_membership_requires_group_list")
end
end
if type == types[:objects]
errors.concat(validate_object_schema_resolve_group_membership(opts[:schema]))
end
errors
end
private
def types
ThemeSetting.types
end
def validate_object_schema_resolve_group_membership(schema)
errors = []
return errors if schema.blank?
schema[:properties].each do |property_name, property_attributes|
if property_attributes[:resolve_group_membership] && property_attributes[:type] != "groups"
errors << I18n.t(
"themes.settings_errors.resolve_group_membership_requires_groups_property",
property: property_name,
)
end
if property_attributes[:type] == "objects"
errors.concat(
validate_object_schema_resolve_group_membership(property_attributes[:schema]),
)
end
end
errors
end
def validate_value_in_range!(value, min:, max:, errors:, translation_prefix:)
if min && max && max != Float::INFINITY && !(min..max).include?(value)
errors << I18n.t(
"themes.settings_errors.#{translation_prefix}_value_not_valid_min_max",
min: min,
max: max,
)
elsif min && value < min
errors << I18n.t(
"themes.settings_errors.#{translation_prefix}_value_not_valid_min",
min: min,
)
elsif max && value > max
errors << I18n.t(
"themes.settings_errors.#{translation_prefix}_value_not_valid_max",
max: max,
)
end
end
end
end