Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MONGOID-5810 make sure we don't leak internal state via as_document (backport to 8.0-stable) #5903

Open
wants to merge 1 commit into
base: 8.0-stable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion lib/mongoid/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,14 @@ def to_key
#
# @return [ Hash ] A hash of all attributes in the hierarchy.
def as_document
BSON::Document.new(as_attributes)
attrs = as_attributes

# legacy attributes have a tendency to leak internal state via
# `as_document`; we have to deep_dup the attributes here to prevent
# that.
attrs = attrs.deep_dup if Mongoid.legacy_attributes

BSON::Document.new(attrs)
end

# Calls #as_json on the document with additional, Mongoid-specific options.
Expand Down
27 changes: 27 additions & 0 deletions spec/mongoid/document_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,33 @@ class << self; attr_accessor :name; end
expect(person.as_document["addresses"].first).to have_key(:locations)
end

context 'when modifying the returned object' do
let(:record) do
RootCategory.create(categories: [{ name: 'tests' }]).reload
end

shared_examples_for 'an object with protected internal state' do
it 'does not expose internal state' do
before_change = record.as_document.dup
record.categories.first.name = 'things'
after_change = record.as_document
expect(before_change['categories'].first['name']).not_to eq('things')
end
end

context 'when legacy_attributes is true' do
config_override :legacy_attributes, true

it_behaves_like 'an object with protected internal state'
end

context 'when legacy_attributes is false' do
config_override :legacy_attributes, false

it_behaves_like 'an object with protected internal state'
end
end

context "with relation define store_as option in embeded_many" do

let!(:phone) do
Expand Down
Loading