Skip to content

Commit 5e4a5e7

Browse files
committed
PYTHON-4747 Sync encryption.py to master
1 parent 4ef252b commit 5e4a5e7

File tree

2 files changed

+105
-34
lines changed

2 files changed

+105
-34
lines changed

pymongo/encryption.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright 2024-present MongoDB, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Re-import of synchronous Encryption API for compatibility."""
16+
from __future__ import annotations
17+
18+
from pymongo.synchronous.encryption import * # noqa: F403
19+
from pymongo.synchronous.encryption import __doc__ as original_doc
20+
21+
__doc__ = original_doc
22+
__all__ = ["Algorithm", "ClientEncryption", "QueryType", "RewrapManyDataKeyResult"] # noqa: F405

pymongo/synchronous/encryption.py

Lines changed: 83 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
TYPE_CHECKING,
2626
Any,
2727
Dict,
28+
Generator,
2829
Generic,
2930
Iterator,
3031
Mapping,
@@ -36,11 +37,15 @@
3637
)
3738

3839
try:
39-
from pymongocrypt.auto_encrypter import AutoEncrypter # type:ignore[import]
4040
from pymongocrypt.errors import MongoCryptError # type:ignore[import]
41-
from pymongocrypt.explicit_encrypter import ExplicitEncrypter # type:ignore[import]
4241
from pymongocrypt.mongocrypt import MongoCryptOptions # type:ignore[import]
43-
from pymongocrypt.state_machine import MongoCryptCallback # type:ignore[import]
42+
from pymongocrypt.synchronous.auto_encrypter import AutoEncrypter # type:ignore[import]
43+
from pymongocrypt.synchronous.explicit_encrypter import ( # type:ignore[import]
44+
ExplicitEncrypter,
45+
)
46+
from pymongocrypt.synchronous.state_machine import ( # type:ignore[import]
47+
MongoCryptCallback,
48+
)
4449

4550
_HAVE_PYMONGOCRYPT = True
4651
except ImportError:
@@ -53,11 +58,8 @@
5358
from bson.errors import BSONError
5459
from bson.raw_bson import DEFAULT_RAW_BSON_OPTIONS, RawBSONDocument, _inflate_bson
5560
from pymongo import _csot
56-
from pymongo.collection import Collection
5761
from pymongo.common import CONNECT_TIMEOUT
58-
from pymongo.cursor import Cursor
5962
from pymongo.daemon import _spawn_daemon
60-
from pymongo.database import Database
6163
from pymongo.encryption_options import AutoEncryptionOpts, RangeOpts
6264
from pymongo.errors import (
6365
ConfigurationError,
@@ -67,20 +69,27 @@
6769
PyMongoError,
6870
ServerSelectionTimeoutError,
6971
)
70-
from pymongo.mongo_client import MongoClient
71-
from pymongo.network import BLOCKING_IO_ERRORS
72+
from pymongo.network_layer import BLOCKING_IO_ERRORS, sendall
7273
from pymongo.operations import UpdateOne
73-
from pymongo.pool import PoolOptions, _configured_socket, _raise_connection_failure
74+
from pymongo.pool_options import PoolOptions
7475
from pymongo.read_concern import ReadConcern
7576
from pymongo.results import BulkWriteResult, DeleteResult
7677
from pymongo.ssl_support import get_ssl_context
78+
from pymongo.synchronous.collection import Collection
79+
from pymongo.synchronous.cursor import Cursor
80+
from pymongo.synchronous.database import Database
81+
from pymongo.synchronous.mongo_client import MongoClient
82+
from pymongo.synchronous.pool import _configured_socket, _raise_connection_failure
7783
from pymongo.typings import _DocumentType, _DocumentTypeArg
7884
from pymongo.uri_parser import parse_host
7985
from pymongo.write_concern import WriteConcern
8086

8187
if TYPE_CHECKING:
8288
from pymongocrypt.mongocrypt import MongoCryptKmsContext
8389

90+
91+
_IS_SYNC = True
92+
8493
_HTTPS_PORT = 443
8594
_KMS_CONNECT_TIMEOUT = CONNECT_TIMEOUT # CDRIVER-3262 redefined this value to CONNECT_TIMEOUT
8695
_MONGOCRYPTD_TIMEOUT_MS = 10000
@@ -167,7 +176,7 @@ def kms_request(self, kms_context: MongoCryptKmsContext) -> None:
167176
try:
168177
conn = _configured_socket((host, port), opts)
169178
try:
170-
conn.sendall(message)
179+
sendall(conn, message)
171180
while kms_context.bytes_needed > 0:
172181
# CSOT: update timeout.
173182
conn.settimeout(max(_csot.clamp_remaining(_KMS_CONNECT_TIMEOUT), 0))
@@ -185,9 +194,7 @@ def kms_request(self, kms_context: MongoCryptKmsContext) -> None:
185194
# Wrap I/O errors in PyMongo exceptions.
186195
_raise_connection_failure((host, port), error)
187196

188-
def collection_info(
189-
self, database: Database[Mapping[str, Any]], filter: bytes
190-
) -> Optional[bytes]:
197+
def collection_info(self, database: str, filter: bytes) -> Optional[bytes]:
191198
"""Get the collection info for a namespace.
192199
193200
The returned collection info is passed to libmongocrypt which reads
@@ -241,7 +248,7 @@ def mark_command(self, database: str, cmd: bytes) -> bytes:
241248
)
242249
return res.raw
243250

