Esempio n. 1
0
 def test_encrypt(self):
     encrypter = AutoEncrypter(
         MockCallback(list_colls_result=bson_data('collection-info.json'),
                      mongocryptd_reply=bson_data('mongocryptd-reply.json'),
                      key_docs=[bson_data('key-document.json')],
                      kms_reply=http_data('kms-reply.txt')),
         self.mongo_crypt_opts())
     self.addCleanup(encrypter.close)
     encrypted = encrypter.encrypt('text', bson_data('command.json'))
     self.assertEqual(
         BSON(encrypted).decode(), json_data('encrypted-command.json'))
     self.assertEqual(encrypted, bson_data('encrypted-command.json'))
Esempio n. 2
0
class _Encrypter(object):
    """Encrypts and decrypts MongoDB commands.

    This class is used to support automatic encryption and decryption of
    MongoDB commands."""
    def __init__(self, client, opts):
        """Create a _Encrypter for a client.

        :Parameters:
          - `client`: The encrypted MongoClient.
          - `opts`: The encrypted client's :class:`AutoEncryptionOpts`.
        """
        if opts._schema_map is None:
            schema_map = None
        else:
            schema_map = _dict_to_bson(opts._schema_map, False, _DATA_KEY_OPTS)
        self._bypass_auto_encryption = opts._bypass_auto_encryption
        self._internal_client = None

        def _get_internal_client(encrypter, mongo_client):
            if mongo_client.max_pool_size is None:
                # Unlimited pool size, use the same client.
                return mongo_client
            # Else - limited pool size, use an internal client.
            if encrypter._internal_client is not None:
                return encrypter._internal_client
            internal_client = mongo_client._duplicate(
                minPoolSize=0, auto_encryption_opts=None)
            encrypter._internal_client = internal_client
            return internal_client

        if opts._key_vault_client is not None:
            key_vault_client = opts._key_vault_client
        else:
            key_vault_client = _get_internal_client(self, client)

        if opts._bypass_auto_encryption:
            metadata_client = None
        else:
            metadata_client = _get_internal_client(self, client)

        db, coll = opts._key_vault_namespace.split('.', 1)
        key_vault_coll = key_vault_client[db][coll]

        mongocryptd_client = MongoClient(
            opts._mongocryptd_uri,
            connect=False,
            serverSelectionTimeoutMS=_MONGOCRYPTD_TIMEOUT_MS)

        io_callbacks = _EncryptionIO(metadata_client, key_vault_coll,
                                     mongocryptd_client, opts)
        self._auto_encrypter = AutoEncrypter(
            io_callbacks, MongoCryptOptions(opts._kms_providers, schema_map))
        self._closed = False

    def encrypt(self, database, cmd, check_keys, codec_options):
        """Encrypt a MongoDB command.

        :Parameters:
          - `database`: The database for this command.
          - `cmd`: A command document.
          - `check_keys`: If True, check `cmd` for invalid keys.
          - `codec_options`: The CodecOptions to use while encoding `cmd`.

        :Returns:
          The encrypted command to execute.
        """
        self._check_closed()
        # Workaround for $clusterTime which is incompatible with
        # check_keys.
        cluster_time = check_keys and cmd.pop('$clusterTime', None)
        encoded_cmd = _dict_to_bson(cmd, check_keys, codec_options)
        with _wrap_encryption_errors():
            encrypted_cmd = self._auto_encrypter.encrypt(database, encoded_cmd)
            # TODO: PYTHON-1922 avoid decoding the encrypted_cmd.
            encrypt_cmd = _inflate_bson(encrypted_cmd,
                                        DEFAULT_RAW_BSON_OPTIONS)
            if cluster_time:
                encrypt_cmd['$clusterTime'] = cluster_time
            return encrypt_cmd

    def decrypt(self, response):
        """Decrypt a MongoDB command response.

        :Parameters:
          - `response`: A MongoDB command response as BSON.

        :Returns:
          The decrypted command response.
        """
        self._check_closed()
        with _wrap_encryption_errors():
            return self._auto_encrypter.decrypt(response)

    def _check_closed(self):
        if self._closed:
            raise InvalidOperation("Cannot use MongoClient after close")

    def close(self):
        """Cleanup resources."""
        self._closed = True
        self._auto_encrypter.close()
        if self._internal_client:
            self._internal_client.close()
            self._internal_client = None
