mongoid 9.1.0 → 9.1.2
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/config/locales/en.yml +69 -0
- data/lib/mongoid/association/accessors.rb +27 -5
- data/lib/mongoid/association/depending.rb +14 -10
- data/lib/mongoid/association/eager.rb +9 -0
- data/lib/mongoid/association/eager_load/discriminated_inclusion.rb +88 -0
- data/lib/mongoid/association/eager_load/embedded_distributor.rb +151 -0
- data/lib/mongoid/association/eager_load/inclusion.rb +173 -0
- data/lib/mongoid/association/eager_load/inclusion_tree.rb +96 -0
- data/lib/mongoid/association/eager_load/lookup_pipeline.rb +121 -0
- data/lib/mongoid/association/eager_load/polymorphic_preloader.rb +61 -0
- data/lib/mongoid/association/eager_load/polymorphic_targets.rb +151 -0
- data/lib/mongoid/association/eager_loadable.rb +65 -101
- data/lib/mongoid/association/embedded/batchable.rb +17 -1
- data/lib/mongoid/association/embedded/embeds_many/proxy.rb +6 -1
- data/lib/mongoid/association/embedded/embeds_one.rb +1 -0
- data/lib/mongoid/association/macros.rb +13 -1
- data/lib/mongoid/association/nested/many.rb +34 -5
- data/lib/mongoid/association/nested/nested_buildable.rb +14 -0
- data/lib/mongoid/association/nested/one.rb +6 -1
- data/lib/mongoid/association/options.rb +17 -0
- data/lib/mongoid/association/referenced/belongs_to/eager.rb +6 -1
- data/lib/mongoid/association/referenced/belongs_to.rb +1 -0
- data/lib/mongoid/association/referenced/counter_cache.rb +3 -3
- data/lib/mongoid/association/referenced/has_and_belongs_to_many/buildable.rb +6 -0
- data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +5 -0
- data/lib/mongoid/association/referenced/has_many/enumerable.rb +3 -1
- data/lib/mongoid/association/referenced/has_many/proxy.rb +100 -1
- data/lib/mongoid/association/referenced/has_many_through/eager.rb +99 -0
- data/lib/mongoid/association/referenced/has_many_through/proxy.rb +68 -0
- data/lib/mongoid/association/referenced/has_many_through.rb +201 -0
- data/lib/mongoid/association/referenced/has_one.rb +1 -0
- data/lib/mongoid/association/referenced/has_one_through/eager.rb +65 -0
- data/lib/mongoid/association/referenced/has_one_through/proxy.rb +30 -0
- data/lib/mongoid/association/referenced/has_one_through.rb +167 -0
- data/lib/mongoid/association/referenced.rb +2 -0
- data/lib/mongoid/association/relatable.rb +58 -0
- data/lib/mongoid/association.rb +15 -0
- data/lib/mongoid/atomic/modifiers.rb +100 -16
- data/lib/mongoid/clients/factory.rb +9 -13
- data/lib/mongoid/collection_configurable.rb +8 -0
- data/lib/mongoid/config/defaults.rb +0 -1
- data/lib/mongoid/config/encryption.rb +170 -22
- data/lib/mongoid/config.rb +56 -5
- data/lib/mongoid/contextual/aggregable/memory.rb +6 -2
- data/lib/mongoid/contextual/memory.rb +18 -7
- data/lib/mongoid/criteria/includable.rb +35 -11
- data/lib/mongoid/criteria/queryable/mergeable.rb +4 -0
- data/lib/mongoid/criteria/queryable/selectable.rb +123 -7
- data/lib/mongoid/deprecable.rb +45 -2
- data/lib/mongoid/document.rb +3 -0
- data/lib/mongoid/encryptable.rb +73 -0
- data/lib/mongoid/errors/config_redactor.rb +43 -0
- data/lib/mongoid/errors/in_memory_regexp_timeout.rb +26 -0
- data/lib/mongoid/errors/invalid_encryption_schema.rb +27 -0
- data/lib/mongoid/errors/mixed_client_configuration.rb +1 -1
- data/lib/mongoid/errors/no_client_database.rb +1 -1
- data/lib/mongoid/errors/no_client_hosts.rb +1 -1
- data/lib/mongoid/errors/no_encryption_schema.rb +30 -0
- data/lib/mongoid/errors/readonly_association.rb +29 -0
- data/lib/mongoid/errors.rb +5 -0
- data/lib/mongoid/extensions/vector.rb +64 -0
- data/lib/mongoid/extensions.rb +1 -0
- data/lib/mongoid/field_readable.rb +69 -0
- data/lib/mongoid/fields.rb +7 -0
- data/lib/mongoid/indexable.rb +38 -26
- data/lib/mongoid/matchable.rb +6 -1
- data/lib/mongoid/matcher/eq_impl_with_regexp.rb +2 -4
- data/lib/mongoid/matcher/regex.rb +11 -12
- data/lib/mongoid/matcher/regexp_budget.rb +372 -0
- data/lib/mongoid/matcher.rb +1 -0
- data/lib/mongoid/persistable/creatable.rb +120 -14
- data/lib/mongoid/persistable/updatable.rb +9 -11
- data/lib/mongoid/persistable.rb +15 -15
- data/lib/mongoid/persistence_context.rb +77 -0
- data/lib/mongoid/search_indexable.rb +69 -24
- data/lib/mongoid/serializable.rb +12 -4
- data/lib/mongoid/tasks/database.rb +33 -16
- data/lib/mongoid/threaded.rb +36 -0
- data/lib/mongoid/timestamps/timeless.rb +84 -10
- data/lib/mongoid/touchable.rb +28 -1
- data/lib/mongoid/traversable.rb +2 -2
- data/lib/mongoid/validatable/associated.rb +18 -2
- data/lib/mongoid/version.rb +1 -1
- data/lib/mongoid/warnings.rb +4 -0
- data/spec/integration/app_spec.rb +7 -1
- data/spec/integration/associations/belongs_to_spec.rb +13 -0
- data/spec/integration/associations/embedded_delayed_atomic_sets_spec.rb +216 -0
- data/spec/integration/associations/has_and_belongs_to_many_spec.rb +31 -2
- data/spec/integration/atomic/modifiers_spec.rb +283 -0
- data/spec/integration/dots_and_dollars_spec.rb +12 -2
- data/spec/integration/encryption_spec.rb +300 -0
- data/spec/integration/matcher_operator_data/regex.yml +21 -0
- data/spec/integration/matcher_regexp_timeout_spec.rb +215 -0
- data/spec/integration/query_operator_guard_spec.rb +89 -0
- data/spec/mongoid/association/accessors_spec.rb +35 -0
- data/spec/mongoid/association/eager_loadable_spec.rb +47 -0
- data/spec/mongoid/association/eager_spec.rb +2 -2
- data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +33 -0
- data/spec/mongoid/association/fallback_spec.rb +425 -0
- data/spec/mongoid/association/macros_spec.rb +26 -0
- data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +17 -0
- data/spec/mongoid/association/referenced/has_and_belongs_to_many/buildable_spec.rb +11 -0
- data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +48 -0
- data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +50 -0
- data/spec/mongoid/association/referenced/has_many/eager_spec.rb +33 -0
- data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +145 -0
- data/spec/mongoid/association/referenced/has_many_through/eager_spec.rb +58 -0
- data/spec/mongoid/association/referenced/has_many_through/proxy_spec.rb +62 -0
- data/spec/mongoid/association/referenced/has_many_through_spec.rb +399 -0
- data/spec/mongoid/association/referenced/has_one_through/eager_spec.rb +58 -0
- data/spec/mongoid/association/referenced/has_one_through/proxy_spec.rb +49 -0
- data/spec/mongoid/association/referenced/has_one_through_spec.rb +211 -0
- data/spec/mongoid/atomic/modifiers_spec.rb +347 -0
- data/spec/mongoid/attributes/nested_spec.rb +278 -8
- data/spec/mongoid/clients/factory_spec.rb +36 -0
- data/spec/mongoid/config/defaults_spec.rb +33 -0
- data/spec/mongoid/config/encryption_spec.rb +451 -0
- data/spec/mongoid/contextual/aggregable/memory_spec.rb +91 -0
- data/spec/mongoid/contextual/memory_spec.rb +177 -0
- data/spec/mongoid/criteria/includable_spec.rb +1221 -0
- data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +15 -1
- data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +201 -0
- data/spec/mongoid/criteria_spec.rb +3 -0
- data/spec/mongoid/encryptable_spec.rb +115 -0
- data/spec/mongoid/errors/config_redactor_spec.rb +112 -0
- data/spec/mongoid/errors/mixed_client_configuration_spec.rb +31 -0
- data/spec/mongoid/errors/no_client_database_spec.rb +29 -0
- data/spec/mongoid/errors/no_client_hosts_spec.rb +26 -0
- data/spec/mongoid/errors/readonly_association_spec.rb +34 -0
- data/spec/mongoid/extensions/vector_spec.rb +175 -0
- data/spec/mongoid/fields_spec.rb +8 -0
- data/spec/mongoid/matcher/regexp_budget_spec.rb +570 -0
- data/spec/mongoid/search_indexable_spec.rb +321 -9
- data/spec/mongoid/tasks/database_spec.rb +31 -0
- data/spec/mongoid/timestamps/timeless_spec.rb +197 -5
- data/spec/mongoid/touchable_spec.rb +242 -0
- data/spec/mongoid/touchable_spec_models.rb +47 -0
- data/spec/mongoid/validatable/associated_spec.rb +19 -0
- data/spec/spec_helper.rb +5 -1
- data/spec/support/crypt/models.rb +253 -0
- data/spec/support/models/modifier_models.rb +65 -0
- metadata +55 -2
|
@@ -72,6 +72,19 @@ describe Mongoid::Config::Encryption do
|
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
# Models are registered in class load order, and Rails eager loads
|
|
76
|
+
# app/models alphabetically, so a child whose file name sorts before
|
|
77
|
+
# its parent's arrives first.
|
|
78
|
+
context 'when the embedded model comes before its parent' do
|
|
79
|
+
let(:models) do
|
|
80
|
+
[ Crypt::Insurance, Crypt::Patient ]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'returns a map of encryption schemas' do
|
|
84
|
+
expect(encryption_schema_map).to eq(expected_schema_map)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
75
88
|
context 'and fields do not have encryption options' do
|
|
76
89
|
let(:models) do
|
|
77
90
|
[ Crypt::Car ]
|
|
@@ -106,6 +119,7 @@ describe Mongoid::Config::Encryption do
|
|
|
106
119
|
let(:expected_schema_map) do
|
|
107
120
|
{
|
|
108
121
|
'mongoid_test.crypt_users' => {
|
|
122
|
+
'bsonType' => 'object',
|
|
109
123
|
'properties' => {
|
|
110
124
|
'name' => {
|
|
111
125
|
'encrypt' => {
|
|
@@ -145,5 +159,442 @@ describe Mongoid::Config::Encryption do
|
|
|
145
159
|
expect(encryption_schema_map).to eq({})
|
|
146
160
|
end
|
|
147
161
|
end
|
|
162
|
+
|
|
163
|
+
# A field left out of the schema map is written in plaintext, with no
|
|
164
|
+
# exception and nothing logged, so an omission here is silent data
|
|
165
|
+
# exposure rather than a broken feature.
|
|
166
|
+
context 'when the encrypted fields are on an embedded model' do
|
|
167
|
+
let(:token_properties) do
|
|
168
|
+
{
|
|
169
|
+
'bsonType' => 'object',
|
|
170
|
+
'properties' => {
|
|
171
|
+
'value' => {
|
|
172
|
+
'encrypt' => {
|
|
173
|
+
'bsonType' => 'string',
|
|
174
|
+
'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
context 'when two models embed the same encrypted model' do
|
|
182
|
+
let(:models) do
|
|
183
|
+
[ Crypt::Vault, Crypt::Chest ]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it 'maps the embedded model for the first parent' do
|
|
187
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_vaults', 'properties', 'token'))
|
|
188
|
+
.to eq(token_properties)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it 'maps the embedded model for the second parent' do
|
|
192
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_chests', 'properties', 'token'))
|
|
193
|
+
.to eq(token_properties)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
context 'when one model embeds the same encrypted model twice' do
|
|
198
|
+
let(:models) do
|
|
199
|
+
[ Crypt::Ledger ]
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
it 'maps the first relation' do
|
|
203
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_ledgers', 'properties', 'primary_token'))
|
|
204
|
+
.to eq(token_properties)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it 'maps the second relation' do
|
|
208
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_ledgers', 'properties', 'backup_token'))
|
|
209
|
+
.to eq(token_properties)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# relation_class constantizes, and a polymorphic embedded_in has no class
|
|
214
|
+
# to resolve, so the walk has to look at the relation type first.
|
|
215
|
+
context 'when the embedded model is embedded polymorphically' do
|
|
216
|
+
let(:models) do
|
|
217
|
+
[ Crypt::Vault ]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'does not raise' do
|
|
221
|
+
expect { encryption_schema_map }.not_to raise_error
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
context 'when the embedded model comes before its parent' do
|
|
226
|
+
let(:models) do
|
|
227
|
+
[ Crypt::Token, Crypt::Vault ]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it 'maps the embedded model' do
|
|
231
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_vaults', 'properties', 'token'))
|
|
232
|
+
.to eq(token_properties)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
context 'when the parent has no encrypted field of its own' do
|
|
237
|
+
let(:models) do
|
|
238
|
+
[ Crypt::Wallet, Crypt::Token ]
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
it 'maps the embedded model' do
|
|
242
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_wallets', 'properties', 'token'))
|
|
243
|
+
.to eq(token_properties)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
context 'when the encrypted model is nested three levels deep' do
|
|
248
|
+
let(:account_properties) do
|
|
249
|
+
{
|
|
250
|
+
'bsonType' => 'object',
|
|
251
|
+
'properties' => {
|
|
252
|
+
'credential' => {
|
|
253
|
+
'bsonType' => 'object',
|
|
254
|
+
'properties' => {
|
|
255
|
+
'secret' => {
|
|
256
|
+
'encrypt' => {
|
|
257
|
+
'bsonType' => 'string',
|
|
258
|
+
'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
context 'when the models are listed outermost first' do
|
|
268
|
+
let(:models) do
|
|
269
|
+
[ Crypt::Owner, Crypt::Account, Crypt::Credential ]
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it 'maps the whole subtree' do
|
|
273
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_owners', 'properties', 'account'))
|
|
274
|
+
.to eq(account_properties)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
context 'when the middle model comes first' do
|
|
279
|
+
let(:models) do
|
|
280
|
+
[ Crypt::Account, Crypt::Owner, Crypt::Credential ]
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it 'maps the whole subtree' do
|
|
284
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_owners', 'properties', 'account'))
|
|
285
|
+
.to eq(account_properties)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# The walk has to keep stopping at a model it is already inside of,
|
|
291
|
+
# otherwise a self-embedding model recurses forever.
|
|
292
|
+
context 'when a model embeds itself' do
|
|
293
|
+
let(:models) do
|
|
294
|
+
[ Crypt::Article ]
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it 'terminates' do
|
|
298
|
+
expect { encryption_schema_map }.not_to raise_error
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
it 'maps the embedded model without descending into the cycle' do
|
|
302
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_articles', 'properties', 'comment')).to eq(
|
|
303
|
+
'bsonType' => 'object',
|
|
304
|
+
'properties' => {
|
|
305
|
+
'body' => {
|
|
306
|
+
'encrypt' => {
|
|
307
|
+
'bsonType' => 'string',
|
|
308
|
+
'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
context 'when an encrypted model has a callable database name' do
|
|
318
|
+
let(:models) do
|
|
319
|
+
[ Crypt::DynamicCar ]
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
it 'does not build a key from the unresolved callable' do
|
|
323
|
+
expect(encryption_schema_map.keys.grep(/Proc/)).to be_empty
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
it 'does not build a key that the model can never write to' do
|
|
327
|
+
expect(encryption_schema_map.keys).to all(satisfy { |key| key.count('.') == 1 })
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
context 'when an encrypted model has a callable database name that needs a tenant' do
|
|
332
|
+
let(:models) do
|
|
333
|
+
[ Crypt::TenantCar ]
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Resolving the callable while the map is built is not a fix: the
|
|
337
|
+
# documented multi-tenant idiom has no correct value at client
|
|
338
|
+
# construction time. Generating the map must not depend on it.
|
|
339
|
+
it 'does not raise' do
|
|
340
|
+
expect { encryption_schema_map }.not_to raise_error
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
it 'does not pin the map to whichever tenant happens to be current' do
|
|
344
|
+
Thread.current[:tenant_database] = 'tenant_a'
|
|
345
|
+
expect(encryption_schema_map.keys).not_to include('tenant_a.crypt_tenant_cars')
|
|
346
|
+
ensure
|
|
347
|
+
Thread.current[:tenant_database] = nil
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# The map is generated over every model in the application, and a model may
|
|
352
|
+
# name an embedded class that is never defined. Resolving that name raises,
|
|
353
|
+
# which would take down every client build.
|
|
354
|
+
context 'when a model names an embedded class that is not defined' do
|
|
355
|
+
let(:models) do
|
|
356
|
+
[ Crypt::Drawer, Crypt::Cabinet ]
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
it 'does not raise' do
|
|
360
|
+
expect { encryption_schema_map }.not_to raise_error
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
it 'leaves out the model with nothing to encrypt' do
|
|
364
|
+
expect(encryption_schema_map.keys).not_to include('mongoid_test.crypt_drawers')
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
it 'still maps the encrypted fields of the model itself' do
|
|
368
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_cabinets', 'properties', 'label')).to eq(
|
|
369
|
+
'encrypt' => {
|
|
370
|
+
'bsonType' => 'string',
|
|
371
|
+
'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
|
|
372
|
+
}
|
|
373
|
+
)
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# The map has one entry per namespace. Every model that writes to the
|
|
378
|
+
# namespace, including subclasses that are never registered in
|
|
379
|
+
# Mongoid.models, has to be covered by that entry, or its encrypted fields
|
|
380
|
+
# are written in plaintext.
|
|
381
|
+
context 'when more than one model writes to a namespace' do
|
|
382
|
+
let(:encrypt_metadata) do
|
|
383
|
+
{
|
|
384
|
+
'keyId' => [ BSON::Binary.new(Base64.decode64('grolrnFVSSW9Gq04Q87R9Q=='), :uuid) ],
|
|
385
|
+
'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
|
|
386
|
+
}
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
let(:encrypted_string) do
|
|
390
|
+
{ 'encrypt' => { 'bsonType' => 'string' } }
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
context 'when two models are stored in one collection' do
|
|
394
|
+
let(:expected_entry) do
|
|
395
|
+
{
|
|
396
|
+
'bsonType' => 'object',
|
|
397
|
+
'encryptMetadata' => encrypt_metadata,
|
|
398
|
+
'properties' => {
|
|
399
|
+
'serial' => encrypted_string,
|
|
400
|
+
'holder' => encrypted_string
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
context 'when the models are registered in one order' do
|
|
406
|
+
let(:models) do
|
|
407
|
+
[ Crypt::Badge, Crypt::BadgeScan ]
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
it 'maps the fields of both models' do
|
|
411
|
+
expect(encryption_schema_map['mongoid_test.crypt_badges']).to eq(expected_entry)
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
context 'when the models are registered in the other order' do
|
|
416
|
+
let(:models) do
|
|
417
|
+
[ Crypt::BadgeScan, Crypt::Badge ]
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
it 'maps the fields of both models' do
|
|
421
|
+
expect(encryption_schema_map['mongoid_test.crypt_badges']).to eq(expected_entry)
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
context 'when a subclass adds an encrypted field to an encrypted parent' do
|
|
427
|
+
let(:expected_entry) do
|
|
428
|
+
{
|
|
429
|
+
'bsonType' => 'object',
|
|
430
|
+
'encryptMetadata' => encrypt_metadata,
|
|
431
|
+
'properties' => {
|
|
432
|
+
'ssn' => encrypted_string,
|
|
433
|
+
'salary' => encrypted_string,
|
|
434
|
+
'rate' => encrypted_string
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Subclasses are not registered in Mongoid.models, so only the parent
|
|
440
|
+
# is passed in.
|
|
441
|
+
context 'when only the parent is registered' do
|
|
442
|
+
let(:models) do
|
|
443
|
+
[ Crypt::Member ]
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
it 'maps the fields of the parent and of every subclass' do
|
|
447
|
+
expect(encryption_schema_map['mongoid_test.crypt_members']).to eq(expected_entry)
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
context 'when a subclass is registered as well' do
|
|
452
|
+
let(:models) do
|
|
453
|
+
[ Crypt::Member, Crypt::Employee ]
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
it 'maps the fields of the parent and of every subclass' do
|
|
457
|
+
expect(encryption_schema_map['mongoid_test.crypt_members']).to eq(expected_entry)
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
context 'when only a subclass has encrypted fields' do
|
|
463
|
+
let(:models) do
|
|
464
|
+
[ Crypt::Listing ]
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
it 'maps the fields of the subclass' do
|
|
468
|
+
expect(encryption_schema_map['mongoid_test.crypt_listings']).to eq(
|
|
469
|
+
'bsonType' => 'object',
|
|
470
|
+
'encryptMetadata' => encrypt_metadata,
|
|
471
|
+
'properties' => {
|
|
472
|
+
'owner_ssn' => encrypted_string
|
|
473
|
+
}
|
|
474
|
+
)
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
context 'when a subclass of an embeds_one target adds an encrypted field' do
|
|
479
|
+
let(:models) do
|
|
480
|
+
[ Crypt::Box ]
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
it 'maps the fields of the target and of its subclass' do
|
|
484
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_boxes', 'properties', 'item')).to eq(
|
|
485
|
+
'bsonType' => 'object',
|
|
486
|
+
'properties' => {
|
|
487
|
+
'label' => encrypted_string,
|
|
488
|
+
'seal' => encrypted_string
|
|
489
|
+
}
|
|
490
|
+
)
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
context 'when only a subclass of an embeds_one target has encrypted fields' do
|
|
495
|
+
let(:models) do
|
|
496
|
+
[ Crypt::Locker ]
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
it 'maps the fields of the subclass' do
|
|
500
|
+
expect(encryption_schema_map.dig('mongoid_test.crypt_lockers', 'properties', 'content')).to eq(
|
|
501
|
+
'bsonType' => 'object',
|
|
502
|
+
'encryptMetadata' => encrypt_metadata,
|
|
503
|
+
'properties' => {
|
|
504
|
+
'code' => encrypted_string
|
|
505
|
+
}
|
|
506
|
+
)
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# These models are not added to the shared support file: every client
|
|
511
|
+
# with automatic encryption builds the map over all registered models,
|
|
512
|
+
# and a conflict would make every such client fail to build.
|
|
513
|
+
context 'when models on one collection disagree on encryption' do
|
|
514
|
+
let(:defined_models) { [] }
|
|
515
|
+
|
|
516
|
+
after do
|
|
517
|
+
defined_models.each { |model| Mongoid.deregister_model(model) }
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def define_model(&block)
|
|
521
|
+
model = Class.new do
|
|
522
|
+
include Mongoid::Document
|
|
523
|
+
|
|
524
|
+
store_in collection: 'crypt_conflicts'
|
|
525
|
+
end
|
|
526
|
+
model.class_eval(&block)
|
|
527
|
+
defined_models << model
|
|
528
|
+
model
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
context 'when the same field is encrypted the same way' do
|
|
532
|
+
let(:models) do
|
|
533
|
+
[
|
|
534
|
+
define_model { field :code, type: String, encrypt: { deterministic: true } },
|
|
535
|
+
define_model { field :code, type: String, encrypt: { deterministic: true } }
|
|
536
|
+
]
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
it 'maps the field once' do
|
|
540
|
+
expect(encryption_schema_map['mongoid_test.crypt_conflicts']).to eq(
|
|
541
|
+
'bsonType' => 'object',
|
|
542
|
+
'properties' => {
|
|
543
|
+
'code' => {
|
|
544
|
+
'encrypt' => {
|
|
545
|
+
'bsonType' => 'string',
|
|
546
|
+
'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
)
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
context 'when the same field is encrypted differently' do
|
|
555
|
+
let(:models) do
|
|
556
|
+
[
|
|
557
|
+
define_model { field :code, type: String, encrypt: { deterministic: true } },
|
|
558
|
+
define_model { field :code, type: String, encrypt: { deterministic: false } }
|
|
559
|
+
]
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
it 'raises' do
|
|
563
|
+
expect { encryption_schema_map }.to raise_error(Mongoid::Errors::InvalidEncryptionSchema)
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
context 'when the collection metadata differs' do
|
|
568
|
+
let(:models) do
|
|
569
|
+
[
|
|
570
|
+
define_model do
|
|
571
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: true
|
|
572
|
+
field :code, type: String, encrypt: true
|
|
573
|
+
end,
|
|
574
|
+
define_model do
|
|
575
|
+
encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: false
|
|
576
|
+
field :name, type: String, encrypt: true
|
|
577
|
+
end
|
|
578
|
+
]
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
it 'raises' do
|
|
582
|
+
expect { encryption_schema_map }.to raise_error(Mongoid::Errors::InvalidEncryptionSchema)
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
context 'when a subclass encrypts an inherited field differently' do
|
|
587
|
+
let(:models) do
|
|
588
|
+
parent = define_model { field :code, type: String, encrypt: { deterministic: true } }
|
|
589
|
+
Class.new(parent) { field :code, type: String, encrypt: { deterministic: false } }
|
|
590
|
+
[ parent ]
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
it 'raises' do
|
|
594
|
+
expect { encryption_schema_map }.to raise_error(Mongoid::Errors::InvalidEncryptionSchema)
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
end
|
|
148
599
|
end
|
|
149
600
|
end
|
|
@@ -545,4 +545,95 @@ describe Mongoid::Contextual::Aggregable::Memory do
|
|
|
545
545
|
end
|
|
546
546
|
end
|
|
547
547
|
end
|
|
548
|
+
|
|
549
|
+
context 'when the field name is a method name' do
|
|
550
|
+
let!(:depeche) do
|
|
551
|
+
Band.create!(name: 'Depeche Mode', likes: 1000)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
let(:criteria) do
|
|
555
|
+
Band.all.tap do |crit|
|
|
556
|
+
crit.documents = [ depeche ]
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
it 'does not call the method for sum' do
|
|
561
|
+
expect(depeche).not_to receive(:delete)
|
|
562
|
+
expect(context.sum(:delete)).to eq(0)
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
it 'does not call the method for min' do
|
|
566
|
+
expect(depeche).not_to receive(:delete)
|
|
567
|
+
expect(context.min(:delete)).to be_nil
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
it 'does not call the method for max' do
|
|
571
|
+
expect(depeche).not_to receive(:delete)
|
|
572
|
+
expect(context.max(:delete)).to be_nil
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
it 'does not call the method for avg' do
|
|
576
|
+
expect(depeche).not_to receive(:delete)
|
|
577
|
+
expect(context.avg(:delete)).to be_nil
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
it 'does not call the method for aggregates' do
|
|
581
|
+
expect(depeche).not_to receive(:delete)
|
|
582
|
+
expect(context.aggregates(:delete))
|
|
583
|
+
.to eq('count' => 0, 'avg' => nil, 'max' => nil, 'min' => nil, 'sum' => 0)
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
it 'does not call a private method for avg' do
|
|
587
|
+
expect(depeche).not_to receive(:exit)
|
|
588
|
+
expect(context.avg(:exit)).to be_nil
|
|
589
|
+
end
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
context 'when the field is not defined' do
|
|
593
|
+
let!(:depeche) do
|
|
594
|
+
Band.create!(name: 'Depeche Mode', likes: 1000)
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
let(:criteria) do
|
|
598
|
+
Band.all.tap do |crit|
|
|
599
|
+
crit.documents = [ depeche ]
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
it 'returns zero for sum' do
|
|
604
|
+
expect(context.sum(:not_a_field)).to eq(0)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
it 'returns nil for min' do
|
|
608
|
+
expect(context.min(:not_a_field)).to be_nil
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
it 'returns nil for max' do
|
|
612
|
+
expect(context.max(:not_a_field)).to be_nil
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
it 'returns nil for avg' do
|
|
616
|
+
expect(context.avg(:not_a_field)).to be_nil
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
context 'when the field is the foreign key of a many to many association' do
|
|
621
|
+
let!(:preference) do
|
|
622
|
+
Preference.create!(name: 'nature')
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
let!(:person) do
|
|
626
|
+
Person.create!(preferences: [ preference ])
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
let(:criteria) do
|
|
630
|
+
Person.all.tap do |crit|
|
|
631
|
+
crit.documents = [ person ]
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
it 'aggregates the stored ids' do
|
|
636
|
+
expect(context.max(:preference_ids)).to eq([ preference.id ])
|
|
637
|
+
end
|
|
638
|
+
end
|
|
548
639
|
end
|