244-
def fetch_keys(self, filter: bytes) -> Iterator[bytes]:
251+
def fetch_keys(self, filter: bytes) -> Generator[bytes, None]:
245252
"""Yields one or more keys from the key vault.
246253
247254
:param filter: The filter to pass to find.
@@ -371,13 +378,16 @@ def _get_internal_client(
371378
)
372379

373380
io_callbacks = _EncryptionIO( # type:ignore[misc]
374-
metadata_client, key_vault_coll, mongocryptd_client, opts
381+
metadata_client,
382+
key_vault_coll, # type:ignore[arg-type]
383+
mongocryptd_client,
384+
opts,
375385
)
376386
self._auto_encrypter = AutoEncrypter(
377387
io_callbacks,
378-
MongoCryptOptions(
379-
opts._kms_providers,
380-
schema_map,
388+
_create_mongocrypt_options(
389+
kms_providers=opts._kms_providers,
390+
schema_map=schema_map,
381391
crypt_shared_lib_path=opts._crypt_shared_lib_path,
382392
crypt_shared_lib_required=opts._crypt_shared_lib_required,
383393
bypass_encryption=opts._bypass_auto_encryption,
@@ -446,11 +456,15 @@ class Algorithm(str, enum.Enum):
446456
447457
.. versionadded:: 4.2
448458
"""
459+
RANGE = "Range"
460+
"""Range.
461+
462+
.. versionadded:: 4.9
463+
"""
449464
RANGEPREVIEW = "RangePreview"
450-
"""RangePreview.
465+
"""**DEPRECATED** - RangePreview.
451466
452-
.. note:: Support for Range queries is in beta.
453-
Backwards-breaking changes may be made before the final release.
467+
.. note:: Support for RangePreview is deprecated. Use :attr:`Algorithm.RANGE` instead.
454468
455469
.. versionadded:: 4.4
456470
"""
@@ -465,12 +479,27 @@ class QueryType(str, enum.Enum):
465479
EQUALITY = "equality"
466480
"""Used to encrypt a value for an equality query."""
467481

468-
RANGEPREVIEW = "rangePreview"
482+
RANGE = "range"
469483
"""Used to encrypt a value for a range query.
470484
471-
.. note:: Support for Range queries is in beta.
472-
Backwards-breaking changes may be made before the final release.
473-
"""
485+
.. versionadded:: 4.9
486+
"""
487+
488+
RANGEPREVIEW = "RangePreview"
489+
"""**DEPRECATED** - Used to encrypt a value for a rangePreview query.
490+
491+
.. note:: Support for RangePreview is deprecated. Use :attr:`QueryType.RANGE` instead.
492+
493+
.. versionadded:: 4.4
494+
"""
495+
496+
497+
def _create_mongocrypt_options(**kwargs: Any) -> MongoCryptOptions:
498+
opts = MongoCryptOptions(**kwargs)
499+
# Opt into range V2 encryption.
500+
if hasattr(opts, "enable_range_v2"):
501+
opts.enable_range_v2 = True
502+
return opts
474503

475504

