mongoid 9.0.9 → 9.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/mongoid/attributes.rb +2 -1
- data/lib/mongoid/clients/factory.rb +4 -0
- data/lib/mongoid/criteria.rb +39 -5
- data/lib/mongoid/version.rb +1 -1
- data/spec/mongoid/attributes_spec.rb +17 -1
- data/spec/mongoid/clients/factory_spec.rb +32 -0
- data/spec/mongoid/criteria_spec.rb +196 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fefd49681b6f78b222f201fbaea83af9f1599464034e52a73117c0ae94e201f2
|
|
4
|
+
data.tar.gz: d86f966109921d2a0f1a4ce0b78d81e0d722123c1d420f0f695385ca43687626
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '09799846ca2a44c7dc4d7aff76fba0f9fdcbe38c5bfb001dccd08eb467800f8fac675cecfa06be94eb76ca35f6f1b57b64557ed171e9c0040bcbe0a701d7827f'
|
|
7
|
+
data.tar.gz: bc0a741e2e26a84e84238f1461a26a29ae13584d9bd050dffc1ccd04f70e4e6a7fa63b20e7dcda8137ce3c37d383a5ae916b0d72ff950d4f330e66e0635ea89c
|
data/lib/mongoid/attributes.rb
CHANGED
|
@@ -104,7 +104,8 @@ module Mongoid
|
|
|
104
104
|
# @api private
|
|
105
105
|
def process_raw_attribute(name, raw, field)
|
|
106
106
|
value = field ? field.demongoize(raw) : raw
|
|
107
|
-
|
|
107
|
+
is_relation = relations.key?(name)
|
|
108
|
+
attribute_will_change!(name) if value.resizable? && !is_relation
|
|
108
109
|
value
|
|
109
110
|
end
|
|
110
111
|
|
|
@@ -131,6 +131,10 @@ module Mongoid
|
|
|
131
131
|
[MONGOID_WRAPPING_LIBRARY] + options[:wrapping_libraries]
|
|
132
132
|
else
|
|
133
133
|
[MONGOID_WRAPPING_LIBRARY]
|
|
134
|
+
end.tap do |wrap|
|
|
135
|
+
if defined?(::Rails) && ::Rails.respond_to?(:version)
|
|
136
|
+
wrap << { name: 'Rails', version: ::Rails.version }
|
|
137
|
+
end
|
|
134
138
|
end
|
|
135
139
|
options[:wrapping_libraries] = wrap_lib
|
|
136
140
|
end
|
data/lib/mongoid/criteria.rb
CHANGED
|
@@ -41,24 +41,57 @@ module Mongoid
|
|
|
41
41
|
include Clients::Sessions
|
|
42
42
|
include Options
|
|
43
43
|
|
|
44
|
+
# Allowed methods for from_hash to prevent arbitrary method execution.
|
|
45
|
+
# Only query-building methods are allowed, not execution or modification methods.
|
|
46
|
+
ALLOWED_FROM_HASH_METHODS = %i[
|
|
47
|
+
all all_in all_of and any_in any_of asc ascending
|
|
48
|
+
batch_size between
|
|
49
|
+
collation comment cursor_type
|
|
50
|
+
desc descending
|
|
51
|
+
elem_match eq exists extras
|
|
52
|
+
geo_spatial group gt gte
|
|
53
|
+
hint
|
|
54
|
+
in includes
|
|
55
|
+
limit lt lte
|
|
56
|
+
max_distance max_scan max_time_ms merge mod
|
|
57
|
+
ne near near_sphere nin no_timeout none none_of nor not not_in
|
|
58
|
+
offset only or order order_by
|
|
59
|
+
project
|
|
60
|
+
raw read reorder
|
|
61
|
+
scoped skip slice snapshot
|
|
62
|
+
text_search type
|
|
63
|
+
unscoped unwind
|
|
64
|
+
where with_size with_type without
|
|
65
|
+
].freeze
|
|
66
|
+
|
|
44
67
|
class << self
|
|
45
68
|
# Convert the given hash to a criteria. Will iterate over each keys in the
|
|
46
|
-
# hash which must correspond to method on a criteria object. The hash
|
|
47
|
-
#
|
|
69
|
+
# hash which must correspond to an allowed method on a criteria object. The hash
|
|
70
|
+
# can include a "klass" key that specifies the model class for the criteria.
|
|
48
71
|
#
|
|
49
72
|
# @example Convert the hash to a criteria.
|
|
50
73
|
# Criteria.from_hash({ klass: Band, where: { name: "Depeche Mode" })
|
|
51
74
|
#
|
|
75
|
+
# @deprecated This method is deprecated and will
|
|
76
|
+
# be removed in a future release.
|
|
77
|
+
#
|
|
52
78
|
# @param [ Hash ] hash The hash to convert.
|
|
53
79
|
#
|
|
54
80
|
# @return [ Criteria ] The criteria.
|
|
81
|
+
#
|
|
82
|
+
# @raise [ ArgumentError ] If a method is not allowed in from_hash.
|
|
55
83
|
def from_hash(hash)
|
|
56
84
|
criteria = Criteria.new(hash.delete(:klass) || hash.delete('klass'))
|
|
57
85
|
hash.each_pair do |method, args|
|
|
58
|
-
|
|
86
|
+
method_sym = method.to_sym
|
|
87
|
+
unless ALLOWED_FROM_HASH_METHODS.include?(method_sym)
|
|
88
|
+
raise ArgumentError, "Method '#{method}' is not allowed in from_hash"
|
|
89
|
+
end
|
|
90
|
+
criteria = criteria.public_send(method_sym, args)
|
|
59
91
|
end
|
|
60
92
|
criteria
|
|
61
93
|
end
|
|
94
|
+
Mongoid.deprecate(self, :from_hash)
|
|
62
95
|
end
|
|
63
96
|
|
|
64
97
|
# Static array used to check with method missing - we only need to ever
|
|
@@ -246,7 +279,8 @@ module Mongoid
|
|
|
246
279
|
# criteria.merge(other_criteria)
|
|
247
280
|
#
|
|
248
281
|
# @example Merge the criteria with a hash. The hash must contain a klass
|
|
249
|
-
# key
|
|
282
|
+
# key that specifies the model class for the criteria and the key/value
|
|
283
|
+
# pairs correspond to method names/args.
|
|
250
284
|
#
|
|
251
285
|
# criteria.merge({
|
|
252
286
|
# klass: Band,
|
|
@@ -254,7 +288,7 @@ module Mongoid
|
|
|
254
288
|
# order_by: { name: 1 }
|
|
255
289
|
# })
|
|
256
290
|
#
|
|
257
|
-
# @param [ Criteria ] other The other criterion to merge with.
|
|
291
|
+
# @param [ Criteria | Hash ] other The other criterion to merge with.
|
|
258
292
|
#
|
|
259
293
|
# @return [ Criteria ] A cloned self.
|
|
260
294
|
def merge(other)
|
data/lib/mongoid/version.rb
CHANGED
|
@@ -2722,7 +2722,23 @@ describe Mongoid::Attributes do
|
|
|
2722
2722
|
end
|
|
2723
2723
|
end
|
|
2724
2724
|
|
|
2725
|
-
context "when
|
|
2725
|
+
context "when accessing an embedded document with the attribute accessor" do
|
|
2726
|
+
let(:band) { Band.create! }
|
|
2727
|
+
|
|
2728
|
+
before do
|
|
2729
|
+
Band.where(id: band.id).update_all({
|
|
2730
|
+
:$push => {records: { _id: BSON::ObjectId.new }}
|
|
2731
|
+
})
|
|
2732
|
+
end
|
|
2733
|
+
|
|
2734
|
+
it "does not throw a conflicting update error" do
|
|
2735
|
+
b1 = Band.find(band.id)
|
|
2736
|
+
b1[:records].is_a?(Array).should be true
|
|
2737
|
+
expect { b1.save! }.not_to raise_error
|
|
2738
|
+
end
|
|
2739
|
+
end
|
|
2740
|
+
|
|
2741
|
+
context "when modifying a set referenced with the [] notation" do
|
|
2726
2742
|
let(:catalog) { Catalog.create!(set_field: [ 1 ].to_set) }
|
|
2727
2743
|
|
|
2728
2744
|
before do
|
|
@@ -30,6 +30,34 @@ describe Mongoid::Clients::Factory do
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
shared_examples_for 'includes rails wrapping library' do
|
|
34
|
+
context 'when Rails is available' do
|
|
35
|
+
around do |example|
|
|
36
|
+
rails_was_defined = defined?(::Rails)
|
|
37
|
+
|
|
38
|
+
if !rails_was_defined
|
|
39
|
+
module ::Rails
|
|
40
|
+
def self.version
|
|
41
|
+
'6.1.0'
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
example.run
|
|
47
|
+
|
|
48
|
+
if !rails_was_defined
|
|
49
|
+
Object.send(:remove_const, :Rails) if defined?(::Rails)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'adds Rails as another wrapping library' do
|
|
54
|
+
expect(client.options[:wrapping_libraries]).to include(
|
|
55
|
+
{'name' => 'Rails', 'version' => '6.1.0'},
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
33
61
|
describe ".create" do
|
|
34
62
|
|
|
35
63
|
context "when provided a name" do
|
|
@@ -89,6 +117,8 @@ describe Mongoid::Clients::Factory do
|
|
|
89
117
|
Mongoid::Clients::Factory::MONGOID_WRAPPING_LIBRARY)]
|
|
90
118
|
end
|
|
91
119
|
|
|
120
|
+
it_behaves_like 'includes rails wrapping library'
|
|
121
|
+
|
|
92
122
|
context 'when configuration specifies a wrapping library' do
|
|
93
123
|
|
|
94
124
|
let(:config) do
|
|
@@ -110,6 +140,8 @@ describe Mongoid::Clients::Factory do
|
|
|
110
140
|
{'name' => 'Foo'},
|
|
111
141
|
]
|
|
112
142
|
end
|
|
143
|
+
|
|
144
|
+
it_behaves_like 'includes rails wrapping library'
|
|
113
145
|
end
|
|
114
146
|
end
|
|
115
147
|
|
|
@@ -3250,5 +3250,201 @@ describe Mongoid::Criteria do
|
|
|
3250
3250
|
expect(criteria.selector).to eq({ 'name' => 'Songs Ohia' })
|
|
3251
3251
|
end
|
|
3252
3252
|
end
|
|
3253
|
+
|
|
3254
|
+
context 'with allowed methods' do
|
|
3255
|
+
context 'when using multiple query methods' do
|
|
3256
|
+
let(:hash) do
|
|
3257
|
+
{
|
|
3258
|
+
klass: Band,
|
|
3259
|
+
where: { active: true },
|
|
3260
|
+
limit: 10,
|
|
3261
|
+
skip: 5,
|
|
3262
|
+
order_by: { name: 1 }
|
|
3263
|
+
}
|
|
3264
|
+
end
|
|
3265
|
+
|
|
3266
|
+
it 'applies all methods successfully' do
|
|
3267
|
+
expect(criteria.selector).to eq({ 'active' => true })
|
|
3268
|
+
expect(criteria.options[:limit]).to eq(10)
|
|
3269
|
+
expect(criteria.options[:skip]).to eq(5)
|
|
3270
|
+
expect(criteria.options[:sort]).to eq({ 'name' => 1 })
|
|
3271
|
+
end
|
|
3272
|
+
end
|
|
3273
|
+
|
|
3274
|
+
context 'when using query selector methods' do
|
|
3275
|
+
let(:hash) do
|
|
3276
|
+
{
|
|
3277
|
+
klass: Band,
|
|
3278
|
+
gt: { members: 2 },
|
|
3279
|
+
in: { genre: ['rock', 'metal'] }
|
|
3280
|
+
}
|
|
3281
|
+
end
|
|
3282
|
+
|
|
3283
|
+
it 'applies selector methods' do
|
|
3284
|
+
expect(criteria.selector['members']).to eq({ '$gt' => 2 })
|
|
3285
|
+
expect(criteria.selector['genre']).to eq({ '$in' => ['rock', 'metal'] })
|
|
3286
|
+
end
|
|
3287
|
+
end
|
|
3288
|
+
|
|
3289
|
+
context 'when using aggregation methods' do
|
|
3290
|
+
let(:hash) do
|
|
3291
|
+
{
|
|
3292
|
+
klass: Band,
|
|
3293
|
+
project: { name: 1, members: 1 }
|
|
3294
|
+
}
|
|
3295
|
+
end
|
|
3296
|
+
|
|
3297
|
+
it 'applies aggregation methods' do
|
|
3298
|
+
expect { criteria }.not_to raise_error
|
|
3299
|
+
end
|
|
3300
|
+
end
|
|
3301
|
+
end
|
|
3302
|
+
|
|
3303
|
+
context 'with disallowed methods' do
|
|
3304
|
+
context 'when attempting to call create' do
|
|
3305
|
+
let(:hash) do
|
|
3306
|
+
{ klass: Band, create: { name: 'Malicious' } }
|
|
3307
|
+
end
|
|
3308
|
+
|
|
3309
|
+
it 'raises ArgumentError' do
|
|
3310
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'create' is not allowed in from_hash")
|
|
3311
|
+
end
|
|
3312
|
+
end
|
|
3313
|
+
|
|
3314
|
+
context 'when attempting to call create!' do
|
|
3315
|
+
let(:hash) do
|
|
3316
|
+
{ klass: Band, 'create!': { name: 'Malicious' } }
|
|
3317
|
+
end
|
|
3318
|
+
|
|
3319
|
+
it 'raises ArgumentError' do
|
|
3320
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'create!' is not allowed in from_hash")
|
|
3321
|
+
end
|
|
3322
|
+
end
|
|
3323
|
+
|
|
3324
|
+
context 'when attempting to call build' do
|
|
3325
|
+
let(:hash) do
|
|
3326
|
+
{ klass: Band, build: { name: 'Malicious' } }
|
|
3327
|
+
end
|
|
3328
|
+
|
|
3329
|
+
it 'raises ArgumentError' do
|
|
3330
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'build' is not allowed in from_hash")
|
|
3331
|
+
end
|
|
3332
|
+
end
|
|
3333
|
+
|
|
3334
|
+
context 'when attempting to call find' do
|
|
3335
|
+
let(:hash) do
|
|
3336
|
+
{ klass: Band, find: 'some_id' }
|
|
3337
|
+
end
|
|
3338
|
+
|
|
3339
|
+
it 'raises ArgumentError' do
|
|
3340
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'find' is not allowed in from_hash")
|
|
3341
|
+
end
|
|
3342
|
+
end
|
|
3343
|
+
|
|
3344
|
+
context 'when attempting to call execute_or_raise' do
|
|
3345
|
+
let(:hash) do
|
|
3346
|
+
{ klass: Band, execute_or_raise: ['id1', 'id2'] }
|
|
3347
|
+
end
|
|
3348
|
+
|
|
3349
|
+
it 'raises ArgumentError' do
|
|
3350
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'execute_or_raise' is not allowed in from_hash")
|
|
3351
|
+
end
|
|
3352
|
+
end
|
|
3353
|
+
|
|
3354
|
+
context 'when attempting to call new' do
|
|
3355
|
+
let(:hash) do
|
|
3356
|
+
{ klass: Band, new: { name: 'Test' } }
|
|
3357
|
+
end
|
|
3358
|
+
|
|
3359
|
+
it 'raises ArgumentError' do
|
|
3360
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'new' is not allowed in from_hash")
|
|
3361
|
+
end
|
|
3362
|
+
end
|
|
3363
|
+
|
|
3364
|
+
context 'when allowed method is combined with disallowed method' do
|
|
3365
|
+
let(:hash) do
|
|
3366
|
+
{
|
|
3367
|
+
klass: Band,
|
|
3368
|
+
where: { active: true },
|
|
3369
|
+
create: { name: 'Malicious' }
|
|
3370
|
+
}
|
|
3371
|
+
end
|
|
3372
|
+
|
|
3373
|
+
it 'raises ArgumentError before executing any methods' do
|
|
3374
|
+
expect { criteria }.to raise_error(ArgumentError, "Method 'create' is not allowed in from_hash")
|
|
3375
|
+
end
|
|
3376
|
+
end
|
|
3377
|
+
end
|
|
3378
|
+
|
|
3379
|
+
context 'security validation' do
|
|
3380
|
+
# This test ensures that ALL public methods not in the allowlist are blocked
|
|
3381
|
+
it 'blocks all dangerous public methods' do
|
|
3382
|
+
dangerous_methods = %i[
|
|
3383
|
+
build create create! new
|
|
3384
|
+
find find_or_create_by find_or_create_by! find_or_initialize_by
|
|
3385
|
+
first_or_create first_or_create! first_or_initialize
|
|
3386
|
+
execute_or_raise multiple_from_db for_ids
|
|
3387
|
+
documents= inclusions= scoping_options=
|
|
3388
|
+
initialize freeze as_json
|
|
3389
|
+
]
|
|
3390
|
+
|
|
3391
|
+
dangerous_methods.each do |method|
|
|
3392
|
+
hash = { klass: Band, method => 'arg' }
|
|
3393
|
+
expect { described_class.from_hash(hash) }.to raise_error(
|
|
3394
|
+
ArgumentError,
|
|
3395
|
+
"Method '#{method}' is not allowed in from_hash"
|
|
3396
|
+
), "Expected method '#{method}' to be blocked but it was allowed"
|
|
3397
|
+
end
|
|
3398
|
+
end
|
|
3399
|
+
|
|
3400
|
+
it 'blocks dangerous inherited methods from Object' do
|
|
3401
|
+
# Critical security test: block send, instance_eval, etc.
|
|
3402
|
+
inherited_dangerous = %i[
|
|
3403
|
+
send __send__ instance_eval instance_exec
|
|
3404
|
+
instance_variable_set method
|
|
3405
|
+
]
|
|
3406
|
+
|
|
3407
|
+
inherited_dangerous.each do |method|
|
|
3408
|
+
hash = { klass: Band, method => 'arg' }
|
|
3409
|
+
expect { described_class.from_hash(hash) }.to raise_error(
|
|
3410
|
+
ArgumentError,
|
|
3411
|
+
"Method '#{method}' is not allowed in from_hash"
|
|
3412
|
+
), "Expected inherited method '#{method}' to be blocked"
|
|
3413
|
+
end
|
|
3414
|
+
end
|
|
3415
|
+
|
|
3416
|
+
it 'blocks Enumerable execution methods' do
|
|
3417
|
+
# from_hash should build queries, not execute them
|
|
3418
|
+
enumerable_methods = %i[each map select count sum]
|
|
3419
|
+
|
|
3420
|
+
enumerable_methods.each do |method|
|
|
3421
|
+
hash = { klass: Band, method => 'arg' }
|
|
3422
|
+
expect { described_class.from_hash(hash) }.to raise_error(
|
|
3423
|
+
ArgumentError,
|
|
3424
|
+
"Method '#{method}' is not allowed in from_hash"
|
|
3425
|
+
), "Expected Enumerable method '#{method}' to be blocked"
|
|
3426
|
+
end
|
|
3427
|
+
end
|
|
3428
|
+
|
|
3429
|
+
it 'allows all whitelisted methods' do
|
|
3430
|
+
# Sample of allowed methods from each category
|
|
3431
|
+
allowed_sample = {
|
|
3432
|
+
where: { name: 'Test' }, # Query selector
|
|
3433
|
+
limit: 10, # Query option
|
|
3434
|
+
skip: 5, # Query option
|
|
3435
|
+
gt: { age: 18 }, # Query selector
|
|
3436
|
+
in: { status: ['active'] }, # Query selector
|
|
3437
|
+
ascending: :name, # Sorting
|
|
3438
|
+
includes: :notes, # Eager loading
|
|
3439
|
+
merge: { klass: Band }, # Merge
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
allowed_sample.each do |method, args|
|
|
3443
|
+
hash = { klass: Band, method => args }
|
|
3444
|
+
expect { described_class.from_hash(hash) }.not_to raise_error,
|
|
3445
|
+
"Expected method '#{method}' to be allowed but it was blocked"
|
|
3446
|
+
end
|
|
3447
|
+
end
|
|
3448
|
+
end
|
|
3253
3449
|
end
|
|
3254
3450
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mongoid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 9.0.
|
|
4
|
+
version: 9.0.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- The MongoDB Ruby Team
|
|
@@ -1231,7 +1231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
1231
1231
|
- !ruby/object:Gem::Version
|
|
1232
1232
|
version: 1.3.6
|
|
1233
1233
|
requirements: []
|
|
1234
|
-
rubygems_version: 4.0.
|
|
1234
|
+
rubygems_version: 4.0.4
|
|
1235
1235
|
specification_version: 4
|
|
1236
1236
|
summary: Elegant Persistence in Ruby for MongoDB.
|
|
1237
1237
|
test_files:
|