Skip to content

RUBY-3226 Update libmongocrypt payloads to new QE protocol #2718

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,56 @@ Bundler/OrderedGems:
Gemspec/OrderedDependencies:
Enabled: false

Layout/SpaceInsideArrayLiteralBrackets:
EnforcedStyle: space

Layout/SpaceInsidePercentLiteralDelimiters:
Enabled: false

Metrics/ModuleLength:
Enabled: false

Metrics/MethodLength:
Max: 20

RSpec/BeforeAfterAll:
Enabled: false

# Ideally, we'd use this one, too, but our tests have not historically followed
# this style and it's not worth changing right now, IMO
RSpec/DescribeClass:
Enabled: false

RSpec/ImplicitExpect:
EnforcedStyle: is_expected

RSpec/MultipleExpectations:
Enabled: false

RSpec/MultipleMemoizedHelpers:
Enabled: false

RSpec/NestedGroups:
Enabled: false

Style/Documentation:
Exclude:
- 'spec/**/*'

Metrics/MethodLength:
Max: 20
Style/ModuleFunction:
EnforcedStyle: extend_self

Style/OptionalBooleanParameter:
Enabled: false

Style/ParallelAssignment:
Enabled: false

Style/TernaryParentheses:
EnforcedStyle: require_parentheses_when_complex

Style/TrailingCommaInArrayLiteral:
Enabled: false

Style/TrailingCommaInHashLiteral:
Enabled: false
9 changes: 4 additions & 5 deletions gemfiles/standard.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true
# rubocop:todo all

# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/BlockLength
def standard_dependencies
gem 'yard'
gem 'ffi'
Expand Down Expand Up @@ -29,7 +29,7 @@ def standard_dependencies

# for static analysis -- ignore ruby < 2.6 because of rubocop
# version incompatibilities
if RUBY_VERSION > "2.5.99"
if RUBY_VERSION > '2.5.99'
gem 'rubocop', '~> 1.45.1'
gem 'rubocop-performance', '~> 1.16.0'
gem 'rubocop-rake', '~> 0.6.0'
Expand Down Expand Up @@ -66,7 +66,6 @@ def standard_dependencies
gem 'solargraph', platforms: :mri
end

if ENV['FLE'] == 'helper'
gem 'libmongocrypt-helper', '~> 1.7.0'
end
gem 'libmongocrypt-helper', '~> 1.8.0' if ENV['FLE'] == 'helper'
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/BlockLength
117 changes: 76 additions & 41 deletions lib/mongo/collection/queryable_encryption.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# frozen_string_literal: true
# rubocop:todo all

# Copyright (C) 2014-2022 MongoDB Inc.
#
Expand All @@ -22,6 +21,8 @@ class Collection
#
# @api private
module QueryableEncryption
# The minimum wire version for QE2 support
QE2_MIN_WIRE_VERSION = 21

# Creates auxiliary collections and indices for queryable encryption if necessary.
#
Expand All @@ -32,32 +33,17 @@ module QueryableEncryption
#
# @return [ Result ] The result of provided block.
def maybe_create_qe_collections(encrypted_fields, client, session)
encrypted_fields = if encrypted_fields
encrypted_fields
elsif encrypted_fields_map
encrypted_fields_map[namespace] || {}
else
{}
end
if encrypted_fields.empty?
return yield
end
encrypted_fields = encrypted_fields_from(encrypted_fields)
return yield if encrypted_fields.empty?

check_wire_version!

emm_collections(encrypted_fields).each do |coll|
context = Operation::Context.new(client: client, session: session)
Operation::Create.new(
selector: {
create: coll,
clusteredIndex: {
key: {
_id: 1
},
unique: true
}
},
db_name: database.name,
).execute(next_primary(nil, session), context: context)
create_operation_for(coll)
.execute(next_primary(nil, session), context: context)
end

yield(encrypted_fields).tap do |result|
indexes.create_one(__safeContent__: 1) if result
end
Expand All @@ -73,31 +59,25 @@ def maybe_create_qe_collections(encrypted_fields, client, session)
# @return [ Result ] The result of provided block.
def maybe_drop_emm_collections(encrypted_fields, client, session)
encrypted_fields = if encrypted_fields
encrypted_fields
elsif encrypted_fields_map
if encrypted_fields_map[namespace]
encrypted_fields_map[namespace]
else
database.list_collections(filter: { name: name })
.first
&.fetch(:options, {})
&.fetch(:encryptedFields, {}) || {}
end
else
{}
end
if encrypted_fields.empty?
return yield
end
encrypted_fields
elsif encrypted_fields_map
encrypted_fields_for_drop_from_map
else
{}
end

return yield if encrypted_fields.empty?

emm_collections(encrypted_fields).each do |coll|
context = Operation::Context.new(client: client, session: session)
operation = Operation::Drop.new(
selector: { drop: coll },
db_name: database.name,
session: session,
session: session
)
do_drop(operation, session, context)
end

yield
end

Expand All @@ -112,11 +92,66 @@ def maybe_drop_emm_collections(encrypted_fields, client, session)
def emm_collections(encrypted_fields)
[
encrypted_fields['escCollection'] || "enxcol_.#{name}.esc",
encrypted_fields['eccCollection'] || "enxcol_.#{name}.ecc",
encrypted_fields['ecocCollection'] || "enxcol_.#{name}.ecoc",
]
end

# Creating encrypted collections is only supported on 7.0.0 and later
# (wire version 21+).
#
# @raise [ Mongo::Error ] if the wire version is not
# recent enough
def check_wire_version!
return unless next_primary.max_wire_version < QE2_MIN_WIRE_VERSION

raise Mongo::Error,
'Driver support of Queryable Encryption is incompatible with server. ' \
'Upgrade server to use Queryable Encryption.'
end

# Tries to return the encrypted fields from the argument. If the argument
# is nil, tries to find the encrypted fields from the
# encrypted_fields_map.
#
# @param [ Hash | nil ] fields the encrypted fields
#
# @return [ Hash ] the encrypted fields
def encrypted_fields_from(fields)
fields ||
(encrypted_fields_map && encrypted_fields_map[namespace]) ||
{}
end

# Tries to return the encrypted fields from the {{encrypted_fields_map}}
# value, for the current namespace.
#
# @return [ Hash | nil ] the encrypted fields, if found
def encrypted_fields_for_drop_from_map
encrypted_fields_map[namespace] ||
database.list_collections(filter: { name: name })
.first
&.fetch(:options, {})
&.fetch(:encryptedFields, {}) ||
{}
end

# Returns a new create operation for the given collection.
#
# @param [ String ] coll the name of the collection to create.
#
# @return [ Operation::Create ] the new create operation.
def create_operation_for(coll)
Operation::Create.new(
selector: {
create: coll,
clusteredIndex: {
key: { _id: 1 },
unique: true
}
},
db_name: database.name
)
end
end
end
end
1 change: 1 addition & 0 deletions lib/mongo/crypt/kms/azure/credentials_retriever.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: true
# rubocop:todo all

# Copyright (C) 2019-2021 MongoDB Inc.
#
Expand Down
Loading