mongo 2.24.1 → 2.26.0

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mongo_console +2 -2
  3. data/lib/mongo/address/validator.rb +1 -1
  4. data/lib/mongo/address.rb +1 -3
  5. data/lib/mongo/auth/aws/conversation.rb +0 -4
  6. data/lib/mongo/auth/aws/credentials_retriever.rb +3 -8
  7. data/lib/mongo/auth/base.rb +1 -1
  8. data/lib/mongo/auth/sasl_conversation_base.rb +1 -1
  9. data/lib/mongo/auth/scram_conversation_base.rb +1 -8
  10. data/lib/mongo/auth/stringprep.rb +1 -6
  11. data/lib/mongo/auth/user/view.rb +1 -1
  12. data/lib/mongo/bulk_write/combineable.rb +0 -7
  13. data/lib/mongo/bulk_write/result_combiner.rb +3 -3
  14. data/lib/mongo/bulk_write/validatable.rb +0 -4
  15. data/lib/mongo/bulk_write.rb +1 -35
  16. data/lib/mongo/client.rb +117 -17
  17. data/lib/mongo/client_encryption.rb +36 -11
  18. data/lib/mongo/cluster/reapers/cursor_reaper.rb +12 -5
  19. data/lib/mongo/cluster/sdam_flow.rb +4 -4
  20. data/lib/mongo/cluster/topology/base.rb +1 -1
  21. data/lib/mongo/cluster.rb +13 -30
  22. data/lib/mongo/collection/queryable_encryption.rb +7 -6
  23. data/lib/mongo/collection/view/aggregation/behavior.rb +1 -1
  24. data/lib/mongo/collection/view/aggregation.rb +2 -4
  25. data/lib/mongo/collection/view/builder/aggregation.rb +3 -5
  26. data/lib/mongo/collection/view/builder/map_reduce.rb +1 -1
  27. data/lib/mongo/collection/view/change_stream.rb +14 -9
  28. data/lib/mongo/collection/view/map_reduce.rb +2 -11
  29. data/lib/mongo/collection/view/readable.rb +11 -7
  30. data/lib/mongo/collection/view/writable.rb +4 -39
  31. data/lib/mongo/collection/view.rb +8 -4
  32. data/lib/mongo/collection.rb +1 -1
  33. data/lib/mongo/crypt/auto_encrypter.rb +6 -2
  34. data/lib/mongo/crypt/binary.rb +7 -4
  35. data/lib/mongo/crypt/binding.rb +154 -11
  36. data/lib/mongo/crypt/context.rb +50 -19
  37. data/lib/mongo/crypt/encryption_io.rb +11 -5
  38. data/lib/mongo/crypt/explicit_encrypter.rb +27 -12
  39. data/lib/mongo/crypt/explicit_encryption_context.rb +51 -12
  40. data/lib/mongo/crypt/handle.rb +35 -5
  41. data/lib/mongo/crypt/kms/credentials.rb +70 -31
  42. data/lib/mongo/crypt/kms/kmip/master_document.rb +30 -1
  43. data/lib/mongo/crypt/kms/master_key_document.rb +11 -6
  44. data/lib/mongo/crypt/kms.rb +12 -1
  45. data/lib/mongo/cursor.rb +8 -2
  46. data/lib/mongo/database/cursor_command_view.rb +95 -0
  47. data/lib/mongo/database.rb +144 -18
  48. data/lib/mongo/error/invalid_uri.rb +5 -1
  49. data/lib/mongo/error/operation_failure.rb +2 -2
  50. data/lib/mongo/error/parser.rb +3 -3
  51. data/lib/mongo/error/sdam_error_detection.rb +2 -3
  52. data/lib/mongo/error/unsupported_option.rb +0 -40
  53. data/lib/mongo/grid/fs_bucket.rb +29 -23
  54. data/lib/mongo/grid/stream/read.rb +2 -2
  55. data/lib/mongo/grid/stream/write.rb +3 -3
  56. data/lib/mongo/index/view.rb +13 -7
  57. data/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb +1 -0
  58. data/lib/mongo/monitoring/event/cmap/connection_check_out_started.rb +1 -0
  59. data/lib/mongo/monitoring/event/cmap/connection_checked_in.rb +1 -0
  60. data/lib/mongo/monitoring/event/cmap/connection_checked_out.rb +1 -0
  61. data/lib/mongo/monitoring/event/cmap/connection_closed.rb +1 -0
  62. data/lib/mongo/monitoring/event/cmap/connection_created.rb +1 -0
  63. data/lib/mongo/monitoring/event/cmap/connection_ready.rb +1 -0
  64. data/lib/mongo/monitoring/event/cmap/pool_cleared.rb +1 -0
  65. data/lib/mongo/monitoring/event/cmap/pool_closed.rb +1 -0
  66. data/lib/mongo/monitoring/event/cmap/pool_created.rb +1 -0
  67. data/lib/mongo/monitoring/event/cmap/pool_ready.rb +1 -0
  68. data/lib/mongo/monitoring/event/command_failed.rb +1 -0
  69. data/lib/mongo/monitoring/event/command_started.rb +1 -0
  70. data/lib/mongo/monitoring/event/command_succeeded.rb +1 -0
  71. data/lib/mongo/monitoring/event/secure.rb +12 -17
  72. data/lib/mongo/monitoring/event/server_closed.rb +1 -0
  73. data/lib/mongo/monitoring/event/server_description_changed.rb +1 -0
  74. data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +1 -0
  75. data/lib/mongo/monitoring/event/server_heartbeat_started.rb +1 -0
  76. data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +1 -0
  77. data/lib/mongo/monitoring/event/server_opening.rb +1 -0
  78. data/lib/mongo/monitoring/event/topology_changed.rb +1 -0
  79. data/lib/mongo/monitoring/event/topology_closed.rb +1 -0
  80. data/lib/mongo/monitoring/event/topology_opening.rb +1 -0
  81. data/lib/mongo/operation/create/op_msg.rb +1 -0
  82. data/lib/mongo/operation/create_index/op_msg.rb +2 -1
  83. data/lib/mongo/operation/cursor_command/op_msg.rb +37 -0
  84. data/lib/mongo/operation/cursor_command/result.rb +60 -0
  85. data/lib/mongo/operation/cursor_command.rb +33 -0
  86. data/lib/mongo/operation/delete/op_msg.rb +1 -0
  87. data/lib/mongo/operation/drop/op_msg.rb +1 -0
  88. data/lib/mongo/operation/drop_database/op_msg.rb +1 -0
  89. data/lib/mongo/operation/drop_index/op_msg.rb +1 -0
  90. data/lib/mongo/operation/find/builder/command.rb +1 -1
  91. data/lib/mongo/operation/insert/bulk_result.rb +2 -4
  92. data/lib/mongo/operation/insert/op_msg.rb +1 -0
  93. data/lib/mongo/operation/result.rb +21 -25
  94. data/lib/mongo/operation/shared/causal_consistency_supported.rb +4 -2
  95. data/lib/mongo/operation/shared/executable.rb +2 -15
  96. data/lib/mongo/operation/shared/result/aggregatable.rb +2 -2
  97. data/lib/mongo/operation/shared/sessions_supported.rb +3 -5
  98. data/lib/mongo/operation/update/bulk_result.rb +1 -1
  99. data/lib/mongo/operation/update/op_msg.rb +1 -0
  100. data/lib/mongo/operation/write_command/op_msg.rb +2 -0
  101. data/lib/mongo/operation.rb +1 -0
  102. data/lib/mongo/options/redacted.rb +2 -2
  103. data/lib/mongo/protocol/compressed.rb +15 -11
  104. data/lib/mongo/protocol/message.rb +16 -20
  105. data/lib/mongo/protocol/msg.rb +1 -12
  106. data/lib/mongo/protocol/registry.rb +1 -1
  107. data/lib/mongo/protocol/serializers.rb +1 -3
  108. data/lib/mongo/query_cache.rb +3 -5
  109. data/lib/mongo/retryable/backpressure.rb +17 -2
  110. data/lib/mongo/retryable/read_worker.rb +1 -1
  111. data/lib/mongo/retryable/retry_policy.rb +2 -2
  112. data/lib/mongo/retryable/write_worker.rb +2 -2
  113. data/lib/mongo/retryable.rb +1 -1
  114. data/lib/mongo/server/app_metadata.rb +1 -1
  115. data/lib/mongo/server/connection.rb +3 -1
  116. data/lib/mongo/server/connection_common.rb +9 -1
  117. data/lib/mongo/server/connection_pool/generation_manager.rb +1 -1
  118. data/lib/mongo/server/connection_pool.rb +24 -10
  119. data/lib/mongo/server/description/features.rb +1 -2
  120. data/lib/mongo/server/description.rb +1 -2
  121. data/lib/mongo/server/monitor/connection.rb +3 -2
  122. data/lib/mongo/server/monitor.rb +133 -30
  123. data/lib/mongo/server/pending_connection.rb +1 -5
  124. data/lib/mongo/server/push_monitor.rb +16 -2
  125. data/lib/mongo/server.rb +28 -9
  126. data/lib/mongo/server_selector/base.rb +3 -6
  127. data/lib/mongo/server_selector/secondary_preferred.rb +4 -1
  128. data/lib/mongo/session.rb +25 -26
  129. data/lib/mongo/socket/ssl.rb +3 -3
  130. data/lib/mongo/socket.rb +1 -7
  131. data/lib/mongo/srv/monitor.rb +11 -2
  132. data/lib/mongo/srv/resolver.rb +1 -1
  133. data/lib/mongo/srv/result.rb +5 -3
  134. data/lib/mongo/timeout.rb +10 -15
  135. data/lib/mongo/tracing/open_telemetry/command_tracer.rb +16 -2
  136. data/lib/mongo/uri/options_mapper.rb +36 -13
  137. data/lib/mongo/uri/srv_protocol.rb +4 -4
  138. data/lib/mongo/uri.rb +56 -8
  139. data/lib/mongo/utils.rb +1 -1
  140. data/lib/mongo/version.rb +1 -1
  141. metadata +6 -4
  142. data/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb +0 -173
  143. data/lib/mongo/auth/stringprep/unicode_normalize/tables.rb +0 -1164