Esempio n. 3
0
class _Encrypter(object):
    def __init__(self, io_callbacks, opts):
        """Encrypts and decrypts MongoDB commands.

        This class is used to support automatic encryption and decryption of
        MongoDB commands.

        :Parameters:
          - `io_callbacks`: A :class:`MongoCryptCallback`.
          - `opts`: The encrypted client's :class:`AutoEncryptionOpts`.
        """
        if opts._schema_map is None:
            schema_map = None
        else:
            schema_map = _dict_to_bson(opts._schema_map, False, _DATA_KEY_OPTS)
        self._auto_encrypter = AutoEncrypter(
            io_callbacks, MongoCryptOptions(opts._kms_providers, schema_map))
        self._bypass_auto_encryption = opts._bypass_auto_encryption
        self._closed = False

    def encrypt(self, database, cmd, check_keys, codec_options):
        """Encrypt a MongoDB command.

        :Parameters:
          - `database`: The database for this command.
          - `cmd`: A command document.
          - `check_keys`: If True, check `cmd` for invalid keys.
          - `codec_options`: The CodecOptions to use while encoding `cmd`.

        :Returns:
          The encrypted command to execute.
        """
        self._check_closed()
        # Workaround for $clusterTime which is incompatible with
        # check_keys.
        cluster_time = check_keys and cmd.pop('$clusterTime', None)
        encoded_cmd = _dict_to_bson(cmd, check_keys, codec_options)
        max_cmd_size = _MAX_ENC_BSON_SIZE + _COMMAND_OVERHEAD
        if len(encoded_cmd) > max_cmd_size:
            raise _raise_document_too_large(next(iter(cmd)), len(encoded_cmd),
                                            max_cmd_size)
        with _wrap_encryption_errors():
            encrypted_cmd = self._auto_encrypter.encrypt(database, encoded_cmd)
            # TODO: PYTHON-1922 avoid decoding the encrypted_cmd.
            encrypt_cmd = _inflate_bson(encrypted_cmd,
                                        DEFAULT_RAW_BSON_OPTIONS)
            if cluster_time:
                encrypt_cmd['$clusterTime'] = cluster_time
            return encrypt_cmd

    def decrypt(self, response):
        """Decrypt a MongoDB command response.

        :Parameters:
          - `response`: A MongoDB command response as BSON.

        :Returns:
          The decrypted command response.
        """
        self._check_closed()
        with _wrap_encryption_errors():
            return self._auto_encrypter.decrypt(response)

    def _check_closed(self):
        if self._closed:
            raise InvalidOperation("Cannot use MongoClient after close")

    def close(self):
        """Cleanup resources."""
        self._closed = True
        self._auto_encrypter.close()

    @staticmethod
    def create(client, opts):
        """Create a _CommandEncyptor for a client.

        :Parameters:
          - `client`: The encrypted MongoClient.
          - `opts`: The encrypted client's :class:`AutoEncryptionOpts`.

        :Returns:
          A :class:`_CommandEncrypter` for this client.
        """
        key_vault_client = opts._key_vault_client or client
        db, coll = opts._key_vault_namespace.split('.', 1)
        key_vault_coll = key_vault_client[db][coll]

        mongocryptd_client = MongoClient(
            opts._mongocryptd_uri,
            connect=False,
            serverSelectionTimeoutMS=_MONGOCRYPTD_TIMEOUT_MS)

        io_callbacks = _EncryptionIO(client, key_vault_coll,
                                     mongocryptd_client, opts)
        return _Encrypter(io_callbacks, opts)