Skip to content

Commit 8d693e8

Browse files
committed
PYTHON-1921 Raise InvalidOperation when using a closed encrypted client
1 parent 56bb5dd commit 8d693e8

File tree

3 files changed

+41
-31
lines changed

3 files changed

+41
-31
lines changed

pymongo/encryption.py

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767

6868

6969
@contextlib.contextmanager
70-
def _wrap_encryption_errors_ctx():
70+
def _wrap_encryption_errors():
7171
"""Context manager to wrap encryption related errors."""
7272
try:
7373
yield
@@ -79,19 +79,6 @@ def _wrap_encryption_errors_ctx():
7979
raise EncryptionError(exc)
8080

8181

82-
def _wrap_encryption_errors(encryption_func=None):
83-
"""Decorator or context manager to wrap encryption related errors."""
84-
if encryption_func:
85-
@functools.wraps(encryption_func)
86-
def wrap_encryption_errors(*args, **kwargs):
87-
with _wrap_encryption_errors_ctx():
88-
return encryption_func(*args, **kwargs)
89-
90-
return wrap_encryption_errors
91-
else:
92-
return _wrap_encryption_errors_ctx()
93-
94-
9582
class _EncryptionIO(MongoCryptCallback):
9683
def __init__(self, client, key_vault_coll, mongocryptd_client, opts):
9784
"""Internal class to perform I/O on behalf of pymongocrypt."""
@@ -260,8 +247,8 @@ def __init__(self, io_callbacks, opts):
260247
self._auto_encrypter = AutoEncrypter(io_callbacks, MongoCryptOptions(
261248
opts._kms_providers, schema_map))
262249
self._bypass_auto_encryption = opts._bypass_auto_encryption
250+
self._closed = False
263251

264-
@_wrap_encryption_errors
265252
def encrypt(self, database, cmd, check_keys, codec_options):
266253
"""Encrypt a MongoDB command.
267254
@@ -274,17 +261,20 @@ def encrypt(self, database, cmd, check_keys, codec_options):
274261
:Returns:
275262
The encrypted command to execute.
276263
"""
277-
# Workaround for $clusterTime which is incompatible with check_keys.
278-
cluster_time = check_keys and cmd.pop('$clusterTime', None)
279-
encoded_cmd = _dict_to_bson(cmd, check_keys, codec_options)
280-
encrypted_cmd = self._auto_encrypter.encrypt(database, encoded_cmd)
281-
# TODO: PYTHON-1922 avoid decoding the encrypted_cmd.
282-
encrypt_cmd = _inflate_bson(encrypted_cmd, DEFAULT_RAW_BSON_OPTIONS)
283-
if cluster_time:
284-
encrypt_cmd['$clusterTime'] = cluster_time
285-
return encrypt_cmd
286-
287-
@_wrap_encryption_errors
264+
self._check_closed()
265+
with _wrap_encryption_errors():
266+
# Workaround for $clusterTime which is incompatible with
267+
# check_keys.
268+
cluster_time = check_keys and cmd.pop('$clusterTime', None)
269+
encoded_cmd = _dict_to_bson(cmd, check_keys, codec_options)
270+
encrypted_cmd = self._auto_encrypter.encrypt(database, encoded_cmd)
271+
# TODO: PYTHON-1922 avoid decoding the encrypted_cmd.
272+
encrypt_cmd = _inflate_bson(
273+
encrypted_cmd, DEFAULT_RAW_BSON_OPTIONS)
274+
if cluster_time:
275+
encrypt_cmd['$clusterTime'] = cluster_time
276+
return encrypt_cmd
277+
288278
def decrypt(self, response):
289279
"""Decrypt a MongoDB command response.
290280
@@ -294,10 +284,17 @@ def decrypt(self, response):
294284
:Returns:
295285
The decrypted command response.
296286
"""
297-
return self._auto_encrypter.decrypt(response)
287+
self._check_closed()
288+
with _wrap_encryption_errors():
289+
return self._auto_encrypter.decrypt(response)
290+
291+
def _check_closed(self):
292+
if self._closed:
293+
raise InvalidOperation("Cannot use MongoClient after close")
298294

299295
def close(self):
300296
"""Cleanup resources."""
297+
self._closed = True
301298
self._auto_encrypter.close()
302299

303300
@staticmethod
@@ -430,7 +427,7 @@ def create_data_key(self, kms_provider, master_key=None,
430427
The ``_id`` of the created data key document.
431428
"""
432429
self._check_closed()
433-
with _wrap_encryption_errors_ctx():
430+
with _wrap_encryption_errors():
434431
return self._encryption.create_data_key(
435432
kms_provider, master_key=master_key,
436433
key_alt_names=key_alt_names)
@@ -461,7 +458,7 @@ def encrypt(self, value, algorithm, key_id=None, key_alt_name=None):
461458
'key_id must be a bson.binary.Binary with subtype 4')
462459

463460
doc = encode({'v': value}, codec_options=self._codec_options)
464-
with _wrap_encryption_errors_ctx():
461+
with _wrap_encryption_errors():
465462
encrypted_doc = self._encryption.encrypt(
466463
doc, algorithm, key_id=key_id, key_alt_name=key_alt_name)
467464
return decode(encrypted_doc)['v']
@@ -481,7 +478,7 @@ def decrypt(self, value):
481478
raise TypeError(
482479
'value to decrypt must be a bson.binary.Binary with subtype 6')
483480

484-
with _wrap_encryption_errors_ctx():
481+
with _wrap_encryption_errors():
485482
doc = encode({'v': value})
486483
decrypted_doc = self._encryption.decrypt(doc)
487484
return decode(decrypted_doc,

pymongo/mongo_client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1156,7 +1156,9 @@ def close(self):
11561156
11571157
Close all sockets in the connection pools and stop the monitor threads.
11581158
If this instance is used again it will be automatically re-opened and
1159-
the threads restarted.
1159+
the threads restarted unless auto encryption is enabled. A client
1160+
enabled with auto encryption cannot be used again after being closed;
1161+
any attempt will raise :exc:`~.errors.InvalidOperation`.
11601162
11611163
.. versionchanged:: 3.6
11621164
End all server sessions created by this client.

test/test_encryption.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,17 @@ def test_auto_encrypt_local_schema_map(self):
244244

245245
self._test_auto_encrypt(opts)
246246

247+
def test_use_after_close(self):
248+
opts = AutoEncryptionOpts(KMS_PROVIDERS, 'admin.datakeys')
249+
client = rs_or_single_client(auto_encryption_opts=opts)
250+
self.addCleanup(client.close)
251+
252+
client.admin.command('isMaster')
253+
client.close()
254+
with self.assertRaisesRegex(InvalidOperation,
255+
'Cannot use MongoClient after close'):
256+
client.admin.command('isMaster')
257+
247258

248259
class TestExplicitSimple(EncryptionIntegrationTest):
249260

0 commit comments

Comments
 (0)