Skip to content

Commit 18db311

Browse files
committed
Bugfix: address unsafe YAML load on destroy actions
Addresses `Psych::DisallowedClass: Tried to load unspecified class`. Reenables three skipped tests. Exposes new `config.yaml_permitted_classes` option for legacy usages, and starts serializing in secure-by-default JSON that doesn't need configuration by hosts apps. [fixes #365]
1 parent be8d656 commit 18db311

7 files changed

Lines changed: 47 additions & 11 deletions

File tree

lib/merit.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ def self.current_user_method
2626
"current_#{@config.user_model_name.downcase}".to_sym
2727
end
2828

29+
def self.yaml_safe_load_permitted_classes
30+
[
31+
ActiveModel::Attribute.const_get(:FromDatabase),
32+
ActiveModel::Attribute.const_get(:FromUser),
33+
ActiveModel::Type::String,
34+
ActiveSupport::HashWithIndifferentAccess,
35+
Array,
36+
Comment,
37+
Hash
38+
] + @config.yaml_safe_load_permitted_classes
39+
end
40+
2941
def self.observers
3042
@config.observers
3143
end
@@ -47,13 +59,14 @@ def self.remove_point_rules
4759

4860
class Configuration
4961
attr_accessor :checks_on_each_request, :orm, :user_model_name, :observers,
50-
:current_user_method
62+
:current_user_method, :yaml_safe_load_permitted_classes
5163

5264
def initialize
5365
@checks_on_each_request = true
5466
@orm = :active_record
5567
@user_model_name = 'User'
5668
@observers = []
69+
@yaml_safe_load_permitted_classes = []
5770
end
5871

5972
def add_observer(class_name)

lib/merit/base_target_finder.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,19 @@ def model_class
2525
end
2626

2727
def reanimate_target_from_action
28-
YAML.load(@action.target_data)
28+
parse_target_data(@action.target_data)
29+
end
30+
31+
private
32+
33+
def parse_target_data(target_data)
34+
model_class.new JSON.parse(target_data)
35+
rescue JSON::ParserError
36+
YAML.safe_load(
37+
target_data,
38+
permitted_classes: Merit.yaml_safe_load_permitted_classes,
39+
aliases: false
40+
)
2941
end
3042
end
3143
end

lib/merit/controller_extensions.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def merit_action_hash
2727
had_errors: had_errors?,
2828
target_model: controller_path,
2929
target_id: target_id,
30-
target_data: target_object.to_yaml,
30+
target_data: JSON.generate(target_object.as_json)
3131
}
3232
end
3333

@@ -42,7 +42,7 @@ def had_errors?
4242
def target_object
4343
variable_name = :"@#{controller_name.singularize}"
4444
if instance_variable_defined?(variable_name)
45-
if target_obj = instance_variable_get(variable_name)
45+
if (target_obj = instance_variable_get(variable_name))
4646
target_obj
4747
else
4848
warn_no_object_found

test/dummy/config/initializers/merit.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Merit.setup do |config|
33
# Add application observers to get notifications any time merit changes reputation.
44
config.add_observer 'DummyObserver'
5-
5+
config.yaml_safe_load_permitted_classes = %w(Comment)
66
config.orm = ENV['ORM'].try(:to_sym)
77
end
88

test/integration/navigation_test.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,6 @@ def teardown
231231
# Destroying a comment should remove points from the comment creator.
232232
comment_to_destroy = user.comments.last
233233
visit '/comments'
234-
skip "see bug https://github.com/merit-gem/merit/issues/365"
235234
assert_difference lambda { user.reload.points }, -5 do
236235
within("tr#c_#{comment_to_destroy.id}") do
237236
click_link 'Destroy'

test/unit/action_test.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22

33
describe Merit::Action do
44
it 'saves correctly with a serialised model' do
5-
skip "see bug https://github.com/merit-gem/merit/issues/365"
65
comment = Comment.new(name: 'the comment name')
76
action = Merit::Action.create(target_model: 'comment',
87
target_id: 2,
9-
target_data: comment.to_yaml)
10-
comment_yaml = Merit::Action.find(action.id).target_data
11-
assert_equal comment.name, YAML::load(comment_yaml).name
8+
target_data: JSON.generate(comment.as_json))
9+
comment_json = Merit::Action.find(action.id).target_data
10+
assert_equal comment.name, JSON.parse(comment_json)['name']
1211
end
1312
end

test/unit/base_target_finder_test.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,20 @@
4747

4848
describe 'target was destroyed' do
4949
it 'gets the object from the JSON data in the merit_actions table' do
50-
skip "see bug https://github.com/merit-gem/merit/issues/365"
50+
comment = Comment.new(name: 'the comment name')
51+
52+
rule = Merit::Rule.new
53+
rule.to = :itself
54+
rule.model_name = 'comment'
55+
action = Merit::Action.new(target_model: 'comment',
56+
target_id: 2,
57+
target_data: JSON.generate(comment.as_json))
58+
59+
finder = Merit::BaseTargetFinder.new(rule, action)
60+
_(finder.find.name).must_be :==, 'the comment name'
61+
end
62+
63+
it 'gets the object from the legacy YAML data in the merit_actions table' do
5164
comment = Comment.new(name: 'the comment name')
5265

5366
rule = Merit::Rule.new

0 commit comments

Comments
 (0)