Skip to content

Commit aadd9c7

Browse files
committed
PYTHON-1952 Test ClientEncryption.close
Support using ClientEncryption in a with-statement.
1 parent fb86f65 commit aadd9c7

File tree

2 files changed

+59
-8
lines changed

2 files changed

+59
-8
lines changed

pymongo/encryption.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444

4545
from pymongo.errors import (ConfigurationError,
4646
EncryptionError,
47+
InvalidOperation,
4748
ServerSelectionTimeoutError)
4849
from pymongo.mongo_client import MongoClient
4950
from pymongo.pool import _configured_socket, PoolOptions
@@ -387,7 +388,6 @@ def __init__(self, kms_providers, key_vault_namespace, key_vault_client,
387388
self._encryption = ExplicitEncrypter(
388389
self._io_callbacks, MongoCryptOptions(kms_providers, None))
389390

390-
@_wrap_encryption_errors
391391
def create_data_key(self, kms_provider, master_key=None,
392392
key_alt_names=None):
393393
"""Create and insert a new data key into the key vault collection.
@@ -419,8 +419,11 @@ def create_data_key(self, kms_provider, master_key=None,
419419
:Returns:
420420
The ``_id`` of the created data key document.
421421
"""
422-
return self._encryption.create_data_key(
423-
kms_provider, master_key=master_key, key_alt_names=key_alt_names)
422+
self._check_closed()
423+
with _wrap_encryption_errors_ctx():
424+
return self._encryption.create_data_key(
425+
kms_provider, master_key=master_key,
426+
key_alt_names=key_alt_names)
424427

425428
def encrypt(self, value, algorithm, key_id=None, key_alt_name=None):
426429
"""Encrypt a BSON value with a given key and algorithm.
@@ -440,6 +443,7 @@ def encrypt(self, value, algorithm, key_id=None, key_alt_name=None):
440443
:Returns:
441444
The encrypted value, a :class:`~bson.binary.Binary` with subtype 6.
442445
"""
446+
self._check_closed()
443447
if (key_id is not None and not (
444448
isinstance(key_id, Binary) and
445449
key_id.subtype == UUID_SUBTYPE)):
@@ -462,6 +466,7 @@ def decrypt(self, value):
462466
:Returns:
463467
The decrypted BSON value.
464468
"""
469+
self._check_closed()
465470
if not (isinstance(value, Binary) and value.subtype == 6):
466471
raise TypeError(
467472
'value to decrypt must be a bson.binary.Binary with subtype 6')
@@ -472,9 +477,29 @@ def decrypt(self, value):
472477
return decode(decrypted_doc,
473478
codec_options=self._codec_options)['v']
474479

480+
def __enter__(self):
481+
return self
482+
483+
def __exit__(self, exc_type, exc_val, exc_tb):
484+
self.close()
485+
486+
def _check_closed(self):
487+
if self._encryption is None:
488+
raise InvalidOperation("Cannot use closed ClientEncryption")
489+
475490
def close(self):
476-
"""Release resources."""
477-
self._io_callbacks.close()
478-
self._encryption.close()
479-
self._io_callbacks = None
480-
self._encryption = None
491+
"""Release resources.
492+
493+
Note that using this class in a with-statement will automatically call
494+
:meth:`close`::
495+
496+
with ClientEncryption(...) as client_encryption:
497+
encrypted = client_encryption.encrypt(value, ...)
498+
decrypted = client_encryption.decrypt(encrypted)
499+
500+
"""
501+
if self._io_callbacks:
502+
self._io_callbacks.close()
503+
self._encryption.close()
504+
self._io_callbacks = None
505+
self._encryption = None

test/test_encryption.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
from pymongo.errors import (ConfigurationError,
3939
EncryptionError,
40+
InvalidOperation,
4041
OperationFailure)
4142
from pymongo.encryption import (Algorithm,
4243
ClientEncryption)
@@ -366,6 +367,31 @@ def test_codec_options(self):
366367
self.assertNotEqual(
367368
client_encryption.decrypt(encrypted_legacy), value)
368369

370+
def test_close(self):
371+
client_encryption = ClientEncryption(
372+
KMS_PROVIDERS, 'admin.datakeys', client_context.client, OPTS)
373+
client_encryption.close()
374+
# Close can be called multiple times.
375+
client_encryption.close()
376+
algo = Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic
377+
msg = 'Cannot use closed ClientEncryption'
378+
with self.assertRaisesRegex(InvalidOperation, msg):
379+
client_encryption.create_data_key('local')
380+
with self.assertRaisesRegex(InvalidOperation, msg):
381+
client_encryption.encrypt('val', algo, key_alt_name='name')
382+
with self.assertRaisesRegex(InvalidOperation, msg):
383+
client_encryption.decrypt(Binary(b'', 6))
384+
385+
def test_with_statement(self):
386+
with ClientEncryption(
387+
KMS_PROVIDERS, 'admin.datakeys',
388+
client_context.client, OPTS) as client_encryption:
389+
pass
390+
with self.assertRaisesRegex(
391+
InvalidOperation, 'Cannot use closed ClientEncryption'):
392+
client_encryption.create_data_key('local')
393+
394+
369395
# Spec tests
370396

371397
AWS_CREDS = {

0 commit comments

Comments
 (0)