YAMLColumn: use YAML.safe_dump if available

One particularly annoying thing with YAMLColumn type restriction
is that it is only checked on load.

Which means if your code insert data with unsupported types, the
insert will work, but now you'll be unable to read the record, which
makes it hard to fix etc.

That's the reason why I implemented `YAML.safe_dump` (https://github.com/ruby/psych/pull/495).

It applies exactly the same restrictions than `safe_load`, which means
if you attempt to store non-permitted fields, it will fail on insertion
and not on further reads, so you won't create an invalid record in your
database.
This commit is contained in:
Jean Boussier 2023-01-22 08:32:03 -05:00
parent d6eec533c1
commit 3635d2d9cf
4 changed files with 40 additions and 5 deletions

@ -333,7 +333,7 @@ GEM
activesupport (>= 7.0.0)
rack
railties (>= 7.0.0)
psych (5.0.1)
psych (5.1.0)
stringio
public_suffix (5.0.1)
puma (6.0.2)

@ -1,3 +1,14 @@
* YAML columns use `YAML.safe_dump` is available.
As of `psych 5.1.0`, `YAML.safe_dump` can now apply the same permitted
types restrictions than `YAML.safe_load`.
It's preferable to ensure the payload only use allowed types when we first
try to serialize it, otherwise you may end up with invalid records in the
database.
*Jean Boussier*
* `ActiveRecord::QueryLogs` better handle broken encoding.
It's not uncommon when building queries with BLOB fields to contain

@ -26,11 +26,24 @@ def init_with(coder) # :nodoc:
@unsafe_load = coder["unsafe_load"]
end
def dump(obj)
return if obj.nil?
if Gem::Version.new(Psych::VERSION) >= "5.1"
def dump(obj)
return if obj.nil?
assert_valid_value(obj, action: "dump")
YAML.dump obj
assert_valid_value(obj, action: "dump")
if unsafe_load?
YAML.dump(obj)
else
YAML.safe_dump(obj, permitted_classes: permitted_classes, aliases: true)
end
end
else
def dump(obj)
return if obj.nil?
assert_valid_value(obj, action: "dump")
YAML.dump obj
end
end
def load(yaml)

@ -97,6 +97,17 @@ def test_yaml_column_permitted_classes_are_consumed_by_safe_load
end
end
def test_yaml_column_permitted_classes_are_consumed_by_safe_dump
if Gem::Version.new(Psych::VERSION) < "5.1"
skip "YAML.safe_dump is either missing on unavailable on #{Psych::VERSION}"
end
coder = YAMLColumn.new("attr_name")
assert_raises(Psych::DisallowedClass) do
coder.dump([Time.new])
end
end
def test_yaml_column_permitted_classes_option
ActiveRecord.yaml_column_permitted_classes = [Symbol]