1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
|
# frozen_string_literal: true
# Copyright (C) 2014-2022 MongoDB Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Mongo
class Collection
# This module contains methods for creating and dropping auxiliary collections
# for queryable encryption.
#
# @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.
#
# @param [ Hash | nil ] encrypted_fields Encrypted fields hash that was
# provided to `create` collection helper.
# @param [ Client ] client Mongo client to be used to create auxiliary collections.
# @param [ Session ] session Session to be used to create auxiliary collections.
#
# @return [ Result ] The result of provided block.
def maybe_create_qe_collections(encrypted_fields, client, session)
encrypted_fields = encrypted_fields_from(encrypted_fields)
return yield if encrypted_fields.empty?
server = next_primary(nil, session)
context = Operation::Context.new(client: client, session: session)
server.with_connection do |connection|
check_wire_version!(connection)
emm_collections(encrypted_fields).each do |coll|
create_operation_for(coll)
.execute_with_connection(connection, context: context)
end
end
yield(encrypted_fields).tap do |result|
indexes.create_one(__safeContent__: 1) if result
end
end
# Drops auxiliary collections and indices for queryable encryption if necessary.
#
# @param [ Hash | nil ] encrypted_fields Encrypted fields hash that was
# provided to `create` collection helper.
# @param [ Client ] client Mongo client to be used to drop auxiliary collections.
# @param [ Session ] session Session to be used to drop auxiliary collections.
#
# @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
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
)
do_drop(operation, session, context)
end
yield
end
private
# Checks if names for auxiliary collections are set and returns them,
# otherwise returns default names.
#
# @param [ Hash ] encrypted_fields Encrypted fields hash.
#
# @return [ Array <String> ] Array of auxiliary collections names.
def emm_collections(encrypted_fields)
[
encrypted_fields['escCollection'] || "enxcol_.#{name}.esc",
encrypted_fields['ecocCollection'] || "enxcol_.#{name}.ecoc",
]
end
# Creating encrypted collections is only supported on 7.0.0 and later
# (wire version 21+).
#
# @param [ Mongo::Connection ] connection The connection to check
# the wire version of.
#
# @raise [ Mongo::Error ] if the wire version is not
# recent enough
def check_wire_version!(connection)
return unless connection.description.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
|