From efe494d1a6484aec1abc5a102eff6d752c74d719 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 15 Apr 2025 13:42:17 -0700 Subject: [PATCH 01/40] add is_sync param --- pymongo/asynchronous/encryption.py | 2 + pymongo/client_options.py | 7 +- pymongo/encryption_options.py | 3 +- pymongo/ssl_support.py | 31 +++-- pymongo/synchronous/encryption.py | 2 + pymongo/uri_parser_shared.py | 6 +- test/asynchronous/test_client_bulk_write.py | 1 + test/asynchronous/test_encryption.py | 145 +++++++++++++++----- test/asynchronous/test_ssl.py | 18 +-- test/test_client_bulk_write.py | 1 + test/test_encryption.py | 145 +++++++++++++++----- test/test_ssl.py | 18 +-- tools/ocsptest.py | 1 + 13 files changed, 279 insertions(+), 101 deletions(-) diff --git a/pymongo/asynchronous/encryption.py b/pymongo/asynchronous/encryption.py index 71a694a619..53512e51fb 100644 --- a/pymongo/asynchronous/encryption.py +++ b/pymongo/asynchronous/encryption.py @@ -180,6 +180,7 @@ async def kms_request(self, kms_context: MongoCryptKmsContext) -> None: False, # allow_invalid_certificates False, # allow_invalid_hostnames False, # disable_ocsp_endpoint_check + _IS_SYNC, ) # CSOT: set timeout for socket creation. connect_timeout = max(_csot.clamp_remaining(_KMS_CONNECT_TIMEOUT), 0.001) @@ -674,6 +675,7 @@ def __init__( key_vault_namespace, kms_tls_options=kms_tls_options, key_expiration_ms=key_expiration_ms, + is_sync=_IS_SYNC, ) self._io_callbacks: Optional[_EncryptionIO] = _EncryptionIO( None, key_vault_coll, None, opts diff --git a/pymongo/client_options.py b/pymongo/client_options.py index a66e87c9f6..bd27dd4eb0 100644 --- a/pymongo/client_options.py +++ b/pymongo/client_options.py @@ -84,7 +84,9 @@ def _parse_read_concern(options: Mapping[str, Any]) -> ReadConcern: return ReadConcern(concern) -def _parse_ssl_options(options: Mapping[str, Any]) -> tuple[Optional[SSLContext], bool]: +def _parse_ssl_options( + options: Mapping[str, Any], is_sync: bool +) -> tuple[Optional[SSLContext], bool]: """Parse ssl options.""" use_tls = options.get("tls") if use_tls is not None: @@ -138,6 +140,7 @@ def _parse_ssl_options(options: Mapping[str, Any]) -> tuple[Optional[SSLContext] allow_invalid_certificates, allow_invalid_hostnames, disable_ocsp_endpoint_check, + is_sync, ) return ctx, allow_invalid_hostnames return None, allow_invalid_hostnames @@ -167,7 +170,7 @@ def _parse_pool_options( compression_settings = CompressionSettings( options.get("compressors", []), options.get("zlibcompressionlevel", -1) ) - ssl_context, tls_allow_invalid_hostnames = _parse_ssl_options(options) + ssl_context, tls_allow_invalid_hostnames = _parse_ssl_options(options, is_sync) load_balanced = options.get("loadbalanced") max_connecting = options.get("maxconnecting", common.MAX_CONNECTING) return PoolOptions( diff --git a/pymongo/encryption_options.py b/pymongo/encryption_options.py index 4cb94cba30..6983cdddd6 100644 --- a/pymongo/encryption_options.py +++ b/pymongo/encryption_options.py @@ -58,6 +58,7 @@ def __init__( bypass_query_analysis: bool = False, encrypted_fields_map: Optional[Mapping[str, Any]] = None, key_expiration_ms: Optional[int] = None, + is_sync: bool = True, ) -> None: """Options to configure automatic client-side field level encryption. @@ -236,7 +237,7 @@ def __init__( if not any("idleShutdownTimeoutSecs" in s for s in self._mongocryptd_spawn_args): self._mongocryptd_spawn_args.append("--idleShutdownTimeoutSecs=60") # Maps KMS provider name to a SSLContext. - self._kms_ssl_contexts = _parse_kms_tls_options(kms_tls_options) + self._kms_ssl_contexts = _parse_kms_tls_options(kms_tls_options, is_sync) self._bypass_query_analysis = bypass_query_analysis self._key_expiration_ms = key_expiration_ms diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index 2e6a509e3e..189b2c8258 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -21,10 +21,12 @@ from pymongo.errors import ConfigurationError HAVE_SSL = True +HAVE_PYSSL = True try: - import pymongo.pyopenssl_context as _ssl + import pymongo.pyopenssl_context as _pyssl except (ImportError, AttributeError) as exc: + HAVE_PYSSL = False if isinstance(exc, AttributeError): warnings.warn( "Failed to use the installed version of PyOpenSSL. " @@ -35,10 +37,10 @@ UserWarning, stacklevel=2, ) - try: - import pymongo.ssl_context as _ssl # type: ignore[no-redef] - except ImportError: - HAVE_SSL = False +try: + import pymongo.ssl_context as _ssl +except ImportError: + HAVE_SSL = False if HAVE_SSL: @@ -65,8 +67,13 @@ def get_ssl_context( allow_invalid_certificates: bool, allow_invalid_hostnames: bool, disable_ocsp_endpoint_check: bool, + is_sync: bool, ) -> _ssl.SSLContext: """Create and return an SSLContext object.""" + if is_sync and HAVE_PYSSL: + ssl_in_use = _pyssl + else: + ssl_in_use = _ssl verify_mode = CERT_NONE if allow_invalid_certificates else CERT_REQUIRED ctx = _ssl.SSLContext(_ssl.PROTOCOL_SSLv23) if verify_mode != CERT_NONE: @@ -80,21 +87,21 @@ def get_ssl_context( # up to date versions of MongoDB 2.4 and above already disable # SSLv2 and SSLv3, python disables SSLv2 by default in >= 2.7.7 # and >= 3.3.4 and SSLv3 in >= 3.4.3. - ctx.options |= _ssl.OP_NO_SSLv2 - ctx.options |= _ssl.OP_NO_SSLv3 - ctx.options |= _ssl.OP_NO_COMPRESSION - ctx.options |= _ssl.OP_NO_RENEGOTIATION + ctx.options |= ssl_in_use.OP_NO_SSLv2 + ctx.options |= ssl_in_use.OP_NO_SSLv3 + ctx.options |= ssl_in_use.OP_NO_COMPRESSION + ctx.options |= ssl_in_use.OP_NO_RENEGOTIATION if certfile is not None: try: ctx.load_cert_chain(certfile, None, passphrase) - except _ssl.SSLError as exc: + except ssl_in_use.SSLError as exc: raise ConfigurationError(f"Private key doesn't match certificate: {exc}") from None if crlfile is not None: - if _ssl.IS_PYOPENSSL: + if ssl_in_use.IS_PYOPENSSL: raise ConfigurationError("tlsCRLFile cannot be used with PyOpenSSL") # Match the server's behavior. ctx.verify_flags = getattr( # type:ignore[attr-defined] - _ssl, "VERIFY_CRL_CHECK_LEAF", 0 + ssl_in_use, "VERIFY_CRL_CHECK_LEAF", 0 ) ctx.load_verify_locations(crlfile) if ca_certs is not None: diff --git a/pymongo/synchronous/encryption.py b/pymongo/synchronous/encryption.py index ed631e135d..f2470c13ea 100644 --- a/pymongo/synchronous/encryption.py +++ b/pymongo/synchronous/encryption.py @@ -179,6 +179,7 @@ def kms_request(self, kms_context: MongoCryptKmsContext) -> None: False, # allow_invalid_certificates False, # allow_invalid_hostnames False, # disable_ocsp_endpoint_check + _IS_SYNC, ) # CSOT: set timeout for socket creation. connect_timeout = max(_csot.clamp_remaining(_KMS_CONNECT_TIMEOUT), 0.001) @@ -667,6 +668,7 @@ def __init__( key_vault_namespace, kms_tls_options=kms_tls_options, key_expiration_ms=key_expiration_ms, + is_sync=_IS_SYNC, ) self._io_callbacks: Optional[_EncryptionIO] = _EncryptionIO( None, key_vault_coll, None, opts diff --git a/pymongo/uri_parser_shared.py b/pymongo/uri_parser_shared.py index e7ba4c9fb5..67d26ea35d 100644 --- a/pymongo/uri_parser_shared.py +++ b/pymongo/uri_parser_shared.py @@ -420,7 +420,9 @@ def _check_options(nodes: Sized, options: Mapping[str, Any]) -> None: raise ConfigurationError("Cannot specify replicaSet with loadBalanced=true") -def _parse_kms_tls_options(kms_tls_options: Optional[Mapping[str, Any]]) -> dict[str, SSLContext]: +def _parse_kms_tls_options( + kms_tls_options: Optional[Mapping[str, Any]], is_sync +) -> dict[str, SSLContext]: """Parse KMS TLS connection options.""" if not kms_tls_options: return {} @@ -435,7 +437,7 @@ def _parse_kms_tls_options(kms_tls_options: Optional[Mapping[str, Any]]) -> dict opts = _handle_security_options(opts) opts = _normalize_options(opts) opts = cast(_CaseInsensitiveDictionary, validate_options(opts)) - ssl_context, allow_invalid_hostnames = _parse_ssl_options(opts) + ssl_context, allow_invalid_hostnames = _parse_ssl_options(opts, is_sync) if ssl_context is None: raise ConfigurationError("TLS is required for KMS providers") if allow_invalid_hostnames: diff --git a/test/asynchronous/test_client_bulk_write.py b/test/asynchronous/test_client_bulk_write.py index 9eb15298a6..ee76e34e33 100644 --- a/test/asynchronous/test_client_bulk_write.py +++ b/test/asynchronous/test_client_bulk_write.py @@ -545,6 +545,7 @@ async def test_returns_error_if_auto_encryption_configured(self): opts = AutoEncryptionOpts( key_vault_namespace="db.coll", kms_providers={"aws": {"accessKeyId": "foo", "secretAccessKey": "bar"}}, + is_sync=_IS_SYNC, ) client = await self.async_rs_or_single_client(auto_encryption_opts=opts) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index f9b03f6303..494cd1e165 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -119,7 +119,10 @@ async def test_crypt_shared(self): # Test that we can pick up crypt_shared lib automatically self.simple_client( auto_encryption_opts=AutoEncryptionOpts( - KMS_PROVIDERS, "keyvault.datakeys", crypt_shared_lib_required=True + KMS_PROVIDERS, + "keyvault.datakeys", + crypt_shared_lib_required=True, + is_sync=_IS_SYNC, ), connect=False, ) @@ -127,11 +130,11 @@ async def test_crypt_shared(self): @unittest.skipIf(_HAVE_PYMONGOCRYPT, "pymongocrypt is installed") def test_init_requires_pymongocrypt(self): with self.assertRaises(ConfigurationError): - AutoEncryptionOpts({}, "keyvault.datakeys") + AutoEncryptionOpts({}, "keyvault.datakeys", is_sync=_IS_SYNC) @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") def test_init(self): - opts = AutoEncryptionOpts({}, "keyvault.datakeys") + opts = AutoEncryptionOpts({}, "keyvault.datakeys", is_sync=_IS_SYNC) self.assertEqual(opts._kms_providers, {}) self.assertEqual(opts._key_vault_namespace, "keyvault.datakeys") self.assertEqual(opts._key_vault_client, None) @@ -147,17 +150,25 @@ def test_init(self): def test_init_spawn_args(self): # User can override idleShutdownTimeoutSecs opts = AutoEncryptionOpts( - {}, "keyvault.datakeys", mongocryptd_spawn_args=["--idleShutdownTimeoutSecs=88"] + {}, + "keyvault.datakeys", + mongocryptd_spawn_args=["--idleShutdownTimeoutSecs=88"], + is_sync=_IS_SYNC, ) self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=88"]) # idleShutdownTimeoutSecs is added by default - opts = AutoEncryptionOpts({}, "keyvault.datakeys", mongocryptd_spawn_args=[]) + opts = AutoEncryptionOpts( + {}, "keyvault.datakeys", mongocryptd_spawn_args=[], is_sync=_IS_SYNC + ) self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=60"]) # Also added when other options are given opts = AutoEncryptionOpts( - {}, "keyvault.datakeys", mongocryptd_spawn_args=["--quiet", "--port=27020"] + {}, + "keyvault.datakeys", + mongocryptd_spawn_args=["--quiet", "--port=27020"], + is_sync=_IS_SYNC, ) self.assertEqual( opts._mongocryptd_spawn_args, @@ -168,7 +179,7 @@ def test_init_spawn_args(self): def test_init_kms_tls_options(self): # Error cases: with self.assertRaisesRegex(TypeError, r'kms_tls_options\["kmip"\] must be a dict'): - AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}) + AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}, is_sync=_IS_SYNC) tls_opts: Any for tls_opts in [ {"kmip": {"tls": True, "tlsInsecure": True}}, @@ -176,15 +187,22 @@ def test_init_kms_tls_options(self): {"kmip": {"tls": True, "tlsAllowInvalidHostnames": True}}, ]: with self.assertRaisesRegex(ConfigurationError, "Insecure TLS options prohibited"): - opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) + opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts, is_sync=_IS_SYNC) with self.assertRaises(FileNotFoundError): - AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}}) + AutoEncryptionOpts( + {}, + "k.d", + kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}}, + is_sync=_IS_SYNC, + ) # Success cases: tls_opts: Any for tls_opts in [None, {}]: - opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) + opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts, is_sync=_IS_SYNC) self.assertEqual(opts._kms_ssl_contexts, {}) - opts = AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}) + opts = AutoEncryptionOpts( + {}, "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}, is_sync=_IS_SYNC + ) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) @@ -195,6 +213,7 @@ def test_init_kms_tls_options(self): {}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": CA_PEM, "tlsCertificateKeyFile": CLIENT_PEM}}, + is_sync=_IS_SYNC, ) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) @@ -211,7 +230,7 @@ async def test_default(self): @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") async def test_kwargs(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) client = self.simple_client(auto_encryption_opts=opts, connect=False) self.assertEqual(get_client_opts(client).auto_encryption_opts, opts) @@ -360,18 +379,24 @@ async def test_auto_encrypt(self): await create_with_schema(self.db.test, json_schema) self.addAsyncCleanup(self.db.test.drop) - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts( + KMS_PROVIDERS, + "keyvault.datakeys", + is_sync=_IS_SYNC, + ) await self._test_auto_encrypt(opts) async def test_auto_encrypt_local_schema_map(self): # Configure the encrypted field via the local schema_map option. schemas = {"pymongo_test.test": json_data("custom", "schema.json")} - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", schema_map=schemas) + opts = AutoEncryptionOpts( + KMS_PROVIDERS, "keyvault.datakeys", schema_map=schemas, is_sync=_IS_SYNC + ) await self._test_auto_encrypt(opts) async def test_use_after_close(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) client = await self.async_rs_or_single_client(auto_encryption_opts=opts) await client.admin.command("ping") @@ -390,7 +415,7 @@ async def test_use_after_close(self): @async_client_context.require_sync async def test_fork(self): self.skipTest("Test is flaky, PYTHON-4738") - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) client = await self.async_rs_or_single_client(auto_encryption_opts=opts) async def target(): @@ -404,7 +429,7 @@ async def target(): class TestEncryptedBulkWrite(AsyncBulkTestBase, AsyncEncryptionIntegrationTest): async def test_upsert_uuid_standard_encrypt(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) client = await self.async_rs_or_single_client(auto_encryption_opts=opts) options = CodecOptions(uuid_representation=UuidRepresentation.STANDARD) @@ -443,7 +468,7 @@ async def asyncSetUp(self): @async_client_context.require_version_max(4, 0, 99) async def test_raise_max_wire_version_error(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) client = await self.async_rs_or_single_client(auto_encryption_opts=opts) msg = "Auto-encryption requires a minimum MongoDB version of 4.2" with self.assertRaisesRegex(ConfigurationError, msg): @@ -456,7 +481,7 @@ async def test_raise_max_wire_version_error(self): await client.test.test.bulk_write([InsertOne({})]) async def test_raise_unsupported_error(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) client = await self.async_rs_or_single_client(auto_encryption_opts=opts) msg = "find_raw_batches does not support auto encryption" with self.assertRaisesRegex(InvalidOperation, msg): @@ -673,7 +698,7 @@ def parse_auto_encrypt_opts(self, opts): opts.update(camel_to_snake_args(opts.pop("extra_options"))) opts = dict(opts) - return AutoEncryptionOpts(**opts) + return AutoEncryptionOpts(**opts, is_sync=_IS_SYNC) def parse_client_options(self, opts): """Override clientOptions parsing to support autoEncryptOpts.""" @@ -855,6 +880,7 @@ async def asyncSetUp(self): "keyvault.datakeys", schema_map=schemas, kms_tls_options=KMS_TLS_OPTS, + is_sync=_IS_SYNC, ) self.client_encrypted = await self.async_rs_or_single_client( auto_encryption_opts=opts, uuidRepresentation="standard" @@ -950,6 +976,7 @@ async def _test_external_key_vault(self, with_external_key_vault): "keyvault.datakeys", schema_map=schemas, key_vault_client=key_vault_client, + is_sync=_IS_SYNC, ) client_encrypted = await self.async_rs_or_single_client( @@ -1003,7 +1030,7 @@ async def test_views_are_prohibited(self): await self.client.db.create_collection("view", viewOn="coll") self.addAsyncCleanup(self.client.db.view.drop) - opts = AutoEncryptionOpts(self.kms_providers(), "keyvault.datakeys") + opts = AutoEncryptionOpts(self.kms_providers(), "keyvault.datakeys", is_sync=_IS_SYNC) client_encrypted = await self.async_rs_or_single_client( auto_encryption_opts=opts, uuidRepresentation="standard" ) @@ -1161,7 +1188,10 @@ async def _test_corpus(self, opts): async def test_corpus(self): opts = AutoEncryptionOpts( - self.kms_providers(), "keyvault.datakeys", kms_tls_options=KMS_TLS_OPTS + self.kms_providers(), + "keyvault.datakeys", + kms_tls_options=KMS_TLS_OPTS, + is_sync=_IS_SYNC, ) await self._test_corpus(opts) @@ -1173,6 +1203,7 @@ async def test_corpus_local_schema(self): "keyvault.datakeys", schema_map=schemas, kms_tls_options=KMS_TLS_OPTS, + is_sync=_IS_SYNC, ) await self._test_corpus(opts) @@ -1210,7 +1241,9 @@ async def asyncSetUp(self): await coll.drop() await coll.insert_one(json_data("limits", "limits-key.json")) - opts = AutoEncryptionOpts({"local": {"key": LOCAL_MASTER_KEY}}, "keyvault.datakeys") + opts = AutoEncryptionOpts( + {"local": {"key": LOCAL_MASTER_KEY}}, "keyvault.datakeys", is_sync=_IS_SYNC + ) self.listener = OvertCommandListener() self.client_encrypted = await self.async_rs_or_single_client( auto_encryption_opts=opts, event_listeners=[self.listener] @@ -1525,6 +1558,7 @@ async def _test_automatic(self, expectation_extjson, payload): self.KMS_PROVIDER_MAP, # type: ignore[arg-type] keyvault_namespace, schema_map=self.SCHEMA_MAP, + is_sync=_IS_SYNC, ) insert_listener = AllowListEventListener("insert") @@ -1665,7 +1699,10 @@ async def test_case_1(self): await self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=False, key_vault_client=None + *self.optargs, + bypass_auto_encryption=False, + key_vault_client=None, + is_sync=_IS_SYNC, ), ) @@ -1686,7 +1723,10 @@ async def test_case_2(self): await self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=False, key_vault_client=self.client_keyvault + *self.optargs, + bypass_auto_encryption=False, + key_vault_client=self.client_keyvault, + is_sync=_IS_SYNC, ), ) @@ -1710,7 +1750,10 @@ async def test_case_3(self): await self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=True, key_vault_client=None + *self.optargs, + bypass_auto_encryption=True, + key_vault_client=None, + is_sync=_IS_SYNC, ), ) @@ -1727,7 +1770,10 @@ async def test_case_4(self): await self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=True, key_vault_client=self.client_keyvault + *self.optargs, + bypass_auto_encryption=True, + key_vault_client=self.client_keyvault, + is_sync=_IS_SYNC, ), ) @@ -1747,7 +1793,10 @@ async def test_case_5(self): await self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=False, key_vault_client=None + *self.optargs, + bypass_auto_encryption=False, + key_vault_client=None, + is_sync=_IS_SYNC, ), ) @@ -1770,7 +1819,10 @@ async def test_case_6(self): await self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=False, key_vault_client=self.client_keyvault + *self.optargs, + bypass_auto_encryption=False, + key_vault_client=self.client_keyvault, + is_sync=_IS_SYNC, ), ) @@ -1794,7 +1846,10 @@ async def test_case_7(self): await self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=True, key_vault_client=None + *self.optargs, + bypass_auto_encryption=True, + key_vault_client=None, + is_sync=_IS_SYNC, ), ) @@ -1811,7 +1866,10 @@ async def test_case_8(self): await self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=True, key_vault_client=self.client_keyvault + *self.optargs, + bypass_auto_encryption=True, + key_vault_client=self.client_keyvault, + is_sync=_IS_SYNC, ), ) @@ -1849,7 +1907,9 @@ async def asyncSetUp(self): ) self.malformed_cipher_text = Binary(self.malformed_cipher_text, 6) opts = AutoEncryptionOpts( - key_vault_namespace="keyvault.datakeys", kms_providers=kms_providers_map + key_vault_namespace="keyvault.datakeys", + kms_providers=kms_providers_map, + is_sync=_IS_SYNC, ) self.listener = AllowListEventListener("aggregate") self.encrypted_client = await self.async_rs_or_single_client( @@ -1930,6 +1990,7 @@ def reset_timeout(): "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27027", ], + is_sync=_IS_SYNC, ) client_encrypted = await self.async_rs_or_single_client(auto_encryption_opts=opts) with self.assertRaisesRegex(EncryptionError, "Timeout"): @@ -1944,6 +2005,7 @@ async def test_bypassAutoEncryption(self): "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27027", ], + is_sync=_IS_SYNC, ) client_encrypted = await self.async_rs_or_single_client(auto_encryption_opts=opts) await client_encrypted.db.coll.insert_one({"unencrypted": "test"}) @@ -1971,6 +2033,7 @@ async def test_via_loading_shared_library(self): "--port=47021", ], crypt_shared_lib_required=True, + is_sync=_IS_SYNC, ) client_encrypted = await self.async_rs_or_single_client(auto_encryption_opts=opts) await client_encrypted.db.coll.drop() @@ -2011,6 +2074,7 @@ def listener(): schema_map=schemas, mongocryptd_uri="mongodb://localhost:47021", crypt_shared_lib_required=False, + is_sync=_IS_SYNC, ) client_encrypted = await self.async_rs_or_single_client(auto_encryption_opts=opts) await client_encrypted.db.coll.drop() @@ -2324,6 +2388,7 @@ async def asyncSetUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True, + is_sync=_IS_SYNC, ) self.encrypted_client = await self.async_rs_or_single_client(auto_encryption_opts=opts) @@ -2430,6 +2495,7 @@ async def asyncSetUp(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) await encrypted_client.drop_database("db") @@ -2481,6 +2547,7 @@ async def test_1_csfle_joins_no_schema(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2509,6 +2576,7 @@ async def test_2_qe_joins_no_schema(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2537,6 +2605,7 @@ async def test_3_no_schema_joins_csfle(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2562,6 +2631,7 @@ async def test_4_no_schema_joins_qe(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2590,6 +2660,7 @@ async def test_5_csfle_joins_csfle2(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2618,6 +2689,7 @@ async def test_6_qe_joins_qe2(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2646,6 +2718,7 @@ async def test_7_no_schema_joins_no_schema2(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2674,6 +2747,7 @@ async def test_8_csfle_joins_qe(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) with self.assertRaises(PyMongoError) as exc: @@ -2700,6 +2774,7 @@ async def test_9_error(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) with self.assertRaises(PyMongoError) as exc: @@ -2890,7 +2965,10 @@ async def AsyncMongoClient(**kwargs): # Create an Queryable Encryption collection. opts = AutoEncryptionOpts( - kms_providers_map, "keyvault.datakeys", encrypted_fields_map=encrypted_fields_map + kms_providers_map, + "keyvault.datakeys", + encrypted_fields_map=encrypted_fields_map, + is_sync=_IS_SYNC, ) encrypted_client = await AsyncMongoClient(auto_encryption_opts=opts) @@ -2945,6 +3023,7 @@ async def asyncSetUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True, + is_sync=_IS_SYNC, ) self.encrypted_client = await self.async_rs_or_single_client(auto_encryption_opts=opts) self.db = self.encrypted_client.db diff --git a/test/asynchronous/test_ssl.py b/test/asynchronous/test_ssl.py index d920b77ac2..5bf6088845 100644 --- a/test/asynchronous/test_ssl.py +++ b/test/asynchronous/test_ssl.py @@ -309,13 +309,13 @@ async def test_cert_ssl_validation_hostname_matching(self): # # --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem - ctx = get_ssl_context(None, None, None, None, True, True, False) + ctx = get_ssl_context(None, None, None, None, True, True, False, _IS_SYNC) self.assertFalse(ctx.check_hostname) - ctx = get_ssl_context(None, None, None, None, True, False, False) + ctx = get_ssl_context(None, None, None, None, True, False, False, _IS_SYNC) self.assertFalse(ctx.check_hostname) - ctx = get_ssl_context(None, None, None, None, False, True, False) + ctx = get_ssl_context(None, None, None, None, False, True, False, _IS_SYNC) self.assertFalse(ctx.check_hostname) - ctx = get_ssl_context(None, None, None, None, False, False, False) + ctx = get_ssl_context(None, None, None, None, False, False, False, _IS_SYNC) self.assertTrue(ctx.check_hostname) response = await self.client.admin.command(HelloCompat.LEGACY_CMD) @@ -469,7 +469,7 @@ async def test_validation_with_system_ca_certs(self): ) def test_system_certs_config_error(self): - ctx = get_ssl_context(None, None, None, None, True, True, False) + ctx = get_ssl_context(None, None, None, None, True, True, False, _IS_SYNC) if (sys.platform != "win32" and hasattr(ctx, "set_default_verify_paths")) or hasattr( ctx, "load_default_certs" ): @@ -500,11 +500,11 @@ def test_certifi_support(self): # Force the test on Windows, regardless of environment. ssl_support.HAVE_WINCERTSTORE = False try: - ctx = get_ssl_context(None, None, CA_PEM, None, False, False, False) + ctx = get_ssl_context(None, None, CA_PEM, None, False, False, False, _IS_SYNC) ssl_sock = ctx.wrap_socket(socket.socket()) self.assertEqual(ssl_sock.ca_certs, CA_PEM) - ctx = get_ssl_context(None, None, None, None, False, False, False) + ctx = get_ssl_context(None, None, None, None, False, False, False, _IS_SYNC) ssl_sock = ctx.wrap_socket(socket.socket()) self.assertEqual(ssl_sock.ca_certs, ssl_support.certifi.where()) finally: @@ -521,11 +521,11 @@ def test_wincertstore(self): if not ssl_support.HAVE_WINCERTSTORE: raise SkipTest("Need wincertstore to test wincertstore.") - ctx = get_ssl_context(None, None, CA_PEM, None, False, False, False) + ctx = get_ssl_context(None, None, CA_PEM, None, False, False, False, _IS_SYNC) ssl_sock = ctx.wrap_socket(socket.socket()) self.assertEqual(ssl_sock.ca_certs, CA_PEM) - ctx = get_ssl_context(None, None, None, None, False, False, False) + ctx = get_ssl_context(None, None, None, None, False, False, False, _IS_SYNC) ssl_sock = ctx.wrap_socket(socket.socket()) self.assertEqual(ssl_sock.ca_certs, ssl_support._WINCERTS.name) diff --git a/test/test_client_bulk_write.py b/test/test_client_bulk_write.py index 866b179c9e..2e37b28696 100644 --- a/test/test_client_bulk_write.py +++ b/test/test_client_bulk_write.py @@ -541,6 +541,7 @@ def test_returns_error_if_auto_encryption_configured(self): opts = AutoEncryptionOpts( key_vault_namespace="db.coll", kms_providers={"aws": {"accessKeyId": "foo", "secretAccessKey": "bar"}}, + is_sync=_IS_SYNC, ) client = self.rs_or_single_client(auto_encryption_opts=opts) diff --git a/test/test_encryption.py b/test/test_encryption.py index 5bbf8c8ad8..269a312134 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -119,7 +119,10 @@ def test_crypt_shared(self): # Test that we can pick up crypt_shared lib automatically self.simple_client( auto_encryption_opts=AutoEncryptionOpts( - KMS_PROVIDERS, "keyvault.datakeys", crypt_shared_lib_required=True + KMS_PROVIDERS, + "keyvault.datakeys", + crypt_shared_lib_required=True, + is_sync=_IS_SYNC, ), connect=False, ) @@ -127,11 +130,11 @@ def test_crypt_shared(self): @unittest.skipIf(_HAVE_PYMONGOCRYPT, "pymongocrypt is installed") def test_init_requires_pymongocrypt(self): with self.assertRaises(ConfigurationError): - AutoEncryptionOpts({}, "keyvault.datakeys") + AutoEncryptionOpts({}, "keyvault.datakeys", is_sync=_IS_SYNC) @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") def test_init(self): - opts = AutoEncryptionOpts({}, "keyvault.datakeys") + opts = AutoEncryptionOpts({}, "keyvault.datakeys", is_sync=_IS_SYNC) self.assertEqual(opts._kms_providers, {}) self.assertEqual(opts._key_vault_namespace, "keyvault.datakeys") self.assertEqual(opts._key_vault_client, None) @@ -147,17 +150,25 @@ def test_init(self): def test_init_spawn_args(self): # User can override idleShutdownTimeoutSecs opts = AutoEncryptionOpts( - {}, "keyvault.datakeys", mongocryptd_spawn_args=["--idleShutdownTimeoutSecs=88"] + {}, + "keyvault.datakeys", + mongocryptd_spawn_args=["--idleShutdownTimeoutSecs=88"], + is_sync=_IS_SYNC, ) self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=88"]) # idleShutdownTimeoutSecs is added by default - opts = AutoEncryptionOpts({}, "keyvault.datakeys", mongocryptd_spawn_args=[]) + opts = AutoEncryptionOpts( + {}, "keyvault.datakeys", mongocryptd_spawn_args=[], is_sync=_IS_SYNC + ) self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=60"]) # Also added when other options are given opts = AutoEncryptionOpts( - {}, "keyvault.datakeys", mongocryptd_spawn_args=["--quiet", "--port=27020"] + {}, + "keyvault.datakeys", + mongocryptd_spawn_args=["--quiet", "--port=27020"], + is_sync=_IS_SYNC, ) self.assertEqual( opts._mongocryptd_spawn_args, @@ -168,7 +179,7 @@ def test_init_spawn_args(self): def test_init_kms_tls_options(self): # Error cases: with self.assertRaisesRegex(TypeError, r'kms_tls_options\["kmip"\] must be a dict'): - AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}) + AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}, is_sync=_IS_SYNC) tls_opts: Any for tls_opts in [ {"kmip": {"tls": True, "tlsInsecure": True}}, @@ -176,15 +187,22 @@ def test_init_kms_tls_options(self): {"kmip": {"tls": True, "tlsAllowInvalidHostnames": True}}, ]: with self.assertRaisesRegex(ConfigurationError, "Insecure TLS options prohibited"): - opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) + opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts, is_sync=_IS_SYNC) with self.assertRaises(FileNotFoundError): - AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}}) + AutoEncryptionOpts( + {}, + "k.d", + kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}}, + is_sync=_IS_SYNC, + ) # Success cases: tls_opts: Any for tls_opts in [None, {}]: - opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) + opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts, is_sync=_IS_SYNC) self.assertEqual(opts._kms_ssl_contexts, {}) - opts = AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}) + opts = AutoEncryptionOpts( + {}, "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}, is_sync=_IS_SYNC + ) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) @@ -195,6 +213,7 @@ def test_init_kms_tls_options(self): {}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": CA_PEM, "tlsCertificateKeyFile": CLIENT_PEM}}, + is_sync=_IS_SYNC, ) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) @@ -211,7 +230,7 @@ def test_default(self): @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") def test_kwargs(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) client = self.simple_client(auto_encryption_opts=opts, connect=False) self.assertEqual(get_client_opts(client).auto_encryption_opts, opts) @@ -360,18 +379,24 @@ def test_auto_encrypt(self): create_with_schema(self.db.test, json_schema) self.addCleanup(self.db.test.drop) - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts( + KMS_PROVIDERS, + "keyvault.datakeys", + is_sync=_IS_SYNC, + ) self._test_auto_encrypt(opts) def test_auto_encrypt_local_schema_map(self): # Configure the encrypted field via the local schema_map option. schemas = {"pymongo_test.test": json_data("custom", "schema.json")} - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", schema_map=schemas) + opts = AutoEncryptionOpts( + KMS_PROVIDERS, "keyvault.datakeys", schema_map=schemas, is_sync=_IS_SYNC + ) self._test_auto_encrypt(opts) def test_use_after_close(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) client = self.rs_or_single_client(auto_encryption_opts=opts) client.admin.command("ping") @@ -390,7 +415,7 @@ def test_use_after_close(self): @client_context.require_sync def test_fork(self): self.skipTest("Test is flaky, PYTHON-4738") - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) client = self.rs_or_single_client(auto_encryption_opts=opts) def target(): @@ -404,7 +429,7 @@ def target(): class TestEncryptedBulkWrite(BulkTestBase, EncryptionIntegrationTest): def test_upsert_uuid_standard_encrypt(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) client = self.rs_or_single_client(auto_encryption_opts=opts) options = CodecOptions(uuid_representation=UuidRepresentation.STANDARD) @@ -443,7 +468,7 @@ def setUp(self): @client_context.require_version_max(4, 0, 99) def test_raise_max_wire_version_error(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) client = self.rs_or_single_client(auto_encryption_opts=opts) msg = "Auto-encryption requires a minimum MongoDB version of 4.2" with self.assertRaisesRegex(ConfigurationError, msg): @@ -456,7 +481,7 @@ def test_raise_max_wire_version_error(self): client.test.test.bulk_write([InsertOne({})]) def test_raise_unsupported_error(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) client = self.rs_or_single_client(auto_encryption_opts=opts) msg = "find_raw_batches does not support auto encryption" with self.assertRaisesRegex(InvalidOperation, msg): @@ -671,7 +696,7 @@ def parse_auto_encrypt_opts(self, opts): opts.update(camel_to_snake_args(opts.pop("extra_options"))) opts = dict(opts) - return AutoEncryptionOpts(**opts) + return AutoEncryptionOpts(**opts, is_sync=_IS_SYNC) def parse_client_options(self, opts): """Override clientOptions parsing to support autoEncryptOpts.""" @@ -853,6 +878,7 @@ def setUp(self): "keyvault.datakeys", schema_map=schemas, kms_tls_options=KMS_TLS_OPTS, + is_sync=_IS_SYNC, ) self.client_encrypted = self.rs_or_single_client( auto_encryption_opts=opts, uuidRepresentation="standard" @@ -946,6 +972,7 @@ def _test_external_key_vault(self, with_external_key_vault): "keyvault.datakeys", schema_map=schemas, key_vault_client=key_vault_client, + is_sync=_IS_SYNC, ) client_encrypted = self.rs_or_single_client( @@ -999,7 +1026,7 @@ def test_views_are_prohibited(self): self.client.db.create_collection("view", viewOn="coll") self.addCleanup(self.client.db.view.drop) - opts = AutoEncryptionOpts(self.kms_providers(), "keyvault.datakeys") + opts = AutoEncryptionOpts(self.kms_providers(), "keyvault.datakeys", is_sync=_IS_SYNC) client_encrypted = self.rs_or_single_client( auto_encryption_opts=opts, uuidRepresentation="standard" ) @@ -1157,7 +1184,10 @@ def _test_corpus(self, opts): def test_corpus(self): opts = AutoEncryptionOpts( - self.kms_providers(), "keyvault.datakeys", kms_tls_options=KMS_TLS_OPTS + self.kms_providers(), + "keyvault.datakeys", + kms_tls_options=KMS_TLS_OPTS, + is_sync=_IS_SYNC, ) self._test_corpus(opts) @@ -1169,6 +1199,7 @@ def test_corpus_local_schema(self): "keyvault.datakeys", schema_map=schemas, kms_tls_options=KMS_TLS_OPTS, + is_sync=_IS_SYNC, ) self._test_corpus(opts) @@ -1206,7 +1237,9 @@ def setUp(self): coll.drop() coll.insert_one(json_data("limits", "limits-key.json")) - opts = AutoEncryptionOpts({"local": {"key": LOCAL_MASTER_KEY}}, "keyvault.datakeys") + opts = AutoEncryptionOpts( + {"local": {"key": LOCAL_MASTER_KEY}}, "keyvault.datakeys", is_sync=_IS_SYNC + ) self.listener = OvertCommandListener() self.client_encrypted = self.rs_or_single_client( auto_encryption_opts=opts, event_listeners=[self.listener] @@ -1519,6 +1552,7 @@ def _test_automatic(self, expectation_extjson, payload): self.KMS_PROVIDER_MAP, # type: ignore[arg-type] keyvault_namespace, schema_map=self.SCHEMA_MAP, + is_sync=_IS_SYNC, ) insert_listener = AllowListEventListener("insert") @@ -1657,7 +1691,10 @@ def test_case_1(self): self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=False, key_vault_client=None + *self.optargs, + bypass_auto_encryption=False, + key_vault_client=None, + is_sync=_IS_SYNC, ), ) @@ -1678,7 +1715,10 @@ def test_case_2(self): self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=False, key_vault_client=self.client_keyvault + *self.optargs, + bypass_auto_encryption=False, + key_vault_client=self.client_keyvault, + is_sync=_IS_SYNC, ), ) @@ -1702,7 +1742,10 @@ def test_case_3(self): self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=True, key_vault_client=None + *self.optargs, + bypass_auto_encryption=True, + key_vault_client=None, + is_sync=_IS_SYNC, ), ) @@ -1719,7 +1762,10 @@ def test_case_4(self): self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=True, key_vault_client=self.client_keyvault + *self.optargs, + bypass_auto_encryption=True, + key_vault_client=self.client_keyvault, + is_sync=_IS_SYNC, ), ) @@ -1739,7 +1785,10 @@ def test_case_5(self): self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=False, key_vault_client=None + *self.optargs, + bypass_auto_encryption=False, + key_vault_client=None, + is_sync=_IS_SYNC, ), ) @@ -1762,7 +1811,10 @@ def test_case_6(self): self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=False, key_vault_client=self.client_keyvault + *self.optargs, + bypass_auto_encryption=False, + key_vault_client=self.client_keyvault, + is_sync=_IS_SYNC, ), ) @@ -1786,7 +1838,10 @@ def test_case_7(self): self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=True, key_vault_client=None + *self.optargs, + bypass_auto_encryption=True, + key_vault_client=None, + is_sync=_IS_SYNC, ), ) @@ -1803,7 +1858,10 @@ def test_case_8(self): self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, bypass_auto_encryption=True, key_vault_client=self.client_keyvault + *self.optargs, + bypass_auto_encryption=True, + key_vault_client=self.client_keyvault, + is_sync=_IS_SYNC, ), ) @@ -1841,7 +1899,9 @@ def setUp(self): ) self.malformed_cipher_text = Binary(self.malformed_cipher_text, 6) opts = AutoEncryptionOpts( - key_vault_namespace="keyvault.datakeys", kms_providers=kms_providers_map + key_vault_namespace="keyvault.datakeys", + kms_providers=kms_providers_map, + is_sync=_IS_SYNC, ) self.listener = AllowListEventListener("aggregate") self.encrypted_client = self.rs_or_single_client( @@ -1922,6 +1982,7 @@ def reset_timeout(): "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27027", ], + is_sync=_IS_SYNC, ) client_encrypted = self.rs_or_single_client(auto_encryption_opts=opts) with self.assertRaisesRegex(EncryptionError, "Timeout"): @@ -1936,6 +1997,7 @@ def test_bypassAutoEncryption(self): "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27027", ], + is_sync=_IS_SYNC, ) client_encrypted = self.rs_or_single_client(auto_encryption_opts=opts) client_encrypted.db.coll.insert_one({"unencrypted": "test"}) @@ -1963,6 +2025,7 @@ def test_via_loading_shared_library(self): "--port=47021", ], crypt_shared_lib_required=True, + is_sync=_IS_SYNC, ) client_encrypted = self.rs_or_single_client(auto_encryption_opts=opts) client_encrypted.db.coll.drop() @@ -2003,6 +2066,7 @@ def listener(): schema_map=schemas, mongocryptd_uri="mongodb://localhost:47021", crypt_shared_lib_required=False, + is_sync=_IS_SYNC, ) client_encrypted = self.rs_or_single_client(auto_encryption_opts=opts) client_encrypted.db.coll.drop() @@ -2312,6 +2376,7 @@ def setUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True, + is_sync=_IS_SYNC, ) self.encrypted_client = self.rs_or_single_client(auto_encryption_opts=opts) @@ -2414,6 +2479,7 @@ def setUp(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) encrypted_client.drop_database("db") @@ -2465,6 +2531,7 @@ def test_1_csfle_joins_no_schema(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = next( @@ -2493,6 +2560,7 @@ def test_2_qe_joins_no_schema(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = next( @@ -2521,6 +2589,7 @@ def test_3_no_schema_joins_csfle(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = next( @@ -2546,6 +2615,7 @@ def test_4_no_schema_joins_qe(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = next( @@ -2574,6 +2644,7 @@ def test_5_csfle_joins_csfle2(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = next( @@ -2602,6 +2673,7 @@ def test_6_qe_joins_qe2(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = next( @@ -2630,6 +2702,7 @@ def test_7_no_schema_joins_no_schema2(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) doc = next( @@ -2658,6 +2731,7 @@ def test_8_csfle_joins_qe(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) with self.assertRaises(PyMongoError) as exc: @@ -2684,6 +2758,7 @@ def test_9_error(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, + is_sync=_IS_SYNC, ) ) with self.assertRaises(PyMongoError) as exc: @@ -2874,7 +2949,10 @@ def MongoClient(**kwargs): # Create an Queryable Encryption collection. opts = AutoEncryptionOpts( - kms_providers_map, "keyvault.datakeys", encrypted_fields_map=encrypted_fields_map + kms_providers_map, + "keyvault.datakeys", + encrypted_fields_map=encrypted_fields_map, + is_sync=_IS_SYNC, ) encrypted_client = MongoClient(auto_encryption_opts=opts) @@ -2929,6 +3007,7 @@ def setUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True, + is_sync=_IS_SYNC, ) self.encrypted_client = self.rs_or_single_client(auto_encryption_opts=opts) self.db = self.encrypted_client.db diff --git a/test/test_ssl.py b/test/test_ssl.py index a66fe21be5..45452aa368 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -309,13 +309,13 @@ def test_cert_ssl_validation_hostname_matching(self): # # --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem - ctx = get_ssl_context(None, None, None, None, True, True, False) + ctx = get_ssl_context(None, None, None, None, True, True, False, _IS_SYNC) self.assertFalse(ctx.check_hostname) - ctx = get_ssl_context(None, None, None, None, True, False, False) + ctx = get_ssl_context(None, None, None, None, True, False, False, _IS_SYNC) self.assertFalse(ctx.check_hostname) - ctx = get_ssl_context(None, None, None, None, False, True, False) + ctx = get_ssl_context(None, None, None, None, False, True, False, _IS_SYNC) self.assertFalse(ctx.check_hostname) - ctx = get_ssl_context(None, None, None, None, False, False, False) + ctx = get_ssl_context(None, None, None, None, False, False, False, _IS_SYNC) self.assertTrue(ctx.check_hostname) response = self.client.admin.command(HelloCompat.LEGACY_CMD) @@ -469,7 +469,7 @@ def test_validation_with_system_ca_certs(self): ) def test_system_certs_config_error(self): - ctx = get_ssl_context(None, None, None, None, True, True, False) + ctx = get_ssl_context(None, None, None, None, True, True, False, _IS_SYNC) if (sys.platform != "win32" and hasattr(ctx, "set_default_verify_paths")) or hasattr( ctx, "load_default_certs" ): @@ -500,11 +500,11 @@ def test_certifi_support(self): # Force the test on Windows, regardless of environment. ssl_support.HAVE_WINCERTSTORE = False try: - ctx = get_ssl_context(None, None, CA_PEM, None, False, False, False) + ctx = get_ssl_context(None, None, CA_PEM, None, False, False, False, _IS_SYNC) ssl_sock = ctx.wrap_socket(socket.socket()) self.assertEqual(ssl_sock.ca_certs, CA_PEM) - ctx = get_ssl_context(None, None, None, None, False, False, False) + ctx = get_ssl_context(None, None, None, None, False, False, False, _IS_SYNC) ssl_sock = ctx.wrap_socket(socket.socket()) self.assertEqual(ssl_sock.ca_certs, ssl_support.certifi.where()) finally: @@ -521,11 +521,11 @@ def test_wincertstore(self): if not ssl_support.HAVE_WINCERTSTORE: raise SkipTest("Need wincertstore to test wincertstore.") - ctx = get_ssl_context(None, None, CA_PEM, None, False, False, False) + ctx = get_ssl_context(None, None, CA_PEM, None, False, False, False, _IS_SYNC) ssl_sock = ctx.wrap_socket(socket.socket()) self.assertEqual(ssl_sock.ca_certs, CA_PEM) - ctx = get_ssl_context(None, None, None, None, False, False, False) + ctx = get_ssl_context(None, None, None, None, False, False, False, _IS_SYNC) ssl_sock = ctx.wrap_socket(socket.socket()) self.assertEqual(ssl_sock.ca_certs, ssl_support._WINCERTS.name) diff --git a/tools/ocsptest.py b/tools/ocsptest.py index 521d048f79..8596db226d 100644 --- a/tools/ocsptest.py +++ b/tools/ocsptest.py @@ -35,6 +35,7 @@ def check_ocsp(host: str, port: int, capath: str) -> None: False, # allow_invalid_certificates False, # allow_invalid_hostnames False, + True, # is sync ) # disable_ocsp_endpoint_check # Ensure we're using pyOpenSSL. From b29d1ba935ccc5b3e24b67c862f9212264938f8e Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 15 Apr 2025 15:01:14 -0700 Subject: [PATCH 02/40] update contributing --- CONTRIBUTING.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60583022b7..812fe31c31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -420,3 +420,21 @@ partially-converted asynchronous version of the same name to the `test/asynchron Use this generated file as a starting point for the completed conversion. The script is used like so: `python tools/convert_test_to_async.py [test_file.py]` + +## Running PyMongo with SSL +Note that `AsyncMongoClient` does not support PyOpenSSL. +Assuming all required packages are installed, set the `tls` and `tlsAllowInvalidCertificates` flags in the URI to enable +the driver to connect with SSL, like so: +```python +from pymongo import MongoClient + +client = MongoClient( + "mongodb://localhost:27017?tls=true&tlsAllowInvalidCertificates=true" +) +``` +Another way of doing this would be to pass these options in as parameters to the MongoClient, like so: +```python +client = MongoClient( + "mongodb://localhost:27017", tls=True, tlsAllowInvalidCertificates=True +) +``` From d9dfb99b33bf44dc17a14dfedd1f86478f6d4f48 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 15 Apr 2025 15:01:47 -0700 Subject: [PATCH 03/40] add vars for pyopenssl and test --- pymongo/asynchronous/encryption.py | 4 ++-- pymongo/asynchronous/pool.py | 6 +++--- pymongo/pool_shared.py | 12 ++++++------ pymongo/ssl_support.py | 14 ++++++++++++++ pymongo/synchronous/encryption.py | 4 ++-- pymongo/synchronous/pool.py | 6 +++--- test/__init__.py | 6 ++++++ test/asynchronous/__init__.py | 6 ++++++ test/asynchronous/test_ssl.py | 10 ++++++++++ test/test_ssl.py | 8 ++++++++ 10 files changed, 60 insertions(+), 16 deletions(-) diff --git a/pymongo/asynchronous/encryption.py b/pymongo/asynchronous/encryption.py index 53512e51fb..7446bbaa51 100644 --- a/pymongo/asynchronous/encryption.py +++ b/pymongo/asynchronous/encryption.py @@ -85,7 +85,7 @@ ) from pymongo.read_concern import ReadConcern from pymongo.results import BulkWriteResult, DeleteResult -from pymongo.ssl_support import BLOCKING_IO_ERRORS, get_ssl_context +from pymongo.ssl_support import BLOCKING_IO_ERRORS, PYBLOCKING_IO_ERRORS, get_ssl_context from pymongo.typings import _DocumentType, _DocumentTypeArg from pymongo.uri_parser_shared import parse_host from pymongo.write_concern import WriteConcern @@ -216,7 +216,7 @@ async def kms_request(self, kms_context: MongoCryptKmsContext) -> None: raise # Propagate MongoCryptError errors directly. except Exception as exc: # Wrap I/O errors in PyMongo exceptions. - if isinstance(exc, BLOCKING_IO_ERRORS): + if isinstance(exc, (BLOCKING_IO_ERRORS, PYBLOCKING_IO_ERRORS)): exc = socket.timeout("timed out") # Async raises an OSError instead of returning empty bytes. if isinstance(exc, OSError): diff --git a/pymongo/asynchronous/pool.py b/pymongo/asynchronous/pool.py index a67cc5f3c8..24803e5344 100644 --- a/pymongo/asynchronous/pool.py +++ b/pymongo/asynchronous/pool.py @@ -85,7 +85,7 @@ from pymongo.server_api import _add_to_command from pymongo.server_type import SERVER_TYPE from pymongo.socket_checker import SocketChecker -from pymongo.ssl_support import SSLError +from pymongo.ssl_support import PYSSLError, SSLError if TYPE_CHECKING: from bson import CodecOptions @@ -637,7 +637,7 @@ async def _raise_connection_failure(self, error: BaseException) -> NoReturn: reason = ConnectionClosedReason.ERROR await self.close_conn(reason) # SSLError from PyOpenSSL inherits directly from Exception. - if isinstance(error, (IOError, OSError, SSLError)): + if isinstance(error, (IOError, OSError, SSLError, PYSSLError)): details = _get_timeout_details(self.opts) _raise_connection_failure(self.address, error, timeout_details=details) else: @@ -1033,7 +1033,7 @@ async def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> A reason=_verbose_connection_error_reason(ConnectionClosedReason.ERROR), error=ConnectionClosedReason.ERROR, ) - if isinstance(error, (IOError, OSError, SSLError)): + if isinstance(error, (IOError, OSError, SSLError, PYSSLError)): details = _get_timeout_details(self.opts) _raise_connection_failure(self.address, error, timeout_details=details) diff --git a/pymongo/pool_shared.py b/pymongo/pool_shared.py index be7c416dc5..bef71a2003 100644 --- a/pymongo/pool_shared.py +++ b/pymongo/pool_shared.py @@ -38,7 +38,7 @@ ) from pymongo.network_layer import AsyncNetworkingInterface, NetworkingInterface, PyMongoProtocol from pymongo.pool_options import PoolOptions -from pymongo.ssl_support import HAS_SNI, SSLError +from pymongo.ssl_support import HAS_SNI, PYSSLError, SSLError if TYPE_CHECKING: from pymongo.pyopenssl_context import _sslConn @@ -138,7 +138,7 @@ def _raise_connection_failure( msg += format_timeout_details(timeout_details) if isinstance(error, socket.timeout): raise NetworkTimeout(msg) from error - elif isinstance(error, SSLError) and "timed out" in str(error): + elif isinstance(error, (SSLError, PYSSLError)) and "timed out" in str(error): # Eventlet does not distinguish TLS network timeouts from other # SSLErrors (https://github.com/eventlet/eventlet/issues/692). # Luckily, we can work around this limitation because the phrase @@ -293,7 +293,7 @@ async def _async_configured_socket( # Raise _CertificateError directly like we do after match_hostname # below. raise - except (OSError, SSLError) as exc: + except (OSError, SSLError, PYSSLError) as exc: sock.close() # We raise AutoReconnect for transient and permanent SSL handshake # failures alike. Permanent handshake failures, like protocol @@ -349,7 +349,7 @@ async def _configured_protocol_interface( # Raise _CertificateError directly like we do after match_hostname # below. raise - except (OSError, SSLError) as exc: + except (OSError, SSLError, PYSSLError) as exc: # We raise AutoReconnect for transient and permanent SSL handshake # failures alike. Permanent handshake failures, like protocol # mismatch, will be turned into ServerSelectionTimeoutErrors later. @@ -467,7 +467,7 @@ def _configured_socket(address: _Address, options: PoolOptions) -> Union[socket. # Raise _CertificateError directly like we do after match_hostname # below. raise - except (OSError, SSLError) as exc: + except (OSError, SSLError, PYSSLError) as exc: sock.close() # We raise AutoReconnect for transient and permanent SSL handshake # failures alike. Permanent handshake failures, like protocol @@ -516,7 +516,7 @@ def _configured_socket_interface(address: _Address, options: PoolOptions) -> Net # Raise _CertificateError directly like we do after match_hostname # below. raise - except (OSError, SSLError) as exc: + except (OSError, SSLError, PYSSLError) as exc: sock.close() # We raise AutoReconnect for transient and permanent SSL handshake # failures alike. Permanent handshake failures, like protocol diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index 189b2c8258..3ef2597680 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -59,6 +59,20 @@ BLOCKING_IO_WRITE_ERROR = _ssl.BLOCKING_IO_WRITE_ERROR BLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR + if HAVE_PYSSL: + PYSSLError = _pyssl.SSLError + PYBLOCKING_IO_ERRORS = _pyssl.BLOCKING_IO_ERRORS + PYBLOCKING_IO_READ_ERROR = _pyssl.BLOCKING_IO_READ_ERROR + PYBLOCKING_IO_WRITE_ERROR = _pyssl.BLOCKING_IO_WRITE_ERROR + PYBLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR + else: + # just make them the same as SSL so imports won't error + PYSSLError = _ssl.SSLError + PYBLOCKING_IO_ERRORS = () + PYBLOCKING_IO_READ_ERROR = _ssl.BLOCKING_IO_READ_ERROR + PYBLOCKING_IO_WRITE_ERROR = _ssl.BLOCKING_IO_WRITE_ERROR + PYBLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR + def get_ssl_context( certfile: Optional[str], passphrase: Optional[str], diff --git a/pymongo/synchronous/encryption.py b/pymongo/synchronous/encryption.py index f2470c13ea..f5425a8baf 100644 --- a/pymongo/synchronous/encryption.py +++ b/pymongo/synchronous/encryption.py @@ -80,7 +80,7 @@ ) from pymongo.read_concern import ReadConcern from pymongo.results import BulkWriteResult, DeleteResult -from pymongo.ssl_support import BLOCKING_IO_ERRORS, get_ssl_context +from pymongo.ssl_support import BLOCKING_IO_ERRORS, PYBLOCKING_IO_ERRORS, get_ssl_context from pymongo.synchronous.collection import Collection from pymongo.synchronous.cursor import Cursor from pymongo.synchronous.database import Database @@ -215,7 +215,7 @@ def kms_request(self, kms_context: MongoCryptKmsContext) -> None: raise # Propagate MongoCryptError errors directly. except Exception as exc: # Wrap I/O errors in PyMongo exceptions. - if isinstance(exc, BLOCKING_IO_ERRORS): + if isinstance(exc, (BLOCKING_IO_ERRORS, PYBLOCKING_IO_ERRORS)): exc = socket.timeout("timed out") # Async raises an OSError instead of returning empty bytes. if isinstance(exc, OSError): diff --git a/pymongo/synchronous/pool.py b/pymongo/synchronous/pool.py index 224834af31..b256f4c315 100644 --- a/pymongo/synchronous/pool.py +++ b/pymongo/synchronous/pool.py @@ -82,7 +82,7 @@ from pymongo.server_api import _add_to_command from pymongo.server_type import SERVER_TYPE from pymongo.socket_checker import SocketChecker -from pymongo.ssl_support import SSLError +from pymongo.ssl_support import PYSSLError, SSLError from pymongo.synchronous.client_session import _validate_session_write_concern from pymongo.synchronous.helpers import _handle_reauth from pymongo.synchronous.network import command @@ -635,7 +635,7 @@ def _raise_connection_failure(self, error: BaseException) -> NoReturn: reason = ConnectionClosedReason.ERROR self.close_conn(reason) # SSLError from PyOpenSSL inherits directly from Exception. - if isinstance(error, (IOError, OSError, SSLError)): + if isinstance(error, (IOError, OSError, SSLError, PYSSLError)): details = _get_timeout_details(self.opts) _raise_connection_failure(self.address, error, timeout_details=details) else: @@ -1029,7 +1029,7 @@ def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> Connect reason=_verbose_connection_error_reason(ConnectionClosedReason.ERROR), error=ConnectionClosedReason.ERROR, ) - if isinstance(error, (IOError, OSError, SSLError)): + if isinstance(error, (IOError, OSError, SSLError, PYSSLError)): details = _get_timeout_details(self.opts) _raise_connection_failure(self.address, error, timeout_details=details) diff --git a/test/__init__.py b/test/__init__.py index a1c5091f3b..e07fdc12a6 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -825,6 +825,12 @@ def require_sync(self, func): lambda: _IS_SYNC, "This test only works with the synchronous API", func=func ) + def require_async(self, func): + """Run a test only if using the synchronous API.""" + return self._require( + lambda: not _IS_SYNC, "This test only works with the synchronous API", func=func + ) + def mongos_seeds(self): return ",".join("{}:{}".format(*address) for address in self.mongoses) diff --git a/test/asynchronous/__init__.py b/test/asynchronous/__init__.py index f8d04f0d5d..afbb00fb38 100644 --- a/test/asynchronous/__init__.py +++ b/test/asynchronous/__init__.py @@ -827,6 +827,12 @@ def require_sync(self, func): lambda: _IS_SYNC, "This test only works with the synchronous API", func=func ) + def require_async(self, func): + """Run a test only if using the synchronous API.""" + return self._require( + lambda: not _IS_SYNC, "This test only works with the asynchronous API", func=func + ) + def mongos_seeds(self): return ",".join("{}:{}".format(*address) for address in self.mongoses) diff --git a/test/asynchronous/test_ssl.py b/test/asynchronous/test_ssl.py index 5bf6088845..46383dee27 100644 --- a/test/asynchronous/test_ssl.py +++ b/test/asynchronous/test_ssl.py @@ -657,6 +657,16 @@ def remove(path): ) as client: self.assertTrue(await client.admin.command("ping")) + @async_client_context.require_async + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + @unittest.skipUnless(HAVE_SSL, "The ssl module is not available.") + async def test_pyopenssl_not_ignored_in_async(self): + client = AsyncMongoClient( + "mongodb://localhost:27017?tls=true&tlsAllowInvalidCertificates=true" + ) + await client.admin.command("ping") # command doesn't matter, just needs it to connect + await client.close() + if __name__ == "__main__": unittest.main() diff --git a/test/test_ssl.py b/test/test_ssl.py index 45452aa368..3af2a8c0fe 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -657,6 +657,14 @@ def remove(path): ) as client: self.assertTrue(client.admin.command("ping")) + @client_context.require_async + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + @unittest.skipUnless(HAVE_SSL, "The ssl module is not available.") + def test_pyopenssl_not_ignored_in_async(self): + client = MongoClient("mongodb://localhost:27017?tls=true&tlsAllowInvalidCertificates=true") + client.admin.command("ping") # command doesn't matter, just needs it to connect + client.close() + if __name__ == "__main__": unittest.main() From c847f252434b6adc0fb6d5ff8848da0ef024e917 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 15 Apr 2025 15:10:49 -0700 Subject: [PATCH 04/40] update evergreen config to run this pyopenssl on async as well (not sure if this is correct or not, we'll seeeeee) --- .evergreen/generated_configs/variants.yml | 8 +++---- .evergreen/scripts/generate_config.py | 27 ++++++++++++++++------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 0ad366dbea..88edf72e04 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -729,8 +729,8 @@ buildvariants: # Pyopenssl tests - name: pyopenssl-macos-python3.9 tasks: - - name: .replica_set .noauth .nossl .sync - - name: .7.0 .noauth .nossl .sync + - name: .replica_set .noauth .nossl .sync_async + - name: .7.0 .noauth .nossl .sync_async display_name: PyOpenSSL macOS Python3.9 run_on: - macos-14 @@ -773,8 +773,8 @@ buildvariants: PYTHON_BINARY: /opt/python/3.12/bin/python3 - name: pyopenssl-win64-python3.13 tasks: - - name: .replica_set .auth .ssl .sync - - name: .7.0 .auth .ssl .sync + - name: .replica_set .auth .ssl .sync_async + - name: .7.0 .auth .ssl .sync_async display_name: PyOpenSSL Win64 Python3.13 run_on: - windows-64-vsMulti-small diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index e99a9a3980..439e506426 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -262,14 +262,25 @@ def create_pyopenssl_variants(): host = DEFAULT_HOST display_name = get_variant_name(base_name, host, python=python) - variant = create_variant( - [f".replica_set .{auth} .{ssl} .sync", f".7.0 .{auth} .{ssl} .sync"], - display_name, - python=python, - host=host, - expansions=expansions, - batchtime=batchtime, - ) + # only need to run some on async + if python in (CPYTHONS[0], CPYTHONS[-1]): + variant = create_variant( + [f".replica_set .{auth} .{ssl} .sync_async", f".7.0 .{auth} .{ssl} .sync_async"], + display_name, + python=python, + host=host, + expansions=expansions, + batchtime=batchtime, + ) + else: + variant = create_variant( + [f".replica_set .{auth} .{ssl} .sync", f".7.0 .{auth} .{ssl} .sync"], + display_name, + python=python, + host=host, + expansions=expansions, + batchtime=batchtime, + ) variants.append(variant) return variants From ae8ecc40a2ca172c199feed61e5c6bf5dd1e0e67 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 15 Apr 2025 15:47:20 -0700 Subject: [PATCH 05/40] fix typing --- pymongo/ssl_support.py | 25 ++++++++++++------------- pymongo/uri_parser_shared.py | 3 ++- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index 3ef2597680..f4bfdf030f 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -15,8 +15,9 @@ """Support for SSL in PyMongo.""" from __future__ import annotations +import types import warnings -from typing import Optional +from typing import Any, Optional, Union from pymongo.errors import ConfigurationError @@ -60,15 +61,15 @@ BLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR if HAVE_PYSSL: - PYSSLError = _pyssl.SSLError - PYBLOCKING_IO_ERRORS = _pyssl.BLOCKING_IO_ERRORS - PYBLOCKING_IO_READ_ERROR = _pyssl.BLOCKING_IO_READ_ERROR - PYBLOCKING_IO_WRITE_ERROR = _pyssl.BLOCKING_IO_WRITE_ERROR - PYBLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR + PYSSLError: Any = _pyssl.SSLError + PYBLOCKING_IO_ERRORS: Any = _pyssl.BLOCKING_IO_ERRORS + PYBLOCKING_IO_READ_ERROR: Any = _pyssl.BLOCKING_IO_READ_ERROR + PYBLOCKING_IO_WRITE_ERROR: Any = _pyssl.BLOCKING_IO_WRITE_ERROR + PYBLOCKING_IO_LOOKUP_ERROR: Any = BLOCKING_IO_READ_ERROR else: # just make them the same as SSL so imports won't error PYSSLError = _ssl.SSLError - PYBLOCKING_IO_ERRORS = () + PYBLOCKING_IO_ERRORS = _ssl.BLOCKING_IO_ERRORS PYBLOCKING_IO_READ_ERROR = _ssl.BLOCKING_IO_READ_ERROR PYBLOCKING_IO_WRITE_ERROR = _ssl.BLOCKING_IO_WRITE_ERROR PYBLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR @@ -82,14 +83,14 @@ def get_ssl_context( allow_invalid_hostnames: bool, disable_ocsp_endpoint_check: bool, is_sync: bool, - ) -> _ssl.SSLContext: + ) -> Union[_pyssl.SSLContext, _ssl.SSLContext]: # type: ignore[name-defined] """Create and return an SSLContext object.""" if is_sync and HAVE_PYSSL: - ssl_in_use = _pyssl + ssl_in_use: types.ModuleType = _pyssl else: ssl_in_use = _ssl verify_mode = CERT_NONE if allow_invalid_certificates else CERT_REQUIRED - ctx = _ssl.SSLContext(_ssl.PROTOCOL_SSLv23) + ctx = ssl_in_use.SSLContext(ssl_in_use.PROTOCOL_SSLv23) if verify_mode != CERT_NONE: ctx.check_hostname = not allow_invalid_hostnames else: @@ -114,9 +115,7 @@ def get_ssl_context( if ssl_in_use.IS_PYOPENSSL: raise ConfigurationError("tlsCRLFile cannot be used with PyOpenSSL") # Match the server's behavior. - ctx.verify_flags = getattr( # type:ignore[attr-defined] - ssl_in_use, "VERIFY_CRL_CHECK_LEAF", 0 - ) + ctx.verify_flags = getattr(ssl_in_use, "VERIFY_CRL_CHECK_LEAF", 0) ctx.load_verify_locations(crlfile) if ca_certs is not None: ctx.load_verify_locations(ca_certs) diff --git a/pymongo/uri_parser_shared.py b/pymongo/uri_parser_shared.py index 67d26ea35d..0cef176bf1 100644 --- a/pymongo/uri_parser_shared.py +++ b/pymongo/uri_parser_shared.py @@ -421,7 +421,8 @@ def _check_options(nodes: Sized, options: Mapping[str, Any]) -> None: def _parse_kms_tls_options( - kms_tls_options: Optional[Mapping[str, Any]], is_sync + kms_tls_options: Optional[Mapping[str, Any]], + is_sync: bool, ) -> dict[str, SSLContext]: """Parse KMS TLS connection options.""" if not kms_tls_options: From 03f4ba189f93edb99f250811030cccdfe04d2b4b Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Wed, 16 Apr 2025 10:02:13 -0700 Subject: [PATCH 06/40] fix tests --- test/asynchronous/test_ssl.py | 10 +++++----- test/test_ssl.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/asynchronous/test_ssl.py b/test/asynchronous/test_ssl.py index 46383dee27..250989ff71 100644 --- a/test/asynchronous/test_ssl.py +++ b/test/asynchronous/test_ssl.py @@ -43,7 +43,7 @@ from pymongo import AsyncMongoClient, ssl_support from pymongo.errors import ConfigurationError, ConnectionFailure, OperationFailure from pymongo.hello import HelloCompat -from pymongo.ssl_support import HAVE_SSL, _ssl, get_ssl_context +from pymongo.ssl_support import HAVE_SSL, _pyssl, _ssl, get_ssl_context from pymongo.write_concern import WriteConcern _HAVE_PYOPENSSL = False @@ -134,7 +134,7 @@ def test_config_ssl(self): @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") def test_use_pyopenssl_when_available(self): - self.assertTrue(_ssl.IS_PYOPENSSL) + self.assertTrue(_pyssl.IS_PYOPENSSL) @unittest.skipUnless(_HAVE_PYOPENSSL, "Cannot test without PyOpenSSL") def test_load_trusted_ca_certs(self): @@ -177,7 +177,7 @@ async def test_tlsCertificateKeyFilePassword(self): # # --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem - if not hasattr(ssl, "SSLContext") and not _ssl.IS_PYOPENSSL: + if not hasattr(ssl, "SSLContext") and not _pyssl.IS_PYOPENSSL: self.assertRaises( ConfigurationError, self.simple_client, @@ -378,7 +378,7 @@ async def test_cert_ssl_validation_hostname_matching(self): @async_client_context.require_tlsCertificateKeyFile @ignore_deprecations async def test_tlsCRLFile_support(self): - if not hasattr(ssl, "VERIFY_CRL_CHECK_LEAF") or _ssl.IS_PYOPENSSL: + if not hasattr(ssl, "VERIFY_CRL_CHECK_LEAF") or _pyssl.IS_PYOPENSSL: self.assertRaises( ConfigurationError, self.simple_client, @@ -660,7 +660,7 @@ def remove(path): @async_client_context.require_async @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") @unittest.skipUnless(HAVE_SSL, "The ssl module is not available.") - async def test_pyopenssl_not_ignored_in_async(self): + async def test_pyopenssl_ignored_in_async(self): client = AsyncMongoClient( "mongodb://localhost:27017?tls=true&tlsAllowInvalidCertificates=true" ) diff --git a/test/test_ssl.py b/test/test_ssl.py index 3af2a8c0fe..328bef77a9 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -43,7 +43,7 @@ from pymongo import MongoClient, ssl_support from pymongo.errors import ConfigurationError, ConnectionFailure, OperationFailure from pymongo.hello import HelloCompat -from pymongo.ssl_support import HAVE_SSL, _ssl, get_ssl_context +from pymongo.ssl_support import HAVE_SSL, _pyssl, _ssl, get_ssl_context from pymongo.write_concern import WriteConcern _HAVE_PYOPENSSL = False @@ -134,7 +134,7 @@ def test_config_ssl(self): @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") def test_use_pyopenssl_when_available(self): - self.assertTrue(_ssl.IS_PYOPENSSL) + self.assertTrue(_pyssl.IS_PYOPENSSL) @unittest.skipUnless(_HAVE_PYOPENSSL, "Cannot test without PyOpenSSL") def test_load_trusted_ca_certs(self): @@ -177,7 +177,7 @@ def test_tlsCertificateKeyFilePassword(self): # # --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem - if not hasattr(ssl, "SSLContext") and not _ssl.IS_PYOPENSSL: + if not hasattr(ssl, "SSLContext") and not _pyssl.IS_PYOPENSSL: self.assertRaises( ConfigurationError, self.simple_client, @@ -378,7 +378,7 @@ def test_cert_ssl_validation_hostname_matching(self): @client_context.require_tlsCertificateKeyFile @ignore_deprecations def test_tlsCRLFile_support(self): - if not hasattr(ssl, "VERIFY_CRL_CHECK_LEAF") or _ssl.IS_PYOPENSSL: + if not hasattr(ssl, "VERIFY_CRL_CHECK_LEAF") or _pyssl.IS_PYOPENSSL: self.assertRaises( ConfigurationError, self.simple_client, @@ -660,7 +660,7 @@ def remove(path): @client_context.require_async @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") @unittest.skipUnless(HAVE_SSL, "The ssl module is not available.") - def test_pyopenssl_not_ignored_in_async(self): + def test_pyopenssl_ignored_in_async(self): client = MongoClient("mongodb://localhost:27017?tls=true&tlsAllowInvalidCertificates=true") client.admin.command("ping") # command doesn't matter, just needs it to connect client.close() From 53491645665b2f253daf10408143469ccf078e86 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Wed, 16 Apr 2025 10:29:20 -0700 Subject: [PATCH 07/40] fix test pt2 --- test/asynchronous/test_ssl.py | 8 ++++---- test/test_ssl.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/asynchronous/test_ssl.py b/test/asynchronous/test_ssl.py index 250989ff71..b4d479e3a6 100644 --- a/test/asynchronous/test_ssl.py +++ b/test/asynchronous/test_ssl.py @@ -43,7 +43,7 @@ from pymongo import AsyncMongoClient, ssl_support from pymongo.errors import ConfigurationError, ConnectionFailure, OperationFailure from pymongo.hello import HelloCompat -from pymongo.ssl_support import HAVE_SSL, _pyssl, _ssl, get_ssl_context +from pymongo.ssl_support import HAVE_PYSSL, HAVE_SSL, _ssl, get_ssl_context from pymongo.write_concern import WriteConcern _HAVE_PYOPENSSL = False @@ -134,7 +134,7 @@ def test_config_ssl(self): @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") def test_use_pyopenssl_when_available(self): - self.assertTrue(_pyssl.IS_PYOPENSSL) + self.assertTrue(HAVE_PYSSL) @unittest.skipUnless(_HAVE_PYOPENSSL, "Cannot test without PyOpenSSL") def test_load_trusted_ca_certs(self): @@ -177,7 +177,7 @@ async def test_tlsCertificateKeyFilePassword(self): # # --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem - if not hasattr(ssl, "SSLContext") and not _pyssl.IS_PYOPENSSL: + if not hasattr(ssl, "SSLContext") and not HAVE_PYSSL: self.assertRaises( ConfigurationError, self.simple_client, @@ -378,7 +378,7 @@ async def test_cert_ssl_validation_hostname_matching(self): @async_client_context.require_tlsCertificateKeyFile @ignore_deprecations async def test_tlsCRLFile_support(self): - if not hasattr(ssl, "VERIFY_CRL_CHECK_LEAF") or _pyssl.IS_PYOPENSSL: + if not hasattr(ssl, "VERIFY_CRL_CHECK_LEAF") or HAVE_PYSSL: self.assertRaises( ConfigurationError, self.simple_client, diff --git a/test/test_ssl.py b/test/test_ssl.py index 328bef77a9..a1bf5d66d4 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -43,7 +43,7 @@ from pymongo import MongoClient, ssl_support from pymongo.errors import ConfigurationError, ConnectionFailure, OperationFailure from pymongo.hello import HelloCompat -from pymongo.ssl_support import HAVE_SSL, _pyssl, _ssl, get_ssl_context +from pymongo.ssl_support import HAVE_PYSSL, HAVE_SSL, _ssl, get_ssl_context from pymongo.write_concern import WriteConcern _HAVE_PYOPENSSL = False @@ -134,7 +134,7 @@ def test_config_ssl(self): @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") def test_use_pyopenssl_when_available(self): - self.assertTrue(_pyssl.IS_PYOPENSSL) + self.assertTrue(HAVE_PYSSL) @unittest.skipUnless(_HAVE_PYOPENSSL, "Cannot test without PyOpenSSL") def test_load_trusted_ca_certs(self): @@ -177,7 +177,7 @@ def test_tlsCertificateKeyFilePassword(self): # # --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem - if not hasattr(ssl, "SSLContext") and not _pyssl.IS_PYOPENSSL: + if not hasattr(ssl, "SSLContext") and not HAVE_PYSSL: self.assertRaises( ConfigurationError, self.simple_client, @@ -378,7 +378,7 @@ def test_cert_ssl_validation_hostname_matching(self): @client_context.require_tlsCertificateKeyFile @ignore_deprecations def test_tlsCRLFile_support(self): - if not hasattr(ssl, "VERIFY_CRL_CHECK_LEAF") or _pyssl.IS_PYOPENSSL: + if not hasattr(ssl, "VERIFY_CRL_CHECK_LEAF") or HAVE_PYSSL: self.assertRaises( ConfigurationError, self.simple_client, From 67100fc836b92e882a5032c39458269c9ded43e3 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Wed, 16 Apr 2025 11:03:56 -0700 Subject: [PATCH 08/40] edit evergreen config --- .evergreen/generated_configs/variants.yml | 8 ++++---- .evergreen/scripts/generate_config.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 88edf72e04..ae4919cbd7 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -729,8 +729,8 @@ buildvariants: # Pyopenssl tests - name: pyopenssl-macos-python3.9 tasks: - - name: .replica_set .noauth .nossl .sync_async - - name: .7.0 .noauth .nossl .sync_async + - name: .replica_set .noauth .nossl .sync + - name: .7.0 .noauth .nossl .sync display_name: PyOpenSSL macOS Python3.9 run_on: - macos-14 @@ -740,8 +740,8 @@ buildvariants: PYTHON_BINARY: /Library/Frameworks/Python.Framework/Versions/3.9/bin/python3 - name: pyopenssl-rhel8-python3.10 tasks: - - name: .replica_set .auth .ssl .sync - - name: .7.0 .auth .ssl .sync + - name: .replica_set .auth .ssl .sync_async + - name: .7.0 .auth .ssl .sync_async display_name: PyOpenSSL RHEL8 Python3.10 run_on: - rhel87-small diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 439e506426..567e994a00 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -263,7 +263,7 @@ def create_pyopenssl_variants(): display_name = get_variant_name(base_name, host, python=python) # only need to run some on async - if python in (CPYTHONS[0], CPYTHONS[-1]): + if python in (CPYTHONS[1], CPYTHONS[-1]): variant = create_variant( [f".replica_set .{auth} .{ssl} .sync_async", f".7.0 .{auth} .{ssl} .sync_async"], display_name, From 88ae34577999da2154bcfbc6b5109c20b2870592 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Wed, 16 Apr 2025 11:20:13 -0700 Subject: [PATCH 09/40] fix test --- test/asynchronous/test_ssl.py | 1 + test/test_ssl.py | 1 + 2 files changed, 2 insertions(+) diff --git a/test/asynchronous/test_ssl.py b/test/asynchronous/test_ssl.py index b4d479e3a6..202b0019d1 100644 --- a/test/asynchronous/test_ssl.py +++ b/test/asynchronous/test_ssl.py @@ -376,6 +376,7 @@ async def test_cert_ssl_validation_hostname_matching(self): ) @async_client_context.require_tlsCertificateKeyFile + @async_client_context.require_sync @ignore_deprecations async def test_tlsCRLFile_support(self): if not hasattr(ssl, "VERIFY_CRL_CHECK_LEAF") or HAVE_PYSSL: diff --git a/test/test_ssl.py b/test/test_ssl.py index a1bf5d66d4..c6f2adc2de 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -376,6 +376,7 @@ def test_cert_ssl_validation_hostname_matching(self): ) @client_context.require_tlsCertificateKeyFile + @client_context.require_sync @ignore_deprecations def test_tlsCRLFile_support(self): if not hasattr(ssl, "VERIFY_CRL_CHECK_LEAF") or HAVE_PYSSL: From e451cebfbb264428ea3f0fea2303773b6b9d88d5 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Fri, 18 Apr 2025 10:27:53 -0700 Subject: [PATCH 10/40] fix test errors --- pymongo/network_layer.py | 71 ++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/pymongo/network_layer.py b/pymongo/network_layer.py index e287655c61..e08aec1340 100644 --- a/pymongo/network_layer.py +++ b/pymongo/network_layer.py @@ -46,22 +46,21 @@ _HAVE_SSL = False try: - from pymongo.pyopenssl_context import ( - BLOCKING_IO_LOOKUP_ERROR, - BLOCKING_IO_READ_ERROR, - BLOCKING_IO_WRITE_ERROR, - _sslConn, - ) + from pymongo.pyopenssl_context import _sslConn as _pysslConn _HAVE_PYOPENSSL = True except ImportError: _HAVE_PYOPENSSL = False - _sslConn = SSLSocket # type: ignore - from pymongo.ssl_support import ( # type: ignore[assignment] - BLOCKING_IO_LOOKUP_ERROR, - BLOCKING_IO_READ_ERROR, - BLOCKING_IO_WRITE_ERROR, - ) + _pysslCon = SSLSocket + +from pymongo.ssl_support import ( # type: ignore[assignment] + BLOCKING_IO_LOOKUP_ERROR, + BLOCKING_IO_READ_ERROR, + BLOCKING_IO_WRITE_ERROR, + PYBLOCKING_IO_LOOKUP_ERROR, + PYBLOCKING_IO_READ_ERROR, + PYBLOCKING_IO_WRITE_ERROR, +) if TYPE_CHECKING: from pymongo.asynchronous.pool import AsyncConnection @@ -71,17 +70,23 @@ _UNPACK_COMPRESSION_HEADER = struct.Struct(" None: +async def async_socket_sendall(sock: Union[socket.socket, _pysslConn], buf: bytes) -> None: timeout = sock.gettimeout() sock.settimeout(0.0) loop = asyncio.get_running_loop() try: - if _HAVE_SSL and isinstance(sock, (SSLSocket, _sslConn)): + if _HAVE_SSL and isinstance(sock, (SSLSocket, _pysslConn)): await asyncio.wait_for(_async_socket_sendall_ssl(sock, buf, loop), timeout=timeout) else: await asyncio.wait_for(loop.sock_sendall(sock, buf), timeout=timeout) # type: ignore[arg-type] @@ -95,7 +100,7 @@ async def async_socket_sendall(sock: Union[socket.socket, _sslConn], buf: bytes) if sys.platform != "win32": async def _async_socket_sendall_ssl( - sock: Union[socket.socket, _sslConn], buf: bytes, loop: AbstractEventLoop + sock: Union[socket.socket, _pysslConn], buf: bytes, loop: AbstractEventLoop ) -> None: view = memoryview(buf) sent = 0 @@ -113,21 +118,23 @@ def _is_ready(fut: Future) -> None: # Check for closed socket. if fd == -1: raise SSLError("Underlying socket has been closed") from None - if isinstance(exc, BLOCKING_IO_READ_ERROR): + if isinstance(exc, (BLOCKING_IO_READ_ERROR, PYBLOCKING_IO_READ_ERROR)): fut = loop.create_future() loop.add_reader(fd, _is_ready, fut) try: await fut finally: loop.remove_reader(fd) - if isinstance(exc, BLOCKING_IO_WRITE_ERROR): + if isinstance(exc, (BLOCKING_IO_WRITE_ERROR, PYBLOCKING_IO_WRITE_ERROR)): fut = loop.create_future() loop.add_writer(fd, _is_ready, fut) try: await fut finally: loop.remove_writer(fd) - if _HAVE_PYOPENSSL and isinstance(exc, BLOCKING_IO_LOOKUP_ERROR): + if _HAVE_PYOPENSSL and isinstance( + exc, (BLOCKING_IO_LOOKUP_ERROR, PYBLOCKING_IO_LOOKUP_ERROR) + ): fut = loop.create_future() loop.add_reader(fd, _is_ready, fut) try: @@ -138,7 +145,7 @@ def _is_ready(fut: Future) -> None: loop.remove_writer(fd) async def _async_socket_receive_ssl( - conn: _sslConn, length: int, loop: AbstractEventLoop, once: Optional[bool] = False + conn: _pysslConn, length: int, loop: AbstractEventLoop, once: Optional[bool] = False ) -> memoryview: mv = memoryview(bytearray(length)) total_read = 0 @@ -162,21 +169,23 @@ def _is_ready(fut: Future) -> None: # Check for closed socket. if fd == -1: raise SSLError("Underlying socket has been closed") from None - if isinstance(exc, BLOCKING_IO_READ_ERROR): + if isinstance(exc, (BLOCKING_IO_READ_ERROR, PYBLOCKING_IO_READ_ERROR)): fut = loop.create_future() loop.add_reader(fd, _is_ready, fut) try: await fut finally: loop.remove_reader(fd) - if isinstance(exc, BLOCKING_IO_WRITE_ERROR): + if isinstance(exc, (BLOCKING_IO_WRITE_ERROR, PYBLOCKING_IO_WRITE_ERROR)): fut = loop.create_future() loop.add_writer(fd, _is_ready, fut) try: await fut finally: loop.remove_writer(fd) - if _HAVE_PYOPENSSL and isinstance(exc, BLOCKING_IO_LOOKUP_ERROR): + if _HAVE_PYOPENSSL and isinstance( + exc, (BLOCKING_IO_LOOKUP_ERROR, PYBLOCKING_IO_LOOKUP_ERROR) + ): fut = loop.create_future() loop.add_reader(fd, _is_ready, fut) try: @@ -192,7 +201,7 @@ def _is_ready(fut: Future) -> None: # https://docs.python.org/3/library/asyncio-platforms.html#asyncio-platform-support # Note: In PYTHON-4493 we plan to replace this code with asyncio streams. async def _async_socket_sendall_ssl( - sock: Union[socket.socket, _sslConn], buf: bytes, dummy: AbstractEventLoop + sock: Union[socket.socket, _pysslConn], buf: bytes, dummy: AbstractEventLoop ) -> None: view = memoryview(buf) total_length = len(buf) @@ -213,7 +222,7 @@ async def _async_socket_sendall_ssl( total_sent += sent async def _async_socket_receive_ssl( - conn: _sslConn, length: int, dummy: AbstractEventLoop, once: Optional[bool] = False + conn: _pysslConn, length: int, dummy: AbstractEventLoop, once: Optional[bool] = False ) -> memoryview: mv = memoryview(bytearray(length)) total_read = 0 @@ -239,7 +248,7 @@ async def _async_socket_receive_ssl( return mv -def sendall(sock: Union[socket.socket, _sslConn], buf: bytes) -> None: +def sendall(sock: Union[socket.socket, _pysslConn], buf: bytes) -> None: sock.sendall(buf) @@ -252,7 +261,7 @@ async def _poll_cancellation(conn: AsyncConnection) -> None: async def async_receive_data_socket( - sock: Union[socket.socket, _sslConn], length: int + sock: Union[socket.socket, _pysslConn], length: int ) -> memoryview: sock_timeout = sock.gettimeout() timeout = sock_timeout @@ -260,7 +269,7 @@ async def async_receive_data_socket( sock.settimeout(0.0) loop = asyncio.get_running_loop() try: - if _HAVE_SSL and isinstance(sock, (SSLSocket, _sslConn)): + if _HAVE_SSL and isinstance(sock, (SSLSocket, _pysslConn)): return await asyncio.wait_for( _async_socket_receive_ssl(sock, length, loop, once=True), # type: ignore[arg-type] timeout=timeout, @@ -435,7 +444,7 @@ def sock(self) -> socket.socket: class NetworkingInterface(NetworkingInterfaceBase): - def __init__(self, conn: Union[socket.socket, _sslConn]): + def __init__(self, conn: Union[socket.socket, _pysslConn]): super().__init__(conn) def gettimeout(self) -> float | None: @@ -451,11 +460,11 @@ def is_closing(self) -> bool: return self.conn.is_closing() @property - def get_conn(self) -> Union[socket.socket, _sslConn]: + def get_conn(self) -> Union[socket.socket, _pysslConn]: return self.conn @property - def sock(self) -> Union[socket.socket, _sslConn]: + def sock(self) -> Union[socket.socket, _pysslConn]: return self.conn def fileno(self) -> int: From 0312acbe6e2746b3f0fa955e1973eb77237f17ab Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Fri, 18 Apr 2025 11:20:04 -0700 Subject: [PATCH 11/40] fix typo... --- pymongo/network_layer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymongo/network_layer.py b/pymongo/network_layer.py index e08aec1340..e903b9ebff 100644 --- a/pymongo/network_layer.py +++ b/pymongo/network_layer.py @@ -51,7 +51,7 @@ _HAVE_PYOPENSSL = True except ImportError: _HAVE_PYOPENSSL = False - _pysslCon = SSLSocket + _pysslConn = SSLSocket from pymongo.ssl_support import ( # type: ignore[assignment] BLOCKING_IO_LOOKUP_ERROR, From 4e85024b671f0d3fc5263d68942c09c1f00093b3 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Fri, 18 Apr 2025 12:34:23 -0700 Subject: [PATCH 12/40] fix typing --- pymongo/network_layer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymongo/network_layer.py b/pymongo/network_layer.py index e903b9ebff..47489a191e 100644 --- a/pymongo/network_layer.py +++ b/pymongo/network_layer.py @@ -51,9 +51,9 @@ _HAVE_PYOPENSSL = True except ImportError: _HAVE_PYOPENSSL = False - _pysslConn = SSLSocket + _pysslConn = SSLSocket # type: ignore[assignment, misc] -from pymongo.ssl_support import ( # type: ignore[assignment] +from pymongo.ssl_support import ( BLOCKING_IO_LOOKUP_ERROR, BLOCKING_IO_READ_ERROR, BLOCKING_IO_WRITE_ERROR, From c86a85f6d940997f4a9d09703e3bf5308e8403e2 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Mon, 21 Apr 2025 09:12:37 -0700 Subject: [PATCH 13/40] maybe this works? --- pymongo/asynchronous/encryption.py | 3 +- pymongo/encryption_options.py | 7 +- pymongo/synchronous/encryption.py | 3 +- test/asynchronous/test_client_bulk_write.py | 1 - test/asynchronous/test_encryption.py | 79 +++++++-------------- test/test_client_bulk_write.py | 1 - test/test_encryption.py | 79 +++++++-------------- 7 files changed, 59 insertions(+), 114 deletions(-) diff --git a/pymongo/asynchronous/encryption.py b/pymongo/asynchronous/encryption.py index 7446bbaa51..061e4ef3f0 100644 --- a/pymongo/asynchronous/encryption.py +++ b/pymongo/asynchronous/encryption.py @@ -165,6 +165,7 @@ async def kms_request(self, kms_context: MongoCryptKmsContext) -> None: :return: None """ + self.opts._parse_kms_tls_options(_IS_SYNC) endpoint = kms_context.endpoint message = kms_context.message provider = kms_context.kms_provider @@ -675,8 +676,8 @@ def __init__( key_vault_namespace, kms_tls_options=kms_tls_options, key_expiration_ms=key_expiration_ms, - is_sync=_IS_SYNC, ) + opts._parse_kms_tls_options(_IS_SYNC) self._io_callbacks: Optional[_EncryptionIO] = _EncryptionIO( None, key_vault_coll, None, opts ) diff --git a/pymongo/encryption_options.py b/pymongo/encryption_options.py index 6983cdddd6..74cd800429 100644 --- a/pymongo/encryption_options.py +++ b/pymongo/encryption_options.py @@ -58,7 +58,6 @@ def __init__( bypass_query_analysis: bool = False, encrypted_fields_map: Optional[Mapping[str, Any]] = None, key_expiration_ms: Optional[int] = None, - is_sync: bool = True, ) -> None: """Options to configure automatic client-side field level encryption. @@ -237,10 +236,14 @@ def __init__( if not any("idleShutdownTimeoutSecs" in s for s in self._mongocryptd_spawn_args): self._mongocryptd_spawn_args.append("--idleShutdownTimeoutSecs=60") # Maps KMS provider name to a SSLContext. - self._kms_ssl_contexts = _parse_kms_tls_options(kms_tls_options, is_sync) + self._kms_tls_options = kms_tls_options + # self._kms_ssl_contexts = _parse_kms_tls_options(kms_tls_options, is_sync) self._bypass_query_analysis = bypass_query_analysis self._key_expiration_ms = key_expiration_ms + def _parse_kms_tls_options(self, is_sync): + self._kms_ssl_contexts = _parse_kms_tls_options(self._kms_tls_options, is_sync) + class RangeOpts: """Options to configure encrypted queries using the range algorithm.""" diff --git a/pymongo/synchronous/encryption.py b/pymongo/synchronous/encryption.py index f5425a8baf..53006955a6 100644 --- a/pymongo/synchronous/encryption.py +++ b/pymongo/synchronous/encryption.py @@ -164,6 +164,7 @@ def kms_request(self, kms_context: MongoCryptKmsContext) -> None: :return: None """ + self.opts._parse_kms_tls_options(_IS_SYNC) endpoint = kms_context.endpoint message = kms_context.message provider = kms_context.kms_provider @@ -668,8 +669,8 @@ def __init__( key_vault_namespace, kms_tls_options=kms_tls_options, key_expiration_ms=key_expiration_ms, - is_sync=_IS_SYNC, ) + opts._parse_kms_tls_options(_IS_SYNC) self._io_callbacks: Optional[_EncryptionIO] = _EncryptionIO( None, key_vault_coll, None, opts ) diff --git a/test/asynchronous/test_client_bulk_write.py b/test/asynchronous/test_client_bulk_write.py index ee76e34e33..9eb15298a6 100644 --- a/test/asynchronous/test_client_bulk_write.py +++ b/test/asynchronous/test_client_bulk_write.py @@ -545,7 +545,6 @@ async def test_returns_error_if_auto_encryption_configured(self): opts = AutoEncryptionOpts( key_vault_namespace="db.coll", kms_providers={"aws": {"accessKeyId": "foo", "secretAccessKey": "bar"}}, - is_sync=_IS_SYNC, ) client = await self.async_rs_or_single_client(auto_encryption_opts=opts) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 494cd1e165..743baaeaed 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -122,7 +122,6 @@ async def test_crypt_shared(self): KMS_PROVIDERS, "keyvault.datakeys", crypt_shared_lib_required=True, - is_sync=_IS_SYNC, ), connect=False, ) @@ -130,11 +129,11 @@ async def test_crypt_shared(self): @unittest.skipIf(_HAVE_PYMONGOCRYPT, "pymongocrypt is installed") def test_init_requires_pymongocrypt(self): with self.assertRaises(ConfigurationError): - AutoEncryptionOpts({}, "keyvault.datakeys", is_sync=_IS_SYNC) + AutoEncryptionOpts({}, "keyvault.datakeys") @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") def test_init(self): - opts = AutoEncryptionOpts({}, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts({}, "keyvault.datakeys") self.assertEqual(opts._kms_providers, {}) self.assertEqual(opts._key_vault_namespace, "keyvault.datakeys") self.assertEqual(opts._key_vault_client, None) @@ -153,13 +152,14 @@ def test_init_spawn_args(self): {}, "keyvault.datakeys", mongocryptd_spawn_args=["--idleShutdownTimeoutSecs=88"], - is_sync=_IS_SYNC, ) self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=88"]) # idleShutdownTimeoutSecs is added by default opts = AutoEncryptionOpts( - {}, "keyvault.datakeys", mongocryptd_spawn_args=[], is_sync=_IS_SYNC + {}, + "keyvault.datakeys", + mongocryptd_spawn_args=[], ) self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=60"]) @@ -168,7 +168,6 @@ def test_init_spawn_args(self): {}, "keyvault.datakeys", mongocryptd_spawn_args=["--quiet", "--port=27020"], - is_sync=_IS_SYNC, ) self.assertEqual( opts._mongocryptd_spawn_args, @@ -179,7 +178,8 @@ def test_init_spawn_args(self): def test_init_kms_tls_options(self): # Error cases: with self.assertRaisesRegex(TypeError, r'kms_tls_options\["kmip"\] must be a dict'): - AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}, is_sync=_IS_SYNC) + AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}) + tls_opts: Any for tls_opts in [ {"kmip": {"tls": True, "tlsInsecure": True}}, @@ -187,21 +187,22 @@ def test_init_kms_tls_options(self): {"kmip": {"tls": True, "tlsAllowInvalidHostnames": True}}, ]: with self.assertRaisesRegex(ConfigurationError, "Insecure TLS options prohibited"): - opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts, is_sync=_IS_SYNC) + opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) with self.assertRaises(FileNotFoundError): AutoEncryptionOpts( {}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}}, - is_sync=_IS_SYNC, ) # Success cases: tls_opts: Any for tls_opts in [None, {}]: - opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts, is_sync=_IS_SYNC) + opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) self.assertEqual(opts._kms_ssl_contexts, {}) opts = AutoEncryptionOpts( - {}, "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}, is_sync=_IS_SYNC + {}, + "k.d", + kms_tls_options={"kmip": {"tls": True}, "aws": {}}, ) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) @@ -213,7 +214,6 @@ def test_init_kms_tls_options(self): {}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": CA_PEM, "tlsCertificateKeyFile": CLIENT_PEM}}, - is_sync=_IS_SYNC, ) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) @@ -230,7 +230,7 @@ async def test_default(self): @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") async def test_kwargs(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") client = self.simple_client(auto_encryption_opts=opts, connect=False) self.assertEqual(get_client_opts(client).auto_encryption_opts, opts) @@ -382,7 +382,6 @@ async def test_auto_encrypt(self): opts = AutoEncryptionOpts( KMS_PROVIDERS, "keyvault.datakeys", - is_sync=_IS_SYNC, ) await self._test_auto_encrypt(opts) @@ -390,13 +389,15 @@ async def test_auto_encrypt_local_schema_map(self): # Configure the encrypted field via the local schema_map option. schemas = {"pymongo_test.test": json_data("custom", "schema.json")} opts = AutoEncryptionOpts( - KMS_PROVIDERS, "keyvault.datakeys", schema_map=schemas, is_sync=_IS_SYNC + KMS_PROVIDERS, + "keyvault.datakeys", + schema_map=schemas, ) await self._test_auto_encrypt(opts) async def test_use_after_close(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") client = await self.async_rs_or_single_client(auto_encryption_opts=opts) await client.admin.command("ping") @@ -415,7 +416,7 @@ async def test_use_after_close(self): @async_client_context.require_sync async def test_fork(self): self.skipTest("Test is flaky, PYTHON-4738") - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") client = await self.async_rs_or_single_client(auto_encryption_opts=opts) async def target(): @@ -429,7 +430,7 @@ async def target(): class TestEncryptedBulkWrite(AsyncBulkTestBase, AsyncEncryptionIntegrationTest): async def test_upsert_uuid_standard_encrypt(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") client = await self.async_rs_or_single_client(auto_encryption_opts=opts) options = CodecOptions(uuid_representation=UuidRepresentation.STANDARD) @@ -468,7 +469,7 @@ async def asyncSetUp(self): @async_client_context.require_version_max(4, 0, 99) async def test_raise_max_wire_version_error(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") client = await self.async_rs_or_single_client(auto_encryption_opts=opts) msg = "Auto-encryption requires a minimum MongoDB version of 4.2" with self.assertRaisesRegex(ConfigurationError, msg): @@ -481,7 +482,7 @@ async def test_raise_max_wire_version_error(self): await client.test.test.bulk_write([InsertOne({})]) async def test_raise_unsupported_error(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") client = await self.async_rs_or_single_client(auto_encryption_opts=opts) msg = "find_raw_batches does not support auto encryption" with self.assertRaisesRegex(InvalidOperation, msg): @@ -698,7 +699,7 @@ def parse_auto_encrypt_opts(self, opts): opts.update(camel_to_snake_args(opts.pop("extra_options"))) opts = dict(opts) - return AutoEncryptionOpts(**opts, is_sync=_IS_SYNC) + return AutoEncryptionOpts(**opts) def parse_client_options(self, opts): """Override clientOptions parsing to support autoEncryptOpts.""" @@ -880,7 +881,6 @@ async def asyncSetUp(self): "keyvault.datakeys", schema_map=schemas, kms_tls_options=KMS_TLS_OPTS, - is_sync=_IS_SYNC, ) self.client_encrypted = await self.async_rs_or_single_client( auto_encryption_opts=opts, uuidRepresentation="standard" @@ -976,7 +976,6 @@ async def _test_external_key_vault(self, with_external_key_vault): "keyvault.datakeys", schema_map=schemas, key_vault_client=key_vault_client, - is_sync=_IS_SYNC, ) client_encrypted = await self.async_rs_or_single_client( @@ -1030,7 +1029,7 @@ async def test_views_are_prohibited(self): await self.client.db.create_collection("view", viewOn="coll") self.addAsyncCleanup(self.client.db.view.drop) - opts = AutoEncryptionOpts(self.kms_providers(), "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(self.kms_providers(), "keyvault.datakeys") client_encrypted = await self.async_rs_or_single_client( auto_encryption_opts=opts, uuidRepresentation="standard" ) @@ -1191,7 +1190,6 @@ async def test_corpus(self): self.kms_providers(), "keyvault.datakeys", kms_tls_options=KMS_TLS_OPTS, - is_sync=_IS_SYNC, ) await self._test_corpus(opts) @@ -1203,7 +1201,6 @@ async def test_corpus_local_schema(self): "keyvault.datakeys", schema_map=schemas, kms_tls_options=KMS_TLS_OPTS, - is_sync=_IS_SYNC, ) await self._test_corpus(opts) @@ -1242,7 +1239,8 @@ async def asyncSetUp(self): await coll.insert_one(json_data("limits", "limits-key.json")) opts = AutoEncryptionOpts( - {"local": {"key": LOCAL_MASTER_KEY}}, "keyvault.datakeys", is_sync=_IS_SYNC + {"local": {"key": LOCAL_MASTER_KEY}}, + "keyvault.datakeys", ) self.listener = OvertCommandListener() self.client_encrypted = await self.async_rs_or_single_client( @@ -1558,7 +1556,6 @@ async def _test_automatic(self, expectation_extjson, payload): self.KMS_PROVIDER_MAP, # type: ignore[arg-type] keyvault_namespace, schema_map=self.SCHEMA_MAP, - is_sync=_IS_SYNC, ) insert_listener = AllowListEventListener("insert") @@ -1702,7 +1699,6 @@ async def test_case_1(self): *self.optargs, bypass_auto_encryption=False, key_vault_client=None, - is_sync=_IS_SYNC, ), ) @@ -1726,7 +1722,6 @@ async def test_case_2(self): *self.optargs, bypass_auto_encryption=False, key_vault_client=self.client_keyvault, - is_sync=_IS_SYNC, ), ) @@ -1753,7 +1748,6 @@ async def test_case_3(self): *self.optargs, bypass_auto_encryption=True, key_vault_client=None, - is_sync=_IS_SYNC, ), ) @@ -1773,7 +1767,6 @@ async def test_case_4(self): *self.optargs, bypass_auto_encryption=True, key_vault_client=self.client_keyvault, - is_sync=_IS_SYNC, ), ) @@ -1796,7 +1789,6 @@ async def test_case_5(self): *self.optargs, bypass_auto_encryption=False, key_vault_client=None, - is_sync=_IS_SYNC, ), ) @@ -1822,7 +1814,6 @@ async def test_case_6(self): *self.optargs, bypass_auto_encryption=False, key_vault_client=self.client_keyvault, - is_sync=_IS_SYNC, ), ) @@ -1849,7 +1840,6 @@ async def test_case_7(self): *self.optargs, bypass_auto_encryption=True, key_vault_client=None, - is_sync=_IS_SYNC, ), ) @@ -1869,7 +1859,6 @@ async def test_case_8(self): *self.optargs, bypass_auto_encryption=True, key_vault_client=self.client_keyvault, - is_sync=_IS_SYNC, ), ) @@ -1909,7 +1898,6 @@ async def asyncSetUp(self): opts = AutoEncryptionOpts( key_vault_namespace="keyvault.datakeys", kms_providers=kms_providers_map, - is_sync=_IS_SYNC, ) self.listener = AllowListEventListener("aggregate") self.encrypted_client = await self.async_rs_or_single_client( @@ -1990,7 +1978,6 @@ def reset_timeout(): "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27027", ], - is_sync=_IS_SYNC, ) client_encrypted = await self.async_rs_or_single_client(auto_encryption_opts=opts) with self.assertRaisesRegex(EncryptionError, "Timeout"): @@ -2005,7 +1992,6 @@ async def test_bypassAutoEncryption(self): "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27027", ], - is_sync=_IS_SYNC, ) client_encrypted = await self.async_rs_or_single_client(auto_encryption_opts=opts) await client_encrypted.db.coll.insert_one({"unencrypted": "test"}) @@ -2033,7 +2019,6 @@ async def test_via_loading_shared_library(self): "--port=47021", ], crypt_shared_lib_required=True, - is_sync=_IS_SYNC, ) client_encrypted = await self.async_rs_or_single_client(auto_encryption_opts=opts) await client_encrypted.db.coll.drop() @@ -2074,7 +2059,6 @@ def listener(): schema_map=schemas, mongocryptd_uri="mongodb://localhost:47021", crypt_shared_lib_required=False, - is_sync=_IS_SYNC, ) client_encrypted = await self.async_rs_or_single_client(auto_encryption_opts=opts) await client_encrypted.db.coll.drop() @@ -2388,7 +2372,6 @@ async def asyncSetUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True, - is_sync=_IS_SYNC, ) self.encrypted_client = await self.async_rs_or_single_client(auto_encryption_opts=opts) @@ -2495,7 +2478,6 @@ async def asyncSetUp(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) await encrypted_client.drop_database("db") @@ -2547,7 +2529,6 @@ async def test_1_csfle_joins_no_schema(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2576,7 +2557,6 @@ async def test_2_qe_joins_no_schema(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2605,7 +2585,6 @@ async def test_3_no_schema_joins_csfle(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2631,7 +2610,6 @@ async def test_4_no_schema_joins_qe(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2660,7 +2638,6 @@ async def test_5_csfle_joins_csfle2(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2689,7 +2666,6 @@ async def test_6_qe_joins_qe2(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2718,7 +2694,6 @@ async def test_7_no_schema_joins_no_schema2(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = await anext( @@ -2747,7 +2722,6 @@ async def test_8_csfle_joins_qe(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) with self.assertRaises(PyMongoError) as exc: @@ -2774,7 +2748,6 @@ async def test_9_error(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) with self.assertRaises(PyMongoError) as exc: @@ -2968,7 +2941,6 @@ async def AsyncMongoClient(**kwargs): kms_providers_map, "keyvault.datakeys", encrypted_fields_map=encrypted_fields_map, - is_sync=_IS_SYNC, ) encrypted_client = await AsyncMongoClient(auto_encryption_opts=opts) @@ -3023,7 +2995,6 @@ async def asyncSetUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True, - is_sync=_IS_SYNC, ) self.encrypted_client = await self.async_rs_or_single_client(auto_encryption_opts=opts) self.db = self.encrypted_client.db diff --git a/test/test_client_bulk_write.py b/test/test_client_bulk_write.py index 2e37b28696..866b179c9e 100644 --- a/test/test_client_bulk_write.py +++ b/test/test_client_bulk_write.py @@ -541,7 +541,6 @@ def test_returns_error_if_auto_encryption_configured(self): opts = AutoEncryptionOpts( key_vault_namespace="db.coll", kms_providers={"aws": {"accessKeyId": "foo", "secretAccessKey": "bar"}}, - is_sync=_IS_SYNC, ) client = self.rs_or_single_client(auto_encryption_opts=opts) diff --git a/test/test_encryption.py b/test/test_encryption.py index 269a312134..3d73471b76 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -122,7 +122,6 @@ def test_crypt_shared(self): KMS_PROVIDERS, "keyvault.datakeys", crypt_shared_lib_required=True, - is_sync=_IS_SYNC, ), connect=False, ) @@ -130,11 +129,11 @@ def test_crypt_shared(self): @unittest.skipIf(_HAVE_PYMONGOCRYPT, "pymongocrypt is installed") def test_init_requires_pymongocrypt(self): with self.assertRaises(ConfigurationError): - AutoEncryptionOpts({}, "keyvault.datakeys", is_sync=_IS_SYNC) + AutoEncryptionOpts({}, "keyvault.datakeys") @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") def test_init(self): - opts = AutoEncryptionOpts({}, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts({}, "keyvault.datakeys") self.assertEqual(opts._kms_providers, {}) self.assertEqual(opts._key_vault_namespace, "keyvault.datakeys") self.assertEqual(opts._key_vault_client, None) @@ -153,13 +152,14 @@ def test_init_spawn_args(self): {}, "keyvault.datakeys", mongocryptd_spawn_args=["--idleShutdownTimeoutSecs=88"], - is_sync=_IS_SYNC, ) self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=88"]) # idleShutdownTimeoutSecs is added by default opts = AutoEncryptionOpts( - {}, "keyvault.datakeys", mongocryptd_spawn_args=[], is_sync=_IS_SYNC + {}, + "keyvault.datakeys", + mongocryptd_spawn_args=[], ) self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=60"]) @@ -168,7 +168,6 @@ def test_init_spawn_args(self): {}, "keyvault.datakeys", mongocryptd_spawn_args=["--quiet", "--port=27020"], - is_sync=_IS_SYNC, ) self.assertEqual( opts._mongocryptd_spawn_args, @@ -179,7 +178,8 @@ def test_init_spawn_args(self): def test_init_kms_tls_options(self): # Error cases: with self.assertRaisesRegex(TypeError, r'kms_tls_options\["kmip"\] must be a dict'): - AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}, is_sync=_IS_SYNC) + AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}) + tls_opts: Any for tls_opts in [ {"kmip": {"tls": True, "tlsInsecure": True}}, @@ -187,21 +187,22 @@ def test_init_kms_tls_options(self): {"kmip": {"tls": True, "tlsAllowInvalidHostnames": True}}, ]: with self.assertRaisesRegex(ConfigurationError, "Insecure TLS options prohibited"): - opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts, is_sync=_IS_SYNC) + opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) with self.assertRaises(FileNotFoundError): AutoEncryptionOpts( {}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}}, - is_sync=_IS_SYNC, ) # Success cases: tls_opts: Any for tls_opts in [None, {}]: - opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts, is_sync=_IS_SYNC) + opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) self.assertEqual(opts._kms_ssl_contexts, {}) opts = AutoEncryptionOpts( - {}, "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}, is_sync=_IS_SYNC + {}, + "k.d", + kms_tls_options={"kmip": {"tls": True}, "aws": {}}, ) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) @@ -213,7 +214,6 @@ def test_init_kms_tls_options(self): {}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": CA_PEM, "tlsCertificateKeyFile": CLIENT_PEM}}, - is_sync=_IS_SYNC, ) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) @@ -230,7 +230,7 @@ def test_default(self): @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") def test_kwargs(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") client = self.simple_client(auto_encryption_opts=opts, connect=False) self.assertEqual(get_client_opts(client).auto_encryption_opts, opts) @@ -382,7 +382,6 @@ def test_auto_encrypt(self): opts = AutoEncryptionOpts( KMS_PROVIDERS, "keyvault.datakeys", - is_sync=_IS_SYNC, ) self._test_auto_encrypt(opts) @@ -390,13 +389,15 @@ def test_auto_encrypt_local_schema_map(self): # Configure the encrypted field via the local schema_map option. schemas = {"pymongo_test.test": json_data("custom", "schema.json")} opts = AutoEncryptionOpts( - KMS_PROVIDERS, "keyvault.datakeys", schema_map=schemas, is_sync=_IS_SYNC + KMS_PROVIDERS, + "keyvault.datakeys", + schema_map=schemas, ) self._test_auto_encrypt(opts) def test_use_after_close(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") client = self.rs_or_single_client(auto_encryption_opts=opts) client.admin.command("ping") @@ -415,7 +416,7 @@ def test_use_after_close(self): @client_context.require_sync def test_fork(self): self.skipTest("Test is flaky, PYTHON-4738") - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") client = self.rs_or_single_client(auto_encryption_opts=opts) def target(): @@ -429,7 +430,7 @@ def target(): class TestEncryptedBulkWrite(BulkTestBase, EncryptionIntegrationTest): def test_upsert_uuid_standard_encrypt(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") client = self.rs_or_single_client(auto_encryption_opts=opts) options = CodecOptions(uuid_representation=UuidRepresentation.STANDARD) @@ -468,7 +469,7 @@ def setUp(self): @client_context.require_version_max(4, 0, 99) def test_raise_max_wire_version_error(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") client = self.rs_or_single_client(auto_encryption_opts=opts) msg = "Auto-encryption requires a minimum MongoDB version of 4.2" with self.assertRaisesRegex(ConfigurationError, msg): @@ -481,7 +482,7 @@ def test_raise_max_wire_version_error(self): client.test.test.bulk_write([InsertOne({})]) def test_raise_unsupported_error(self): - opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") client = self.rs_or_single_client(auto_encryption_opts=opts) msg = "find_raw_batches does not support auto encryption" with self.assertRaisesRegex(InvalidOperation, msg): @@ -696,7 +697,7 @@ def parse_auto_encrypt_opts(self, opts): opts.update(camel_to_snake_args(opts.pop("extra_options"))) opts = dict(opts) - return AutoEncryptionOpts(**opts, is_sync=_IS_SYNC) + return AutoEncryptionOpts(**opts) def parse_client_options(self, opts): """Override clientOptions parsing to support autoEncryptOpts.""" @@ -878,7 +879,6 @@ def setUp(self): "keyvault.datakeys", schema_map=schemas, kms_tls_options=KMS_TLS_OPTS, - is_sync=_IS_SYNC, ) self.client_encrypted = self.rs_or_single_client( auto_encryption_opts=opts, uuidRepresentation="standard" @@ -972,7 +972,6 @@ def _test_external_key_vault(self, with_external_key_vault): "keyvault.datakeys", schema_map=schemas, key_vault_client=key_vault_client, - is_sync=_IS_SYNC, ) client_encrypted = self.rs_or_single_client( @@ -1026,7 +1025,7 @@ def test_views_are_prohibited(self): self.client.db.create_collection("view", viewOn="coll") self.addCleanup(self.client.db.view.drop) - opts = AutoEncryptionOpts(self.kms_providers(), "keyvault.datakeys", is_sync=_IS_SYNC) + opts = AutoEncryptionOpts(self.kms_providers(), "keyvault.datakeys") client_encrypted = self.rs_or_single_client( auto_encryption_opts=opts, uuidRepresentation="standard" ) @@ -1187,7 +1186,6 @@ def test_corpus(self): self.kms_providers(), "keyvault.datakeys", kms_tls_options=KMS_TLS_OPTS, - is_sync=_IS_SYNC, ) self._test_corpus(opts) @@ -1199,7 +1197,6 @@ def test_corpus_local_schema(self): "keyvault.datakeys", schema_map=schemas, kms_tls_options=KMS_TLS_OPTS, - is_sync=_IS_SYNC, ) self._test_corpus(opts) @@ -1238,7 +1235,8 @@ def setUp(self): coll.insert_one(json_data("limits", "limits-key.json")) opts = AutoEncryptionOpts( - {"local": {"key": LOCAL_MASTER_KEY}}, "keyvault.datakeys", is_sync=_IS_SYNC + {"local": {"key": LOCAL_MASTER_KEY}}, + "keyvault.datakeys", ) self.listener = OvertCommandListener() self.client_encrypted = self.rs_or_single_client( @@ -1552,7 +1550,6 @@ def _test_automatic(self, expectation_extjson, payload): self.KMS_PROVIDER_MAP, # type: ignore[arg-type] keyvault_namespace, schema_map=self.SCHEMA_MAP, - is_sync=_IS_SYNC, ) insert_listener = AllowListEventListener("insert") @@ -1694,7 +1691,6 @@ def test_case_1(self): *self.optargs, bypass_auto_encryption=False, key_vault_client=None, - is_sync=_IS_SYNC, ), ) @@ -1718,7 +1714,6 @@ def test_case_2(self): *self.optargs, bypass_auto_encryption=False, key_vault_client=self.client_keyvault, - is_sync=_IS_SYNC, ), ) @@ -1745,7 +1740,6 @@ def test_case_3(self): *self.optargs, bypass_auto_encryption=True, key_vault_client=None, - is_sync=_IS_SYNC, ), ) @@ -1765,7 +1759,6 @@ def test_case_4(self): *self.optargs, bypass_auto_encryption=True, key_vault_client=self.client_keyvault, - is_sync=_IS_SYNC, ), ) @@ -1788,7 +1781,6 @@ def test_case_5(self): *self.optargs, bypass_auto_encryption=False, key_vault_client=None, - is_sync=_IS_SYNC, ), ) @@ -1814,7 +1806,6 @@ def test_case_6(self): *self.optargs, bypass_auto_encryption=False, key_vault_client=self.client_keyvault, - is_sync=_IS_SYNC, ), ) @@ -1841,7 +1832,6 @@ def test_case_7(self): *self.optargs, bypass_auto_encryption=True, key_vault_client=None, - is_sync=_IS_SYNC, ), ) @@ -1861,7 +1851,6 @@ def test_case_8(self): *self.optargs, bypass_auto_encryption=True, key_vault_client=self.client_keyvault, - is_sync=_IS_SYNC, ), ) @@ -1901,7 +1890,6 @@ def setUp(self): opts = AutoEncryptionOpts( key_vault_namespace="keyvault.datakeys", kms_providers=kms_providers_map, - is_sync=_IS_SYNC, ) self.listener = AllowListEventListener("aggregate") self.encrypted_client = self.rs_or_single_client( @@ -1982,7 +1970,6 @@ def reset_timeout(): "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27027", ], - is_sync=_IS_SYNC, ) client_encrypted = self.rs_or_single_client(auto_encryption_opts=opts) with self.assertRaisesRegex(EncryptionError, "Timeout"): @@ -1997,7 +1984,6 @@ def test_bypassAutoEncryption(self): "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27027", ], - is_sync=_IS_SYNC, ) client_encrypted = self.rs_or_single_client(auto_encryption_opts=opts) client_encrypted.db.coll.insert_one({"unencrypted": "test"}) @@ -2025,7 +2011,6 @@ def test_via_loading_shared_library(self): "--port=47021", ], crypt_shared_lib_required=True, - is_sync=_IS_SYNC, ) client_encrypted = self.rs_or_single_client(auto_encryption_opts=opts) client_encrypted.db.coll.drop() @@ -2066,7 +2051,6 @@ def listener(): schema_map=schemas, mongocryptd_uri="mongodb://localhost:47021", crypt_shared_lib_required=False, - is_sync=_IS_SYNC, ) client_encrypted = self.rs_or_single_client(auto_encryption_opts=opts) client_encrypted.db.coll.drop() @@ -2376,7 +2360,6 @@ def setUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True, - is_sync=_IS_SYNC, ) self.encrypted_client = self.rs_or_single_client(auto_encryption_opts=opts) @@ -2479,7 +2462,6 @@ def setUp(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) encrypted_client.drop_database("db") @@ -2531,7 +2513,6 @@ def test_1_csfle_joins_no_schema(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = next( @@ -2560,7 +2541,6 @@ def test_2_qe_joins_no_schema(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = next( @@ -2589,7 +2569,6 @@ def test_3_no_schema_joins_csfle(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = next( @@ -2615,7 +2594,6 @@ def test_4_no_schema_joins_qe(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = next( @@ -2644,7 +2622,6 @@ def test_5_csfle_joins_csfle2(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = next( @@ -2673,7 +2650,6 @@ def test_6_qe_joins_qe2(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = next( @@ -2702,7 +2678,6 @@ def test_7_no_schema_joins_no_schema2(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) doc = next( @@ -2731,7 +2706,6 @@ def test_8_csfle_joins_qe(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) with self.assertRaises(PyMongoError) as exc: @@ -2758,7 +2732,6 @@ def test_9_error(self): auto_encryption_opts=AutoEncryptionOpts( key_vault_namespace="db.keyvault", kms_providers={"local": {"key": LOCAL_MASTER_KEY}}, - is_sync=_IS_SYNC, ) ) with self.assertRaises(PyMongoError) as exc: @@ -2952,7 +2925,6 @@ def MongoClient(**kwargs): kms_providers_map, "keyvault.datakeys", encrypted_fields_map=encrypted_fields_map, - is_sync=_IS_SYNC, ) encrypted_client = MongoClient(auto_encryption_opts=opts) @@ -3007,7 +2979,6 @@ def setUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True, - is_sync=_IS_SYNC, ) self.encrypted_client = self.rs_or_single_client(auto_encryption_opts=opts) self.db = self.encrypted_client.db From 12ef99381dce504453e1de5afc955dab94f12f44 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Mon, 21 Apr 2025 09:24:37 -0700 Subject: [PATCH 14/40] fix typing --- pymongo/encryption_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymongo/encryption_options.py b/pymongo/encryption_options.py index 74cd800429..f531f4ef0a 100644 --- a/pymongo/encryption_options.py +++ b/pymongo/encryption_options.py @@ -241,7 +241,7 @@ def __init__( self._bypass_query_analysis = bypass_query_analysis self._key_expiration_ms = key_expiration_ms - def _parse_kms_tls_options(self, is_sync): + def _parse_kms_tls_options(self, is_sync: bool) -> None: self._kms_ssl_contexts = _parse_kms_tls_options(self._kms_tls_options, is_sync) From bc76aae9db131e4fb07a28f4a2a70bd9bb0cb12b Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Mon, 21 Apr 2025 09:27:49 -0700 Subject: [PATCH 15/40] fix tests --- pymongo/encryption_options.py | 2 +- test/asynchronous/test_encryption.py | 3 ++- test/test_encryption.py | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pymongo/encryption_options.py b/pymongo/encryption_options.py index f531f4ef0a..f894e01799 100644 --- a/pymongo/encryption_options.py +++ b/pymongo/encryption_options.py @@ -237,7 +237,7 @@ def __init__( self._mongocryptd_spawn_args.append("--idleShutdownTimeoutSecs=60") # Maps KMS provider name to a SSLContext. self._kms_tls_options = kms_tls_options - # self._kms_ssl_contexts = _parse_kms_tls_options(kms_tls_options, is_sync) + self._kms_ssl_contexts = {} self._bypass_query_analysis = bypass_query_analysis self._key_expiration_ms = key_expiration_ms diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 743baaeaed..f7f49bf7bc 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -178,7 +178,8 @@ def test_init_spawn_args(self): def test_init_kms_tls_options(self): # Error cases: with self.assertRaisesRegex(TypeError, r'kms_tls_options\["kmip"\] must be a dict'): - AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}) + opts = AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}) + opts._parse_kms_tls_options(_IS_SYNC) tls_opts: Any for tls_opts in [ diff --git a/test/test_encryption.py b/test/test_encryption.py index 3d73471b76..a957ca8c8a 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -178,7 +178,8 @@ def test_init_spawn_args(self): def test_init_kms_tls_options(self): # Error cases: with self.assertRaisesRegex(TypeError, r'kms_tls_options\["kmip"\] must be a dict'): - AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}) + opts = AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}) + opts._parse_kms_tls_options(_IS_SYNC) tls_opts: Any for tls_opts in [ From a9c63c881ffbc8b56af93a1fccf8f5a872f8693b Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Mon, 21 Apr 2025 09:32:23 -0700 Subject: [PATCH 16/40] fix typing again --- pymongo/encryption_options.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pymongo/encryption_options.py b/pymongo/encryption_options.py index f894e01799..d132597226 100644 --- a/pymongo/encryption_options.py +++ b/pymongo/encryption_options.py @@ -35,6 +35,7 @@ from pymongo.uri_parser_shared import _parse_kms_tls_options if TYPE_CHECKING: + from pymongo.pyopenssl_context import SSLContext from pymongo.typings import _AgnosticMongoClient, _DocumentTypeArg @@ -237,7 +238,7 @@ def __init__( self._mongocryptd_spawn_args.append("--idleShutdownTimeoutSecs=60") # Maps KMS provider name to a SSLContext. self._kms_tls_options = kms_tls_options - self._kms_ssl_contexts = {} + self._kms_ssl_contexts: dict[str, SSLContext] = {} self._bypass_query_analysis = bypass_query_analysis self._key_expiration_ms = key_expiration_ms From 67c6738319e02b72f22ef466979d423f8a242b25 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Mon, 21 Apr 2025 09:48:16 -0700 Subject: [PATCH 17/40] fix tests pt 2? --- test/asynchronous/test_encryption.py | 4 +++- test/test_encryption.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index f7f49bf7bc..e3bdeaf9bf 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -189,12 +189,14 @@ def test_init_kms_tls_options(self): ]: with self.assertRaisesRegex(ConfigurationError, "Insecure TLS options prohibited"): opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) + opts._parse_kms_tls_options(_IS_SYNC) with self.assertRaises(FileNotFoundError): - AutoEncryptionOpts( + opts = AutoEncryptionOpts( {}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}}, ) + opts._parse_kms_tls_options(_IS_SYNC) # Success cases: tls_opts: Any for tls_opts in [None, {}]: diff --git a/test/test_encryption.py b/test/test_encryption.py index a957ca8c8a..000eddfcd1 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -189,12 +189,14 @@ def test_init_kms_tls_options(self): ]: with self.assertRaisesRegex(ConfigurationError, "Insecure TLS options prohibited"): opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) + opts._parse_kms_tls_options(_IS_SYNC) with self.assertRaises(FileNotFoundError): - AutoEncryptionOpts( + opts = AutoEncryptionOpts( {}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}}, ) + opts._parse_kms_tls_options(_IS_SYNC) # Success cases: tls_opts: Any for tls_opts in [None, {}]: From 3ea4de7c44a49237842fef103177a16a690dcfd9 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Mon, 21 Apr 2025 09:57:12 -0700 Subject: [PATCH 18/40] fix test pt3 --- test/asynchronous/test_encryption.py | 2 ++ test/test_encryption.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index e3bdeaf9bf..85d32faf46 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -207,6 +207,7 @@ def test_init_kms_tls_options(self): "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}, ) + opts._parse_kms_tls_options(_IS_SYNC) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) @@ -218,6 +219,7 @@ def test_init_kms_tls_options(self): "k.d", kms_tls_options={"kmip": {"tlsCAFile": CA_PEM, "tlsCertificateKeyFile": CLIENT_PEM}}, ) + opts._parse_kms_tls_options(_IS_SYNC) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) diff --git a/test/test_encryption.py b/test/test_encryption.py index 000eddfcd1..81c6185e23 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -207,6 +207,7 @@ def test_init_kms_tls_options(self): "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}, ) + opts._parse_kms_tls_options(_IS_SYNC) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) @@ -218,6 +219,7 @@ def test_init_kms_tls_options(self): "k.d", kms_tls_options={"kmip": {"tlsCAFile": CA_PEM, "tlsCertificateKeyFile": CLIENT_PEM}}, ) + opts._parse_kms_tls_options(_IS_SYNC) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) From c57aed2eb5c2b80e2fe5b2755d41f281f5d122d4 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Mon, 21 Apr 2025 12:30:53 -0700 Subject: [PATCH 19/40] add test_name --- .evergreen/generated_configs/variants.yml | 6 ++++++ .evergreen/scripts/generate_config.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 6568dae2e7..8ba16273de 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -620,6 +620,7 @@ buildvariants: - macos-14 batchtime: 10080 expansions: + TEST_NAME: default SUB_TEST_NAME: pyopenssl PYTHON_BINARY: /Library/Frameworks/Python.Framework/Versions/3.9/bin/python3 - name: pyopenssl-rhel8-python3.10 @@ -631,6 +632,7 @@ buildvariants: - rhel87-small batchtime: 10080 expansions: + TEST_NAME: default SUB_TEST_NAME: pyopenssl PYTHON_BINARY: /opt/python/3.10/bin/python3 - name: pyopenssl-rhel8-python3.11 @@ -642,6 +644,7 @@ buildvariants: - rhel87-small batchtime: 10080 expansions: + TEST_NAME: default SUB_TEST_NAME: pyopenssl PYTHON_BINARY: /opt/python/3.11/bin/python3 - name: pyopenssl-rhel8-python3.12 @@ -653,6 +656,7 @@ buildvariants: - rhel87-small batchtime: 10080 expansions: + TEST_NAME: default SUB_TEST_NAME: pyopenssl PYTHON_BINARY: /opt/python/3.12/bin/python3 - name: pyopenssl-win64-python3.13 @@ -664,6 +668,7 @@ buildvariants: - windows-64-vsMulti-small batchtime: 10080 expansions: + TEST_NAME: default SUB_TEST_NAME: pyopenssl PYTHON_BINARY: C:/python/Python313/python.exe - name: pyopenssl-rhel8-pypy3.10 @@ -675,6 +680,7 @@ buildvariants: - rhel87-small batchtime: 10080 expansions: + TEST_NAME: default SUB_TEST_NAME: pyopenssl PYTHON_BINARY: /opt/python/pypy3.10/bin/python3 diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 5101eaee20..be1a960db2 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -250,7 +250,7 @@ def create_enterprise_auth_variants(): def create_pyopenssl_variants(): base_name = "PyOpenSSL" batchtime = BATCHTIME_WEEK - expansions = dict(SUB_TEST_NAME="pyopenssl") + expansions = dict(TEST_NAME="default", SUB_TEST_NAME="pyopenssl") variants = [] for python in ALL_PYTHONS: From 2591169d4fe5b2599457b88e4448c6a567b8dbfc Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 22 Apr 2025 11:26:27 -0700 Subject: [PATCH 20/40] address review --- pymongo/asynchronous/encryption.py | 4 ++-- pymongo/network_layer.py | 21 ++++++--------------- pymongo/ssl_support.py | 30 ++++++++++++------------------ pymongo/synchronous/encryption.py | 4 ++-- 4 files changed, 22 insertions(+), 37 deletions(-) diff --git a/pymongo/asynchronous/encryption.py b/pymongo/asynchronous/encryption.py index 061e4ef3f0..0aa175e8b1 100644 --- a/pymongo/asynchronous/encryption.py +++ b/pymongo/asynchronous/encryption.py @@ -85,7 +85,7 @@ ) from pymongo.read_concern import ReadConcern from pymongo.results import BulkWriteResult, DeleteResult -from pymongo.ssl_support import BLOCKING_IO_ERRORS, PYBLOCKING_IO_ERRORS, get_ssl_context +from pymongo.ssl_support import BLOCKING_IO_ERRORS, get_ssl_context from pymongo.typings import _DocumentType, _DocumentTypeArg from pymongo.uri_parser_shared import parse_host from pymongo.write_concern import WriteConcern @@ -217,7 +217,7 @@ async def kms_request(self, kms_context: MongoCryptKmsContext) -> None: raise # Propagate MongoCryptError errors directly. except Exception as exc: # Wrap I/O errors in PyMongo exceptions. - if isinstance(exc, (BLOCKING_IO_ERRORS, PYBLOCKING_IO_ERRORS)): + if isinstance(exc, BLOCKING_IO_ERRORS): exc = socket.timeout("timed out") # Async raises an OSError instead of returning empty bytes. if isinstance(exc, OSError): diff --git a/pymongo/network_layer.py b/pymongo/network_layer.py index 47489a191e..70318b7803 100644 --- a/pymongo/network_layer.py +++ b/pymongo/network_layer.py @@ -57,9 +57,6 @@ BLOCKING_IO_LOOKUP_ERROR, BLOCKING_IO_READ_ERROR, BLOCKING_IO_WRITE_ERROR, - PYBLOCKING_IO_LOOKUP_ERROR, - PYBLOCKING_IO_READ_ERROR, - PYBLOCKING_IO_WRITE_ERROR, ) if TYPE_CHECKING: @@ -73,9 +70,7 @@ BLOCKING_IO_ERRORS = ( BlockingIOError, BLOCKING_IO_LOOKUP_ERROR, - PYBLOCKING_IO_LOOKUP_ERROR, *ssl_support.BLOCKING_IO_ERRORS, - *ssl_support.PYBLOCKING_IO_ERRORS, ) @@ -118,23 +113,21 @@ def _is_ready(fut: Future) -> None: # Check for closed socket. if fd == -1: raise SSLError("Underlying socket has been closed") from None - if isinstance(exc, (BLOCKING_IO_READ_ERROR, PYBLOCKING_IO_READ_ERROR)): + if isinstance(exc, BLOCKING_IO_READ_ERROR): fut = loop.create_future() loop.add_reader(fd, _is_ready, fut) try: await fut finally: loop.remove_reader(fd) - if isinstance(exc, (BLOCKING_IO_WRITE_ERROR, PYBLOCKING_IO_WRITE_ERROR)): + if isinstance(exc, BLOCKING_IO_WRITE_ERROR): fut = loop.create_future() loop.add_writer(fd, _is_ready, fut) try: await fut finally: loop.remove_writer(fd) - if _HAVE_PYOPENSSL and isinstance( - exc, (BLOCKING_IO_LOOKUP_ERROR, PYBLOCKING_IO_LOOKUP_ERROR) - ): + if _HAVE_PYOPENSSL and isinstance(exc, BLOCKING_IO_LOOKUP_ERROR): fut = loop.create_future() loop.add_reader(fd, _is_ready, fut) try: @@ -169,23 +162,21 @@ def _is_ready(fut: Future) -> None: # Check for closed socket. if fd == -1: raise SSLError("Underlying socket has been closed") from None - if isinstance(exc, (BLOCKING_IO_READ_ERROR, PYBLOCKING_IO_READ_ERROR)): + if isinstance(exc, BLOCKING_IO_READ_ERROR): fut = loop.create_future() loop.add_reader(fd, _is_ready, fut) try: await fut finally: loop.remove_reader(fd) - if isinstance(exc, (BLOCKING_IO_WRITE_ERROR, PYBLOCKING_IO_WRITE_ERROR)): + if isinstance(exc, BLOCKING_IO_WRITE_ERROR): fut = loop.create_future() loop.add_writer(fd, _is_ready, fut) try: await fut finally: loop.remove_writer(fd) - if _HAVE_PYOPENSSL and isinstance( - exc, (BLOCKING_IO_LOOKUP_ERROR, PYBLOCKING_IO_LOOKUP_ERROR) - ): + if _HAVE_PYOPENSSL and isinstance(exc, BLOCKING_IO_LOOKUP_ERROR): fut = loop.create_future() loop.add_reader(fd, _is_ready, fut) try: diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index f4bfdf030f..71e92237fd 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -17,7 +17,7 @@ import types import warnings -from typing import Any, Optional, Union +from typing import Optional, Union from pymongo.errors import ConfigurationError @@ -52,27 +52,21 @@ import ssl as _stdlibssl # noqa: F401 from ssl import CERT_NONE, CERT_REQUIRED - HAS_SNI = _ssl.HAS_SNI IPADDR_SAFE = True - SSLError = _ssl.SSLError - BLOCKING_IO_ERRORS = _ssl.BLOCKING_IO_ERRORS - BLOCKING_IO_READ_ERROR = _ssl.BLOCKING_IO_READ_ERROR - BLOCKING_IO_WRITE_ERROR = _ssl.BLOCKING_IO_WRITE_ERROR - BLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR if HAVE_PYSSL: - PYSSLError: Any = _pyssl.SSLError - PYBLOCKING_IO_ERRORS: Any = _pyssl.BLOCKING_IO_ERRORS - PYBLOCKING_IO_READ_ERROR: Any = _pyssl.BLOCKING_IO_READ_ERROR - PYBLOCKING_IO_WRITE_ERROR: Any = _pyssl.BLOCKING_IO_WRITE_ERROR - PYBLOCKING_IO_LOOKUP_ERROR: Any = BLOCKING_IO_READ_ERROR + HAS_SNI = _pyssl.HAS_SNI | _ssl.HAS_SNI + SSLError = _pyssl.SSLError | _ssl.SSLError + BLOCKING_IO_ERRORS = _pyssl.BLOCKING_IO_ERRORS | _ssl.BLOCKING_IO_ERRORS + BLOCKING_IO_READ_ERROR = _pyssl.BLOCKING_IO_READ_ERROR | _ssl.BLOCKING_IO_READ_ERROR + BLOCKING_IO_WRITE_ERROR = _pyssl.BLOCKING_IO_WRITE_ERROR | _ssl.BLOCKING_IO_WRITE_ERROR else: - # just make them the same as SSL so imports won't error - PYSSLError = _ssl.SSLError - PYBLOCKING_IO_ERRORS = _ssl.BLOCKING_IO_ERRORS - PYBLOCKING_IO_READ_ERROR = _ssl.BLOCKING_IO_READ_ERROR - PYBLOCKING_IO_WRITE_ERROR = _ssl.BLOCKING_IO_WRITE_ERROR - PYBLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR + HAS_SNI = _ssl.HAS_SNI + SSLError = _ssl.SSLError + BLOCKING_IO_ERRORS = _ssl.BLOCKING_IO_ERRORS + BLOCKING_IO_READ_ERROR = _ssl.BLOCKING_IO_READ_ERROR + BLOCKING_IO_WRITE_ERROR = _ssl.BLOCKING_IO_WRITE_ERROR + BLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR def get_ssl_context( certfile: Optional[str], diff --git a/pymongo/synchronous/encryption.py b/pymongo/synchronous/encryption.py index 53006955a6..0aa5e38263 100644 --- a/pymongo/synchronous/encryption.py +++ b/pymongo/synchronous/encryption.py @@ -80,7 +80,7 @@ ) from pymongo.read_concern import ReadConcern from pymongo.results import BulkWriteResult, DeleteResult -from pymongo.ssl_support import BLOCKING_IO_ERRORS, PYBLOCKING_IO_ERRORS, get_ssl_context +from pymongo.ssl_support import BLOCKING_IO_ERRORS, get_ssl_context from pymongo.synchronous.collection import Collection from pymongo.synchronous.cursor import Cursor from pymongo.synchronous.database import Database @@ -216,7 +216,7 @@ def kms_request(self, kms_context: MongoCryptKmsContext) -> None: raise # Propagate MongoCryptError errors directly. except Exception as exc: # Wrap I/O errors in PyMongo exceptions. - if isinstance(exc, (BLOCKING_IO_ERRORS, PYBLOCKING_IO_ERRORS)): + if isinstance(exc, BLOCKING_IO_ERRORS): exc = socket.timeout("timed out") # Async raises an OSError instead of returning empty bytes. if isinstance(exc, OSError): From 06a710d5e6399c5d5bdc2e3185ae597a2810f715 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 22 Apr 2025 11:27:49 -0700 Subject: [PATCH 21/40] update changelog --- doc/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.rst b/doc/changelog.rst index 2fb225e2e1..68bb044974 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -12,6 +12,7 @@ Version 4.12.1 is a bug fix release. - Fixed a bug that caused direct use of ``pymongo.uri_parser`` to raise an ``AttributeError``. - Removed Eventlet testing against Python versions newer than 3.9 since Eventlet is actively being sunset by its maintainers and has compatibility issues with PyMongo's dnspython dependency. +- Fixed a bug that would try to use PyOpenSSL with AsyncMongoClient, causing the client to fail. Issues Resolved From ef4111e70bc3e982a25676c8ccab899d3de29eae Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 22 Apr 2025 11:29:39 -0700 Subject: [PATCH 22/40] undo whitespace changes --- test/asynchronous/test_encryption.py | 4 +--- test/test_encryption.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 85d32faf46..397bfedfa2 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -119,9 +119,7 @@ async def test_crypt_shared(self): # Test that we can pick up crypt_shared lib automatically self.simple_client( auto_encryption_opts=AutoEncryptionOpts( - KMS_PROVIDERS, - "keyvault.datakeys", - crypt_shared_lib_required=True, + KMS_PROVIDERS, "keyvault.datakeys", crypt_shared_lib_required=True ), connect=False, ) diff --git a/test/test_encryption.py b/test/test_encryption.py index 81c6185e23..1b39bbe34c 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -119,9 +119,7 @@ def test_crypt_shared(self): # Test that we can pick up crypt_shared lib automatically self.simple_client( auto_encryption_opts=AutoEncryptionOpts( - KMS_PROVIDERS, - "keyvault.datakeys", - crypt_shared_lib_required=True, + KMS_PROVIDERS, "keyvault.datakeys", crypt_shared_lib_required=True ), connect=False, ) From 0b3c6bb905e3a0ceb0604a407ab6d3d9fe59dd2d Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 22 Apr 2025 11:48:29 -0700 Subject: [PATCH 23/40] fix failures --- pymongo/ssl_support.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index 71e92237fd..c2a207c88f 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -17,7 +17,7 @@ import types import warnings -from typing import Optional, Union +from typing import Any, Optional, Union from pymongo.errors import ConfigurationError @@ -56,16 +56,17 @@ if HAVE_PYSSL: HAS_SNI = _pyssl.HAS_SNI | _ssl.HAS_SNI - SSLError = _pyssl.SSLError | _ssl.SSLError - BLOCKING_IO_ERRORS = _pyssl.BLOCKING_IO_ERRORS | _ssl.BLOCKING_IO_ERRORS - BLOCKING_IO_READ_ERROR = _pyssl.BLOCKING_IO_READ_ERROR | _ssl.BLOCKING_IO_READ_ERROR - BLOCKING_IO_WRITE_ERROR = _pyssl.BLOCKING_IO_WRITE_ERROR | _ssl.BLOCKING_IO_WRITE_ERROR + PYSSLError = _pyssl.SSLError + BLOCKING_IO_ERRORS: Any = _pyssl.BLOCKING_IO_ERRORS + _ssl.BLOCKING_IO_ERRORS + BLOCKING_IO_READ_ERROR: Any = _pyssl.BLOCKING_IO_READ_ERROR | _ssl.BLOCKING_IO_READ_ERROR + BLOCKING_IO_WRITE_ERROR: Any = _pyssl.BLOCKING_IO_WRITE_ERROR | _ssl.BLOCKING_IO_WRITE_ERROR else: HAS_SNI = _ssl.HAS_SNI - SSLError = _ssl.SSLError + PYSSLError = _ssl.SSLError BLOCKING_IO_ERRORS = _ssl.BLOCKING_IO_ERRORS BLOCKING_IO_READ_ERROR = _ssl.BLOCKING_IO_READ_ERROR BLOCKING_IO_WRITE_ERROR = _ssl.BLOCKING_IO_WRITE_ERROR + SSLError = _ssl.SSLError BLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR def get_ssl_context( @@ -125,7 +126,7 @@ class SSLError(Exception): # type: ignore HAS_SNI = False IPADDR_SAFE = False - BLOCKING_IO_ERRORS = () # type:ignore[assignment] + BLOCKING_IO_ERRORS = () def get_ssl_context(*dummy): # type: ignore """No ssl module, raise ConfigurationError.""" From 9336f582b64683c7f82d7e1f7cd0ed60f76d9e76 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 22 Apr 2025 12:01:40 -0700 Subject: [PATCH 24/40] fix typing --- pymongo/ssl_support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index c2a207c88f..e7717aa772 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -56,7 +56,7 @@ if HAVE_PYSSL: HAS_SNI = _pyssl.HAS_SNI | _ssl.HAS_SNI - PYSSLError = _pyssl.SSLError + PYSSLError: Any = _pyssl.SSLError BLOCKING_IO_ERRORS: Any = _pyssl.BLOCKING_IO_ERRORS + _ssl.BLOCKING_IO_ERRORS BLOCKING_IO_READ_ERROR: Any = _pyssl.BLOCKING_IO_READ_ERROR | _ssl.BLOCKING_IO_READ_ERROR BLOCKING_IO_WRITE_ERROR: Any = _pyssl.BLOCKING_IO_WRITE_ERROR | _ssl.BLOCKING_IO_WRITE_ERROR From 23b7cbe0bd7fc5dc5281ec19ee78eebd8e93762a Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 22 Apr 2025 12:07:19 -0700 Subject: [PATCH 25/40] fix typing? --- pymongo/ssl_support.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index e7717aa772..bd3ca0f393 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -58,14 +58,17 @@ HAS_SNI = _pyssl.HAS_SNI | _ssl.HAS_SNI PYSSLError: Any = _pyssl.SSLError BLOCKING_IO_ERRORS: Any = _pyssl.BLOCKING_IO_ERRORS + _ssl.BLOCKING_IO_ERRORS - BLOCKING_IO_READ_ERROR: Any = _pyssl.BLOCKING_IO_READ_ERROR | _ssl.BLOCKING_IO_READ_ERROR - BLOCKING_IO_WRITE_ERROR: Any = _pyssl.BLOCKING_IO_WRITE_ERROR | _ssl.BLOCKING_IO_WRITE_ERROR + BLOCKING_IO_READ_ERROR: Any = (_pyssl.BLOCKING_IO_READ_ERROR, _ssl.BLOCKING_IO_READ_ERROR) + BLOCKING_IO_WRITE_ERROR: Any = ( + _pyssl.BLOCKING_IO_WRITE_ERROR, + _ssl.BLOCKING_IO_WRITE_ERROR, + ) else: HAS_SNI = _ssl.HAS_SNI PYSSLError = _ssl.SSLError BLOCKING_IO_ERRORS = _ssl.BLOCKING_IO_ERRORS - BLOCKING_IO_READ_ERROR = _ssl.BLOCKING_IO_READ_ERROR - BLOCKING_IO_WRITE_ERROR = _ssl.BLOCKING_IO_WRITE_ERROR + BLOCKING_IO_READ_ERROR = (_ssl.BLOCKING_IO_READ_ERROR,) + BLOCKING_IO_WRITE_ERROR = (_ssl.BLOCKING_IO_WRITE_ERROR,) SSLError = _ssl.SSLError BLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR From 760fa9788e24c8c68e935d3611c9a48e44dcb685 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 22 Apr 2025 12:20:29 -0700 Subject: [PATCH 26/40] fix error? --- pymongo/network_layer.py | 2 +- pymongo/ssl_support.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pymongo/network_layer.py b/pymongo/network_layer.py index 70318b7803..3145442076 100644 --- a/pymongo/network_layer.py +++ b/pymongo/network_layer.py @@ -69,7 +69,7 @@ # Errors raised by sockets (and TLS sockets) when in non-blocking mode. BLOCKING_IO_ERRORS = ( BlockingIOError, - BLOCKING_IO_LOOKUP_ERROR, + *BLOCKING_IO_LOOKUP_ERROR, *ssl_support.BLOCKING_IO_ERRORS, ) diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index bd3ca0f393..888e09a46a 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -57,9 +57,9 @@ if HAVE_PYSSL: HAS_SNI = _pyssl.HAS_SNI | _ssl.HAS_SNI PYSSLError: Any = _pyssl.SSLError - BLOCKING_IO_ERRORS: Any = _pyssl.BLOCKING_IO_ERRORS + _ssl.BLOCKING_IO_ERRORS - BLOCKING_IO_READ_ERROR: Any = (_pyssl.BLOCKING_IO_READ_ERROR, _ssl.BLOCKING_IO_READ_ERROR) - BLOCKING_IO_WRITE_ERROR: Any = ( + BLOCKING_IO_ERRORS: tuple = _pyssl.BLOCKING_IO_ERRORS + _ssl.BLOCKING_IO_ERRORS + BLOCKING_IO_READ_ERROR: tuple = (_pyssl.BLOCKING_IO_READ_ERROR, _ssl.BLOCKING_IO_READ_ERROR) + BLOCKING_IO_WRITE_ERROR: tuple = ( _pyssl.BLOCKING_IO_WRITE_ERROR, _ssl.BLOCKING_IO_WRITE_ERROR, ) From 4b8a4ed3bc04dec3d7e928945d826abfb2e2c7dc Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 22 Apr 2025 15:01:30 -0700 Subject: [PATCH 27/40] undo whitespace changes --- test/asynchronous/test_encryption.py | 91 +++++++--------------------- test/test_encryption.py | 91 +++++++--------------------- 2 files changed, 42 insertions(+), 140 deletions(-) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 397bfedfa2..0517068fb6 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -147,25 +147,17 @@ def test_init(self): def test_init_spawn_args(self): # User can override idleShutdownTimeoutSecs opts = AutoEncryptionOpts( - {}, - "keyvault.datakeys", - mongocryptd_spawn_args=["--idleShutdownTimeoutSecs=88"], + {}, "keyvault.datakeys", mongocryptd_spawn_args=["--idleShutdownTimeoutSecs=88"] ) self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=88"]) # idleShutdownTimeoutSecs is added by default - opts = AutoEncryptionOpts( - {}, - "keyvault.datakeys", - mongocryptd_spawn_args=[], - ) + opts = AutoEncryptionOpts({}, "keyvault.datakeys", mongocryptd_spawn_args=[]) self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=60"]) # Also added when other options are given opts = AutoEncryptionOpts( - {}, - "keyvault.datakeys", - mongocryptd_spawn_args=["--quiet", "--port=27020"], + {}, "keyvault.datakeys", mongocryptd_spawn_args=["--quiet", "--port=27020"] ) self.assertEqual( opts._mongocryptd_spawn_args, @@ -190,9 +182,7 @@ def test_init_kms_tls_options(self): opts._parse_kms_tls_options(_IS_SYNC) with self.assertRaises(FileNotFoundError): opts = AutoEncryptionOpts( - {}, - "k.d", - kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}}, + {}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}} ) opts._parse_kms_tls_options(_IS_SYNC) # Success cases: @@ -200,11 +190,7 @@ def test_init_kms_tls_options(self): for tls_opts in [None, {}]: opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) self.assertEqual(opts._kms_ssl_contexts, {}) - opts = AutoEncryptionOpts( - {}, - "k.d", - kms_tls_options={"kmip": {"tls": True}, "aws": {}}, - ) + opts = AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}) opts._parse_kms_tls_options(_IS_SYNC) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) @@ -382,20 +368,13 @@ async def test_auto_encrypt(self): await create_with_schema(self.db.test, json_schema) self.addAsyncCleanup(self.db.test.drop) - opts = AutoEncryptionOpts( - KMS_PROVIDERS, - "keyvault.datakeys", - ) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") await self._test_auto_encrypt(opts) async def test_auto_encrypt_local_schema_map(self): # Configure the encrypted field via the local schema_map option. schemas = {"pymongo_test.test": json_data("custom", "schema.json")} - opts = AutoEncryptionOpts( - KMS_PROVIDERS, - "keyvault.datakeys", - schema_map=schemas, - ) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", schema_map=schemas) await self._test_auto_encrypt(opts) @@ -1190,9 +1169,7 @@ async def _test_corpus(self, opts): async def test_corpus(self): opts = AutoEncryptionOpts( - self.kms_providers(), - "keyvault.datakeys", - kms_tls_options=KMS_TLS_OPTS, + self.kms_providers(), "keyvault.datakeys", kms_tls_options=KMS_TLS_OPTS ) await self._test_corpus(opts) @@ -1241,10 +1218,7 @@ async def asyncSetUp(self): await coll.drop() await coll.insert_one(json_data("limits", "limits-key.json")) - opts = AutoEncryptionOpts( - {"local": {"key": LOCAL_MASTER_KEY}}, - "keyvault.datakeys", - ) + opts = AutoEncryptionOpts({"local": {"key": LOCAL_MASTER_KEY}}, "keyvault.datakeys") self.listener = OvertCommandListener() self.client_encrypted = await self.async_rs_or_single_client( auto_encryption_opts=opts, event_listeners=[self.listener] @@ -1699,9 +1673,7 @@ async def test_case_1(self): await self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=False, - key_vault_client=None, + *self.optargs, bypass_auto_encryption=False, key_vault_client=None ), ) @@ -1722,9 +1694,7 @@ async def test_case_2(self): await self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=False, - key_vault_client=self.client_keyvault, + *self.optargs, bypass_auto_encryption=False, key_vault_client=self.client_keyvault ), ) @@ -1748,9 +1718,7 @@ async def test_case_3(self): await self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=True, - key_vault_client=None, + *self.optargs, bypass_auto_encryption=True, key_vault_client=None ), ) @@ -1767,9 +1735,7 @@ async def test_case_4(self): await self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=True, - key_vault_client=self.client_keyvault, + *self.optargs, bypass_auto_encryption=True, key_vault_client=self.client_keyvault ), ) @@ -1789,9 +1755,7 @@ async def test_case_5(self): await self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=False, - key_vault_client=None, + *self.optargs, bypass_auto_encryption=False, key_vault_client=None ), ) @@ -1814,9 +1778,7 @@ async def test_case_6(self): await self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=False, - key_vault_client=self.client_keyvault, + *self.optargs, bypass_auto_encryption=False, key_vault_client=self.client_keyvault ), ) @@ -1840,9 +1802,7 @@ async def test_case_7(self): await self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=True, - key_vault_client=None, + *self.optargs, bypass_auto_encryption=True, key_vault_client=None ), ) @@ -1859,9 +1819,7 @@ async def test_case_8(self): await self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=True, - key_vault_client=self.client_keyvault, + *self.optargs, bypass_auto_encryption=True, key_vault_client=self.client_keyvault ), ) @@ -1899,8 +1857,7 @@ async def asyncSetUp(self): ) self.malformed_cipher_text = Binary(self.malformed_cipher_text, 6) opts = AutoEncryptionOpts( - key_vault_namespace="keyvault.datakeys", - kms_providers=kms_providers_map, + key_vault_namespace="keyvault.datakeys", kms_providers=kms_providers_map ) self.listener = AllowListEventListener("aggregate") self.encrypted_client = await self.async_rs_or_single_client( @@ -2372,9 +2329,7 @@ async def asyncSetUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, self.key_vault_client, OPTS ) opts = AutoEncryptionOpts( - {"local": {"key": LOCAL_MASTER_KEY}}, - key_vault.full_name, - bypass_query_analysis=True, + {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True ) self.encrypted_client = await self.async_rs_or_single_client(auto_encryption_opts=opts) @@ -2941,9 +2896,7 @@ async def AsyncMongoClient(**kwargs): # Create an Queryable Encryption collection. opts = AutoEncryptionOpts( - kms_providers_map, - "keyvault.datakeys", - encrypted_fields_map=encrypted_fields_map, + kms_providers_map, "keyvault.datakeys", encrypted_fields_map=encrypted_fields_map ) encrypted_client = await AsyncMongoClient(auto_encryption_opts=opts) @@ -2995,9 +2948,7 @@ async def asyncSetUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, self.key_vault_client, OPTS ) opts = AutoEncryptionOpts( - {"local": {"key": LOCAL_MASTER_KEY}}, - key_vault.full_name, - bypass_query_analysis=True, + {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True ) self.encrypted_client = await self.async_rs_or_single_client(auto_encryption_opts=opts) self.db = self.encrypted_client.db diff --git a/test/test_encryption.py b/test/test_encryption.py index 1b39bbe34c..57e21c96f2 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -147,25 +147,17 @@ def test_init(self): def test_init_spawn_args(self): # User can override idleShutdownTimeoutSecs opts = AutoEncryptionOpts( - {}, - "keyvault.datakeys", - mongocryptd_spawn_args=["--idleShutdownTimeoutSecs=88"], + {}, "keyvault.datakeys", mongocryptd_spawn_args=["--idleShutdownTimeoutSecs=88"] ) self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=88"]) # idleShutdownTimeoutSecs is added by default - opts = AutoEncryptionOpts( - {}, - "keyvault.datakeys", - mongocryptd_spawn_args=[], - ) + opts = AutoEncryptionOpts({}, "keyvault.datakeys", mongocryptd_spawn_args=[]) self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=60"]) # Also added when other options are given opts = AutoEncryptionOpts( - {}, - "keyvault.datakeys", - mongocryptd_spawn_args=["--quiet", "--port=27020"], + {}, "keyvault.datakeys", mongocryptd_spawn_args=["--quiet", "--port=27020"] ) self.assertEqual( opts._mongocryptd_spawn_args, @@ -190,9 +182,7 @@ def test_init_kms_tls_options(self): opts._parse_kms_tls_options(_IS_SYNC) with self.assertRaises(FileNotFoundError): opts = AutoEncryptionOpts( - {}, - "k.d", - kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}}, + {}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}} ) opts._parse_kms_tls_options(_IS_SYNC) # Success cases: @@ -200,11 +190,7 @@ def test_init_kms_tls_options(self): for tls_opts in [None, {}]: opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) self.assertEqual(opts._kms_ssl_contexts, {}) - opts = AutoEncryptionOpts( - {}, - "k.d", - kms_tls_options={"kmip": {"tls": True}, "aws": {}}, - ) + opts = AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}) opts._parse_kms_tls_options(_IS_SYNC) ctx = opts._kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) @@ -382,20 +368,13 @@ def test_auto_encrypt(self): create_with_schema(self.db.test, json_schema) self.addCleanup(self.db.test.drop) - opts = AutoEncryptionOpts( - KMS_PROVIDERS, - "keyvault.datakeys", - ) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys") self._test_auto_encrypt(opts) def test_auto_encrypt_local_schema_map(self): # Configure the encrypted field via the local schema_map option. schemas = {"pymongo_test.test": json_data("custom", "schema.json")} - opts = AutoEncryptionOpts( - KMS_PROVIDERS, - "keyvault.datakeys", - schema_map=schemas, - ) + opts = AutoEncryptionOpts(KMS_PROVIDERS, "keyvault.datakeys", schema_map=schemas) self._test_auto_encrypt(opts) @@ -1186,9 +1165,7 @@ def _test_corpus(self, opts): def test_corpus(self): opts = AutoEncryptionOpts( - self.kms_providers(), - "keyvault.datakeys", - kms_tls_options=KMS_TLS_OPTS, + self.kms_providers(), "keyvault.datakeys", kms_tls_options=KMS_TLS_OPTS ) self._test_corpus(opts) @@ -1237,10 +1214,7 @@ def setUp(self): coll.drop() coll.insert_one(json_data("limits", "limits-key.json")) - opts = AutoEncryptionOpts( - {"local": {"key": LOCAL_MASTER_KEY}}, - "keyvault.datakeys", - ) + opts = AutoEncryptionOpts({"local": {"key": LOCAL_MASTER_KEY}}, "keyvault.datakeys") self.listener = OvertCommandListener() self.client_encrypted = self.rs_or_single_client( auto_encryption_opts=opts, event_listeners=[self.listener] @@ -1691,9 +1665,7 @@ def test_case_1(self): self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=False, - key_vault_client=None, + *self.optargs, bypass_auto_encryption=False, key_vault_client=None ), ) @@ -1714,9 +1686,7 @@ def test_case_2(self): self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=False, - key_vault_client=self.client_keyvault, + *self.optargs, bypass_auto_encryption=False, key_vault_client=self.client_keyvault ), ) @@ -1740,9 +1710,7 @@ def test_case_3(self): self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=True, - key_vault_client=None, + *self.optargs, bypass_auto_encryption=True, key_vault_client=None ), ) @@ -1759,9 +1727,7 @@ def test_case_4(self): self._run_test( max_pool_size=1, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=True, - key_vault_client=self.client_keyvault, + *self.optargs, bypass_auto_encryption=True, key_vault_client=self.client_keyvault ), ) @@ -1781,9 +1747,7 @@ def test_case_5(self): self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=False, - key_vault_client=None, + *self.optargs, bypass_auto_encryption=False, key_vault_client=None ), ) @@ -1806,9 +1770,7 @@ def test_case_6(self): self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=False, - key_vault_client=self.client_keyvault, + *self.optargs, bypass_auto_encryption=False, key_vault_client=self.client_keyvault ), ) @@ -1832,9 +1794,7 @@ def test_case_7(self): self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=True, - key_vault_client=None, + *self.optargs, bypass_auto_encryption=True, key_vault_client=None ), ) @@ -1851,9 +1811,7 @@ def test_case_8(self): self._run_test( max_pool_size=None, auto_encryption_opts=AutoEncryptionOpts( - *self.optargs, - bypass_auto_encryption=True, - key_vault_client=self.client_keyvault, + *self.optargs, bypass_auto_encryption=True, key_vault_client=self.client_keyvault ), ) @@ -1891,8 +1849,7 @@ def setUp(self): ) self.malformed_cipher_text = Binary(self.malformed_cipher_text, 6) opts = AutoEncryptionOpts( - key_vault_namespace="keyvault.datakeys", - kms_providers=kms_providers_map, + key_vault_namespace="keyvault.datakeys", kms_providers=kms_providers_map ) self.listener = AllowListEventListener("aggregate") self.encrypted_client = self.rs_or_single_client( @@ -2360,9 +2317,7 @@ def setUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, self.key_vault_client, OPTS ) opts = AutoEncryptionOpts( - {"local": {"key": LOCAL_MASTER_KEY}}, - key_vault.full_name, - bypass_query_analysis=True, + {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True ) self.encrypted_client = self.rs_or_single_client(auto_encryption_opts=opts) @@ -2925,9 +2880,7 @@ def MongoClient(**kwargs): # Create an Queryable Encryption collection. opts = AutoEncryptionOpts( - kms_providers_map, - "keyvault.datakeys", - encrypted_fields_map=encrypted_fields_map, + kms_providers_map, "keyvault.datakeys", encrypted_fields_map=encrypted_fields_map ) encrypted_client = MongoClient(auto_encryption_opts=opts) @@ -2979,9 +2932,7 @@ def setUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, self.key_vault_client, OPTS ) opts = AutoEncryptionOpts( - {"local": {"key": LOCAL_MASTER_KEY}}, - key_vault.full_name, - bypass_query_analysis=True, + {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True ) self.encrypted_client = self.rs_or_single_client(auto_encryption_opts=opts) self.db = self.encrypted_client.db From 5807ba1430edc0d69817f1fdfc508219c9148872 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 22 Apr 2025 15:04:17 -0700 Subject: [PATCH 28/40] more whitespace changes --- test/asynchronous/test_encryption.py | 8 ++++++-- test/test_encryption.py | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 0517068fb6..4ad588fa7b 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -2329,7 +2329,9 @@ async def asyncSetUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, self.key_vault_client, OPTS ) opts = AutoEncryptionOpts( - {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True + {"local": {"key": LOCAL_MASTER_KEY}}, + key_vault.full_name, + bypass_query_analysis=True, ) self.encrypted_client = await self.async_rs_or_single_client(auto_encryption_opts=opts) @@ -2948,7 +2950,9 @@ async def asyncSetUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, self.key_vault_client, OPTS ) opts = AutoEncryptionOpts( - {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True + {"local": {"key": LOCAL_MASTER_KEY}}, + key_vault.full_name, + bypass_query_analysis=True, ) self.encrypted_client = await self.async_rs_or_single_client(auto_encryption_opts=opts) self.db = self.encrypted_client.db diff --git a/test/test_encryption.py b/test/test_encryption.py index 57e21c96f2..a69a44b9ca 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -2317,7 +2317,9 @@ def setUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, self.key_vault_client, OPTS ) opts = AutoEncryptionOpts( - {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True + {"local": {"key": LOCAL_MASTER_KEY}}, + key_vault.full_name, + bypass_query_analysis=True, ) self.encrypted_client = self.rs_or_single_client(auto_encryption_opts=opts) @@ -2932,7 +2934,9 @@ def setUp(self): {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, self.key_vault_client, OPTS ) opts = AutoEncryptionOpts( - {"local": {"key": LOCAL_MASTER_KEY}}, key_vault.full_name, bypass_query_analysis=True + {"local": {"key": LOCAL_MASTER_KEY}}, + key_vault.full_name, + bypass_query_analysis=True, ) self.encrypted_client = self.rs_or_single_client(auto_encryption_opts=opts) self.db = self.encrypted_client.db From 683ba33c724f1ad62bed108749906249cd2b95c8 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 22 Apr 2025 15:28:54 -0700 Subject: [PATCH 29/40] move kms_ssl_contexts --- pymongo/asynchronous/encryption.py | 8 ++++---- pymongo/encryption_options.py | 6 ------ pymongo/synchronous/encryption.py | 8 ++++---- test/asynchronous/test_encryption.py | 17 +++++++++-------- test/test_encryption.py | 17 +++++++++-------- 5 files changed, 26 insertions(+), 30 deletions(-) diff --git a/pymongo/asynchronous/encryption.py b/pymongo/asynchronous/encryption.py index 0aa175e8b1..f97ab3b605 100644 --- a/pymongo/asynchronous/encryption.py +++ b/pymongo/asynchronous/encryption.py @@ -87,7 +87,7 @@ from pymongo.results import BulkWriteResult, DeleteResult from pymongo.ssl_support import BLOCKING_IO_ERRORS, get_ssl_context from pymongo.typings import _DocumentType, _DocumentTypeArg -from pymongo.uri_parser_shared import parse_host +from pymongo.uri_parser_shared import _parse_kms_tls_options, parse_host from pymongo.write_concern import WriteConcern if TYPE_CHECKING: @@ -157,6 +157,7 @@ def __init__( self.mongocryptd_client = mongocryptd_client self.opts = opts self._spawned = False + self._kms_ssl_contexts = _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) async def kms_request(self, kms_context: MongoCryptKmsContext) -> None: """Complete a KMS request. @@ -165,11 +166,10 @@ async def kms_request(self, kms_context: MongoCryptKmsContext) -> None: :return: None """ - self.opts._parse_kms_tls_options(_IS_SYNC) endpoint = kms_context.endpoint message = kms_context.message provider = kms_context.kms_provider - ctx = self.opts._kms_ssl_contexts.get(provider) + ctx = self._kms_ssl_contexts.get(provider) if ctx is None: # Enable strict certificate verification, OCSP, match hostname, and # SNI using the system default CA certificates. @@ -677,7 +677,7 @@ def __init__( kms_tls_options=kms_tls_options, key_expiration_ms=key_expiration_ms, ) - opts._parse_kms_tls_options(_IS_SYNC) + self._kms_ssl_contexts = _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) self._io_callbacks: Optional[_EncryptionIO] = _EncryptionIO( None, key_vault_coll, None, opts ) diff --git a/pymongo/encryption_options.py b/pymongo/encryption_options.py index d132597226..721e9ce31f 100644 --- a/pymongo/encryption_options.py +++ b/pymongo/encryption_options.py @@ -32,10 +32,8 @@ from bson import int64 from pymongo.common import validate_is_mapping from pymongo.errors import ConfigurationError -from pymongo.uri_parser_shared import _parse_kms_tls_options if TYPE_CHECKING: - from pymongo.pyopenssl_context import SSLContext from pymongo.typings import _AgnosticMongoClient, _DocumentTypeArg @@ -238,13 +236,9 @@ def __init__( self._mongocryptd_spawn_args.append("--idleShutdownTimeoutSecs=60") # Maps KMS provider name to a SSLContext. self._kms_tls_options = kms_tls_options - self._kms_ssl_contexts: dict[str, SSLContext] = {} self._bypass_query_analysis = bypass_query_analysis self._key_expiration_ms = key_expiration_ms - def _parse_kms_tls_options(self, is_sync: bool) -> None: - self._kms_ssl_contexts = _parse_kms_tls_options(self._kms_tls_options, is_sync) - class RangeOpts: """Options to configure encrypted queries using the range algorithm.""" diff --git a/pymongo/synchronous/encryption.py b/pymongo/synchronous/encryption.py index 0aa5e38263..418b2e469b 100644 --- a/pymongo/synchronous/encryption.py +++ b/pymongo/synchronous/encryption.py @@ -86,7 +86,7 @@ from pymongo.synchronous.database import Database from pymongo.synchronous.mongo_client import MongoClient from pymongo.typings import _DocumentType, _DocumentTypeArg -from pymongo.uri_parser_shared import parse_host +from pymongo.uri_parser_shared import _parse_kms_tls_options, parse_host from pymongo.write_concern import WriteConcern if TYPE_CHECKING: @@ -156,6 +156,7 @@ def __init__( self.mongocryptd_client = mongocryptd_client self.opts = opts self._spawned = False + self._kms_ssl_contexts = _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) def kms_request(self, kms_context: MongoCryptKmsContext) -> None: """Complete a KMS request. @@ -164,11 +165,10 @@ def kms_request(self, kms_context: MongoCryptKmsContext) -> None: :return: None """ - self.opts._parse_kms_tls_options(_IS_SYNC) endpoint = kms_context.endpoint message = kms_context.message provider = kms_context.kms_provider - ctx = self.opts._kms_ssl_contexts.get(provider) + ctx = self._kms_ssl_contexts.get(provider) if ctx is None: # Enable strict certificate verification, OCSP, match hostname, and # SNI using the system default CA certificates. @@ -670,7 +670,7 @@ def __init__( kms_tls_options=kms_tls_options, key_expiration_ms=key_expiration_ms, ) - opts._parse_kms_tls_options(_IS_SYNC) + self._kms_ssl_contexts = _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) self._io_callbacks: Optional[_EncryptionIO] = _EncryptionIO( None, key_vault_coll, None, opts ) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 4ad588fa7b..4f8e2b85a8 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -41,6 +41,7 @@ from pymongo.asynchronous.collection import AsyncCollection from pymongo.asynchronous.helpers import anext from pymongo.daemon import _spawn_daemon +from pymongo.uri_parser import _parse_kms_tls_options try: from pymongo.pyopenssl_context import IS_PYOPENSSL @@ -141,7 +142,7 @@ def test_init(self): self.assertEqual(opts._mongocryptd_bypass_spawn, False) self.assertEqual(opts._mongocryptd_spawn_path, "mongocryptd") self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=60"]) - self.assertEqual(opts._kms_ssl_contexts, {}) + self.assertEqual(opts._kms_tls_options, {}) @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") def test_init_spawn_args(self): @@ -189,13 +190,13 @@ def test_init_kms_tls_options(self): tls_opts: Any for tls_opts in [None, {}]: opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) - self.assertEqual(opts._kms_ssl_contexts, {}) + self.assertEqual(opts._kms_tls_options, {}) opts = AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}) - opts._parse_kms_tls_options(_IS_SYNC) - ctx = opts._kms_ssl_contexts["kmip"] + _kms_ssl_contexts = _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) + ctx = _kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) - ctx = opts._kms_ssl_contexts["aws"] + ctx = _kms_ssl_contexts["aws"] self.assertEqual(ctx.check_hostname, True) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) opts = AutoEncryptionOpts( @@ -203,8 +204,8 @@ def test_init_kms_tls_options(self): "k.d", kms_tls_options={"kmip": {"tlsCAFile": CA_PEM, "tlsCertificateKeyFile": CLIENT_PEM}}, ) - opts._parse_kms_tls_options(_IS_SYNC) - ctx = opts._kms_ssl_contexts["kmip"] + _kms_ssl_contexts = _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) + ctx = _kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) @@ -2233,7 +2234,7 @@ async def test_05_tlsDisableOCSPEndpointCheck_is_permitted(self): encryption = self.create_client_encryption( providers, "keyvault.datakeys", self.client, OPTS, kms_tls_options=options ) - ctx = encryption._io_callbacks.opts._kms_ssl_contexts["aws"] + ctx = encryption._io_callbacks._kms_ssl_contexts["aws"] if not hasattr(ctx, "check_ocsp_endpoint"): raise self.skipTest("OCSP not enabled") self.assertFalse(ctx.check_ocsp_endpoint) diff --git a/test/test_encryption.py b/test/test_encryption.py index a69a44b9ca..8e39f8be38 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -41,6 +41,7 @@ from pymongo.daemon import _spawn_daemon from pymongo.synchronous.collection import Collection from pymongo.synchronous.helpers import next +from pymongo.uri_parser import _parse_kms_tls_options try: from pymongo.pyopenssl_context import IS_PYOPENSSL @@ -141,7 +142,7 @@ def test_init(self): self.assertEqual(opts._mongocryptd_bypass_spawn, False) self.assertEqual(opts._mongocryptd_spawn_path, "mongocryptd") self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=60"]) - self.assertEqual(opts._kms_ssl_contexts, {}) + self.assertEqual(opts._kms_tls_options, {}) @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") def test_init_spawn_args(self): @@ -189,13 +190,13 @@ def test_init_kms_tls_options(self): tls_opts: Any for tls_opts in [None, {}]: opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) - self.assertEqual(opts._kms_ssl_contexts, {}) + self.assertEqual(opts._kms_tls_options, {}) opts = AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}) - opts._parse_kms_tls_options(_IS_SYNC) - ctx = opts._kms_ssl_contexts["kmip"] + _kms_ssl_contexts = _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) + ctx = _kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) - ctx = opts._kms_ssl_contexts["aws"] + ctx = _kms_ssl_contexts["aws"] self.assertEqual(ctx.check_hostname, True) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) opts = AutoEncryptionOpts( @@ -203,8 +204,8 @@ def test_init_kms_tls_options(self): "k.d", kms_tls_options={"kmip": {"tlsCAFile": CA_PEM, "tlsCertificateKeyFile": CLIENT_PEM}}, ) - opts._parse_kms_tls_options(_IS_SYNC) - ctx = opts._kms_ssl_contexts["kmip"] + _kms_ssl_contexts = _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) + ctx = _kms_ssl_contexts["kmip"] self.assertEqual(ctx.check_hostname, True) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) @@ -2225,7 +2226,7 @@ def test_05_tlsDisableOCSPEndpointCheck_is_permitted(self): encryption = self.create_client_encryption( providers, "keyvault.datakeys", self.client, OPTS, kms_tls_options=options ) - ctx = encryption._io_callbacks.opts._kms_ssl_contexts["aws"] + ctx = encryption._io_callbacks._kms_ssl_contexts["aws"] if not hasattr(ctx, "check_ocsp_endpoint"): raise self.skipTest("OCSP not enabled") self.assertFalse(ctx.check_ocsp_endpoint) From d007c5fdef74402adea00fde72066c034286a700 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 22 Apr 2025 15:32:26 -0700 Subject: [PATCH 30/40] fix import --- test/asynchronous/test_encryption.py | 2 +- test/test_encryption.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 4f8e2b85a8..04dcfec706 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -41,7 +41,7 @@ from pymongo.asynchronous.collection import AsyncCollection from pymongo.asynchronous.helpers import anext from pymongo.daemon import _spawn_daemon -from pymongo.uri_parser import _parse_kms_tls_options +from pymongo.uri_parser_shared import _parse_kms_tls_options try: from pymongo.pyopenssl_context import IS_PYOPENSSL diff --git a/test/test_encryption.py b/test/test_encryption.py index 8e39f8be38..a1fe014eb5 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -41,7 +41,7 @@ from pymongo.daemon import _spawn_daemon from pymongo.synchronous.collection import Collection from pymongo.synchronous.helpers import next -from pymongo.uri_parser import _parse_kms_tls_options +from pymongo.uri_parser_shared import _parse_kms_tls_options try: from pymongo.pyopenssl_context import IS_PYOPENSSL From 05c061aa0276cbdaf63841e7869058bb4ed2d3c3 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 22 Apr 2025 15:49:30 -0700 Subject: [PATCH 31/40] fix test failures --- test/asynchronous/test_encryption.py | 8 ++++---- test/test_encryption.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 04dcfec706..b50614056e 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -142,7 +142,7 @@ def test_init(self): self.assertEqual(opts._mongocryptd_bypass_spawn, False) self.assertEqual(opts._mongocryptd_spawn_path, "mongocryptd") self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=60"]) - self.assertEqual(opts._kms_tls_options, {}) + self.assertEqual(opts._kms_tls_options, None) @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") def test_init_spawn_args(self): @@ -170,7 +170,7 @@ def test_init_kms_tls_options(self): # Error cases: with self.assertRaisesRegex(TypeError, r'kms_tls_options\["kmip"\] must be a dict'): opts = AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}) - opts._parse_kms_tls_options(_IS_SYNC) + _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) tls_opts: Any for tls_opts in [ @@ -180,12 +180,12 @@ def test_init_kms_tls_options(self): ]: with self.assertRaisesRegex(ConfigurationError, "Insecure TLS options prohibited"): opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) - opts._parse_kms_tls_options(_IS_SYNC) + _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) with self.assertRaises(FileNotFoundError): opts = AutoEncryptionOpts( {}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}} ) - opts._parse_kms_tls_options(_IS_SYNC) + _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) # Success cases: tls_opts: Any for tls_opts in [None, {}]: diff --git a/test/test_encryption.py b/test/test_encryption.py index a1fe014eb5..8ab492716f 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -142,7 +142,7 @@ def test_init(self): self.assertEqual(opts._mongocryptd_bypass_spawn, False) self.assertEqual(opts._mongocryptd_spawn_path, "mongocryptd") self.assertEqual(opts._mongocryptd_spawn_args, ["--idleShutdownTimeoutSecs=60"]) - self.assertEqual(opts._kms_tls_options, {}) + self.assertEqual(opts._kms_tls_options, None) @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") def test_init_spawn_args(self): @@ -170,7 +170,7 @@ def test_init_kms_tls_options(self): # Error cases: with self.assertRaisesRegex(TypeError, r'kms_tls_options\["kmip"\] must be a dict'): opts = AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": 1}) - opts._parse_kms_tls_options(_IS_SYNC) + _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) tls_opts: Any for tls_opts in [ @@ -180,12 +180,12 @@ def test_init_kms_tls_options(self): ]: with self.assertRaisesRegex(ConfigurationError, "Insecure TLS options prohibited"): opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) - opts._parse_kms_tls_options(_IS_SYNC) + _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) with self.assertRaises(FileNotFoundError): opts = AutoEncryptionOpts( {}, "k.d", kms_tls_options={"kmip": {"tlsCAFile": "does-not-exist"}} ) - opts._parse_kms_tls_options(_IS_SYNC) + _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) # Success cases: tls_opts: Any for tls_opts in [None, {}]: From 350f1035fa2a322f8d905fb2eabe483451e13e16 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Tue, 22 Apr 2025 16:03:14 -0700 Subject: [PATCH 32/40] fix test failure pt2 --- test/asynchronous/test_encryption.py | 3 ++- test/test_encryption.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index b50614056e..2ed3aeeab3 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -190,7 +190,8 @@ def test_init_kms_tls_options(self): tls_opts: Any for tls_opts in [None, {}]: opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) - self.assertEqual(opts._kms_tls_options, {}) + kms_tls_contexts = _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) + self.assertEqual(kms_tls_contexts, {}) opts = AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}) _kms_ssl_contexts = _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) ctx = _kms_ssl_contexts["kmip"] diff --git a/test/test_encryption.py b/test/test_encryption.py index 8ab492716f..6611b20e37 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -190,7 +190,8 @@ def test_init_kms_tls_options(self): tls_opts: Any for tls_opts in [None, {}]: opts = AutoEncryptionOpts({}, "k.d", kms_tls_options=tls_opts) - self.assertEqual(opts._kms_tls_options, {}) + kms_tls_contexts = _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) + self.assertEqual(kms_tls_contexts, {}) opts = AutoEncryptionOpts({}, "k.d", kms_tls_options={"kmip": {"tls": True}, "aws": {}}) _kms_ssl_contexts = _parse_kms_tls_options(opts._kms_tls_options, _IS_SYNC) ctx = _kms_ssl_contexts["kmip"] From 5fa117f3a2945e4139a150bbe4b3693d64ea4c7a Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Wed, 23 Apr 2025 09:57:12 -0700 Subject: [PATCH 33/40] change changelog line ft noah's suggestion --- doc/changelog.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 68bb044974..c4a3b0033f 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -12,7 +12,8 @@ Version 4.12.1 is a bug fix release. - Fixed a bug that caused direct use of ``pymongo.uri_parser`` to raise an ``AttributeError``. - Removed Eventlet testing against Python versions newer than 3.9 since Eventlet is actively being sunset by its maintainers and has compatibility issues with PyMongo's dnspython dependency. -- Fixed a bug that would try to use PyOpenSSL with AsyncMongoClient, causing the client to fail. +- Fixed a bug that would cause AsyncMongoClient to attempt to use PyOpenSSL when available, resulting in errors such as + "pymongo.errors.ServerSelectionTimeoutError: 'SSLContext' object has no attribute 'wrap_bio'" Issues Resolved From 56c9662d8e9c3b88de31e0b70682f1830da6b0f6 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Wed, 23 Apr 2025 10:03:30 -0700 Subject: [PATCH 34/40] _ssl -> _stdssl and ssl_in_use -> _ssl --- pymongo/ssl_support.py | 47 ++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index 888e09a46a..c71152dbf6 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -39,7 +39,7 @@ stacklevel=2, ) try: - import pymongo.ssl_context as _ssl + import pymongo.ssl_context as _stdssl except ImportError: HAVE_SSL = False @@ -55,21 +55,24 @@ IPADDR_SAFE = True if HAVE_PYSSL: - HAS_SNI = _pyssl.HAS_SNI | _ssl.HAS_SNI + HAS_SNI = _pyssl.HAS_SNI | _stdssl.HAS_SNI PYSSLError: Any = _pyssl.SSLError - BLOCKING_IO_ERRORS: tuple = _pyssl.BLOCKING_IO_ERRORS + _ssl.BLOCKING_IO_ERRORS - BLOCKING_IO_READ_ERROR: tuple = (_pyssl.BLOCKING_IO_READ_ERROR, _ssl.BLOCKING_IO_READ_ERROR) + BLOCKING_IO_ERRORS: tuple = _pyssl.BLOCKING_IO_ERRORS + _stdssl.BLOCKING_IO_ERRORS + BLOCKING_IO_READ_ERROR: tuple = ( + _pyssl.BLOCKING_IO_READ_ERROR, + _stdssl.BLOCKING_IO_READ_ERROR, + ) BLOCKING_IO_WRITE_ERROR: tuple = ( _pyssl.BLOCKING_IO_WRITE_ERROR, - _ssl.BLOCKING_IO_WRITE_ERROR, + _stdssl.BLOCKING_IO_WRITE_ERROR, ) else: - HAS_SNI = _ssl.HAS_SNI - PYSSLError = _ssl.SSLError - BLOCKING_IO_ERRORS = _ssl.BLOCKING_IO_ERRORS - BLOCKING_IO_READ_ERROR = (_ssl.BLOCKING_IO_READ_ERROR,) - BLOCKING_IO_WRITE_ERROR = (_ssl.BLOCKING_IO_WRITE_ERROR,) - SSLError = _ssl.SSLError + HAS_SNI = _stdssl.HAS_SNI + PYSSLError = _stdssl.SSLError + BLOCKING_IO_ERRORS = _stdssl.BLOCKING_IO_ERRORS + BLOCKING_IO_READ_ERROR = (_stdssl.BLOCKING_IO_READ_ERROR,) + BLOCKING_IO_WRITE_ERROR = (_stdssl.BLOCKING_IO_WRITE_ERROR,) + SSLError = _stdssl.SSLError BLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR def get_ssl_context( @@ -81,14 +84,14 @@ def get_ssl_context( allow_invalid_hostnames: bool, disable_ocsp_endpoint_check: bool, is_sync: bool, - ) -> Union[_pyssl.SSLContext, _ssl.SSLContext]: # type: ignore[name-defined] + ) -> Union[_pyssl.SSLContext, _stdssl.SSLContext]: # type: ignore[name-defined] """Create and return an SSLContext object.""" if is_sync and HAVE_PYSSL: - ssl_in_use: types.ModuleType = _pyssl + _ssl: types.ModuleType = _pyssl else: - ssl_in_use = _ssl + _ssl = _stdssl verify_mode = CERT_NONE if allow_invalid_certificates else CERT_REQUIRED - ctx = ssl_in_use.SSLContext(ssl_in_use.PROTOCOL_SSLv23) + ctx = _ssl.SSLContext(_ssl.PROTOCOL_SSLv23) if verify_mode != CERT_NONE: ctx.check_hostname = not allow_invalid_hostnames else: @@ -100,20 +103,20 @@ def get_ssl_context( # up to date versions of MongoDB 2.4 and above already disable # SSLv2 and SSLv3, python disables SSLv2 by default in >= 2.7.7 # and >= 3.3.4 and SSLv3 in >= 3.4.3. - ctx.options |= ssl_in_use.OP_NO_SSLv2 - ctx.options |= ssl_in_use.OP_NO_SSLv3 - ctx.options |= ssl_in_use.OP_NO_COMPRESSION - ctx.options |= ssl_in_use.OP_NO_RENEGOTIATION + ctx.options |= _ssl.OP_NO_SSLv2 + ctx.options |= _ssl.OP_NO_SSLv3 + ctx.options |= _ssl.OP_NO_COMPRESSION + ctx.options |= _ssl.OP_NO_RENEGOTIATION if certfile is not None: try: ctx.load_cert_chain(certfile, None, passphrase) - except ssl_in_use.SSLError as exc: + except _ssl.SSLError as exc: raise ConfigurationError(f"Private key doesn't match certificate: {exc}") from None if crlfile is not None: - if ssl_in_use.IS_PYOPENSSL: + if _ssl.IS_PYOPENSSL: raise ConfigurationError("tlsCRLFile cannot be used with PyOpenSSL") # Match the server's behavior. - ctx.verify_flags = getattr(ssl_in_use, "VERIFY_CRL_CHECK_LEAF", 0) + ctx.verify_flags = getattr(_ssl, "VERIFY_CRL_CHECK_LEAF", 0) ctx.load_verify_locations(crlfile) if ca_certs is not None: ctx.load_verify_locations(ca_certs) From a7324e51b0214b086ac16d76ec28bb631ff49ff7 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Wed, 23 Apr 2025 10:37:44 -0700 Subject: [PATCH 35/40] make combined error type --- pymongo/pool_shared.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pymongo/pool_shared.py b/pymongo/pool_shared.py index bef71a2003..300bc99d19 100644 --- a/pymongo/pool_shared.py +++ b/pymongo/pool_shared.py @@ -40,6 +40,7 @@ from pymongo.pool_options import PoolOptions from pymongo.ssl_support import HAS_SNI, PYSSLError, SSLError +SSLErrors = (PYSSLError, SSLError) if TYPE_CHECKING: from pymongo.pyopenssl_context import _sslConn from pymongo.typings import _Address @@ -138,7 +139,7 @@ def _raise_connection_failure( msg += format_timeout_details(timeout_details) if isinstance(error, socket.timeout): raise NetworkTimeout(msg) from error - elif isinstance(error, (SSLError, PYSSLError)) and "timed out" in str(error): + elif isinstance(error, SSLErrors) and "timed out" in str(error): # Eventlet does not distinguish TLS network timeouts from other # SSLErrors (https://github.com/eventlet/eventlet/issues/692). # Luckily, we can work around this limitation because the phrase @@ -293,7 +294,7 @@ async def _async_configured_socket( # Raise _CertificateError directly like we do after match_hostname # below. raise - except (OSError, SSLError, PYSSLError) as exc: + except (OSError, *SSLErrors) as exc: sock.close() # We raise AutoReconnect for transient and permanent SSL handshake # failures alike. Permanent handshake failures, like protocol @@ -349,7 +350,7 @@ async def _configured_protocol_interface( # Raise _CertificateError directly like we do after match_hostname # below. raise - except (OSError, SSLError, PYSSLError) as exc: + except (OSError, *SSLErrors) as exc: # We raise AutoReconnect for transient and permanent SSL handshake # failures alike. Permanent handshake failures, like protocol # mismatch, will be turned into ServerSelectionTimeoutErrors later. @@ -467,7 +468,7 @@ def _configured_socket(address: _Address, options: PoolOptions) -> Union[socket. # Raise _CertificateError directly like we do after match_hostname # below. raise - except (OSError, SSLError, PYSSLError) as exc: + except (OSError, *SSLErrors) as exc: sock.close() # We raise AutoReconnect for transient and permanent SSL handshake # failures alike. Permanent handshake failures, like protocol @@ -516,7 +517,7 @@ def _configured_socket_interface(address: _Address, options: PoolOptions) -> Net # Raise _CertificateError directly like we do after match_hostname # below. raise - except (OSError, SSLError, PYSSLError) as exc: + except (OSError, *SSLErrors) as exc: sock.close() # We raise AutoReconnect for transient and permanent SSL handshake # failures alike. Permanent handshake failures, like protocol From af83d81b124dd0fc9a386e57c460d7dd4956ae7a Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Wed, 23 Apr 2025 10:42:07 -0700 Subject: [PATCH 36/40] jk cant do _stdssl --- pymongo/ssl_support.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index c71152dbf6..d2ffdcb551 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -39,7 +39,7 @@ stacklevel=2, ) try: - import pymongo.ssl_context as _stdssl + import pymongo.ssl_context as _ssl except ImportError: HAVE_SSL = False @@ -55,24 +55,28 @@ IPADDR_SAFE = True if HAVE_PYSSL: - HAS_SNI = _pyssl.HAS_SNI | _stdssl.HAS_SNI + # # We have to pass hostname / ip address to wrap_socket + # # to use SSLContext.check_hostname. + # if ssl_context.has_sni: + # ... + HAS_SNI = _pyssl.HAS_SNI and _ssl.HAS_SNI PYSSLError: Any = _pyssl.SSLError - BLOCKING_IO_ERRORS: tuple = _pyssl.BLOCKING_IO_ERRORS + _stdssl.BLOCKING_IO_ERRORS + BLOCKING_IO_ERRORS: tuple = _pyssl.BLOCKING_IO_ERRORS + _ssl.BLOCKING_IO_ERRORS BLOCKING_IO_READ_ERROR: tuple = ( _pyssl.BLOCKING_IO_READ_ERROR, - _stdssl.BLOCKING_IO_READ_ERROR, + _ssl.BLOCKING_IO_READ_ERROR, ) BLOCKING_IO_WRITE_ERROR: tuple = ( _pyssl.BLOCKING_IO_WRITE_ERROR, - _stdssl.BLOCKING_IO_WRITE_ERROR, + _ssl.BLOCKING_IO_WRITE_ERROR, ) else: - HAS_SNI = _stdssl.HAS_SNI - PYSSLError = _stdssl.SSLError - BLOCKING_IO_ERRORS = _stdssl.BLOCKING_IO_ERRORS - BLOCKING_IO_READ_ERROR = (_stdssl.BLOCKING_IO_READ_ERROR,) - BLOCKING_IO_WRITE_ERROR = (_stdssl.BLOCKING_IO_WRITE_ERROR,) - SSLError = _stdssl.SSLError + HAS_SNI = _ssl.HAS_SNI + PYSSLError = _ssl.SSLError + BLOCKING_IO_ERRORS = _ssl.BLOCKING_IO_ERRORS + BLOCKING_IO_READ_ERROR = (_ssl.BLOCKING_IO_READ_ERROR,) + BLOCKING_IO_WRITE_ERROR = (_ssl.BLOCKING_IO_WRITE_ERROR,) + SSLError = _ssl.SSLError BLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR def get_ssl_context( @@ -84,12 +88,10 @@ def get_ssl_context( allow_invalid_hostnames: bool, disable_ocsp_endpoint_check: bool, is_sync: bool, - ) -> Union[_pyssl.SSLContext, _stdssl.SSLContext]: # type: ignore[name-defined] + ) -> Union[_pyssl.SSLContext, _ssl.SSLContext]: # type: ignore[name-defined] """Create and return an SSLContext object.""" if is_sync and HAVE_PYSSL: _ssl: types.ModuleType = _pyssl - else: - _ssl = _stdssl verify_mode = CERT_NONE if allow_invalid_certificates else CERT_REQUIRED ctx = _ssl.SSLContext(_ssl.PROTOCOL_SSLv23) if verify_mode != CERT_NONE: From f6b17dd6da31d868f084eb1c2e9b334212183ef2 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Wed, 23 Apr 2025 10:59:28 -0700 Subject: [PATCH 37/40] _pysslConn back to _sslConn --- pymongo/network_layer.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pymongo/network_layer.py b/pymongo/network_layer.py index 3145442076..243e6aaa55 100644 --- a/pymongo/network_layer.py +++ b/pymongo/network_layer.py @@ -46,12 +46,12 @@ _HAVE_SSL = False try: - from pymongo.pyopenssl_context import _sslConn as _pysslConn + from pymongo.pyopenssl_context import _sslConn _HAVE_PYOPENSSL = True except ImportError: _HAVE_PYOPENSSL = False - _pysslConn = SSLSocket # type: ignore[assignment, misc] + _sslConn = SSLSocket # type: ignore[assignment, misc] from pymongo.ssl_support import ( BLOCKING_IO_LOOKUP_ERROR, @@ -76,12 +76,12 @@ # These socket-based I/O methods are for KMS requests and any other network operations that do not use # the MongoDB wire protocol -async def async_socket_sendall(sock: Union[socket.socket, _pysslConn], buf: bytes) -> None: +async def async_socket_sendall(sock: Union[socket.socket, _sslConn], buf: bytes) -> None: timeout = sock.gettimeout() sock.settimeout(0.0) loop = asyncio.get_running_loop() try: - if _HAVE_SSL and isinstance(sock, (SSLSocket, _pysslConn)): + if _HAVE_SSL and isinstance(sock, (SSLSocket, _sslConn)): await asyncio.wait_for(_async_socket_sendall_ssl(sock, buf, loop), timeout=timeout) else: await asyncio.wait_for(loop.sock_sendall(sock, buf), timeout=timeout) # type: ignore[arg-type] @@ -95,7 +95,7 @@ async def async_socket_sendall(sock: Union[socket.socket, _pysslConn], buf: byte if sys.platform != "win32": async def _async_socket_sendall_ssl( - sock: Union[socket.socket, _pysslConn], buf: bytes, loop: AbstractEventLoop + sock: Union[socket.socket, _sslConn], buf: bytes, loop: AbstractEventLoop ) -> None: view = memoryview(buf) sent = 0 @@ -138,7 +138,7 @@ def _is_ready(fut: Future) -> None: loop.remove_writer(fd) async def _async_socket_receive_ssl( - conn: _pysslConn, length: int, loop: AbstractEventLoop, once: Optional[bool] = False + conn: _sslConn, length: int, loop: AbstractEventLoop, once: Optional[bool] = False ) -> memoryview: mv = memoryview(bytearray(length)) total_read = 0 @@ -192,7 +192,7 @@ def _is_ready(fut: Future) -> None: # https://docs.python.org/3/library/asyncio-platforms.html#asyncio-platform-support # Note: In PYTHON-4493 we plan to replace this code with asyncio streams. async def _async_socket_sendall_ssl( - sock: Union[socket.socket, _pysslConn], buf: bytes, dummy: AbstractEventLoop + sock: Union[socket.socket, _sslConn], buf: bytes, dummy: AbstractEventLoop ) -> None: view = memoryview(buf) total_length = len(buf) @@ -213,7 +213,7 @@ async def _async_socket_sendall_ssl( total_sent += sent async def _async_socket_receive_ssl( - conn: _pysslConn, length: int, dummy: AbstractEventLoop, once: Optional[bool] = False + conn: _sslConn, length: int, dummy: AbstractEventLoop, once: Optional[bool] = False ) -> memoryview: mv = memoryview(bytearray(length)) total_read = 0 @@ -239,7 +239,7 @@ async def _async_socket_receive_ssl( return mv -def sendall(sock: Union[socket.socket, _pysslConn], buf: bytes) -> None: +def sendall(sock: Union[socket.socket, _sslConn], buf: bytes) -> None: sock.sendall(buf) @@ -252,7 +252,7 @@ async def _poll_cancellation(conn: AsyncConnection) -> None: async def async_receive_data_socket( - sock: Union[socket.socket, _pysslConn], length: int + sock: Union[socket.socket, _sslConn], length: int ) -> memoryview: sock_timeout = sock.gettimeout() timeout = sock_timeout @@ -260,7 +260,7 @@ async def async_receive_data_socket( sock.settimeout(0.0) loop = asyncio.get_running_loop() try: - if _HAVE_SSL and isinstance(sock, (SSLSocket, _pysslConn)): + if _HAVE_SSL and isinstance(sock, (SSLSocket, _sslConn)): return await asyncio.wait_for( _async_socket_receive_ssl(sock, length, loop, once=True), # type: ignore[arg-type] timeout=timeout, @@ -435,7 +435,7 @@ def sock(self) -> socket.socket: class NetworkingInterface(NetworkingInterfaceBase): - def __init__(self, conn: Union[socket.socket, _pysslConn]): + def __init__(self, conn: Union[socket.socket, _sslConn]): super().__init__(conn) def gettimeout(self) -> float | None: @@ -451,11 +451,11 @@ def is_closing(self) -> bool: return self.conn.is_closing() @property - def get_conn(self) -> Union[socket.socket, _pysslConn]: + def get_conn(self) -> Union[socket.socket, _sslConn]: return self.conn @property - def sock(self) -> Union[socket.socket, _pysslConn]: + def sock(self) -> Union[socket.socket, _sslConn]: return self.conn def fileno(self) -> int: From 74ca8be03fdc5dd9399aab958369530a7b2c1fe0 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Wed, 23 Apr 2025 11:57:56 -0700 Subject: [PATCH 38/40] fix _ssl and has_sni --- pymongo/pool_shared.py | 8 ++++---- pymongo/ssl_support.py | 17 ++++++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/pymongo/pool_shared.py b/pymongo/pool_shared.py index 300bc99d19..308ecef349 100644 --- a/pymongo/pool_shared.py +++ b/pymongo/pool_shared.py @@ -38,7 +38,7 @@ ) from pymongo.network_layer import AsyncNetworkingInterface, NetworkingInterface, PyMongoProtocol from pymongo.pool_options import PoolOptions -from pymongo.ssl_support import HAS_SNI, PYSSLError, SSLError +from pymongo.ssl_support import PYSSLError, SSLError, _has_sni SSLErrors = (PYSSLError, SSLError) if TYPE_CHECKING: @@ -280,7 +280,7 @@ async def _async_configured_socket( try: # We have to pass hostname / ip address to wrap_socket # to use SSLContext.check_hostname. - if HAS_SNI: + if _has_sni(False): loop = asyncio.get_running_loop() ssl_sock = await loop.run_in_executor( None, @@ -459,7 +459,7 @@ def _configured_socket(address: _Address, options: PoolOptions) -> Union[socket. try: # We have to pass hostname / ip address to wrap_socket # to use SSLContext.check_hostname. - if HAS_SNI: + if _has_sni(True): ssl_sock = ssl_context.wrap_socket(sock, server_hostname=host) # type: ignore[assignment, misc, unused-ignore] else: ssl_sock = ssl_context.wrap_socket(sock) # type: ignore[assignment, misc, unused-ignore] @@ -508,7 +508,7 @@ def _configured_socket_interface(address: _Address, options: PoolOptions) -> Net try: # We have to pass hostname / ip address to wrap_socket # to use SSLContext.check_hostname. - if HAS_SNI: + if _has_sni(True): ssl_sock = ssl_context.wrap_socket(sock, server_hostname=host) else: ssl_sock = ssl_context.wrap_socket(sock) diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index d2ffdcb551..8d7b575a8c 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -55,11 +55,6 @@ IPADDR_SAFE = True if HAVE_PYSSL: - # # We have to pass hostname / ip address to wrap_socket - # # to use SSLContext.check_hostname. - # if ssl_context.has_sni: - # ... - HAS_SNI = _pyssl.HAS_SNI and _ssl.HAS_SNI PYSSLError: Any = _pyssl.SSLError BLOCKING_IO_ERRORS: tuple = _pyssl.BLOCKING_IO_ERRORS + _ssl.BLOCKING_IO_ERRORS BLOCKING_IO_READ_ERROR: tuple = ( @@ -71,7 +66,6 @@ _ssl.BLOCKING_IO_WRITE_ERROR, ) else: - HAS_SNI = _ssl.HAS_SNI PYSSLError = _ssl.SSLError BLOCKING_IO_ERRORS = _ssl.BLOCKING_IO_ERRORS BLOCKING_IO_READ_ERROR = (_ssl.BLOCKING_IO_READ_ERROR,) @@ -79,6 +73,11 @@ SSLError = _ssl.SSLError BLOCKING_IO_LOOKUP_ERROR = BLOCKING_IO_READ_ERROR + def _has_sni(is_sync: bool) -> bool: + if is_sync and HAVE_PYSSL: + return _pyssl.HAS_SNI + return _ssl.HAS_SNI + def get_ssl_context( certfile: Optional[str], passphrase: Optional[str], @@ -92,6 +91,8 @@ def get_ssl_context( """Create and return an SSLContext object.""" if is_sync and HAVE_PYSSL: _ssl: types.ModuleType = _pyssl + else: + _ssl = globals()["_ssl"] verify_mode = CERT_NONE if allow_invalid_certificates else CERT_REQUIRED ctx = _ssl.SSLContext(_ssl.PROTOCOL_SSLv23) if verify_mode != CERT_NONE: @@ -132,10 +133,12 @@ def get_ssl_context( class SSLError(Exception): # type: ignore pass - HAS_SNI = False IPADDR_SAFE = False BLOCKING_IO_ERRORS = () + def _has_sni(is_sync: bool) -> bool: # noqa: ARG001 + return False + def get_ssl_context(*dummy): # type: ignore """No ssl module, raise ConfigurationError.""" raise ConfigurationError("The ssl module is not available") From 536f189c2a329c1a8c30eaf7c89bc56cd2732c4b Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Wed, 23 Apr 2025 12:43:50 -0700 Subject: [PATCH 39/40] fix test --- test/atlas/test_connection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/atlas/test_connection.py b/test/atlas/test_connection.py index 3d34ff326e..a3e8b0b1d5 100644 --- a/test/atlas/test_connection.py +++ b/test/atlas/test_connection.py @@ -26,7 +26,7 @@ sys.path[0:0] = [""] import pymongo -from pymongo.ssl_support import HAS_SNI +from pymongo.ssl_support import _has_sni pytestmark = pytest.mark.atlas_connect @@ -57,7 +57,7 @@ def connect(self, uri): # No auth error client.test.test.count_documents({}) - @unittest.skipUnless(HAS_SNI, "Free tier requires SNI support") + @unittest.skipUnless(_has_sni(True), "Free tier requires SNI support") def test_free_tier(self): self.connect(URIS["ATLAS_FREE"]) @@ -80,7 +80,7 @@ def connect_srv(self, uri): self.connect(uri) self.assertIn("mongodb+srv://", uri) - @unittest.skipUnless(HAS_SNI, "Free tier requires SNI support") + @unittest.skipUnless(_has_sni(True), "Free tier requires SNI support") def test_srv_free_tier(self): self.connect_srv(URIS["ATLAS_SRV_FREE"]) From 24354b4585d90ba293581f4055dd5d74d7a33ce0 Mon Sep 17 00:00:00 2001 From: Iris Ho Date: Wed, 23 Apr 2025 13:22:01 -0700 Subject: [PATCH 40/40] _ssl -> ssl --- pymongo/ssl_support.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index 8d7b575a8c..aa6301fe73 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -90,11 +90,11 @@ def get_ssl_context( ) -> Union[_pyssl.SSLContext, _ssl.SSLContext]: # type: ignore[name-defined] """Create and return an SSLContext object.""" if is_sync and HAVE_PYSSL: - _ssl: types.ModuleType = _pyssl + ssl: types.ModuleType = _pyssl else: - _ssl = globals()["_ssl"] + ssl = _ssl verify_mode = CERT_NONE if allow_invalid_certificates else CERT_REQUIRED - ctx = _ssl.SSLContext(_ssl.PROTOCOL_SSLv23) + ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) if verify_mode != CERT_NONE: ctx.check_hostname = not allow_invalid_hostnames else: @@ -106,20 +106,20 @@ def get_ssl_context( # up to date versions of MongoDB 2.4 and above already disable # SSLv2 and SSLv3, python disables SSLv2 by default in >= 2.7.7 # and >= 3.3.4 and SSLv3 in >= 3.4.3. - ctx.options |= _ssl.OP_NO_SSLv2 - ctx.options |= _ssl.OP_NO_SSLv3 - ctx.options |= _ssl.OP_NO_COMPRESSION - ctx.options |= _ssl.OP_NO_RENEGOTIATION + ctx.options |= ssl.OP_NO_SSLv2 + ctx.options |= ssl.OP_NO_SSLv3 + ctx.options |= ssl.OP_NO_COMPRESSION + ctx.options |= ssl.OP_NO_RENEGOTIATION if certfile is not None: try: ctx.load_cert_chain(certfile, None, passphrase) - except _ssl.SSLError as exc: + except ssl.SSLError as exc: raise ConfigurationError(f"Private key doesn't match certificate: {exc}") from None if crlfile is not None: - if _ssl.IS_PYOPENSSL: + if ssl.IS_PYOPENSSL: raise ConfigurationError("tlsCRLFile cannot be used with PyOpenSSL") # Match the server's behavior. - ctx.verify_flags = getattr(_ssl, "VERIFY_CRL_CHECK_LEAF", 0) + ctx.verify_flags = getattr(ssl, "VERIFY_CRL_CHECK_LEAF", 0) ctx.load_verify_locations(crlfile) if ca_certs is not None: ctx.load_verify_locations(ca_certs)