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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/config/locales/en.yml +31 -11
  3. data/lib/mongoid/association/accessors.rb +18 -4
  4. data/lib/mongoid/association/eager_load/discriminated_inclusion.rb +10 -10
  5. data/lib/mongoid/association/eager_load/embedded_distributor.rb +6 -6
  6. data/lib/mongoid/association/eager_load/lookup_pipeline.rb +1 -1
  7. data/lib/mongoid/association/eager_loadable.rb +8 -1
  8. data/lib/mongoid/association/embedded/batchable.rb +17 -1
  9. data/lib/mongoid/association/nested/many.rb +1 -1
  10. data/lib/mongoid/association/referenced/has_and_belongs_to_many/buildable.rb +6 -0
  11. data/lib/mongoid/association/relatable.rb +18 -0
  12. data/lib/mongoid/association.rb +8 -0
  13. data/lib/mongoid/atomic/modifiers.rb +100 -16
  14. data/lib/mongoid/clients/factory.rb +2 -0
  15. data/lib/mongoid/config/encryption.rb +148 -17
  16. data/lib/mongoid/document.rb +3 -0
  17. data/lib/mongoid/encryptable.rb +42 -15
  18. data/lib/mongoid/errors/invalid_encryption_schema.rb +27 -0
  19. data/lib/mongoid/errors/no_encryption_schema.rb +5 -3
  20. data/lib/mongoid/errors.rb +1 -0
  21. data/lib/mongoid/fields.rb +7 -0
  22. data/lib/mongoid/persistable/creatable.rb +120 -14
  23. data/lib/mongoid/persistable.rb +15 -15
  24. data/lib/mongoid/persistence_context.rb +46 -4
  25. data/lib/mongoid/search_indexable.rb +5 -1
  26. data/lib/mongoid/tasks/database.rb +10 -7
  27. data/lib/mongoid/touchable.rb +28 -1
  28. data/lib/mongoid/version.rb +1 -1
  29. data/lib/mongoid/warnings.rb +4 -0
  30. data/spec/integration/associations/embedded_delayed_atomic_sets_spec.rb +216 -0
  31. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +14 -0
  32. data/spec/integration/atomic/modifiers_spec.rb +283 -0
  33. data/spec/integration/encryption_spec.rb +149 -0
  34. data/spec/mongoid/association/accessors_spec.rb +35 -0
  35. data/spec/mongoid/association/referenced/has_and_belongs_to_many/buildable_spec.rb +11 -0
  36. data/spec/mongoid/atomic/modifiers_spec.rb +347 -0
  37. data/spec/mongoid/attributes/nested_spec.rb +29 -0
  38. data/spec/mongoid/clients/factory_spec.rb +36 -0
  39. data/spec/mongoid/config/encryption_spec.rb +223 -0
  40. data/spec/mongoid/criteria/includable_spec.rb +346 -0
  41. data/spec/mongoid/encryptable_spec.rb +54 -0
  42. data/spec/mongoid/fields_spec.rb +8 -0
  43. data/spec/mongoid/search_indexable_spec.rb +26 -0
  44. data/spec/mongoid/tasks/database_spec.rb +13 -0
  45. data/spec/mongoid/touchable_spec.rb +217 -0
  46. data/spec/mongoid/touchable_spec_models.rb +37 -0
  47. data/spec/support/crypt/models.rb +96 -0
  48. data/spec/support/models/modifier_models.rb +65 -0
  49. 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