File: queryable_encryption.rb

package info (click to toggle)
ruby-mongo 2.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 15,020 kB
  • sloc: ruby: 110,810; makefile: 5
file content (162 lines) | stat: -rw-r--r-- 5,914 bytes parent folder | download
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