476505
class ClientEncryption(Generic[_DocumentType]):
@@ -559,12 +588,15 @@ def __init__(
559588
raise ConfigurationError(
560589
"client-side field level encryption requires the pymongocrypt "
561590
"library: install a compatible version with: "
562-
"python -m pip install 'pymongo[encryption]'"
591+
"python -m pip install --upgrade 'pymongo[encryption]'"
563592
)
564593

565594
if not isinstance(codec_options, CodecOptions):
566595
raise TypeError("codec_options must be an instance of bson.codec_options.CodecOptions")
567596

597+
if not isinstance(key_vault_client, MongoClient):
598+
raise TypeError(f"MongoClient required but given {type(key_vault_client)}")
599+
568600
self._kms_providers = kms_providers
569601
self._key_vault_namespace = key_vault_namespace
570602
self._key_vault_client = key_vault_client
@@ -580,7 +612,8 @@ def __init__(
580612
None, key_vault_coll, None, opts
581613
)
582614
self._encryption = ExplicitEncrypter(
583-
self._io_callbacks, MongoCryptOptions(kms_providers, None)
615+
self._io_callbacks,
616+
_create_mongocrypt_options(kms_providers=kms_providers, schema_map=None),
584617
)
585618
# Use the same key vault collection as the callback.
586619
assert self._io_callbacks.key_vault_coll is not None
@@ -649,6 +682,11 @@ def create_encrypted_collection(
649682
https://mongodb.com/docs/manual/reference/command/create
650683
651684
"""
685+
if not isinstance(database, Database):
686+
raise TypeError(
687+
f"create_encrypted_collection() requires a Database but {type(database)} given"
688+
)
689+
652690
encrypted_fields = deepcopy(encrypted_fields)
653691
for i, field in enumerate(encrypted_fields["fields"]):
654692
if isinstance(field, dict) and field.get("keyId") is None:
@@ -724,6 +762,9 @@ def create_data_key(
724762
Secret Data managed object.
725763
- `endpoint` (string): Optional. Host with optional
726764
port, e.g. "example.vault.azure.net:".
765+
- `delegated` (bool): Optional. If True (recommended), the
766+
KMIP server will perform encryption and decryption. If
767+
delegated is not provided, defaults to false.
727768
728769
:param key_alt_names: An optional list of string alternate
729770
names used to reference a key. If a key is created with alternate
@@ -826,10 +867,14 @@ def encrypt(
826867
when the algorithm is :attr:`Algorithm.INDEXED`. An integer value
827868
*must* be given when the :attr:`Algorithm.INDEXED` algorithm is
828869
used.
829-
:param range_opts: Experimental only, not intended for public use.
870+
:param range_opts: Index options for `range` queries. See
871+
:class:`RangeOpts` for some valid options.
830872
831873
:return: The encrypted value, a :class:`~bson.binary.Binary` with subtype 6.
832874
875+
.. versionchanged:: 4.9
876+
Added the `range_opts` parameter.
877+
833878
.. versionchanged:: 4.7
834879
``key_id`` can now be passed in as a :class:`uuid.UUID`.
835880
@@ -878,10 +923,14 @@ def encrypt_expression(
878923
when the algorithm is :attr:`Algorithm.INDEXED`. An integer value
879924
*must* be given when the :attr:`Algorithm.INDEXED` algorithm is
880925
used.
881-
:param range_opts: Experimental only, not intended for public use.
926+
:param range_opts: Index options for `range` queries. See
927+
:class:`RangeOpts` for some valid options.
882928
883929
:return: The encrypted expression, a :class:`~bson.RawBSONDocument`.
884930
931+
.. versionchanged:: 4.9
932+
Added the `range_opts` parameter.
933+
885934
.. versionchanged:: 4.7
886935
``key_id`` can now be passed in as a :class:`uuid.UUID`.
887936
@@ -963,10 +1012,10 @@ def delete_key(self, id: Binary) -> DeleteResult:
9631012
def add_key_alt_name(self, id: Binary, key_alt_name: str) -> Any:
9641013
"""Add ``key_alt_name`` to the set of alternate names in the key document with UUID ``key_id``.
9651014
966-
:param `id`: The UUID of a key a which must be a
1015+
:param id: The UUID of a key a which must be a
9671016
:class:`~bson.binary.Binary` with subtype 4 (
9681017
:attr:`~bson.binary.UUID_SUBTYPE`).
969-
:param `key_alt_name`: The key alternate name to add.
1018+
:param key_alt_name: The key alternate name to add.
9701019
9711020
:return: The previous version of the key document.
9721021
@@ -995,10 +1044,10 @@ def remove_key_alt_name(self, id: Binary, key_alt_name: str) -> Optional[RawBSON
9951044
9961045
Also removes the ``keyAltNames`` field from the key document if it would otherwise be empty.
9971046
998-
:param `id`: The UUID of a key a which must be a
1047+
:param id: The UUID of a key a which must be a
9991048
:class:`~bson.binary.Binary` with subtype 4 (
10001049
:attr:`~bson.binary.UUID_SUBTYPE`).
1001-
:param `key_alt_name`: The key alternate name to remove.
1050+
:param key_alt_name: The key alternate name to remove.
10021051
10031052
:return: Returns the previous version of the key document.
10041053
@@ -1037,7 +1086,7 @@ def rewrap_many_data_key(
10371086
:param filter: A document used to filter the data keys.
10381087
:param provider: The new KMS provider to use to encrypt the data keys,
10391088
or ``None`` to use the current KMS provider(s).
1040-
:param `master_key`: The master key fields corresponding to the new KMS
1089+
:param master_key: The master key fields corresponding to the new KMS
10411090
provider when ``provider`` is not ``None``.
10421091
10431092
:return: A :class:`RewrapManyDataKeyResult`.

0 commit comments

Comments
 (0)