mongoid 9.1.1 → 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 +31 -11
- data/lib/mongoid/association/accessors.rb +18 -4
- data/lib/mongoid/association/eager_load/discriminated_inclusion.rb +10 -10
- data/lib/mongoid/association/eager_load/embedded_distributor.rb +6 -6
- data/lib/mongoid/association/eager_load/lookup_pipeline.rb +1 -1
- data/lib/mongoid/association/eager_loadable.rb +8 -1
- data/lib/mongoid/association/embedded/batchable.rb +17 -1
- data/lib/mongoid/association/nested/many.rb +1 -1
- data/lib/mongoid/association/referenced/has_and_belongs_to_many/buildable.rb +6 -0
- data/lib/mongoid/association/relatable.rb +18 -0
- data/lib/mongoid/association.rb +8 -0
- data/lib/mongoid/atomic/modifiers.rb +100 -16
- data/lib/mongoid/clients/factory.rb +2 -0
- data/lib/mongoid/config/encryption.rb +148 -17
- data/lib/mongoid/document.rb +3 -0
- data/lib/mongoid/encryptable.rb +42 -15
- data/lib/mongoid/errors/invalid_encryption_schema.rb +27 -0
- data/lib/mongoid/errors/no_encryption_schema.rb +5 -3
- data/lib/mongoid/errors.rb +1 -0
- data/lib/mongoid/fields.rb +7 -0
- data/lib/mongoid/persistable/creatable.rb +120 -14
- data/lib/mongoid/persistable.rb +15 -15
- data/lib/mongoid/persistence_context.rb +46 -4
- data/lib/mongoid/search_indexable.rb +5 -1
- data/lib/mongoid/tasks/database.rb +10 -7
- data/lib/mongoid/touchable.rb +28 -1
- data/lib/mongoid/version.rb +1 -1
- data/lib/mongoid/warnings.rb +4 -0
- data/spec/integration/associations/embedded_delayed_atomic_sets_spec.rb +216 -0
- data/spec/integration/associations/has_and_belongs_to_many_spec.rb +14 -0
- data/spec/integration/atomic/modifiers_spec.rb +283 -0
- data/spec/integration/encryption_spec.rb +149 -0
- data/spec/mongoid/association/accessors_spec.rb +35 -0
- data/spec/mongoid/association/referenced/has_and_belongs_to_many/buildable_spec.rb +11 -0
- data/spec/mongoid/atomic/modifiers_spec.rb +347 -0
- data/spec/mongoid/attributes/nested_spec.rb +29 -0
- data/spec/mongoid/clients/factory_spec.rb +36 -0
- data/spec/mongoid/config/encryption_spec.rb +223 -0
- data/spec/mongoid/criteria/includable_spec.rb +346 -0
- data/spec/mongoid/encryptable_spec.rb +54 -0
- data/spec/mongoid/fields_spec.rb +8 -0
- data/spec/mongoid/search_indexable_spec.rb +26 -0
- data/spec/mongoid/tasks/database_spec.rb +13 -0
- data/spec/mongoid/touchable_spec.rb +217 -0
- data/spec/mongoid/touchable_spec_models.rb +37 -0
- data/spec/support/crypt/models.rb +96 -0
- data/spec/support/models/modifier_models.rb +65 -0
- metadata +5 -2
|
@@ -373,5 +373,228 @@ describe Mongoid::Config::Encryption do
|
|
|
373
373
|
)
|
|
374
374
|
end
|
|
375
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
|
|
376
599
|
end
|
|
377
600
|
end
|