@@ -81,7 +81,7 @@ module Mongo
81
81
  # will cause a `LoadError`.
82
82
  #
83
83
  # @api private
84
- MIN_LIBMONGOCRYPT_VERSION = Gem::Version.new('1.12.0')
84
+ MIN_LIBMONGOCRYPT_VERSION = Gem::Version.new('1.20.0')
85
85
 
86
86
  # @!method self.mongocrypt_version(len)
87
87
  # @api private
@@ -106,7 +106,7 @@ module Mongo
106
106
  def self.parse_version(version)
107
107
  Gem::Version.new(version)
108
108
  rescue ArgumentError
109
- match = version.match(/\A(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?(-[A-Za-z+\d]+)?\z/)
109
+ match = version.match(/\A(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?(?:-[A-Za-z+\d]+)?\z/)
110
110
  raise ArgumentError.new("Malformed version number string #{version}") if match.nil?
111
111
 
112
112
  Gem::Version.new(
@@ -823,14 +823,15 @@ module Mongo
823
823
 
824
824
  # An enum labeling different libmognocrypt state machine states
825
825
  enum :mongocrypt_ctx_state, [
826
- :error, 0,
827
- :need_mongo_collinfo, 1,
828
- :need_mongo_markings, 2,
829
- :need_mongo_keys, 3,
830
- :need_kms, 4,
831
- :ready, 5,
832
- :done, 6,
833
- :need_kms_credentials, 7,
826
+ :error, 0,
827
+ :need_mongo_collinfo, 1,
828
+ :need_mongo_markings, 2,
829
+ :need_mongo_keys, 3,
830
+ :need_kms, 4,
831
+ :ready, 5,
832
+ :done, 6,
833
+ :need_kms_credentials, 7,
834
+ :need_mongo_collinfo_with_db, 8,
834
835
  ]
835
836
 
836
837
  # @!method self.mongocrypt_ctx_state(ctx)
@@ -1105,10 +1106,20 @@ module Mongo
1105
1106
  def self.check_kms_ctx_status(kms_context)
1106
1107
  return if yield
1107
1108
 
1109
+ kms_ctx_status(kms_context).raise_crypt_error(kms: true)
1110
+ end
1111
+
1112
+ # Read the status information for the given KmsContext into a Status
1113
+ # object without raising.
1114
+ #
1115
+ # @param [ Mongo::Crypt::KmsContext ] kms_context
1116
+ #
1117
+ # @return [ Mongo::Crypt::Status ] The KMS context status.
1118
+ def self.kms_ctx_status(kms_context)
1108
1119
  status = Status.new
1109
1120
 
1110
1121
  mongocrypt_kms_ctx_status(kms_context.kms_ctx_p, status.ref)
1111
- status.raise_crypt_error(kms: true)
1122
+ status
1112
1123
  end
1113
1124
 
1114
1125
  # @!method self.mongocrypt_kms_ctx_usleep(ctx)
@@ -1468,6 +1479,31 @@ module Mongo
1468
1479
  mongocrypt_setopt_bypass_query_analysis(handle.ref)
1469
1480
  end
1470
1481
 
1482
+ # @!method self.mongocrypt_setopt_key_expiration(crypt, cache_expiration_ms)
1483
+ # @api private
1484
+ #
1485
+ # Set the expiration time for the data encryption key cache.
1486
+ #
1487
+ # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object.
1488
+ # @param [ Integer ] cache_expiration_ms The cache expiration time in
1489
+ # milliseconds. If zero, the cache never expires.
1490
+ # @return [ Boolean ] Returns whether the option was set successfully.
1491
+ attach_function(:mongocrypt_setopt_key_expiration, %i[pointer uint64], :bool)
1492
+
1493
+ # Set the expiration time for the data encryption key cache on the
1494
+ # Mongo::Crypt::Handle object.
1495
+ #
1496
+ # @param [ Mongo::Crypt::Handle ] handle
1497
+ # @param [ Integer ] cache_expiration_ms The cache expiration time in
1498
+ # milliseconds. If zero, the cache never expires.
1499
+ #
1500
+ # @raise [ Mongo::Error::CryptError ] If the option is not set successfully.
1501
+ def self.setopt_key_expiration(handle, cache_expiration_ms)
1502
+ check_status(handle) do
1503
+ mongocrypt_setopt_key_expiration(handle.ref, cache_expiration_ms)
1504
+ end
1505
+ end
1506
+
1471
1507
  # @!method self.mongocrypt_setopt_aes_256_ctr(crypt, aes_256_ctr_encrypt, aes_256_ctr_decrypt, ctx)
1472
1508
  # @api private
1473
1509
  #
@@ -1646,6 +1682,73 @@ module Mongo
1646
1682
  mongocrypt_setopt_use_need_kms_credentials_state(handle.ref)
1647
1683
  end
1648
1684
 
1685
+ # @!method self.mongocrypt_setopt_enable_multiple_collinfo(crypt)
1686
+ # @api private
1687
+ #
1688
+ # Enable support for multiple collection schemas. Required to support $lookup.
1689
+ #
1690
+ # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object.
1691
+ # @pre mongocrypt_init has not been called on crypt.
1692
+ # @return [ Boolean ] Whether the option was set successfully.
1693
+ attach_function(
1694
+ :mongocrypt_setopt_enable_multiple_collinfo,
1695
+ [ :pointer ],
1696
+ :bool
1697
+ )
1698
+
1699
+ # Enable support for multiple collection schemas. Required to support $lookup.
1700
+ #
1701
+ # @param [ Mongo::Crypt::Handle ] handle
1702
+ # @return [ Boolean ] Whether the option was set successfully.
1703
+ def self.setopt_enable_multiple_collinfo(handle)
1704
+ check_status(handle) do
1705
+ mongocrypt_setopt_enable_multiple_collinfo(handle.ref)
1706
+ end
1707
+ end
1708
+
1709
+ # @!method self.mongocrypt_setopt_use_need_mongo_collinfo_with_db_state(crypt)
1710
+ # @api private
1711
+ #
1712
+ # Opt into handling the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state.
1713
+ # A context enters this state when processing a bulkWrite command whose
1714
+ # target database differs from the command database ("admin").
1715
+ #
1716
+ # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object.
1717
+ attach_function(
1718
+ :mongocrypt_setopt_use_need_mongo_collinfo_with_db_state,
1719
+ [ :pointer ],
1720
+ :void
1721
+ )
1722
+
1723
+ # Opt into handling the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state.
1724
+ #
1725
+ # @param [ Mongo::Crypt::Handle ] handle
1726
+ def self.setopt_use_need_mongo_collinfo_with_db_state(handle)
1727
+ mongocrypt_setopt_use_need_mongo_collinfo_with_db_state(handle.ref)
1728
+ end
1729
+
1730
+ # @!method self.mongocrypt_ctx_mongo_db(ctx)
1731
+ # @api private
1732
+ #
1733
+ # Get the database name for the current collinfo operation. Used in the
1734
+ # MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state to determine which
1735
+ # database to run listCollections against.
1736
+ #
1737
+ # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object.
1738
+ # @return [ String, nil ] The database name, or nil on error.
1739
+ attach_function :mongocrypt_ctx_mongo_db, [ :pointer ], :string
1740
+
1741
+ # Get the database name for the current collinfo operation.
1742
+ #
1743
+ # @param [ Mongo::Crypt::Context ] context
1744
+ # @return [ String ] The database name.
1745
+ def self.ctx_mongo_db(context)
1746
+ db_name = mongocrypt_ctx_mongo_db(context.ctx_p)
1747
+ return db_name if db_name
1748
+
1749
+ check_ctx_status(context)
1750
+ end
1751
+
1649
1752
  # @!method self.mongocrypt_ctx_provide_kms_providers(ctx, kms_providers)
1650
1753
  # @api private
1651
1754
  #
@@ -1795,6 +1898,46 @@ module Mongo
1795
1898
  end
1796
1899
  end
1797
1900
 
1901
+ # @!method self.mongocrypt_ctx_setopt_algorithm_text(ctx, opts)
1902
+ # @api private
1903
+ #
1904
+ # Set options for explicit encryption with the "String" algorithm.
1905
+ #
1906
+ # @note The libmongocrypt C function is named `..._algorithm_text` (the
1907
+ # original "text" name), even though the algorithm string passed to
1908
+ # mongocrypt_ctx_setopt_algorithm is "String" (renamed for GA). There is
1909
+ # no `..._algorithm_string` symbol; `..._algorithm_text` is the correct
1910
+ # and only setter for these options.
1911
+ #
1912
+ # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object.
1913
+ # @param [ FFI::Pointer ] opts A pointer to a string options document.
1914
+ #
1915
+ # @return [ Boolean ] Whether setting this option succeeded.
1916
+ attach_function(
1917
+ :mongocrypt_ctx_setopt_algorithm_text,
1918
+ %i[
1919
+ pointer
1920
+ pointer
1921
+ ],
1922
+ :bool
1923
+ )
1924
+
1925
+ # Set options for explicit encryption with the "String" algorithm.
1926
+ #
1927
+ # @param [ Mongo::Crypt::Context ] context
1928
+ # @param [ Hash ] opts options
1929
+ #
1930
+ # @raise [ Mongo::Error::CryptError ] If the operation failed
1931
+ def self.ctx_setopt_algorithm_text(context, opts)
1932
+ validate_document(opts)
1933
+ data = opts.to_bson.to_s
1934
+ Binary.wrap_string(data) do |data_p|
1935
+ check_ctx_status(context) do
1936
+ mongocrypt_ctx_setopt_algorithm_text(context.ctx_p, data_p)
1937
+ end
1938
+ end
1939
+ end
1940
+
1798
1941
  # Raise a Mongo::Error::CryptError based on the status of the underlying
1799
1942
  # mongocrypt_t object.
1800
1943
  #
@@ -76,7 +76,7 @@ module Mongo
76
76
  # This method is not currently unit tested. It is integration tested
77
77
  # in spec/integration/explicit_encryption_spec.rb
78
78
  def run_state_machine(timeout_holder)
79
- while true
79
+ loop do
80
80
  timeout_ms = timeout_holder.remaining_timeout_ms!
81
81
  case state
82
82
  when :error
@@ -90,6 +90,8 @@ module Mongo
90
90
  provide_keys(timeout_ms)
91
91
  when :need_mongo_collinfo
92
92
  provide_collection_info(timeout_ms)
93
+ when :need_mongo_collinfo_with_db
94
+ provide_collection_info_with_db(timeout_ms)
93
95
  when :need_mongo_markings
94
96
  provide_markings(timeout_ms)
95
97
  when :need_kms
@@ -122,17 +124,26 @@ module Mongo
122
124
  def provide_markings(timeout_ms)
123
125
  cmd = Binding.ctx_mongo_op(self)
124
126
 
125
- result = @encryption_io.mark_command(cmd, timeout_ms: timeout_ms)
127
+ result = @encryption_io.mark_command(cmd, db_name: @db_name, timeout_ms: timeout_ms)
126
128
  mongocrypt_feed(result)
127
129
 
128
130
  mongocrypt_done
129
131
  end
130
132
 
131
133
  def provide_collection_info(timeout_ms)
134
+ feed_collection_info(@db_name, timeout_ms)
135
+ end
136
+
137
+ def provide_collection_info_with_db(timeout_ms)
138
+ feed_collection_info(Binding.ctx_mongo_db(self), timeout_ms)
139
+ end
140
+
141
+ def feed_collection_info(db_name, timeout_ms)
132
142
  filter = Binding.ctx_mongo_op(self)
133
143
 
134
- result = @encryption_io.collection_info(@db_name, filter, timeout_ms: timeout_ms)
135
- mongocrypt_feed(result) if result
144
+ @encryption_io.collection_info(db_name, filter, timeout_ms: timeout_ms).each do |result|
145
+ mongocrypt_feed(result)
146
+ end
136
147
 
137
148
  mongocrypt_done
138
149
  end
@@ -159,12 +170,29 @@ module Mongo
159
170
  raise unless e.network_error?
160
171
  next if Binding.kms_ctx_fail(kms_context)
161
172
 
162
- raise
173
+ raise_kms_retry_error(kms_context, e)
163
174
  end
164
175
  end
165
176
  Binding.ctx_kms_done(self)
166
177
  end
167
178
 
179
+ # Raise a KmsError that wraps the KMS status message (which describes the
180
+ # retry exhaustion) with the error from the last attempt.
181
+ #
182
+ # @param [ Mongo::Crypt::KmsContext ] kms_context
183
+ # @param [ Mongo::Error::KmsError ] last_error The error from the last
184
+ # KMS request attempt.
185
+ #
186
+ # @raise [ Mongo::Error::KmsError ]
187
+ def raise_kms_retry_error(kms_context, last_error)
188
+ status = Binding.kms_ctx_status(kms_context)
189
+ raise Error::KmsError.new(
190
+ "#{status.message}, last attempt failed with: #{last_error.message}",
191
+ code: status.code,
192
+ network_error: true
193
+ )
194
+ end
195
+
168
196
  # Indicate that state machine is done feeding I/O responses back to libmongocrypt
169
197
  def mongocrypt_done
170
198
  Binding.mongocrypt_ctx_mongo_done(ctx_p)
@@ -188,21 +216,24 @@ module Mongo
188
216
  # KMS providers.
189
217
  def retrieve_kms_credentials(timeout_holder)
190
218
  providers = {}
191
- if kms_providers.aws && kms_providers.aws.empty?
192
- begin
193
- aws_credentials = Mongo::Auth::Aws::CredentialsRetriever.new.credentials(timeout_holder)
194
- rescue Auth::Aws::CredentialsNotFound
195
- raise Error::CryptError.new(
196
- 'Could not locate AWS credentials (checked environment variables, ECS and EC2 metadata)'
197
- )
219
+ kms_providers.credentials_map.each do |identifier, creds|
220
+ next unless creds.empty?
221
+
222
+ case KMS.provider_base_type(identifier)
223
+ when 'aws'
224
+ begin
225
+ aws_credentials = Mongo::Auth::Aws::CredentialsRetriever.new.credentials(timeout_holder)
226
+ rescue Auth::Aws::CredentialsNotFound
227
+ raise Error::CryptError.new(
228
+ 'Could not locate AWS credentials (checked environment variables, ECS and EC2 metadata)'
229
+ )
230
+ end
231
+ providers[identifier] = aws_credentials.to_h
232
+ when 'gcp'
233
+ providers[identifier] = { access_token: gcp_access_token(timeout_holder) }
234
+ when 'azure'
235
+ providers[identifier] = { access_token: azure_access_token(timeout_holder) }
198
236
  end
199
- providers[:aws] = aws_credentials.to_h
200
- end
201
- if kms_providers.gcp && kms_providers.gcp.empty?
202
- providers[:gcp] = { access_token: gcp_access_token(timeout_holder) }
203
- end
204
- if kms_providers.azure && kms_providers.azure.empty?
205
- providers[:azure] = { access_token: azure_access_token(timeout_holder) }
206
237
  end
207
238
  KMS::Credentials.new(providers)
208
239
  end
@@ -97,7 +97,7 @@ module Mongo
97
97
  # Must be a non-negative integer. An explicit value of 0 means infinite.
98
98
  # The default value is unset which means the feature is not enabled.
99
99
  #
100
- # @return [ Hash ] The collection information
100
+ # @return [ Array<BSON::Document> ] The collection information documents
101
101
  def collection_info(db_name, filter, timeout_ms: nil)
102
102
  unless @metadata_client
103
103
  raise ArgumentError,
@@ -108,18 +108,22 @@ module Mongo
108
108
  .use(db_name)
109
109
  .database
110
110
  .list_collections(filter: filter, deserialize_as_bson: true, timeout_ms: timeout_ms)
111
- .first
111
+ .to_a
112
112
  end
113
113
 
114
114
  # Send the command to mongocryptd to be marked with intent-to-encrypt markings
115
115
  #
116
116
  # @param [ Hash ] cmd
117
+ # @param [ String | nil ] :db_name The database against which the command
118
+ # is being run. When provided, the command is sent to mongocryptd using
119
+ # this database so that the namespace in the command matches the namespace
120
+ # in encryptionInformation.
117
121
  # @param [ Integer ] :timeout_ms The operation timeout in milliseconds.
118
122
  # Must be a non-negative integer. An explicit value of 0 means infinite.
119
123
  # The default value is unset which means the feature is not enabled.
120
124
  #
121
125
  # @return [ Hash ] The marked command
122
- def mark_command(cmd, timeout_ms: nil)
126
+ def mark_command(cmd, db_name: nil, timeout_ms: nil)
123
127
  unless @mongocryptd_client
124
128
  raise ArgumentError,
125
129
  'mark_command requires mongocryptd_client to have been passed to the constructor, but it was not'
@@ -132,13 +136,15 @@ module Mongo
132
136
  timeout_ms: timeout_ms
133
137
  }
134
138
 
139
+ mongocryptd = db_name ? @mongocryptd_client.use(db_name) : @mongocryptd_client
140
+
135
141
  begin
136
- response = @mongocryptd_client.database.command(cmd, options)
142
+ response = mongocryptd.database.command(cmd, options)
137
143
  rescue Error::NoServerAvailable => e
138
144
  raise e if @options[:mongocryptd_bypass_spawn]
139
145
 
140
146
  spawn_mongocryptd
141
- response = @mongocryptd_client.database.command(cmd, options)
147
+ response = mongocryptd.database.command(cmd, options)
142
148
  end
143
149
 
144
150
  response.first
@@ -37,12 +37,19 @@ module Mongo
37
37
  # to TLS connection options of Mongo::Client.
38
38
  # @param [ Integer | nil ] timeout_ms Timeout for every operation executed
39
39
  # on this object.
40
- def initialize(key_vault_client, key_vault_namespace, kms_providers, kms_tls_options, timeout_ms = nil)
40
+ # @param [ Integer | nil ] key_expiration_ms The lifetime of the data
41
+ # encryption key cache, in milliseconds. A value of 0 means the cache
42
+ # never expires. When nil, libmongocrypt's default of 60000 is used.
43
+ def initialize(
44
+ key_vault_client, key_vault_namespace, kms_providers, kms_tls_options,
45
+ timeout_ms = nil, key_expiration_ms = nil
46
+ )
41
47
  Crypt.validate_ffi!
42
48
  @crypt_handle = Handle.new(
43
49
  kms_providers,
44
50
  kms_tls_options,
45
- explicit_encryption_only: true
51
+ explicit_encryption_only: true,
52
+ key_expiration_ms: key_expiration_ms
46
53
  )
47
54
  @encryption_io = EncryptionIO.new(
48
55
  key_vault_client: key_vault_client,
@@ -93,23 +100,31 @@ module Mongo
93
100
  # encryption key.
94
101
  # @option options [ String ] :algorithm The algorithm used to encrypt the value.
95
102
  # Valid algorithms are "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
96
- # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed".
103
+ # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed",
104
+ # "Range", "String".
97
105
  # @option options [ Integer | nil ] :contention_factor Contention factor
98
- # to be applied if encryption algorithm is set to "Indexed". If not
99
- # provided, it defaults to a value of 0. Contention factor should be set
100
- # only if encryption algorithm is set to "Indexed".
101
- # @option options [ String | nil ] query_type Query type to be applied
102
- # if encryption algorithm is set to "Indexed". Query type should be set
103
- # only if encryption algorithm is set to "Indexed". The only allowed
104
- # value is "equality".
106
+ # to be applied if encryption algorithm is set to "Indexed", "Range", or
107
+ # "String". If not provided, it defaults to a value of 0. Contention
108
+ # factor should be set only if encryption algorithm is set to "Indexed",
109
+ # "Range", or "String".
110
+ # @option options [ String | nil ] :query_type Query type to be applied
111
+ # if encryption algorithm is set to "Indexed", "Range", or "String".
112
+ # Allowed values are "equality" (for "Indexed"), "range" (for "Range"),
113
+ # and "prefix", "suffix", "substring" (for "String").
114
+ # @option options [ Hash | nil ] :range_opts Specifies index options for a
115
+ # Queryable Encryption field supporting "range" queries. Required when
116
+ # algorithm is "Range".
117
+ # @option options [ Hash | nil ] :string_opts Specifies index options for a
118
+ # Queryable Encryption field supporting "prefix", "suffix", or
119
+ # "substring" queries. Required when algorithm is "String".
105
120
  #
106
121
  # @note The :key_id and :key_alt_name options are mutually exclusive. Only
107
122
  # one is required to perform explicit encryption.
108
123
  #
109
124
  # @return [ BSON::Binary ] A BSON Binary object of subtype 6 (ciphertext)
110
125
  # representing the encrypted value
111
- # @raise [ ArgumentError ] if either contention_factor or query_type
112
- # is set, and algorithm is not "Indexed".
126
+ # @raise [ ArgumentError ] if either contention_factor or query_type is
127
+ # set, and algorithm is not "Indexed", "Range", or "String".
113
128
  def encrypt(value, options)
114
129
  Crypt::ExplicitEncryptionContext.new(
115
130
  @crypt_handle,
@@ -36,14 +36,16 @@ module Mongo
36
36
  # that will be used to encrypt the value.
37
37
  # @option options [ String ] :algorithm The algorithm used to encrypt the
38
38
  # value. Valid algorithms are "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
39
- # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed", "Range".
39
+ # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed", "Range",
40
+ # "String".
40
41
  # @option options [ Integer | nil ] :contention_factor Contention factor
41
- # to be applied if encryption algorithm is set to "Indexed". If not
42
- # provided, it defaults to a value of 0. Contention factor should be set
43
- # only if encryption algorithm is set to "Indexed".
42
+ # to be applied if encryption algorithm is set to "Indexed", "Range", or "String".
43
+ # If not provided, it defaults to a value of 0. Contention factor should be set
44
+ # only if encryption algorithm is set to "Indexed", "Range", or "String".
44
45
  # @option options [ String | nil ] query_type Query type to be applied
45
- # if encryption algorithm is set to "Indexed" or "Range".
46
- # Allowed values are "equality" and "range".
46
+ # if encryption algorithm is set to "Indexed", "Range", or "String".
47
+ # Allowed values are "equality", "range", "prefix", "suffix", and
48
+ # "substring".
47
49
  # @option options [ Hash | nil ] :range_opts Specifies index options for
48
50
  # a Queryable Encryption field supporting "range" queries.
49
51
  # Allowed options are:
@@ -56,9 +58,22 @@ module Mongo
56
58
  # the encryptedFields of the destination collection.
57
59
  # For double and decimal128, min/max/precision must all be set,
58
60
  # or all be unset.
61
+ # @option options [ Hash | nil ] :string_opts Specifies index options for
62
+ # a Queryable Encryption field supporting "prefix", "suffix", or
63
+ # "substring" queries (algorithm "String"). Allowed options are:
64
+ # - :case_sensitive
65
+ # - :diacritic_sensitive
66
+ # - :prefix (Hash with :str_min_query_length, :str_max_query_length)
67
+ # - :suffix (Hash with :str_min_query_length, :str_max_query_length)
68
+ # - :substring (Hash with :str_max_length, :str_min_query_length,
69
+ # :str_max_query_length)
70
+ # The options must match the values set in the encryptedFields of the
71
+ # destination collection.
59
72
  #
60
73
  # @note The Range algorithm is experimental only. It is not intended for
61
74
  # public use.
75
+ # @note The "substring" query type is unstable and subject to backwards
76
+ # breaking changes.
62
77
  #
63
78
  # @raise [ ArgumentError|Mongo::Error::CryptError ] If invalid options are provided
64
79
  def initialize(mongocrypt, io, doc, options = {})
@@ -114,28 +129,52 @@ module Mongo
114
129
 
115
130
  def set_algorithm_opts(options)
116
131
  Binding.ctx_setopt_algorithm(self, options[:algorithm])
117
- if %w[Indexed Range].include?(options[:algorithm])
132
+ if %w[Indexed Range String].include?(options[:algorithm])
118
133
  Binding.ctx_setopt_contention_factor(self, options[:contention_factor]) if options[:contention_factor]
119
134
  Binding.ctx_setopt_query_type(self, options[:query_type]) if options[:query_type]
120
135
  else
121
136
  if options[:contention_factor]
122
- raise ArgumentError.new(':contention_factor is allowed only for "Indexed" or "Range" algorithms')
137
+ raise ArgumentError.new(':contention_factor is allowed only for "Indexed", "Range", or "String" algorithms')
123
138
  end
124
139
  if options[:query_type]
125
- raise ArgumentError.new(':query_type is allowed only for "Indexed" or "Range" algorithms')
140
+ raise ArgumentError.new(':query_type is allowed only for "Indexed", "Range", or "String" algorithms')
126
141
  end
127
142
  end
128
- return unless options[:algorithm] == 'Range'
129
-
130
- Binding.ctx_setopt_algorithm_range(self, convert_range_opts(options[:range_opts]))
143
+ if options[:algorithm] == 'Range'
144
+ Binding.ctx_setopt_algorithm_range(self, convert_range_opts(options[:range_opts]))
145
+ elsif options[:algorithm] == 'String'
146
+ Binding.ctx_setopt_algorithm_text(self, convert_string_opts(options[:string_opts]))
147
+ end
131
148
  end
132
149
 
133
150
  def convert_range_opts(range_opts)
151
+ raise ArgumentError.new(':range_opts is required for the "Range" algorithm') if range_opts.nil?
152
+
134
153
  range_opts.dup.tap do |opts|
135
154
  opts[:sparsity] = BSON::Int64.new(opts[:sparsity]) if opts[:sparsity] && !opts[:sparsity].is_a?(BSON::Int64)
136
155
  opts[:trimFactor] = opts.delete(:trim_factor) if opts[:trim_factor]
137
156
  end
138
157
  end
158
+
159
+ def convert_string_opts(string_opts)
160
+ raise ArgumentError.new(':string_opts is required for the "String" algorithm') if string_opts.nil?
161
+
162
+ string_opts.dup.tap do |opts|
163
+ opts[:caseSensitive] = opts.delete(:case_sensitive) if opts.key?(:case_sensitive)
164
+ opts[:diacriticSensitive] = opts.delete(:diacritic_sensitive) if opts.key?(:diacritic_sensitive)
165
+ %i[substring prefix suffix].each do |query_type|
166
+ opts[query_type] = convert_string_query_opts(opts[query_type]) if opts[query_type]
167
+ end
168
+ end
169
+ end
170
+
171
+ def convert_string_query_opts(query_opts)
172
+ query_opts.dup.tap do |opts|
173
+ opts[:strMaxLength] = opts.delete(:str_max_length) if opts.key?(:str_max_length)
174
+ opts[:strMinQueryLength] = opts.delete(:str_min_query_length) if opts.key?(:str_min_query_length)
175
+ opts[:strMaxQueryLength] = opts.delete(:str_max_query_length) if opts.key?(:str_max_query_length)
176
+ end
177
+ end
139
178
  end
140
179
  end
141
180
  end
@@ -66,6 +66,10 @@ module Mongo
66
66
  # error that libmongocrypt raises on a subsequent "$SYSTEM" search.
67
67
  # @option options [ Logger ] :logger A Logger object to which libmongocrypt logs
68
68
  # will be sent
69
+ # @option options [ Integer | nil ] :key_expiration_ms The lifetime of the
70
+ # data encryption key cache, in milliseconds. A value of 0 means the
71
+ # cache never expires. When nil, libmongocrypt's default of 60000 is
72
+ # used.
69
73
  def initialize(kms_providers, kms_tls_options, options = {})
70
74
  # FFI::AutoPointer uses a custom release strategy to automatically free
71
75
  # the pointer once this object goes out of scope
@@ -74,6 +78,8 @@ module Mongo
74
78
  Binding.method(:mongocrypt_destroy)
75
79
  )
76
80
  Binding.kms_ctx_setopt_retry_kms(self, true)
81
+ Binding.setopt_enable_multiple_collinfo(self)
82
+ Binding.setopt_use_need_mongo_collinfo_with_db_state(self)
77
83
  @kms_providers = kms_providers
78
84
  @kms_tls_options = kms_tls_options
79
85
 
@@ -85,6 +91,9 @@ module Mongo
85
91
  @bypass_query_analysis = options[:bypass_query_analysis]
86
92
  set_bypass_query_analysis if @bypass_query_analysis
87
93
 
94
+ @key_expiration_ms = options[:key_expiration_ms]
95
+ set_key_expiration unless @key_expiration_ms.nil?
96
+
88
97
  @crypt_shared_lib_path = options[:crypt_shared_lib_path]
89
98
  @explicit_encryption_only = options[:explicit_encryption_only]
90
99
  @disable_crypt_shared_lib_search = options[:disable_crypt_shared_lib_search]
@@ -101,7 +110,7 @@ module Mongo
101
110
 
102
111
  Binding.setopt_kms_providers(self, @kms_providers.to_document)
103
112
 
104
- if @kms_providers.aws&.empty? || @kms_providers.gcp&.empty? || @kms_providers.azure&.empty?
113
+ if @kms_providers.any_on_demand?
105
114
  Binding.setopt_use_need_kms_credentials_state(self)
106
115
  end
107
116
 
@@ -123,13 +132,23 @@ module Mongo
123
132
  end
124
133
 
125
134
  # Return TLS options for KMS provider. If there are no TLS options set,
126
- # empty hash is returned.
135
+ # empty hash is returned. Named providers (e.g. "kmip:name1") fall back
136
+ # to the base-type options (e.g. :kmip) when no exact match is found.
127
137
  #
128
- # @param [ String ] provider KSM provider name.
138
+ # @param [ String ] provider KMS provider name or named identifier.
129
139
  #
130
140
  # @return [ Hash ] TLS options to connect to KMS provider.
131
141
  def kms_tls_options(provider)
132
- @kms_tls_options.fetch(provider, {})
142
+ provider_str = provider.to_s
143
+ base_type = KMS.provider_base_type(provider_str)
144
+
145
+ @kms_tls_options.fetch(provider_str) do
146
+ @kms_tls_options.fetch(provider_str.to_sym) do
147
+ @kms_tls_options.fetch(base_type) do
148
+ @kms_tls_options.fetch(base_type.to_sym, {})
149
+ end
150
+ end
151
+ end
133
152
  end
134
153
 
135
154
  def crypt_shared_lib_version
@@ -188,10 +207,21 @@ module Mongo
188
207
  Binding.setopt_bypass_query_analysis(self) if @bypass_query_analysis
189
208
  end
190
209
 
210
+ def set_key_expiration
211
+ unless @key_expiration_ms.is_a?(Integer) && !@key_expiration_ms.negative?
212
+ raise ArgumentError.new(
213
+ "#{@key_expiration_ms} is an invalid key_expiration_ms value; " \
214
+ 'must be a non-negative Integer or nil'
215
+ )
216
+ end
217
+
218
+ Binding.setopt_key_expiration(self, @key_expiration_ms)
219
+ end
220
+
191
221
  # Send the logs from libmongocrypt to the Mongo::Logger
192
222
  def set_logger_callback
193
223
  @log_callback = proc do |level, msg|
194
- @logger.send(level, msg)
224
+ @logger.public_send(level, msg)
195
225
  end
196
226
 
197
227
  Binding.setopt_log_handler(@mongocrypt, @log_callback)