Skip to content

Commit e313fc5

Browse files
xjuniorrafaelfranca
authored andcommitted
Permit YAML classes and unsafe load per attribute
Allow unsafe_load to override application setting Process options on load Add CHANGELOG entry
1 parent 2434e20 commit e313fc5

File tree

4 files changed

+75
-7
lines changed

4 files changed

+75
-7
lines changed

activerecord/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* Allow per attribute setting of YAML permitted classes (safe load) and unsafe load.
2+
3+
*Carlos Palhares*
4+
15
* Add a build persistence method
26

37
Provides a wrapper for `new`, to provide feature parity with `create`s

activerecord/lib/active_record/attribute_methods/serialization.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ module ClassMethods
5151
# using the coder's <tt>dump(value)</tt> method, and will be
5252
# deserialized using the coder's <tt>load(string)</tt> method. The
5353
# +dump+ method may return +nil+ to serialize the value as +NULL+.
54+
# * +yaml+ - Optional. Yaml specific options. The allowed config is:
55+
# * +:permitted_classes+ - +Array+ with the permitted classes.
56+
# * +:unsafe_load+ - Unsafely load YAML blobs, allow YAML to load any class
5457
#
5558
# ==== Options
5659
#
@@ -78,6 +81,12 @@ module ClassMethods
7881
# serialize :preferences, Hash
7982
# end
8083
#
84+
# ===== Serializes +preferences+ to YAML, permitting select classes
85+
#
86+
# class User < ActiveRecord::Base
87+
# serialize :preferences, yaml: { permitted_classes: [Symbol, Time] }
88+
# end
89+
#
8190
# ===== Serialize the +preferences+ attribute using a custom coder
8291
#
8392
# class Rot13JSON
@@ -100,7 +109,7 @@ module ClassMethods
100109
# serialize :preferences, Rot13JSON
101110
# end
102111
#
103-
def serialize(attr_name, class_name_or_coder = Object, **options)
112+
def serialize(attr_name, class_name_or_coder = Object, yaml: {}, **options)
104113
# When ::JSON is used, force it to go through the Active Support JSON encoder
105114
# to ensure special objects (e.g. Active Record models) are dumped correctly
106115
# using the #as_json hook.
@@ -109,7 +118,7 @@ def serialize(attr_name, class_name_or_coder = Object, **options)
109118
elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
110119
class_name_or_coder
111120
else
112-
Coders::YAMLColumn.new(attr_name, class_name_or_coder)
121+
Coders::YAMLColumn.new(attr_name, class_name_or_coder, **yaml)
113122
end
114123

115124
attribute(attr_name, **options) do |cast_type|

activerecord/lib/active_record/coders/yaml_column.rb

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ module Coders # :nodoc:
77
class YAMLColumn # :nodoc:
88
attr_accessor :object_class
99

10-
def initialize(attr_name, object_class = Object)
10+
def initialize(attr_name, object_class = Object, permitted_classes: [], unsafe_load: nil)
1111
@attr_name = attr_name
1212
@object_class = object_class
13+
@permitted_classes = permitted_classes
14+
@unsafe_load = unsafe_load
1315
check_arity_of_constructor
1416
end
1517

@@ -39,6 +41,14 @@ def assert_valid_value(obj, action:)
3941
end
4042

4143
private
44+
def permitted_classes
45+
[*ActiveRecord.yaml_column_permitted_classes, *@permitted_classes]
46+
end
47+
48+
def unsafe_load?
49+
@unsafe_load.nil? ? ActiveRecord.use_yaml_unsafe_load : @unsafe_load
50+
end
51+
4252
def check_arity_of_constructor
4353
load(nil)
4454
rescue ArgumentError
@@ -47,18 +57,18 @@ def check_arity_of_constructor
4757

4858
if YAML.respond_to?(:unsafe_load)
4959
def yaml_load(payload)
50-
if ActiveRecord.use_yaml_unsafe_load
60+
if unsafe_load?
5161
YAML.unsafe_load(payload)
5262
else
53-
YAML.safe_load(payload, permitted_classes: ActiveRecord.yaml_column_permitted_classes, aliases: true)
63+
YAML.safe_load(payload, permitted_classes: permitted_classes, aliases: true)
5464
end
5565
end
5666
else
5767
def yaml_load(payload)
58-
if ActiveRecord.use_yaml_unsafe_load
68+
if unsafe_load?
5969
YAML.load(payload)
6070
else
61-
YAML.safe_load(payload, permitted_classes: ActiveRecord.yaml_column_permitted_classes, aliases: true)
71+
YAML.safe_load(payload, permitted_classes: permitted_classes, aliases: true)
6272
end
6373
end
6474
end

activerecord/test/cases/coders/yaml_column_test.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,51 @@ def test_yaml_column_permitted_classes_are_consumed_by_safe_load
8888
ActiveRecord.yaml_column_permitted_classes = @yaml_column_permitted_classes_default
8989
end
9090

91+
def test_yaml_column_permitted_classes_option
92+
ActiveRecord.yaml_column_permitted_classes = [Symbol]
93+
94+
coder = YAMLColumn.new("attr_name", permitted_classes: [Time])
95+
time_yaml = YAML.dump(Time.new)
96+
symbol_yaml = YAML.dump(:somesymbol)
97+
98+
assert_nothing_raised do
99+
coder.load(time_yaml)
100+
coder.load(symbol_yaml)
101+
end
102+
103+
ActiveRecord.yaml_column_permitted_classes = @yaml_column_permitted_classes_default
104+
end
105+
106+
def test_yaml_column_unsafe_load_option
107+
ActiveRecord.use_yaml_unsafe_load = false
108+
ActiveRecord.yaml_column_permitted_classes = []
109+
110+
coder = YAMLColumn.new("attr_name", unsafe_load: true)
111+
time_yaml = YAML.dump(Time.new)
112+
symbol_yaml = YAML.dump(:somesymbol)
113+
114+
assert_nothing_raised do
115+
coder.load(time_yaml)
116+
coder.load(symbol_yaml)
117+
end
118+
119+
ActiveRecord.yaml_column_permitted_classes = @yaml_column_permitted_classes_default
120+
end
121+
122+
def test_yaml_column_override_unsafe_load_option
123+
ActiveRecord.use_yaml_unsafe_load = true
124+
ActiveRecord.yaml_column_permitted_classes = []
125+
126+
coder = YAMLColumn.new("attr_name", unsafe_load: false)
127+
time_yaml = YAML.dump(Time.new)
128+
129+
assert_raises(Psych::DisallowedClass) do
130+
coder.load(time_yaml)
131+
end
132+
133+
ActiveRecord.yaml_column_permitted_classes = @yaml_column_permitted_classes_default
134+
end
135+
91136
def test_load_doesnt_handle_undefined_class_or_module
92137
coder = YAMLColumn.new("attr_name")
93138
missing_class_yaml = '--- !ruby/object:DoesNotExistAndShouldntEver {}\n'

0 commit comments

Comments
 (0)