mongo 2.25.0 → 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.
- checksums.yaml +4 -4
- data/lib/mongo/bulk_write/combineable.rb +0 -7
- data/lib/mongo/bulk_write/validatable.rb +0 -4
- data/lib/mongo/bulk_write.rb +0 -34
- data/lib/mongo/client.rb +35 -5
- data/lib/mongo/client_encryption.rb +36 -11
- data/lib/mongo/cluster.rb +1 -2
- data/lib/mongo/collection/queryable_encryption.rb +7 -6
- data/lib/mongo/collection/view/writable.rb +0 -35
- data/lib/mongo/crypt/auto_encrypter.rb +5 -1
- data/lib/mongo/crypt/binding.rb +66 -1
- data/lib/mongo/crypt/explicit_encrypter.rb +27 -12
- data/lib/mongo/crypt/explicit_encryption_context.rb +51 -12
- data/lib/mongo/crypt/handle.rb +18 -0
- data/lib/mongo/error/unsupported_option.rb +0 -40
- data/lib/mongo/grid/fs_bucket.rb +2 -2
- data/lib/mongo/grid/stream/read.rb +2 -2
- data/lib/mongo/grid/stream/write.rb +1 -1
- data/lib/mongo/operation/result.rb +9 -0
- data/lib/mongo/operation/shared/executable.rb +1 -6
- data/lib/mongo/protocol/message.rb +0 -4
- data/lib/mongo/protocol/msg.rb +1 -12
- data/lib/mongo/retryable/backpressure.rb +17 -2
- data/lib/mongo/retryable/read_worker.rb +1 -1
- data/lib/mongo/retryable/retry_policy.rb +2 -2
- data/lib/mongo/retryable/write_worker.rb +2 -2
- data/lib/mongo/retryable.rb +1 -1
- data/lib/mongo/server/app_metadata.rb +1 -1
- data/lib/mongo/server/description/features.rb +2 -3
- data/lib/mongo/server/description.rb +0 -1
- data/lib/mongo/server/pending_connection.rb +0 -5
- data/lib/mongo/server.rb +1 -2
- data/lib/mongo/session.rb +3 -3
- data/lib/mongo/uri/options_mapper.rb +15 -4
- data/lib/mongo/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 439e028e8b9ce062d109e1760508977f13a87dd6ee4cb9d5221a6a3108fec214
|
|
4
|
+
data.tar.gz: bb346a893717256c3eac12af4a63c718bfd4f567b1ec17b95727e45f9f8e11d5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3d650edf8067fbe760d1c0526e62bef7cfda7c27284b725ffcc5a8e3c33c712011b3aa478e87db2abea876d36e332daf733d1180954ed6bd1e5003a258a05f49
|
|
7
|
+
data.tar.gz: 6832d11c4cae68a7776c8e5f7ef8e5bd9fcbda45bedf8e8cb208fdc5103060582a315cb75434e003462500b4da05deb5e71618a6856159262ff3655fbe16888d
|
|
@@ -39,7 +39,6 @@ module Mongo
|
|
|
39
39
|
@requests = requests
|
|
40
40
|
@has_collation = false
|
|
41
41
|
@has_array_filters = false
|
|
42
|
-
@has_hint = false
|
|
43
42
|
end
|
|
44
43
|
|
|
45
44
|
# @return [ Boolean ] Whether one or more operation specifies the collation
|
|
@@ -54,12 +53,6 @@ module Mongo
|
|
|
54
53
|
@has_array_filters
|
|
55
54
|
end
|
|
56
55
|
|
|
57
|
-
# @return [ Boolean ] Whether one or more operation specifies the
|
|
58
|
-
# hint option.
|
|
59
|
-
def has_hint?
|
|
60
|
-
@has_hint
|
|
61
|
-
end
|
|
62
|
-
|
|
63
56
|
private
|
|
64
57
|
|
|
65
58
|
def combine_requests(ops)
|
|
@@ -43,10 +43,6 @@ module Mongo
|
|
|
43
43
|
@has_collation = true if document.respond_to?(:keys) && (document[:collation] || document[Operation::COLLATION])
|
|
44
44
|
|
|
45
45
|
@has_array_filters = true if document.respond_to?(:keys) && document[:array_filters]
|
|
46
|
-
|
|
47
|
-
return unless document.respond_to?(:keys) && document[:hint]
|
|
48
|
-
|
|
49
|
-
@has_hint = true
|
|
50
46
|
end
|
|
51
47
|
|
|
52
48
|
private
|
data/lib/mongo/bulk_write.rb
CHANGED
|
@@ -236,8 +236,6 @@ module Mongo
|
|
|
236
236
|
end
|
|
237
237
|
|
|
238
238
|
def execute_operation(name, values, connection, context, operation_id, result_combiner, session, txn_num = nil)
|
|
239
|
-
validate_hint!(connection)
|
|
240
|
-
|
|
241
239
|
unpin_maybe(session, connection) do
|
|
242
240
|
if values.size > connection.description.max_write_batch_size
|
|
243
241
|
split_execute(name, values, connection, context, operation_id, result_combiner, session, txn_num)
|
|
@@ -310,38 +308,6 @@ module Mongo
|
|
|
310
308
|
Operation::Update.new(spec).bulk_execute(connection, context: context)
|
|
311
309
|
end
|
|
312
310
|
|
|
313
|
-
def validate_hint!(connection)
|
|
314
|
-
return unless op_combiner.has_hint?
|
|
315
|
-
return unless !can_hint?(connection) && write_concern && !write_concern.acknowledged?
|
|
316
|
-
|
|
317
|
-
raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
# Loop through the requests and check if each operation is allowed to send
|
|
321
|
-
# a hint for each operation on the given server version.
|
|
322
|
-
#
|
|
323
|
-
# For the following operations, the client can send a hint for all supported
|
|
324
|
-
# server versions, and for the rest, the client can only send it for 4.4+:
|
|
325
|
-
# - updateOne
|
|
326
|
-
# - updateMany
|
|
327
|
-
# - replaceOne
|
|
328
|
-
#
|
|
329
|
-
# @param [ Connection ] connection The connection object.
|
|
330
|
-
#
|
|
331
|
-
# @return [ true | false ] Whether the request is able to send hints for
|
|
332
|
-
# the current server version.
|
|
333
|
-
def can_hint?(connection)
|
|
334
|
-
gte_4_4 = connection.server.description.server_version_gte?('4.4')
|
|
335
|
-
op_combiner.requests.all? do |req|
|
|
336
|
-
op = req.keys.first
|
|
337
|
-
if req[op].keys.include?(:hint)
|
|
338
|
-
%i[update_one update_many replace_one].include?(op) || gte_4_4
|
|
339
|
-
else
|
|
340
|
-
true
|
|
341
|
-
end
|
|
342
|
-
end
|
|
343
|
-
end
|
|
344
|
-
|
|
345
311
|
# Perform the request document validation required by driver specifications.
|
|
346
312
|
# This method validates the first key of each update request document to be
|
|
347
313
|
# an operator (i.e. start with $) and the first key of each replacement
|
data/lib/mongo/client.rb
CHANGED
|
@@ -241,7 +241,11 @@ module Mongo
|
|
|
241
241
|
# printed to the mongod logs upon establishing a connection
|
|
242
242
|
# @option options [ Symbol ] :auth_mech The authentication mechanism to
|
|
243
243
|
# use. One of :mongodb_cr, :mongodb_x509, :plain, :scram, :scram256
|
|
244
|
-
# @option options [ Hash ] :auth_mech_properties
|
|
244
|
+
# @option options [ Hash ] :auth_mech_properties When passed in a
|
|
245
|
+
# connection string as authMechanismProperties, key-value pairs are
|
|
246
|
+
# delimited by commas, so a value containing a comma (",") must not be
|
|
247
|
+
# provided as part of the connection string. Such a value must be passed
|
|
248
|
+
# through this option instead. Values may contain colons (":").
|
|
245
249
|
# @option options [ String ] :auth_source The source to authenticate from.
|
|
246
250
|
# @option options [ true | false | nil | Integer ] :bg_error_backtrace
|
|
247
251
|
# Experimental. Set to true to log complete backtraces for errors in
|
|
@@ -509,6 +513,10 @@ module Mongo
|
|
|
509
513
|
# (this part of the API is subject to change).
|
|
510
514
|
# - :encrypted_fields_map => Hash | nil, maps a collection namespace to
|
|
511
515
|
# a hash describing encrypted fields for queryable encryption.
|
|
516
|
+
# - Note: Supplying an encrypted_fields_map provides more security
|
|
517
|
+
# than relying on an encryptedFields obtained from the server. It
|
|
518
|
+
# protects against a malicious server advertising a false
|
|
519
|
+
# encryptedFields.
|
|
512
520
|
# - Note: If a collection is present on both the encryptedFieldsMap
|
|
513
521
|
# and schemaMap, an error will be raised.
|
|
514
522
|
# - :bypass_query_analysis => Boolean | nil, when true disables automatic
|
|
@@ -519,6 +527,10 @@ module Mongo
|
|
|
519
527
|
# - :crypt_shared_lib_required => [ Boolean | nil ] Whether
|
|
520
528
|
# crypt shared library is required. If 'true', an error will be raised
|
|
521
529
|
# if a crypt_shared library cannot be loaded by libmongocrypt.
|
|
530
|
+
# - :key_expiration_ms => Integer | nil, the lifetime of the data
|
|
531
|
+
# encryption key cache, in milliseconds. Must be a non-negative
|
|
532
|
+
# integer. A value of 0 means the cache never expires. Defaults to
|
|
533
|
+
# 60000.
|
|
522
534
|
#
|
|
523
535
|
# Notes on automatic encryption:
|
|
524
536
|
# - Automatic encryption is an enterprise only feature that only applies
|
|
@@ -601,9 +613,7 @@ module Mongo
|
|
|
601
613
|
sdam_proc.call(self) if sdam_proc
|
|
602
614
|
|
|
603
615
|
@connect_lock = Mutex.new
|
|
604
|
-
@retry_policy =
|
|
605
|
-
max_retries: @options[:max_adaptive_retries] || Retryable::Backpressure::DEFAULT_MAX_RETRIES
|
|
606
|
-
)
|
|
616
|
+
@retry_policy = build_retry_policy
|
|
607
617
|
@connect_lock.synchronize do
|
|
608
618
|
@cluster = Cluster.new(
|
|
609
619
|
addresses,
|
|
@@ -621,7 +631,11 @@ module Mongo
|
|
|
621
631
|
build_encrypter
|
|
622
632
|
end
|
|
623
633
|
end
|
|
624
|
-
|
|
634
|
+
# ScriptError is rescued in addition to StandardError because
|
|
635
|
+
# Mongo::Crypt::Binding raises a LoadError when libmongocrypt cannot be
|
|
636
|
+
# found. Without it the cluster built above would be left open, leaking
|
|
637
|
+
# its monitoring and connection pool threads.
|
|
638
|
+
rescue StandardError, ScriptError
|
|
625
639
|
begin
|
|
626
640
|
@cluster.close
|
|
627
641
|
rescue StandardError => e
|
|
@@ -830,6 +844,12 @@ module Mongo
|
|
|
830
844
|
options.update(opts)
|
|
831
845
|
@options = options.freeze
|
|
832
846
|
|
|
847
|
+
# The retry policy is built from the options, so a client created by
|
|
848
|
+
# #with needs its own policy when the option changed.
|
|
849
|
+
if @options[:max_adaptive_retries] != old_options[:max_adaptive_retries]
|
|
850
|
+
@retry_policy = build_retry_policy
|
|
851
|
+
end
|
|
852
|
+
|
|
833
853
|
auto_encryption_options_changed =
|
|
834
854
|
@options[:auto_encryption_options] != old_options[:auto_encryption_options]
|
|
835
855
|
|
|
@@ -1260,6 +1280,16 @@ module Mongo
|
|
|
1260
1280
|
|
|
1261
1281
|
private
|
|
1262
1282
|
|
|
1283
|
+
# Builds the retry policy for the backpressure retry loops from the
|
|
1284
|
+
# client's options.
|
|
1285
|
+
#
|
|
1286
|
+
# @return [ Retryable::RetryPolicy ] The retry policy.
|
|
1287
|
+
def build_retry_policy
|
|
1288
|
+
Retryable::RetryPolicy.new(
|
|
1289
|
+
max_retries: @options[:max_adaptive_retries] || Retryable::Backpressure::DEFAULT_MAX_RETRIES
|
|
1290
|
+
)
|
|
1291
|
+
end
|
|
1292
|
+
|
|
1263
1293
|
# Attempts to parse the given list of addresses, using the provided options.
|
|
1264
1294
|
#
|
|
1265
1295
|
# @param [ String | Array<String> ] addresses the list of addresses
|
|
@@ -42,6 +42,10 @@ module Mongo
|
|
|
42
42
|
# @option options [ Integer ] :timeout_ms The operation timeout in milliseconds.
|
|
43
43
|
# Must be a non-negative integer. An explicit value of 0 means infinite.
|
|
44
44
|
# The default value is unset which means the feature is disabled.
|
|
45
|
+
# @option options [ Integer ] :key_expiration_ms The lifetime of the data
|
|
46
|
+
# encryption key cache, in milliseconds. Must be a non-negative integer.
|
|
47
|
+
# An explicit value of 0 means the cache never expires. The default is
|
|
48
|
+
# 60000.
|
|
45
49
|
#
|
|
46
50
|
# @raise [ ArgumentError ] If required options are missing or incorrectly
|
|
47
51
|
# formatted.
|
|
@@ -50,7 +54,9 @@ module Mongo
|
|
|
50
54
|
key_vault_client,
|
|
51
55
|
options[:key_vault_namespace],
|
|
52
56
|
Crypt::KMS::Credentials.new(options[:kms_providers]),
|
|
53
|
-
Crypt::KMS::Validations.validate_tls_options(options[:kms_tls_options])
|
|
57
|
+
Crypt::KMS::Validations.validate_tls_options(options[:kms_tls_options]),
|
|
58
|
+
options[:timeout_ms],
|
|
59
|
+
options[:key_expiration_ms]
|
|
54
60
|
)
|
|
55
61
|
end
|
|
56
62
|
|
|
@@ -100,15 +106,34 @@ module Mongo
|
|
|
100
106
|
# encryption key.
|
|
101
107
|
# @option options [ String ] :algorithm The algorithm used to encrypt the value.
|
|
102
108
|
# Valid algorithms are "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
|
|
103
|
-
# "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed"
|
|
109
|
+
# "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed", "Range",
|
|
110
|
+
# "String".
|
|
104
111
|
# @option options [ Integer | nil ] :contention_factor Contention factor
|
|
105
|
-
# to be applied if encryption algorithm is set to "Indexed"
|
|
106
|
-
# provided, it defaults to a value of 0. Contention factor
|
|
107
|
-
# only if encryption algorithm is set to "Indexed"
|
|
112
|
+
# to be applied if encryption algorithm is set to "Indexed", "Range", or
|
|
113
|
+
# "String". If not provided, it defaults to a value of 0. Contention factor
|
|
114
|
+
# should be set only if encryption algorithm is set to "Indexed", "Range",
|
|
115
|
+
# or "String".
|
|
108
116
|
# @option options [ String | nil ] query_type Query type to be applied
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
#
|
|
117
|
+
# if encryption algorithm is set to "Indexed", "Range", or "String".
|
|
118
|
+
# Allowed values are "equality" (for "Indexed"), "range" (for "Range"),
|
|
119
|
+
# and "prefix", "suffix", "substring" (for "String").
|
|
120
|
+
# @option options [ Hash | nil ] :range_opts Specifies index options for a
|
|
121
|
+
# Queryable Encryption field supporting "range" queries. Required when
|
|
122
|
+
# algorithm is "Range". Allowed options are :min, :max, :trim_factor,
|
|
123
|
+
# :sparsity, :precision.
|
|
124
|
+
# @option options [ Hash | nil ] :string_opts Specifies index options for a
|
|
125
|
+
# Queryable Encryption field supporting "prefix", "suffix", or "substring"
|
|
126
|
+
# queries. Required when algorithm is "String". Allowed options are
|
|
127
|
+
# :case_sensitive, :diacritic_sensitive, :prefix, :suffix, :substring.
|
|
128
|
+
#
|
|
129
|
+
# @note The result of explicit encryption with the "Indexed", "Range", or
|
|
130
|
+
# "String" algorithm must be processed by the server to insert or query.
|
|
131
|
+
# To insert or query with such a payload, use a Mongo::Client configured
|
|
132
|
+
# with :auto_encryption_options. The :bypass_query_analysis option may be
|
|
133
|
+
# true; the :bypass_auto_encryption option must be false.
|
|
134
|
+
#
|
|
135
|
+
# @note The "substring" query type is unstable and subject to backwards
|
|
136
|
+
# breaking changes.
|
|
112
137
|
#
|
|
113
138
|
# @note The :key_id and :key_alt_name options are mutually exclusive. Only
|
|
114
139
|
# one is required to perform explicit encryption.
|
|
@@ -116,8 +141,8 @@ module Mongo
|
|
|
116
141
|
# @return [ BSON::Binary ] A BSON Binary object of subtype 6 (ciphertext)
|
|
117
142
|
# representing the encrypted value.
|
|
118
143
|
#
|
|
119
|
-
# @raise [ ArgumentError ] if either contention_factor or query_type
|
|
120
|
-
#
|
|
144
|
+
# @raise [ ArgumentError ] if either contention_factor or query_type is set,
|
|
145
|
+
# and algorithm is not "Indexed", "Range", or "String".
|
|
121
146
|
def encrypt(value, options = {})
|
|
122
147
|
@encrypter.encrypt(value, options)
|
|
123
148
|
end
|
|
@@ -148,7 +173,7 @@ module Mongo
|
|
|
148
173
|
# expression. The only allowed value is "Range"
|
|
149
174
|
# @option options [ Integer | nil ] :contention_factor Contention factor
|
|
150
175
|
# to be applied If not provided, it defaults to a value of 0.
|
|
151
|
-
# @option options [ String | nil ] query_type Query type to be applied.
|
|
176
|
+
# @option options [ String | nil ] :query_type Query type to be applied.
|
|
152
177
|
# The only allowed value is "range".
|
|
153
178
|
#
|
|
154
179
|
# @note The :key_id and :key_alt_name options are mutually exclusive. Only
|
data/lib/mongo/cluster.rb
CHANGED
|
@@ -586,8 +586,7 @@ module Mongo
|
|
|
586
586
|
# @option options [ true | false ] :keep_connection_pool Usually when the
|
|
587
587
|
# new server description is unknown, the connection pool on the
|
|
588
588
|
# respective server is cleared. Set this option to true to keep the
|
|
589
|
-
# existing connection pool (required when handling not master errors
|
|
590
|
-
# on 4.2+ servers).
|
|
589
|
+
# existing connection pool (required when handling not master errors).
|
|
591
590
|
# @option options [ true | false ] :awaited Whether the updated description
|
|
592
591
|
# was a result of processing an awaited hello.
|
|
593
592
|
# @option options [ Object ] :service_id Change state for the specified
|
|
@@ -62,8 +62,8 @@ module Mongo
|
|
|
62
62
|
def maybe_drop_emm_collections(encrypted_fields, client, session)
|
|
63
63
|
encrypted_fields = if encrypted_fields
|
|
64
64
|
encrypted_fields
|
|
65
|
-
elsif
|
|
66
|
-
|
|
65
|
+
elsif client.options[:auto_encryption_options]
|
|
66
|
+
encrypted_fields_for_drop
|
|
67
67
|
else
|
|
68
68
|
{}
|
|
69
69
|
end
|
|
@@ -127,12 +127,13 @@ module Mongo
|
|
|
127
127
|
{}
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
-
# Tries to return the encrypted fields
|
|
131
|
-
# value,
|
|
130
|
+
# Tries to return the encrypted fields for the current namespace from
|
|
131
|
+
# the {{encrypted_fields_map}} value, falling back to the encrypted
|
|
132
|
+
# fields advertised by the server for the collection.
|
|
132
133
|
#
|
|
133
134
|
# @return [ Hash | nil ] the encrypted fields, if found
|
|
134
|
-
def
|
|
135
|
-
encrypted_fields_map[namespace] ||
|
|
135
|
+
def encrypted_fields_for_drop
|
|
136
|
+
(encrypted_fields_map && encrypted_fields_map[namespace]) ||
|
|
136
137
|
database.list_collections(filter: { name: name })
|
|
137
138
|
.first
|
|
138
139
|
&.fetch(:options, {})
|
|
@@ -88,11 +88,6 @@ module Mongo
|
|
|
88
88
|
operation_timeouts: operation_timeouts(opts)
|
|
89
89
|
)
|
|
90
90
|
write_with_retry(write_concern, context: context) do |connection, txn_num, context|
|
|
91
|
-
gte_4_4 = connection.server.description.server_version_gte?('4.4')
|
|
92
|
-
if !gte_4_4 && opts[:hint] && write_concern && !write_concern.acknowledged?
|
|
93
|
-
raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
|
|
94
|
-
end
|
|
95
|
-
|
|
96
91
|
Operation::WriteCommand.new(
|
|
97
92
|
selector: cmd,
|
|
98
93
|
db_name: database.name,
|
|
@@ -216,11 +211,6 @@ module Mongo
|
|
|
216
211
|
)
|
|
217
212
|
value = tracer.trace_operation(operation, context, op_name: 'findOneAndUpdate') do
|
|
218
213
|
write_with_retry(write_concern, context: context) do |connection, txn_num, context|
|
|
219
|
-
gte_4_4 = connection.server.description.server_version_gte?('4.4')
|
|
220
|
-
if !gte_4_4 && opts[:hint] && write_concern && !write_concern.acknowledged?
|
|
221
|
-
raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
|
|
222
|
-
end
|
|
223
|
-
|
|
224
214
|
operation.txn_num = txn_num
|
|
225
215
|
operation.execute_with_connection(connection, context: context)
|
|
226
216
|
end
|
|
@@ -287,11 +277,6 @@ module Mongo
|
|
|
287
277
|
)
|
|
288
278
|
tracer.trace_operation(operation, context, op_name: 'deleteMany') do
|
|
289
279
|
nro_write_with_retry(write_concern, context: context) do |connection, _txn_num, context|
|
|
290
|
-
gte_4_4 = connection.server.description.server_version_gte?('4.4')
|
|
291
|
-
if !gte_4_4 && opts[:hint] && write_concern && !write_concern.acknowledged?
|
|
292
|
-
raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
|
|
293
|
-
end
|
|
294
|
-
|
|
295
280
|
operation.execute_with_connection(connection, context: context)
|
|
296
281
|
end
|
|
297
282
|
end
|
|
@@ -346,11 +331,6 @@ module Mongo
|
|
|
346
331
|
operation_timeouts: operation_timeouts(opts)
|
|
347
332
|
)
|
|
348
333
|
write_with_retry(write_concern, context: context) do |connection, txn_num, context|
|
|
349
|
-
gte_4_4 = connection.server.description.server_version_gte?('4.4')
|
|
350
|
-
if !gte_4_4 && opts[:hint] && write_concern && !write_concern.acknowledged?
|
|
351
|
-
raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
|
|
352
|
-
end
|
|
353
|
-
|
|
354
334
|
Operation::Delete.new(
|
|
355
335
|
deletes: [ delete_doc ],
|
|
356
336
|
db_name: collection.database.name,
|
|
@@ -428,11 +408,6 @@ module Mongo
|
|
|
428
408
|
operation_timeouts: operation_timeouts(opts)
|
|
429
409
|
)
|
|
430
410
|
write_with_retry(write_concern, context: context) do |connection, txn_num, context|
|
|
431
|
-
gte_4_2 = connection.server.description.server_version_gte?('4.2')
|
|
432
|
-
if !gte_4_2 && opts[:hint] && write_concern && !write_concern.acknowledged?
|
|
433
|
-
raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
|
|
434
|
-
end
|
|
435
|
-
|
|
436
411
|
Operation::Update.new(
|
|
437
412
|
updates: [ update_doc ],
|
|
438
413
|
db_name: collection.database.name,
|
|
@@ -507,11 +482,6 @@ module Mongo
|
|
|
507
482
|
operation_timeouts: operation_timeouts(opts)
|
|
508
483
|
)
|
|
509
484
|
nro_write_with_retry(write_concern, context: context) do |connection, _txn_num, context|
|
|
510
|
-
gte_4_2 = connection.server.description.server_version_gte?('4.2')
|
|
511
|
-
if !gte_4_2 && opts[:hint] && write_concern && !write_concern.acknowledged?
|
|
512
|
-
raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
|
|
513
|
-
end
|
|
514
|
-
|
|
515
485
|
Operation::Update.new(
|
|
516
486
|
updates: [ update_doc ],
|
|
517
487
|
db_name: collection.database.name,
|
|
@@ -601,11 +571,6 @@ module Mongo
|
|
|
601
571
|
)
|
|
602
572
|
tracer.trace_operation(operation, context) do
|
|
603
573
|
write_with_retry(write_concern, context: context) do |connection, txn_num, context|
|
|
604
|
-
gte_4_2 = connection.server.description.server_version_gte?('4.2')
|
|
605
|
-
if !gte_4_2 && opts[:hint] && write_concern && !write_concern.acknowledged?
|
|
606
|
-
raise Error::UnsupportedOption.hint_error(unacknowledged_write: true)
|
|
607
|
-
end
|
|
608
|
-
|
|
609
574
|
operation.txn_num = txn_num
|
|
610
575
|
operation.execute_with_connection(connection, context: context)
|
|
611
576
|
end
|
|
@@ -81,6 +81,9 @@ module Mongo
|
|
|
81
81
|
# @option options [ Boolean | nil ] :crypt_shared_lib_required Whether
|
|
82
82
|
# crypt shared library is required. If 'true', an error will be raised
|
|
83
83
|
# if a crypt_shared library cannot be loaded by libmongocrypt.
|
|
84
|
+
# @option options [ Integer | nil ] :key_expiration_ms The lifetime of the
|
|
85
|
+
# data encryption key cache, in milliseconds. A value of 0 means the
|
|
86
|
+
# cache never expires. Defaults to 60000.
|
|
84
87
|
#
|
|
85
88
|
# @raise [ ArgumentError ] If required options are missing or incorrectly
|
|
86
89
|
# formatted.
|
|
@@ -99,7 +102,8 @@ module Mongo
|
|
|
99
102
|
bypass_query_analysis: @options[:bypass_query_analysis],
|
|
100
103
|
crypt_shared_lib_path: @options[:extra_options][:crypt_shared_lib_path],
|
|
101
104
|
crypt_shared_lib_required: @options[:extra_options][:crypt_shared_lib_required],
|
|
102
|
-
disable_crypt_shared_lib_search: @options[:extra_options][:disable_crypt_shared_lib_search]
|
|
105
|
+
disable_crypt_shared_lib_search: @options[:extra_options][:disable_crypt_shared_lib_search],
|
|
106
|
+
key_expiration_ms: @options[:key_expiration_ms]
|
|
103
107
|
)
|
|
104
108
|
|
|
105
109
|
@mongocryptd_options = @options[:extra_options].slice(
|
data/lib/mongo/crypt/binding.rb
CHANGED
|
@@ -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.
|
|
84
|
+
MIN_LIBMONGOCRYPT_VERSION = Gem::Version.new('1.20.0')
|
|
85
85
|
|
|
86
86
|
# @!method self.mongocrypt_version(len)
|
|
87
87
|
# @api private
|
|
@@ -1479,6 +1479,31 @@ module Mongo
|
|
|
1479
1479
|
mongocrypt_setopt_bypass_query_analysis(handle.ref)
|
|
1480
1480
|
end
|
|
1481
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
|
+
|
|
1482
1507
|
# @!method self.mongocrypt_setopt_aes_256_ctr(crypt, aes_256_ctr_encrypt, aes_256_ctr_decrypt, ctx)
|
|
1483
1508
|
# @api private
|
|
1484
1509
|
#
|
|
@@ -1873,6 +1898,46 @@ module Mongo
|
|
|
1873
1898
|
end
|
|
1874
1899
|
end
|
|
1875
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
|
+
|
|
1876
1941
|
# Raise a Mongo::Error::CryptError based on the status of the underlying
|
|
1877
1942
|
# mongocrypt_t object.
|
|
1878
1943
|
#
|
|
@@ -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
|
-
|
|
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"
|
|
99
|
-
# provided, it defaults to a value of 0. Contention
|
|
100
|
-
# only if encryption algorithm is set to "Indexed"
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
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
|
-
#
|
|
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"
|
|
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 "
|
|
46
|
-
# Allowed values are "equality"
|
|
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 "
|
|
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 "
|
|
140
|
+
raise ArgumentError.new(':query_type is allowed only for "Indexed", "Range", or "String" algorithms')
|
|
126
141
|
end
|
|
127
142
|
end
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
data/lib/mongo/crypt/handle.rb
CHANGED
|
@@ -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
|
|
@@ -87,6 +91,9 @@ module Mongo
|
|
|
87
91
|
@bypass_query_analysis = options[:bypass_query_analysis]
|
|
88
92
|
set_bypass_query_analysis if @bypass_query_analysis
|
|
89
93
|
|
|
94
|
+
@key_expiration_ms = options[:key_expiration_ms]
|
|
95
|
+
set_key_expiration unless @key_expiration_ms.nil?
|
|
96
|
+
|
|
90
97
|
@crypt_shared_lib_path = options[:crypt_shared_lib_path]
|
|
91
98
|
@explicit_encryption_only = options[:explicit_encryption_only]
|
|
92
99
|
@disable_crypt_shared_lib_search = options[:disable_crypt_shared_lib_search]
|
|
@@ -200,6 +207,17 @@ module Mongo
|
|
|
200
207
|
Binding.setopt_bypass_query_analysis(self) if @bypass_query_analysis
|
|
201
208
|
end
|
|
202
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
|
+
|
|
203
221
|
# Send the logs from libmongocrypt to the Mongo::Logger
|
|
204
222
|
def set_logger_callback
|
|
205
223
|
@log_callback = proc do |level, msg|
|
|
@@ -18,24 +18,6 @@ module Mongo
|
|
|
18
18
|
class Error
|
|
19
19
|
# Raised if an unsupported option is specified for an operation.
|
|
20
20
|
class UnsupportedOption < Error
|
|
21
|
-
# The error message provided when the user passes the hint option to
|
|
22
|
-
# a write operation against a server that does not support the hint
|
|
23
|
-
# option and does not provide option validation.
|
|
24
|
-
#
|
|
25
|
-
# @api private
|
|
26
|
-
HINT_MESSAGE = 'The MongoDB server handling this request does not support ' \
|
|
27
|
-
'the hint option on this command. The hint option is supported on update ' \
|
|
28
|
-
'commands on MongoDB server versions 4.2 and later and on findAndModify ' \
|
|
29
|
-
'and delete commands on MongoDB server versions 4.4 and later'
|
|
30
|
-
|
|
31
|
-
# The error message provided when the user passes the hint option to
|
|
32
|
-
# an unacknowledged write operation.
|
|
33
|
-
#
|
|
34
|
-
# @api private
|
|
35
|
-
UNACKNOWLEDGED_HINT_MESSAGE = 'The hint option cannot be specified on ' \
|
|
36
|
-
'an unacknowledged write operation. Remove the hint option or perform ' \
|
|
37
|
-
'this operation with a write concern of at least { w: 1 }'
|
|
38
|
-
|
|
39
21
|
# The error message provided when the user passes the allow_disk_use
|
|
40
22
|
# option to a find operation against a server that does not support the
|
|
41
23
|
# allow_disk_use operation and does not provide option validation.
|
|
@@ -56,28 +38,6 @@ module Mongo
|
|
|
56
38
|
'option is supported on createIndexes commands on MongoDB server versions ' \
|
|
57
39
|
'4.4 and later'
|
|
58
40
|
|
|
59
|
-
# Raise an error about an unsupported hint option.
|
|
60
|
-
#
|
|
61
|
-
# @option options [ Boolean ] unacknowledged_write Whether this error
|
|
62
|
-
# pertains to a hint option passed to an unacknowledged write. Defaults
|
|
63
|
-
# to false.
|
|
64
|
-
#
|
|
65
|
-
# @return [ Mongo::Error::UnsupportedOption ] An error with a default
|
|
66
|
-
# error message.
|
|
67
|
-
#
|
|
68
|
-
# @api private
|
|
69
|
-
def self.hint_error(**options)
|
|
70
|
-
unacknowledged_write = options[:unacknowledged_write] || false
|
|
71
|
-
|
|
72
|
-
error_message = if unacknowledged_write
|
|
73
|
-
UNACKNOWLEDGED_HINT_MESSAGE
|
|
74
|
-
else
|
|
75
|
-
HINT_MESSAGE
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
new(error_message)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
41
|
# Raise an error about an unsupported allow_disk_use option.
|
|
82
42
|
#
|
|
83
43
|
# @return [ Mongo::Error::UnsupportedOption ] An error with a default
|
data/lib/mongo/grid/fs_bucket.rb
CHANGED
|
@@ -218,10 +218,10 @@ module Mongo
|
|
|
218
218
|
def delete(id, opts = {})
|
|
219
219
|
timeout_holder = CsotTimeoutHolder.new(operation_timeouts: operation_timeouts(opts))
|
|
220
220
|
result = files_collection
|
|
221
|
-
.find({ _id: id }, @options.merge(timeout_ms: timeout_holder.remaining_timeout_ms))
|
|
221
|
+
.find({ _id: { '$eq' => id } }, @options.merge(timeout_ms: timeout_holder.remaining_timeout_ms))
|
|
222
222
|
.delete_one(timeout_ms: timeout_holder.remaining_timeout_ms)
|
|
223
223
|
chunks_collection
|
|
224
|
-
.find({ files_id: id }, @options.merge(timeout_ms: timeout_holder.remaining_timeout_ms))
|
|
224
|
+
.find({ files_id: { '$eq' => id } }, @options.merge(timeout_ms: timeout_holder.remaining_timeout_ms))
|
|
225
225
|
.delete_many(timeout_ms: timeout_holder.remaining_timeout_ms)
|
|
226
226
|
raise Error::FileNotFound.new(id, :id) if result.n == 0
|
|
227
227
|
|
|
@@ -182,7 +182,7 @@ module Mongo
|
|
|
182
182
|
@file_info ||= begin
|
|
183
183
|
doc = options[:file_info_doc] ||
|
|
184
184
|
fs.files_collection.find(
|
|
185
|
-
{ _id: file_id },
|
|
185
|
+
{ _id: { '$eq' => file_id } },
|
|
186
186
|
{ timeout_ms: @timeout_holder.remaining_timeout_ms! }
|
|
187
187
|
).first
|
|
188
188
|
File::Info.new(Options::Mapper.transform(doc, File::Info::MAPPINGS.invert)) if doc
|
|
@@ -216,7 +216,7 @@ module Mongo
|
|
|
216
216
|
opts[:timeout_mode] = :cursor_lifetime
|
|
217
217
|
end
|
|
218
218
|
|
|
219
|
-
fs.chunks_collection.find({ files_id: file_id }, opts).sort(n: 1)
|
|
219
|
+
fs.chunks_collection.find({ files_id: { '$eq' => file_id } }, opts).sort(n: 1)
|
|
220
220
|
end
|
|
221
221
|
end
|
|
222
222
|
|
|
@@ -177,7 +177,7 @@ module Mongo
|
|
|
177
177
|
# @since 2.1.0
|
|
178
178
|
def abort
|
|
179
179
|
fs.chunks_collection.find(
|
|
180
|
-
{ files_id: file_id },
|
|
180
|
+
{ files_id: { '$eq' => file_id } },
|
|
181
181
|
@options.merge(timeout_ms: @timeout_holder.remaining_timeout_ms!)
|
|
182
182
|
).delete_many
|
|
183
183
|
(@open = false) || true
|
|
@@ -456,6 +456,15 @@ module Mongo
|
|
|
456
456
|
doc['cursor']&.[]('atClusterTime') || doc['atClusterTime']
|
|
457
457
|
end
|
|
458
458
|
|
|
459
|
+
# Returns the base backoff in milliseconds for a server overload error, if present.
|
|
460
|
+
#
|
|
461
|
+
# @return [ Integer | nil ] The base backoff in milliseconds.
|
|
462
|
+
#
|
|
463
|
+
# @api private
|
|
464
|
+
def base_backoff_ms
|
|
465
|
+
first_document && first_document['baseBackoffMS']
|
|
466
|
+
end
|
|
467
|
+
|
|
459
468
|
private
|
|
460
469
|
|
|
461
470
|
def operation_failure_class
|
|
@@ -132,12 +132,7 @@ module Mongo
|
|
|
132
132
|
def process_result_for_sdam(result, connection)
|
|
133
133
|
if (result.not_master? || result.node_recovering?) &&
|
|
134
134
|
connection.generation >= connection.server.pool.generation(service_id: connection.service_id)
|
|
135
|
-
keep_pool =
|
|
136
|
-
false
|
|
137
|
-
else
|
|
138
|
-
# Max wire version needs to be examined while the server is known
|
|
139
|
-
connection.description.server_version_gte?('4.2')
|
|
140
|
-
end
|
|
135
|
+
keep_pool = !result.node_shutting_down?
|
|
141
136
|
|
|
142
137
|
connection.server.unknown!(
|
|
143
138
|
keep_connection_pool: keep_pool,
|
|
@@ -148,10 +148,6 @@ module Mongo
|
|
|
148
148
|
# @return [ Mongo::Protocol::Msg ] The decrypted message, or the original
|
|
149
149
|
# message if decryption was not possible or necessary.
|
|
150
150
|
def maybe_decrypt(_context)
|
|
151
|
-
# TODO: determine if we should be decrypting data coming from pre-4.2
|
|
152
|
-
# servers, potentially using legacy wire protocols. If so we need
|
|
153
|
-
# to implement decryption for those wire protocols as our current
|
|
154
|
-
# encryption/decryption code is OP_MSG-specific.
|
|
155
151
|
self
|
|
156
152
|
end
|
|
157
153
|
|
data/lib/mongo/protocol/msg.rb
CHANGED
|
@@ -201,23 +201,12 @@ module Mongo
|
|
|
201
201
|
# represents one of the command types allow-listed by libmongocrypt and it
|
|
202
202
|
# contains data that is required to be encrypted by a local or remote json schema.
|
|
203
203
|
#
|
|
204
|
-
# @param [ Mongo::Server::Connection ] connection The connection on which
|
|
205
|
-
# the operation is performed.
|
|
206
204
|
# @param [ Mongo::Operation::Context ] context The operation context.
|
|
207
205
|
#
|
|
208
206
|
# @return [ Mongo::Protocol::Msg ] The encrypted message, or the original
|
|
209
207
|
# message if encryption was not possible or necessary.
|
|
210
|
-
def maybe_encrypt(
|
|
208
|
+
def maybe_encrypt(_connection, context)
|
|
211
209
|
if context.encrypt?
|
|
212
|
-
if connection.description.max_wire_version < 8
|
|
213
|
-
raise Error::CryptError.new(
|
|
214
|
-
'Cannot perform encryption against a MongoDB server older than ' +
|
|
215
|
-
'4.2 (wire version less than 8). Currently connected to server ' +
|
|
216
|
-
"with max wire version #{connection.description.max_wire_version}} " +
|
|
217
|
-
'(Auto-encryption requires a minimum MongoDB version of 4.2)'
|
|
218
|
-
)
|
|
219
|
-
end
|
|
220
|
-
|
|
221
210
|
db_name = @main_document[DATABASE_IDENTIFIER]
|
|
222
211
|
cmd = merge_sections
|
|
223
212
|
enc_cmd = context.encrypt(db_name, cmd)
|
|
@@ -23,9 +23,24 @@ module Mongo
|
|
|
23
23
|
# a random value. Can be injected for deterministic testing.
|
|
24
24
|
#
|
|
25
25
|
# @return [ Float ] The backoff delay in seconds.
|
|
26
|
-
def self.backoff_delay(attempt, jitter: rand)
|
|
27
|
-
jitter * [ MAX_BACKOFF,
|
|
26
|
+
def self.backoff_delay(attempt, jitter: rand, err: nil)
|
|
27
|
+
jitter * [ MAX_BACKOFF, base_backoff(err) * (2**attempt) ].min
|
|
28
28
|
end
|
|
29
|
+
|
|
30
|
+
def self.base_backoff(err)
|
|
31
|
+
return BASE_BACKOFF if err.nil?
|
|
32
|
+
return BASE_BACKOFF unless err.respond_to?(:result) && err.result.respond_to?(:base_backoff_ms)
|
|
33
|
+
|
|
34
|
+
base_backoff_ms = err.result.base_backoff_ms
|
|
35
|
+
|
|
36
|
+
if base_backoff_ms && base_backoff_ms > 0
|
|
37
|
+
err.result.base_backoff_ms / 1000.0
|
|
38
|
+
else
|
|
39
|
+
BASE_BACKOFF
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private_class_method :base_backoff
|
|
29
44
|
end
|
|
30
45
|
end
|
|
31
46
|
end
|
|
@@ -340,7 +340,7 @@ module Mongo
|
|
|
340
340
|
def overload_read_retry(last_error, session, server_selector, context, failed_server, error_count:)
|
|
341
341
|
last_was_overload = true
|
|
342
342
|
loop do
|
|
343
|
-
delay = last_was_overload ? retry_policy.backoff_delay(error_count) : 0
|
|
343
|
+
delay = last_was_overload ? retry_policy.backoff_delay(error_count, err: last_error) : 0
|
|
344
344
|
raise last_error unless retry_policy.should_retry_overload?(error_count, delay, context: context)
|
|
345
345
|
|
|
346
346
|
log_retry(last_error, message: 'Read retry (overload backoff)')
|
|
@@ -27,8 +27,8 @@ module Mongo
|
|
|
27
27
|
# @param [ Float ] jitter A random float in [0.0, 1.0).
|
|
28
28
|
#
|
|
29
29
|
# @return [ Float ] The backoff delay in seconds.
|
|
30
|
-
def backoff_delay(attempt, jitter: rand)
|
|
31
|
-
Backpressure.backoff_delay(attempt, jitter: jitter)
|
|
30
|
+
def backoff_delay(attempt, jitter: rand, err: nil)
|
|
31
|
+
Backpressure.backoff_delay(attempt, jitter: jitter, err: err)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# Determine whether an overload retry should be attempted.
|
|
@@ -123,7 +123,7 @@ module Mongo
|
|
|
123
123
|
unless e.respond_to?(:label?) && e.label?('NoWritesPerformed')
|
|
124
124
|
error_to_raise = e
|
|
125
125
|
end
|
|
126
|
-
delay = retry_policy.backoff_delay(error_count)
|
|
126
|
+
delay = retry_policy.backoff_delay(error_count, err: e)
|
|
127
127
|
raise error_to_raise unless retry_policy.should_retry_overload?(error_count, delay, context: context)
|
|
128
128
|
|
|
129
129
|
log_retry(e, message: 'Write retry (overload backoff)')
|
|
@@ -386,7 +386,7 @@ module Mongo
|
|
|
386
386
|
last_was_overload = true
|
|
387
387
|
|
|
388
388
|
loop do
|
|
389
|
-
delay = last_was_overload ? retry_policy.backoff_delay(error_count) : 0
|
|
389
|
+
delay = last_was_overload ? retry_policy.backoff_delay(error_count, err: last_error) : 0
|
|
390
390
|
raise error_to_raise unless retry_policy.should_retry_overload?(error_count, delay, context: context)
|
|
391
391
|
|
|
392
392
|
log_retry(last_error, message: 'Write retry (overload backoff)')
|
data/lib/mongo/retryable.rb
CHANGED
|
@@ -129,7 +129,7 @@ module Mongo
|
|
|
129
129
|
|
|
130
130
|
error_count += 1
|
|
131
131
|
policy = client.retry_policy
|
|
132
|
-
delay = policy.backoff_delay(error_count)
|
|
132
|
+
delay = policy.backoff_delay(error_count, err: e)
|
|
133
133
|
raise e unless policy.should_retry_overload?(error_count, delay, context: context)
|
|
134
134
|
|
|
135
135
|
Logger.logger.warn("Overload retry due to: #{e.class.name}: #{e.message}")
|
|
@@ -24,7 +24,6 @@ module Mongo
|
|
|
24
24
|
# List of features and the wire protocol version they appear in.
|
|
25
25
|
#
|
|
26
26
|
# Wire protocol versions map to server releases as follows:
|
|
27
|
-
# - 8 => 4.2
|
|
28
27
|
# - 9 => 4.4
|
|
29
28
|
# - 13 => 5.0
|
|
30
29
|
# - 14 => 5.1
|
|
@@ -61,7 +60,7 @@ module Mongo
|
|
|
61
60
|
# The wire protocol versions that this version of the driver supports.
|
|
62
61
|
#
|
|
63
62
|
# @since 2.0.0
|
|
64
|
-
DRIVER_WIRE_VERSIONS =
|
|
63
|
+
DRIVER_WIRE_VERSIONS = 9..29
|
|
65
64
|
|
|
66
65
|
# The wire protocol versions that are deprecated in this version of the
|
|
67
66
|
# driver. Support for these versions will be removed in the future.
|
|
@@ -74,7 +73,7 @@ module Mongo
|
|
|
74
73
|
#
|
|
75
74
|
# If there are no currently-deprecated wire versions, this should be
|
|
76
75
|
# set to an empty range (e.g. the EMPTY_RANGE constant).
|
|
77
|
-
DEPRECATED_WIRE_VERSIONS =
|
|
76
|
+
DEPRECATED_WIRE_VERSIONS = EMPTY_RANGE
|
|
78
77
|
|
|
79
78
|
# make sure the deprecated versions are valid
|
|
80
79
|
if DEPRECATED_WIRE_VERSIONS.min && (DRIVER_WIRE_VERSIONS.min > DEPRECATED_WIRE_VERSIONS.max)
|
|
@@ -52,11 +52,6 @@ module Mongo
|
|
|
52
52
|
# SCRAM-SHA-1. The use of SCRAM-SHA-256 by default is thus
|
|
53
53
|
# sensible, and it is also mandated by the speculative auth spec.
|
|
54
54
|
#
|
|
55
|
-
# If no mechanism was specified and we are talking to a 4.2
|
|
56
|
-
# server, we'll send speculative auth document, the server will
|
|
57
|
-
# ignore it and we'll perform authentication using explicit
|
|
58
|
-
# command.
|
|
59
|
-
#
|
|
60
55
|
# If no mechanism was specified and we are talking to a 4.4+
|
|
61
56
|
# server and the user account doesn't allow SCRAM-SHA-256, we will
|
|
62
57
|
# authenticate in a separate command with SCRAM-SHA-1 after
|
data/lib/mongo/server.rb
CHANGED
|
@@ -567,8 +567,7 @@ module Mongo
|
|
|
567
567
|
# @option options [ true | false ] :keep_connection_pool Usually when the
|
|
568
568
|
# new server description is unknown, the connection pool on the
|
|
569
569
|
# respective server is cleared. Set this option to true to keep the
|
|
570
|
-
# existing connection pool (required when handling not master errors
|
|
571
|
-
# on 4.2+ servers).
|
|
570
|
+
# existing connection pool (required when handling not master errors).
|
|
572
571
|
# @option options [ TopologyVersion ] :topology_version Topology version
|
|
573
572
|
# of the error response that is causing the server to be marked unknown.
|
|
574
573
|
# @option options [ true | false ] :stop_push_monitor Set when the server
|
data/lib/mongo/session.rb
CHANGED
|
@@ -471,7 +471,7 @@ module Mongo
|
|
|
471
471
|
loop do
|
|
472
472
|
if transaction_attempt > 0
|
|
473
473
|
if overload_encountered
|
|
474
|
-
delay = @client.retry_policy.backoff_delay(overload_error_count)
|
|
474
|
+
delay = @client.retry_policy.backoff_delay(overload_error_count, err: last_error)
|
|
475
475
|
if backoff_would_exceed_deadline?(deadline, delay)
|
|
476
476
|
make_timeout_error_from(last_error, 'CSOT timeout expired waiting to retry withTransaction')
|
|
477
477
|
end
|
|
@@ -562,7 +562,7 @@ module Mongo
|
|
|
562
562
|
end
|
|
563
563
|
|
|
564
564
|
if overload_encountered
|
|
565
|
-
delay = @client.retry_policy.backoff_delay(overload_error_count)
|
|
565
|
+
delay = @client.retry_policy.backoff_delay(overload_error_count, err: e)
|
|
566
566
|
if backoff_would_exceed_deadline?(deadline, delay)
|
|
567
567
|
transaction_in_progress = false
|
|
568
568
|
make_timeout_error_from(e, 'CSOT timeout expired during withTransaction commit')
|
|
@@ -1429,7 +1429,7 @@ module Mongo
|
|
|
1429
1429
|
private_constant :BACKOFF_INITIAL, :BACKOFF_MAX
|
|
1430
1430
|
|
|
1431
1431
|
def backoff_seconds_for_retry(transaction_attempt)
|
|
1432
|
-
exponential = BACKOFF_INITIAL * (1.5**
|
|
1432
|
+
exponential = BACKOFF_INITIAL * (1.5**transaction_attempt)
|
|
1433
1433
|
Random.rand * [ exponential, BACKOFF_MAX ].min
|
|
1434
1434
|
end
|
|
1435
1435
|
|
|
@@ -614,7 +614,7 @@ module Mongo
|
|
|
614
614
|
#
|
|
615
615
|
# @return [ Hash | nil ] The auth mechanism properties hash.
|
|
616
616
|
def convert_auth_mech_props(_name, value)
|
|
617
|
-
properties = hash_extractor('authMechanismProperties', value)
|
|
617
|
+
properties = hash_extractor('authMechanismProperties', value, invalidate_on_error: true)
|
|
618
618
|
if properties
|
|
619
619
|
properties.each do |k, v|
|
|
620
620
|
properties[k] = (v.downcase == 'true') if k.to_s.downcase == 'canonicalize_host_name' && v
|
|
@@ -863,16 +863,27 @@ module Mongo
|
|
|
863
863
|
|
|
864
864
|
# Extract values from the string and put them into a nested hash.
|
|
865
865
|
#
|
|
866
|
+
# Multiple key-value pairs are delimited by a comma. Within each pair,
|
|
867
|
+
# the key is everything up to the first colon and the value is everything
|
|
868
|
+
# after it, so values may themselves contain colons (for example
|
|
869
|
+
# TOKEN_RESOURCE:mongodb://host).
|
|
870
|
+
#
|
|
866
871
|
# @param [ String ] name Name of the URI option being processed.
|
|
867
872
|
# @param [ String ] value The string to build a hash from.
|
|
873
|
+
# @param [ true | false ] invalidate_on_error When a pair is malformed
|
|
874
|
+
# (has no value), discard the whole option and return nil instead of
|
|
875
|
+
# skipping just that pair. Used for options such as
|
|
876
|
+
# authMechanismProperties whose values must not contain commas.
|
|
868
877
|
#
|
|
869
|
-
# @return [ Hash ] The hash built from the string.
|
|
870
|
-
def hash_extractor(name, value)
|
|
878
|
+
# @return [ Hash | nil ] The hash built from the string, or nil.
|
|
879
|
+
def hash_extractor(name, value, invalidate_on_error: false)
|
|
871
880
|
h = {}
|
|
872
881
|
value.split(',').each do |tag|
|
|
873
|
-
k, v = tag.split(':')
|
|
882
|
+
k, v = tag.split(':', 2)
|
|
874
883
|
if v.nil?
|
|
875
884
|
log_warn("Invalid hash value for #{name}: key `#{k}` does not have a value: #{value}")
|
|
885
|
+
return nil if invalidate_on_error
|
|
886
|
+
|
|
876
887
|
next
|
|
877
888
|
end
|
|
878
889
|
|
data/lib/mongo/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mongo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.26.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- The MongoDB Ruby Team
|
|
@@ -561,7 +561,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
561
561
|
- !ruby/object:Gem::Version
|
|
562
562
|
version: '0'
|
|
563
563
|
requirements: []
|
|
564
|
-
rubygems_version: 4.0.
|
|
564
|
+
rubygems_version: 4.0.20
|
|
565
565
|
specification_version: 4
|
|
566
566
|
summary: Ruby driver for MongoDB
|
|
567
567
|
test_files: []
|