DEV: Block accidental serialization of entire AR models (#27668)
This commit is contained in:
parent
1ae902fa60
commit
6599b85a75
|
@ -83,7 +83,7 @@ class Admin::ThemesController < Admin::AdminController
|
||||||
|
|
||||||
if @theme.save
|
if @theme.save
|
||||||
log_theme_change(nil, @theme)
|
log_theme_change(nil, @theme)
|
||||||
render json: @theme, status: :created
|
render json: serialize_data(@theme, ThemeSerializer), status: :created
|
||||||
else
|
else
|
||||||
render json: @theme.errors, status: :unprocessable_entity
|
render json: @theme.errors, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
@ -113,7 +113,7 @@ class Admin::ThemesController < Admin::AdminController
|
||||||
|
|
||||||
@theme =
|
@theme =
|
||||||
RemoteTheme.import_theme(remote, theme_user, private_key: private_key, branch: branch)
|
RemoteTheme.import_theme(remote, theme_user, private_key: private_key, branch: branch)
|
||||||
render json: @theme, status: :created
|
render json: serialize_data(@theme, ThemeSerializer), status: :created
|
||||||
rescue RemoteTheme::ImportError => e
|
rescue RemoteTheme::ImportError => e
|
||||||
if params[:force]
|
if params[:force]
|
||||||
theme_name = params[:remote].gsub(/.git\z/, "").split("/").last
|
theme_name = params[:remote].gsub(/.git\z/, "").split("/").last
|
||||||
|
@ -128,7 +128,7 @@ class Admin::ThemesController < Admin::AdminController
|
||||||
@theme.remote_theme = remote_theme
|
@theme.remote_theme = remote_theme
|
||||||
@theme.save!
|
@theme.save!
|
||||||
|
|
||||||
render json: @theme, status: :created
|
render json: serialize_data(@theme, ThemeSerializer), status: :created
|
||||||
else
|
else
|
||||||
render_json_error e.message
|
render_json_error e.message
|
||||||
end
|
end
|
||||||
|
@ -156,7 +156,7 @@ class Admin::ThemesController < Admin::AdminController
|
||||||
)
|
)
|
||||||
|
|
||||||
log_theme_change(nil, @theme)
|
log_theme_change(nil, @theme)
|
||||||
render json: @theme, status: :created
|
render json: serialize_data(@theme, ThemeSerializer), status: :created
|
||||||
rescue RemoteTheme::ImportError => e
|
rescue RemoteTheme::ImportError => e
|
||||||
render_json_error e.message
|
render_json_error e.message
|
||||||
end
|
end
|
||||||
|
@ -200,7 +200,7 @@ class Admin::ThemesController < Admin::AdminController
|
||||||
if @theme.save
|
if @theme.save
|
||||||
update_default_theme
|
update_default_theme
|
||||||
log_theme_change(nil, @theme)
|
log_theme_change(nil, @theme)
|
||||||
format.json { render json: @theme, status: :created }
|
format.json { render json: serialize_data(@theme, ThemeSerializer), status: :created }
|
||||||
else
|
else
|
||||||
format.json { render json: @theme.errors, status: :unprocessable_entity }
|
format.json { render json: @theme.errors, status: :unprocessable_entity }
|
||||||
end
|
end
|
||||||
|
@ -250,7 +250,7 @@ class Admin::ThemesController < Admin::AdminController
|
||||||
log_theme_component_disabled if disables_component
|
log_theme_component_disabled if disables_component
|
||||||
log_theme_component_enabled if enables_component
|
log_theme_component_enabled if enables_component
|
||||||
|
|
||||||
format.json { render json: @theme, status: :ok }
|
format.json { render json: serialize_data(@theme, ThemeSerializer), status: :ok }
|
||||||
else
|
else
|
||||||
format.json do
|
format.json do
|
||||||
error = @theme.errors.full_messages.join(", ").presence
|
error = @theme.errors.full_messages.join(", ").presence
|
||||||
|
|
|
@ -17,12 +17,18 @@ class Admin::WebHooksController < Admin::AdminController
|
||||||
|
|
||||||
data = serialize_data(web_hooks, AdminWebHookSerializer, root: "web_hooks")
|
data = serialize_data(web_hooks, AdminWebHookSerializer, root: "web_hooks")
|
||||||
|
|
||||||
|
serialized_grouped_event_types =
|
||||||
|
WebHookEventType.active_grouped.transform_values do |array|
|
||||||
|
serialize_data(array, WebHookEventTypeSerializer)
|
||||||
|
end
|
||||||
|
|
||||||
json = {
|
json = {
|
||||||
web_hooks: data.delete("web_hooks"),
|
web_hooks: data.delete("web_hooks"),
|
||||||
extras:
|
extras:
|
||||||
data.merge(
|
data.merge(
|
||||||
grouped_event_types: WebHookEventType.active_grouped,
|
grouped_event_types: serialized_grouped_event_types,
|
||||||
default_event_types: WebHook.default_event_types,
|
default_event_types:
|
||||||
|
serialize_data(WebHook.default_event_types, WebHookEventTypeSerializer),
|
||||||
content_types: WebHook.content_types.map { |name, id| { id: id, name: name } },
|
content_types: WebHook.content_types.map { |name, id| { id: id, name: name } },
|
||||||
delivery_statuses:
|
delivery_statuses:
|
||||||
WebHook.last_delivery_statuses.map { |name, id| { id: id, name: name.to_s } },
|
WebHook.last_delivery_statuses.map { |name, id| { id: id, name: name.to_s } },
|
||||||
|
|
|
@ -140,7 +140,7 @@ class TopicsController < ApplicationController
|
||||||
custom_message_params: {
|
custom_message_params: {
|
||||||
group: group.name,
|
group: group.name,
|
||||||
},
|
},
|
||||||
group: group,
|
group: serialize_data(group, BasicGroupSerializer, root: false),
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1544,12 +1544,14 @@ class UsersController < ApplicationController
|
||||||
.select(:id, :name, :last_used, :created_at, :method)
|
.select(:id, :name, :last_used, :created_at, :method)
|
||||||
.where(enabled: true)
|
.where(enabled: true)
|
||||||
.order(:created_at)
|
.order(:created_at)
|
||||||
|
.as_json(only: %i[id name method last_used])
|
||||||
|
|
||||||
security_keys =
|
security_keys =
|
||||||
current_user
|
current_user
|
||||||
.security_keys
|
.security_keys
|
||||||
.where(factor_type: UserSecurityKey.factor_types[:second_factor])
|
.where(factor_type: UserSecurityKey.factor_types[:second_factor])
|
||||||
.order(:created_at)
|
.order(:created_at)
|
||||||
|
.as_json(only: %i[id user_id credential_id public_key factor_type enabled name last_used])
|
||||||
|
|
||||||
render json: success_json.merge(totps: totp_second_factors, security_keys: security_keys)
|
render json: success_json.merge(totps: totp_second_factors, security_keys: security_keys)
|
||||||
else
|
else
|
||||||
|
|
|
@ -533,7 +533,10 @@ module Jobs
|
||||||
def get_user_archive_fields(user_archive)
|
def get_user_archive_fields(user_archive)
|
||||||
user_archive_array = []
|
user_archive_array = []
|
||||||
topic_data = user_archive.topic
|
topic_data = user_archive.topic
|
||||||
user_archive = user_archive.as_json
|
user_archive =
|
||||||
|
user_archive.as_json(
|
||||||
|
only: %i[topic_id post_number raw cooked like_count reply_count created_at id],
|
||||||
|
)
|
||||||
topic_data =
|
topic_data =
|
||||||
Topic
|
Topic
|
||||||
.with_deleted
|
.with_deleted
|
||||||
|
|
|
@ -8,8 +8,7 @@ class AdminWebHookSerializer < ApplicationSerializer
|
||||||
:secret,
|
:secret,
|
||||||
:wildcard_web_hook,
|
:wildcard_web_hook,
|
||||||
:verify_certificate,
|
:verify_certificate,
|
||||||
:active,
|
:active
|
||||||
:web_hook_event_types
|
|
||||||
|
|
||||||
has_many :categories, serializer: BasicCategorySerializer, embed: :ids, include: true
|
has_many :categories, serializer: BasicCategorySerializer, embed: :ids, include: true
|
||||||
has_many :tags,
|
has_many :tags,
|
||||||
|
@ -19,10 +18,10 @@ class AdminWebHookSerializer < ApplicationSerializer
|
||||||
embed_key: :name,
|
embed_key: :name,
|
||||||
include: false
|
include: false
|
||||||
has_many :groups, serializer: BasicGroupSerializer, embed: :ids, include: false
|
has_many :groups, serializer: BasicGroupSerializer, embed: :ids, include: false
|
||||||
|
has_many :web_hook_event_types,
|
||||||
def web_hook_event_types
|
serializer: WebHookEventTypeSerializer,
|
||||||
ActiveModel::ArraySerializer.new(object.web_hook_event_types).as_json
|
root: false,
|
||||||
end
|
embed: :objects
|
||||||
|
|
||||||
def last_delivery_status
|
def last_delivery_status
|
||||||
object.active ? object.last_delivery_status : WebHook.last_delivery_statuses[:disabled]
|
object.active ? object.last_delivery_status : WebHook.last_delivery_statuses[:disabled]
|
||||||
|
|
|
@ -84,7 +84,10 @@ class SiteSerializer < ApplicationSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_dark_color_scheme
|
def default_dark_color_scheme
|
||||||
ColorScheme.find_by_id(SiteSetting.default_dark_mode_color_scheme_id).as_json
|
ColorSchemeSerializer.new(
|
||||||
|
ColorScheme.find_by_id(SiteSetting.default_dark_mode_color_scheme_id),
|
||||||
|
root: false,
|
||||||
|
).as_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def groups
|
def groups
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
require "base64"
|
require "base64"
|
||||||
|
|
||||||
class ThemeSerializer < BasicThemeSerializer
|
class ThemeSerializer < BasicThemeSerializer
|
||||||
attributes :color_scheme,
|
attributes :color_scheme_id,
|
||||||
:color_scheme_id,
|
|
||||||
:user_selectable,
|
:user_selectable,
|
||||||
:auto_update,
|
:auto_update,
|
||||||
:remote_theme_id,
|
:remote_theme_id,
|
||||||
|
@ -16,6 +15,7 @@ class ThemeSerializer < BasicThemeSerializer
|
||||||
:disabled_at,
|
:disabled_at,
|
||||||
:theme_fields
|
:theme_fields
|
||||||
|
|
||||||
|
has_one :color_scheme, serializer: ColorSchemeSerializer, embed: :object
|
||||||
has_one :user, serializer: UserNameSerializer, embed: :object
|
has_one :user, serializer: UserNameSerializer, embed: :object
|
||||||
has_one :disabled_by, serializer: UserNameSerializer, embed: :object
|
has_one :disabled_by, serializer: UserNameSerializer, embed: :object
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TopicViewBookmarkSerializer < ApplicationSerializer
|
||||||
|
attributes :id, :bookmarkable_id, :bookmarkable_type, :reminder_at, :name, :auto_delete_preference
|
||||||
|
end
|
|
@ -64,7 +64,6 @@ class TopicViewSerializer < ApplicationSerializer
|
||||||
:is_warning,
|
:is_warning,
|
||||||
:chunk_size,
|
:chunk_size,
|
||||||
:bookmarked,
|
:bookmarked,
|
||||||
:bookmarks,
|
|
||||||
:message_archived,
|
:message_archived,
|
||||||
:topic_timer,
|
:topic_timer,
|
||||||
:unicode_title,
|
:unicode_title,
|
||||||
|
@ -85,6 +84,7 @@ class TopicViewSerializer < ApplicationSerializer
|
||||||
has_one :details, serializer: TopicViewDetailsSerializer, root: false, embed: :objects
|
has_one :details, serializer: TopicViewDetailsSerializer, root: false, embed: :objects
|
||||||
has_many :pending_posts, serializer: TopicPendingPostSerializer, root: false, embed: :objects
|
has_many :pending_posts, serializer: TopicPendingPostSerializer, root: false, embed: :objects
|
||||||
has_many :categories, serializer: CategoryBadgeSerializer, embed: :objects
|
has_many :categories, serializer: CategoryBadgeSerializer, embed: :objects
|
||||||
|
has_many :bookmarks, serializer: TopicViewBookmarkSerializer, root: false, embed: :objects
|
||||||
|
|
||||||
has_one :published_page, embed: :objects
|
has_one :published_page, embed: :objects
|
||||||
|
|
||||||
|
@ -201,10 +201,6 @@ class TopicViewSerializer < ApplicationSerializer
|
||||||
object.has_bookmarks?
|
object.has_bookmarks?
|
||||||
end
|
end
|
||||||
|
|
||||||
def bookmarks
|
|
||||||
object.bookmarks
|
|
||||||
end
|
|
||||||
|
|
||||||
def topic_timer
|
def topic_timer
|
||||||
topic_timer = object.topic.public_topic_timer
|
topic_timer = object.topic.public_topic_timer
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class WebHookEventTypeSerializer < ApplicationSerializer
|
||||||
|
attributes :id, :name, :group
|
||||||
|
end
|
|
@ -15,10 +15,10 @@
|
||||||
<a href="<%= path "/login" %>" class='btn btn-primary'><%= SvgSprite.raw_svg('fa-user') %><%= I18n.t('log_in') %></a>
|
<a href="<%= path "/login" %>" class='btn btn-primary'><%= SvgSprite.raw_svg('fa-user') %><%= I18n.t('log_in') %></a>
|
||||||
<%- end %>
|
<%- end %>
|
||||||
|
|
||||||
<%- if @group&.allow_membership_requests %>
|
<%- if @group&.dig(:allow_membership_requests) %>
|
||||||
<a href="<%= group_path @group.name %>" class='btn btn-primary'><%= SvgSprite.raw_svg('user-plus') %> <%= I18n.t('not_in_group.request_membership') %></a>
|
<a href="<%= group_path @group[:name] %>" class='btn btn-primary'><%= SvgSprite.raw_svg('user-plus') %> <%= I18n.t('not_in_group.request_membership') %></a>
|
||||||
<%- elsif @group&.public_admission %>
|
<%- elsif @group&.dig(:public_admission) %>
|
||||||
<a href="<%= group_path @group.name %>" class='btn btn-primary'><%= SvgSprite.raw_svg('user-plus') %> <%= I18n.t('not_in_group.join_group') %></a>
|
<a href="<%= group_path @group[:name] %>" class='btn btn-primary'><%= SvgSprite.raw_svg('user-plus') %> <%= I18n.t('not_in_group.join_group') %></a>
|
||||||
<%- end %>
|
<%- end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ActiveRecordSerializationSafety
|
||||||
|
class BlockedSerializationError < StandardError
|
||||||
|
end
|
||||||
|
|
||||||
|
def serializable_hash(options = nil)
|
||||||
|
if options.nil? || options[:only].nil?
|
||||||
|
message =
|
||||||
|
"Serializing ActiveRecord models (#{self.class.name}) without specifying fields is not allowed. Use a Serializer, or pass the :only option to #serializable_hash. More info: https://meta.discourse.org/t/-/314495"
|
||||||
|
|
||||||
|
if Rails.env == "production"
|
||||||
|
Rails.logger.info(message)
|
||||||
|
else
|
||||||
|
raise BlockedSerializationError.new(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveRecord::Base.prepend(ActiveRecordSerializationSafety)
|
|
@ -113,6 +113,12 @@ module ChatSpecHelpers
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_draft(channel, thread: nil, user: Discourse.system_user, data: { message: "draft" })
|
def create_draft(channel, thread: nil, user: Discourse.system_user, data: { message: "draft" })
|
||||||
|
if data[:uploads]
|
||||||
|
data[:uploads] = data[:uploads].map do |upload|
|
||||||
|
UploadSerializer.new(upload, root: false).as_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
result =
|
result =
|
||||||
::Chat::UpsertDraft.call(
|
::Chat::UpsertDraft.call(
|
||||||
guardian: user.guardian,
|
guardian: user.guardian,
|
||||||
|
|
|
@ -1410,7 +1410,7 @@ RSpec.describe TopicQuery do
|
||||||
end
|
end
|
||||||
|
|
||||||
def read(user, topic, post_number)
|
def read(user, topic, post_number)
|
||||||
TopicUser.update_last_read(user, topic, post_number, post_number, 10_000)
|
TopicUser.update_last_read(user, topic.id, post_number, post_number, 10_000)
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
|
|
@ -222,7 +222,7 @@ RSpec.describe Admin::ThemesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can import a theme from Git" do
|
it "can import a theme from Git" do
|
||||||
RemoteTheme.stubs(:import_theme)
|
RemoteTheme.stubs(:import_theme).returns(Fabricate(:theme))
|
||||||
post "/admin/themes/import.json",
|
post "/admin/themes/import.json",
|
||||||
params: {
|
params: {
|
||||||
remote: " https://github.com/discourse/discourse-brand-header.git ",
|
remote: " https://github.com/discourse/discourse-brand-header.git ",
|
||||||
|
@ -1076,12 +1076,11 @@ RSpec.describe Admin::ThemesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return the right error when value used to update a theme setting of `objects` typed is invalid" do
|
it "should return the right error when value used to update a theme setting of `objects` typed is invalid" do
|
||||||
field =
|
theme.set_field(
|
||||||
theme.set_field(
|
target: :settings,
|
||||||
target: :settings,
|
name: "yaml",
|
||||||
name: "yaml",
|
value: File.read("#{Rails.root}/spec/fixtures/theme_settings/objects_settings.yaml"),
|
||||||
value: File.read("#{Rails.root}/spec/fixtures/theme_settings/objects_settings.yaml"),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
theme.save!
|
theme.save!
|
||||||
|
|
||||||
|
@ -1101,12 +1100,11 @@ RSpec.describe Admin::ThemesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should be able to update a theme setting of `objects` typed" do
|
it "should be able to update a theme setting of `objects` typed" do
|
||||||
field =
|
theme.set_field(
|
||||||
theme.set_field(
|
target: :settings,
|
||||||
target: :settings,
|
name: "yaml",
|
||||||
name: "yaml",
|
value: File.read("#{Rails.root}/spec/fixtures/theme_settings/objects_settings.yaml"),
|
||||||
value: File.read("#{Rails.root}/spec/fixtures/theme_settings/objects_settings.yaml"),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
theme.save!
|
theme.save!
|
||||||
|
|
||||||
|
@ -1354,7 +1352,7 @@ RSpec.describe Admin::ThemesController do
|
||||||
|
|
||||||
let(:theme_setting) do
|
let(:theme_setting) do
|
||||||
yaml = File.read("#{Rails.root}/spec/fixtures/theme_settings/objects_settings.yaml")
|
yaml = File.read("#{Rails.root}/spec/fixtures/theme_settings/objects_settings.yaml")
|
||||||
field = theme.set_field(target: :settings, name: "yaml", value: yaml)
|
theme.set_field(target: :settings, name: "yaml", value: yaml)
|
||||||
theme.save!
|
theme.save!
|
||||||
theme.settings
|
theme.settings
|
||||||
end
|
end
|
||||||
|
|
|
@ -108,6 +108,11 @@ RSpec.describe "notifications" do
|
||||||
response "200", "notifications marked read" do
|
response "200", "notifications marked read" do
|
||||||
schema type: :object, properties: { success: { type: :string } }
|
schema type: :object, properties: { success: { type: :string } }
|
||||||
|
|
||||||
|
let(:notification) do
|
||||||
|
notification = Fabricate(:notification)
|
||||||
|
NotificationSerializer.new(notification).as_json
|
||||||
|
end
|
||||||
|
|
||||||
run_test!
|
run_test!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -221,7 +221,10 @@ RSpec.describe "posts" do
|
||||||
expected_response_schema = load_spec_schema("topic_create_response")
|
expected_response_schema = load_spec_schema("topic_create_response")
|
||||||
schema expected_response_schema
|
schema expected_response_schema
|
||||||
|
|
||||||
let(:params) { Fabricate(:post) }
|
let(:params) do
|
||||||
|
post = Fabricate(:post)
|
||||||
|
post.serializable_hash(only: %i[topic_id raw created_at]).as_json
|
||||||
|
end
|
||||||
|
|
||||||
it_behaves_like "a JSON endpoint", 200 do
|
it_behaves_like "a JSON endpoint", 200 do
|
||||||
let(:expected_response_schema) { expected_response_schema }
|
let(:expected_response_schema) { expected_response_schema }
|
||||||
|
|
|
@ -113,7 +113,7 @@ RSpec.describe SiteSerializer do
|
||||||
scheme = ColorScheme.last
|
scheme = ColorScheme.last
|
||||||
SiteSetting.default_dark_mode_color_scheme_id = scheme.id
|
SiteSetting.default_dark_mode_color_scheme_id = scheme.id
|
||||||
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
|
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
|
||||||
default_dark_scheme = expect(serialized[:default_dark_color_scheme]["name"]).to eq(scheme.name)
|
default_dark_scheme = expect(serialized[:default_dark_color_scheme][:name]).to eq(scheme.name)
|
||||||
|
|
||||||
SiteSetting.default_dark_mode_color_scheme_id = -1
|
SiteSetting.default_dark_mode_color_scheme_id = -1
|
||||||
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
|
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
|
||||||
|
|
Loading…
Reference in New Issue