SECURITY: Prevent arbitrary topic custom fields from being set

Why this change?

The `PostsController#create` action allows arbitrary topic custom fields
to be set by any user that can create a topic. Without any restrictions,
this opens us up to potential security issues where plugins may be using
topic custom fields in security sensitive areas.

What does this change do?

1. This change introduces the `register_editable_topic_custom_field` plugin
API which allows plugins to register topic custom fields that are
editable either by staff users only or all users. The registered
editable topic custom fields are stored in `DiscoursePluginRegistry` and
is called by a new method `Topic#editable_custom_fields` which is then
used in the `PostsController#create` controller action. When an unpermitted custom fields is present in the `meta_data` params,
a 400 response code is returned.

2. Removes all reference to `meta_data` on a topic as it is confusing
   since we actually mean topic custom fields instead.
This commit is contained in:
Alan Guo Xiang Tan
2023-10-10 11:23:56 +08:00
committed by Penar Musaraj
parent 0ed20fe1cd
commit 4cb7472376
10 changed files with 162 additions and 90 deletions

View File

@@ -850,8 +850,22 @@ class PostsController < ApplicationController
.permit(*permitted)
.tap do |allowed|
allowed[:image_sizes] = params[:image_sizes]
# TODO this does not feel right, we should name what meta_data is allowed
allowed[:meta_data] = params[:meta_data]
if params.has_key?(:meta_data)
Discourse.deprecate(
"the :meta_data param is deprecated, use the :topic_custom_fields param instead",
since: "3.2",
drop_from: "3.3",
)
end
topic_custom_fields = {}
topic_custom_fields.merge!(editable_topic_custom_fields(:meta_data))
topic_custom_fields.merge!(editable_topic_custom_fields(:topic_custom_fields))
if topic_custom_fields.present?
allowed[:topic_opts] = { custom_fields: topic_custom_fields }
end
end
# Staff are allowed to pass `is_warning`
@@ -913,6 +927,25 @@ class PostsController < ApplicationController
result.to_h
end
def editable_topic_custom_fields(params_key)
if (topic_custom_fields = params[params_key]).present?
editable_topic_custom_fields = Topic.editable_custom_fields(guardian)
if (
unpermitted_topic_custom_fields =
topic_custom_fields.except(*editable_topic_custom_fields)
).present?
raise Discourse::InvalidParameters.new(
"The following keys in :#{params_key} are not permitted: #{unpermitted_topic_custom_fields.keys.join(", ")}",
)
end
topic_custom_fields.permit(*editable_topic_custom_fields).to_h
else
{}
end
end
def signature_for(args)
+"post##" << Digest::SHA1.hexdigest(
args