mongoid 9.1.0 → 9.1.1
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 +49 -0
- data/lib/mongoid/association/accessors.rb +9 -1
- 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 +57 -100
- 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.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 +40 -0
- data/lib/mongoid/association.rb +7 -0
- data/lib/mongoid/clients/factory.rb +7 -13
- data/lib/mongoid/collection_configurable.rb +8 -0
- data/lib/mongoid/config/defaults.rb +0 -1
- data/lib/mongoid/config/encryption.rb +36 -19
- 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/encryptable.rb +46 -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/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 +28 -0
- data/lib/mongoid/errors/readonly_association.rb +29 -0
- data/lib/mongoid/errors.rb +4 -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/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 +1 -1
- data/lib/mongoid/persistable/updatable.rb +9 -11
- data/lib/mongoid/persistence_context.rb +35 -0
- data/lib/mongoid/search_indexable.rb +64 -23
- data/lib/mongoid/serializable.rb +12 -4
- data/lib/mongoid/tasks/database.rb +23 -9
- data/lib/mongoid/threaded.rb +36 -0
- data/lib/mongoid/timestamps/timeless.rb +84 -10
- data/lib/mongoid/traversable.rb +2 -2
- data/lib/mongoid/validatable/associated.rb +18 -2
- data/lib/mongoid/version.rb +1 -1
- data/spec/integration/app_spec.rb +7 -1
- data/spec/integration/associations/belongs_to_spec.rb +13 -0
- data/spec/integration/associations/has_and_belongs_to_many_spec.rb +17 -2
- data/spec/integration/dots_and_dollars_spec.rb +12 -2
- data/spec/integration/encryption_spec.rb +151 -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/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/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/attributes/nested_spec.rb +249 -8
- data/spec/mongoid/config/defaults_spec.rb +33 -0
- data/spec/mongoid/config/encryption_spec.rb +228 -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 +875 -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 +61 -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/matcher/regexp_budget_spec.rb +570 -0
- data/spec/mongoid/search_indexable_spec.rb +295 -9
- data/spec/mongoid/tasks/database_spec.rb +18 -0
- data/spec/mongoid/timestamps/timeless_spec.rb +197 -5
- data/spec/mongoid/touchable_spec.rb +25 -0
- data/spec/mongoid/touchable_spec_models.rb +10 -0
- data/spec/mongoid/validatable/associated_spec.rb +19 -0
- data/spec/spec_helper.rb +5 -1
- data/spec/support/crypt/models.rb +157 -0
- metadata +52 -2
|
@@ -1589,7 +1589,14 @@ describe Mongoid::Criteria::Queryable::Selectable do
|
|
|
1589
1589
|
end
|
|
1590
1590
|
|
|
1591
1591
|
describe '#not' do
|
|
1592
|
+
# deprecated functionality
|
|
1592
1593
|
context 'when provided no criterion' do
|
|
1594
|
+
around do |example|
|
|
1595
|
+
Mongoid.deprecator.silence do
|
|
1596
|
+
example.run
|
|
1597
|
+
end
|
|
1598
|
+
end
|
|
1599
|
+
|
|
1593
1600
|
let(:selection) do
|
|
1594
1601
|
query.not
|
|
1595
1602
|
end
|
|
@@ -1725,6 +1732,9 @@ describe Mongoid::Criteria::Queryable::Selectable do
|
|
|
1725
1732
|
end
|
|
1726
1733
|
|
|
1727
1734
|
context 'when the following criteria uses string were form' do
|
|
1735
|
+
# String criteria compile to $where, which requires the opt-in.
|
|
1736
|
+
config_override :allow_unsafe_query_operators, true
|
|
1737
|
+
|
|
1728
1738
|
let(:selection) do
|
|
1729
1739
|
query.not.where('hello world')
|
|
1730
1740
|
end
|
|
@@ -1855,9 +1865,13 @@ describe Mongoid::Criteria::Queryable::Selectable do
|
|
|
1855
1865
|
it_behaves_like 'returns a cloned query'
|
|
1856
1866
|
end
|
|
1857
1867
|
|
|
1868
|
+
# This context tests a deprecated syntax and behavior, and must be
|
|
1869
|
+
# removed when the deprecated parameterless-not syntax is removed.
|
|
1858
1870
|
context 'when the criterion are a double negative' do
|
|
1859
1871
|
let(:selection) do
|
|
1860
|
-
|
|
1872
|
+
Mongoid.deprecator.silence do
|
|
1873
|
+
query.not.where(:first.not => /1/)
|
|
1874
|
+
end
|
|
1861
1875
|
end
|
|
1862
1876
|
|
|
1863
1877
|
it 'does not double the $not selector' do
|
|
@@ -32,6 +32,9 @@ describe Mongoid::Criteria::Queryable::Selectable do
|
|
|
32
32
|
it_behaves_like 'requires a non-nil argument'
|
|
33
33
|
|
|
34
34
|
context 'when provided a string' do
|
|
35
|
+
# String criteria compile to $where, which requires the opt-in.
|
|
36
|
+
config_override :allow_unsafe_query_operators, true
|
|
37
|
+
|
|
35
38
|
let(:selection) do
|
|
36
39
|
query.where('this.value = 10')
|
|
37
40
|
end
|
|
@@ -550,4 +553,202 @@ describe Mongoid::Criteria::Queryable::Selectable do
|
|
|
550
553
|
end
|
|
551
554
|
end
|
|
552
555
|
end
|
|
556
|
+
|
|
557
|
+
describe 'top-level operator injection guard' do
|
|
558
|
+
context 'when allow_unsafe_query_operators is true' do
|
|
559
|
+
config_override :allow_unsafe_query_operators, true
|
|
560
|
+
|
|
561
|
+
context 'when passing $where' do
|
|
562
|
+
it 'does not raise' do
|
|
563
|
+
expect do
|
|
564
|
+
query.where('$where' => 'this.name == "admin"')
|
|
565
|
+
end.not_to raise_error
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
context 'when passing $function' do
|
|
570
|
+
it 'does not raise' do
|
|
571
|
+
expect do
|
|
572
|
+
query.where('$function' => { body: 'function() { return true; }', args: [], lang: 'js' })
|
|
573
|
+
end.not_to raise_error
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
context 'when passing a string criterion' do
|
|
578
|
+
it 'does not raise' do
|
|
579
|
+
expect do
|
|
580
|
+
query.where('this.name == "admin"')
|
|
581
|
+
end.not_to raise_error
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
context 'when passing a string criterion to a negated query' do
|
|
586
|
+
it 'does not raise' do
|
|
587
|
+
expect do
|
|
588
|
+
query.not.where('this.name == "admin"')
|
|
589
|
+
end.not_to raise_error
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
context 'when allow_unsafe_query_operators is false' do
|
|
595
|
+
config_override :allow_unsafe_query_operators, false
|
|
596
|
+
|
|
597
|
+
context 'when passing $where' do
|
|
598
|
+
it 'raises InvalidQuery' do
|
|
599
|
+
expect do
|
|
600
|
+
query.where('$where' => 'this.name == "admin"')
|
|
601
|
+
end.to raise_error(Mongoid::Errors::InvalidQuery, /\$where/)
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
context 'when passing $function' do
|
|
606
|
+
it 'raises InvalidQuery' do
|
|
607
|
+
expect do
|
|
608
|
+
query.where('$function' => { body: 'function() { return true; }', args: [], lang: 'js' })
|
|
609
|
+
end.to raise_error(Mongoid::Errors::InvalidQuery, /\$function/)
|
|
610
|
+
end
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
context 'when the error mentions allow_unsafe_query_operators' do
|
|
614
|
+
it 'includes the config opt-in in the message' do
|
|
615
|
+
expect do
|
|
616
|
+
query.where('$where' => 'true')
|
|
617
|
+
end.to raise_error(Mongoid::Errors::InvalidQuery, /allow_unsafe_query_operators/)
|
|
618
|
+
end
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
context 'when passing a string criterion' do
|
|
622
|
+
it 'raises InvalidQuery' do
|
|
623
|
+
expect do
|
|
624
|
+
query.where('this.name == "admin"')
|
|
625
|
+
end.to raise_error(Mongoid::Errors::InvalidQuery, /\$where/)
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
it 'includes the config opt-in in the message' do
|
|
629
|
+
expect do
|
|
630
|
+
query.where('this.name == "admin"')
|
|
631
|
+
end.to raise_error(Mongoid::Errors::InvalidQuery, /allow_unsafe_query_operators/)
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
context 'when passing a string criterion to a negated query' do
|
|
636
|
+
it 'raises InvalidQuery' do
|
|
637
|
+
expect do
|
|
638
|
+
query.not.where('this.name == "admin"')
|
|
639
|
+
end.to raise_error(Mongoid::Errors::InvalidQuery, /\$where/)
|
|
640
|
+
end
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
%w[$and $or $nor $not $text $comment $expr $jsonSchema $alwaysFalse $alwaysTrue].each do |op|
|
|
644
|
+
context "when passing #{op} (allowlisted)" do
|
|
645
|
+
it 'does not raise' do
|
|
646
|
+
value = case op
|
|
647
|
+
when '$and', '$or', '$nor', '$not' then [ { 'x' => 1 } ]
|
|
648
|
+
when '$text' then { '$search' => 'hi' }
|
|
649
|
+
when '$expr' then { '$gt' => [ '$a', 1 ] }
|
|
650
|
+
when '$jsonSchema' then { 'required' => [ 'x' ] }
|
|
651
|
+
else true
|
|
652
|
+
end
|
|
653
|
+
expect { query.where(op => value) }.not_to raise_error
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
describe 'nested operator injection guard' do
|
|
661
|
+
js = 'this.name == "admin"'
|
|
662
|
+
|
|
663
|
+
function_expr = {
|
|
664
|
+
'$function' => { 'body' => 'function() { return true; }', 'args' => [], 'lang' => 'js' }
|
|
665
|
+
}.freeze
|
|
666
|
+
|
|
667
|
+
accumulator_expr = {
|
|
668
|
+
'$accumulator' => {
|
|
669
|
+
'init' => 'function() { return 0; }',
|
|
670
|
+
'accumulate' => 'function() { return 0; }',
|
|
671
|
+
'accumulateArgs' => [],
|
|
672
|
+
'merge' => 'function() { return 0; }',
|
|
673
|
+
'lang' => 'js'
|
|
674
|
+
}
|
|
675
|
+
}.freeze
|
|
676
|
+
|
|
677
|
+
# Every path into the selector that does not route through #expr_query,
|
|
678
|
+
# plus the nested forms that a top-level key check cannot see.
|
|
679
|
+
unsafe_queries = {
|
|
680
|
+
'and with a $where key' => ->(query) { query.and('$where' => js) },
|
|
681
|
+
'or with a $where key' => ->(query) { query.or('$where' => js) },
|
|
682
|
+
'nor with a $where key' => ->(query) { query.nor('$where' => js) },
|
|
683
|
+
'not with a $where key' => ->(query) { query.not('$where' => js) },
|
|
684
|
+
'any_of with a $where key' => ->(query) { query.any_of('$where' => js) },
|
|
685
|
+
'none_of with a $where key' => ->(query) { query.none_of('$where' => js) },
|
|
686
|
+
'elem_match with a nested $where' => ->(query) { query.elem_match(a: { '$where' => js }) },
|
|
687
|
+
'where with $where inside $or' => ->(query) { query.where('$or' => [ { '$where' => js } ]) },
|
|
688
|
+
'where with $function inside $expr' => ->(query) { query.where('$expr' => function_expr) },
|
|
689
|
+
'where with $accumulator inside $expr' => ->(query) { query.where('$expr' => accumulator_expr) },
|
|
690
|
+
'where with $where three levels deep' => lambda { |query|
|
|
691
|
+
query.where('$and' => [ { '$or' => [ { '$where' => js } ] } ])
|
|
692
|
+
},
|
|
693
|
+
'where with a symbol $where key inside $or' => ->(query) { query.where('$or' => [ { :$where => js } ]) }
|
|
694
|
+
}.freeze
|
|
695
|
+
|
|
696
|
+
context 'when allow_unsafe_query_operators is false' do
|
|
697
|
+
config_override :allow_unsafe_query_operators, false
|
|
698
|
+
|
|
699
|
+
unsafe_queries.each do |description, builder|
|
|
700
|
+
context "when querying with #{description}" do
|
|
701
|
+
it 'raises InvalidQuery' do
|
|
702
|
+
expect do
|
|
703
|
+
builder.call(query)
|
|
704
|
+
end.to raise_error(Mongoid::Errors::InvalidQuery, /\$where|\$function|\$accumulator/)
|
|
705
|
+
end
|
|
706
|
+
end
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
it 'includes the config opt-in in the message' do
|
|
710
|
+
expect do
|
|
711
|
+
query.or('$where' => js)
|
|
712
|
+
end.to raise_error(Mongoid::Errors::InvalidQuery, /allow_unsafe_query_operators/)
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
context 'when the nested expression is safe' do
|
|
716
|
+
it 'permits $or with field expressions' do
|
|
717
|
+
expect do
|
|
718
|
+
query.where('$or' => [ { 'a' => 1 }, { 'b' => { '$gt' => 2 } } ])
|
|
719
|
+
end.not_to raise_error
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
it 'permits $expr with aggregation operators' do
|
|
723
|
+
expect do
|
|
724
|
+
query.where('$expr' => { '$gt' => [ '$a', '$b' ] })
|
|
725
|
+
end.not_to raise_error
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
it 'permits a field whose name resembles a javascript operator' do
|
|
729
|
+
expect do
|
|
730
|
+
query.where('where' => js)
|
|
731
|
+
end.not_to raise_error
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
it 'permits a value that resembles a javascript operator' do
|
|
735
|
+
expect do
|
|
736
|
+
query.where('name' => '$where')
|
|
737
|
+
end.not_to raise_error
|
|
738
|
+
end
|
|
739
|
+
end
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
context 'when allow_unsafe_query_operators is true' do
|
|
743
|
+
config_override :allow_unsafe_query_operators, true
|
|
744
|
+
|
|
745
|
+
unsafe_queries.each do |description, builder|
|
|
746
|
+
context "when querying with #{description}" do
|
|
747
|
+
it 'does not raise' do
|
|
748
|
+
expect { builder.call(query) }.not_to raise_error
|
|
749
|
+
end
|
|
750
|
+
end
|
|
751
|
+
end
|
|
752
|
+
end
|
|
753
|
+
end
|
|
553
754
|
end
|
|
@@ -2440,6 +2440,9 @@ describe Mongoid::Criteria do
|
|
|
2440
2440
|
end
|
|
2441
2441
|
|
|
2442
2442
|
context 'when the criteria is not embedded' do
|
|
2443
|
+
# String criteria compile to $where, which requires the opt-in.
|
|
2444
|
+
config_override :allow_unsafe_query_operators, true
|
|
2445
|
+
|
|
2443
2446
|
let(:criteria) do
|
|
2444
2447
|
Band.where("this.name == 'Depeche Mode'")
|
|
2445
2448
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'support/crypt/models'
|
|
5
|
+
|
|
6
|
+
describe Mongoid::Encryptable do
|
|
7
|
+
describe '.requires_encryption_schema?' do
|
|
8
|
+
context 'when the model declares encrypt_with' do
|
|
9
|
+
it 'returns true' do
|
|
10
|
+
expect(Crypt::Vault.requires_encryption_schema?).to be true
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context 'when the model has an encrypted field' do
|
|
15
|
+
it 'returns true' do
|
|
16
|
+
expect(Crypt::User.requires_encryption_schema?).to be true
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
context 'when the model embeds an encrypted model' do
|
|
21
|
+
it 'returns true' do
|
|
22
|
+
expect(Crypt::Wallet.requires_encryption_schema?).to be true
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context 'when the encrypted model is nested three levels deep' do
|
|
27
|
+
it 'returns true' do
|
|
28
|
+
expect(Crypt::Owner.requires_encryption_schema?).to be true
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context 'when the model declares no encryption anywhere' do
|
|
33
|
+
it 'returns false' do
|
|
34
|
+
expect(Person.requires_encryption_schema?).to be false
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# An association target does not have to be a Mongoid document. Truck
|
|
39
|
+
# embeds a plain Ruby class.
|
|
40
|
+
context 'when an embedded association target is not a document' do
|
|
41
|
+
it 'returns false' do
|
|
42
|
+
expect(Truck.requires_encryption_schema?).to be false
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context 'when the model embeds itself' do
|
|
47
|
+
it 'terminates' do
|
|
48
|
+
expect(Crypt::Comment.requires_encryption_schema?).to be true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Every model is asked this question, including ones naming an embedded
|
|
53
|
+
# class that is never defined. Such an association cannot be used, so
|
|
54
|
+
# nothing is embedded through it and nothing needs encrypting.
|
|
55
|
+
context 'when an embedded association names a class that is not defined' do
|
|
56
|
+
it 'returns false' do
|
|
57
|
+
expect(Crypt::Drawer.requires_encryption_schema?).to be false
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Mongoid::Errors::ConfigRedactor do
|
|
6
|
+
describe '.redact' do
|
|
7
|
+
subject(:redacted) { described_class.redact(config) }
|
|
8
|
+
|
|
9
|
+
context 'when the config is not a hash' do
|
|
10
|
+
let(:config) { 'not a hash' }
|
|
11
|
+
|
|
12
|
+
it 'returns the value unchanged' do
|
|
13
|
+
expect(redacted).to eq('not a hash')
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
context 'when the config has a :password key' do
|
|
18
|
+
let(:config) { { hosts: [ 'localhost:27017' ], password: 's3cr3t' } }
|
|
19
|
+
|
|
20
|
+
it 'replaces the password value' do
|
|
21
|
+
expect(redacted[:password]).to eq('[REDACTED]')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'leaves other values intact' do
|
|
25
|
+
expect(redacted[:hosts]).to eq([ 'localhost:27017' ])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'when the config has a string "password" key' do
|
|
30
|
+
let(:config) { { 'password' => 's3cr3t' } }
|
|
31
|
+
|
|
32
|
+
it 'replaces the password value' do
|
|
33
|
+
expect(redacted['password']).to eq('[REDACTED]')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context 'when the config has a :uri with embedded credentials' do
|
|
38
|
+
let(:config) { { uri: 'mongodb://admin:s3cr3t@cluster.example.com/db' } }
|
|
39
|
+
|
|
40
|
+
it 'strips the userinfo from the URI' do
|
|
41
|
+
expect(redacted[:uri]).to eq('mongodb://[REDACTED]@cluster.example.com/db')
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context 'when the config has a mongodb+srv URI' do
|
|
46
|
+
let(:config) { { uri: 'mongodb+srv://admin:s3cr3t@cluster.example.com/db' } }
|
|
47
|
+
|
|
48
|
+
it 'strips the userinfo from the URI' do
|
|
49
|
+
expect(redacted[:uri]).to eq('mongodb+srv://[REDACTED]@cluster.example.com/db')
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context 'when the URI has no userinfo' do
|
|
54
|
+
let(:config) { { uri: 'mongodb://cluster.example.com/db' } }
|
|
55
|
+
|
|
56
|
+
it 'leaves the URI unchanged' do
|
|
57
|
+
expect(redacted[:uri]).to eq('mongodb://cluster.example.com/db')
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
context 'when the URI value is not a string' do
|
|
62
|
+
let(:config) { { uri: nil } }
|
|
63
|
+
|
|
64
|
+
it 'leaves the value unchanged' do
|
|
65
|
+
expect(redacted[:uri]).to be_nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'when the config has nested auto_encryption_options' do
|
|
70
|
+
let(:kms) do
|
|
71
|
+
{
|
|
72
|
+
aws: { access_key_id: 'AKIA...', secret_access_key: 'secret-value' },
|
|
73
|
+
local: { key: 'A' * 96 }
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
let(:config) do
|
|
77
|
+
{
|
|
78
|
+
database: 'mongoid_test',
|
|
79
|
+
options: {
|
|
80
|
+
auto_encryption_options: {
|
|
81
|
+
key_vault_namespace: 'admin.datakeys',
|
|
82
|
+
kms_providers: kms
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'redacts the entire auto_encryption_options value' do
|
|
89
|
+
expect(redacted[:options][:auto_encryption_options]).to eq('[REDACTED]')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'preserves sibling values in the parent hash' do
|
|
93
|
+
expect(redacted[:database]).to eq('mongoid_test')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'does not contain any kms secret in its serialized form' do
|
|
97
|
+
expect(redacted.to_s).not_to include('secret-value')
|
|
98
|
+
expect(redacted.to_s).not_to include('AKIA')
|
|
99
|
+
expect(redacted.to_s).not_to include('A' * 96)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context 'when redaction would mutate the input' do
|
|
104
|
+
let(:config) { { password: 's3cr3t', options: { foo: 'bar' } } }
|
|
105
|
+
|
|
106
|
+
it 'does not modify the original hash' do
|
|
107
|
+
described_class.redact(config)
|
|
108
|
+
expect(config[:password]).to eq('s3cr3t')
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -25,5 +25,36 @@ describe Mongoid::Errors::MixedClientConfiguration do
|
|
|
25
25
|
'Provide either only a uri as configuration'
|
|
26
26
|
)
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
context 'when the config contains sensitive values' do
|
|
30
|
+
let(:error) do
|
|
31
|
+
described_class.new(
|
|
32
|
+
:testing,
|
|
33
|
+
{
|
|
34
|
+
uri: 'mongodb://admin:s3cr3t@cluster.example.com/db',
|
|
35
|
+
password: 'standalone-secret',
|
|
36
|
+
options: {
|
|
37
|
+
auto_encryption_options: {
|
|
38
|
+
kms_providers: { local: { key: 'A' * 96 } }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'redacts the URI userinfo' do
|
|
46
|
+
expect(error.message).not_to include('admin:s3cr3t')
|
|
47
|
+
expect(error.message).to include('mongodb://[REDACTED]@cluster.example.com/db')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'redacts the standalone password value' do
|
|
51
|
+
expect(error.message).not_to include('standalone-secret')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'redacts auto_encryption_options entirely' do
|
|
55
|
+
expect(error.message).not_to include('kms_providers')
|
|
56
|
+
expect(error.message).not_to include('A' * 96)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
28
59
|
end
|
|
29
60
|
end
|
|
@@ -25,5 +25,34 @@ describe Mongoid::Errors::NoClientDatabase do
|
|
|
25
25
|
'If configuring via a mongoid.yml, ensure that within your :analytics'
|
|
26
26
|
)
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
context 'when the config contains sensitive values' do
|
|
30
|
+
let(:error) do
|
|
31
|
+
described_class.new(
|
|
32
|
+
:analytics,
|
|
33
|
+
{
|
|
34
|
+
hosts: [ '127.0.0.1:27017' ],
|
|
35
|
+
password: 's3cr3t',
|
|
36
|
+
options: {
|
|
37
|
+
auto_encryption_options: {
|
|
38
|
+
kms_providers: {
|
|
39
|
+
aws: { access_key_id: 'AKIAEXAMPLE', secret_access_key: 'aws-secret' }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'redacts the password value' do
|
|
48
|
+
expect(error.message).not_to include('s3cr3t')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'redacts auto_encryption_options entirely' do
|
|
52
|
+
expect(error.message).not_to include('kms_providers')
|
|
53
|
+
expect(error.message).not_to include('aws-secret')
|
|
54
|
+
expect(error.message).not_to include('AKIAEXAMPLE')
|
|
55
|
+
end
|
|
56
|
+
end
|
|
28
57
|
end
|
|
29
58
|
end
|
|
@@ -25,5 +25,31 @@ describe Mongoid::Errors::NoClientHosts do
|
|
|
25
25
|
'If configuring via a mongoid.yml, ensure that within your :analytics'
|
|
26
26
|
)
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
context 'when the config contains sensitive values' do
|
|
30
|
+
let(:error) do
|
|
31
|
+
described_class.new(
|
|
32
|
+
:analytics,
|
|
33
|
+
{
|
|
34
|
+
database: 'mongoid_test',
|
|
35
|
+
password: 's3cr3t',
|
|
36
|
+
options: {
|
|
37
|
+
auto_encryption_options: {
|
|
38
|
+
kms_providers: { local: { key: 'A' * 96 } }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'redacts the password value' do
|
|
46
|
+
expect(error.message).not_to include('s3cr3t')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'redacts auto_encryption_options entirely' do
|
|
50
|
+
expect(error.message).not_to include('kms_providers')
|
|
51
|
+
expect(error.message).not_to include('A' * 96)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
28
54
|
end
|
|
29
55
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Mongoid::Errors::ReadonlyAssociation do
|
|
6
|
+
let(:owner_class) do
|
|
7
|
+
Class.new do
|
|
8
|
+
def self.to_s
|
|
9
|
+
'SomeOwner'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
let(:association) do
|
|
15
|
+
double(name: :patients, options: { through: :appointments })
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe '#message' do
|
|
19
|
+
it 'names the association' do
|
|
20
|
+
error = described_class.new(owner_class, association)
|
|
21
|
+
expect(error.message).to include(':patients')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'names the owner class' do
|
|
25
|
+
error = described_class.new(owner_class, association)
|
|
26
|
+
expect(error.message).to include('SomeOwner')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'names the intermediate association' do
|
|
30
|
+
error = described_class.new(owner_class, association)
|
|
31
|
+
expect(error.message).to include(':appointments')
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|