PYTHON-1827 Follow-on work for unifying URI options

This commit is contained in:
Prashant Mital 2019-04-25 16:57:27 -07:00
parent 820d884ef7
commit 8dbf41a5ff
No known key found for this signature in database
GPG Key ID: 3D2DAA9E483ABE51
7 changed files with 392 additions and 188 deletions

View File

@ -700,17 +700,25 @@ def get_validated_options(options, warn=True):
Returns a copy of options with invalid entries removed.
:Parameters:
- `opts`: A dict of MongoDB URI options.
- `opts`: A dict containing MongoDB URI options.
- `warn` (optional): If ``True`` then warnings will be logged and
invalid options will be ignored. Otherwise, invalid options will
cause errors.
"""
validated_options = {}
if isinstance(options, _CaseInsensitiveDictionary):
validated_options = _CaseInsensitiveDictionary()
get_normed_key = lambda x: x
get_setter_key = lambda x: options.cased_key(x)
else:
validated_options = {}
get_normed_key = lambda x: x.lower()
get_setter_key = lambda x: x
for opt, value in iteritems(options):
lower = opt.lower()
normed_key = get_normed_key(opt)
try:
validator = URI_OPTIONS_VALIDATOR_MAP.get(
lower, raise_config_error)
normed_key, raise_config_error)
value = validator(opt, value)
except (ValueError, TypeError, ConfigurationError) as exc:
if warn:
@ -718,7 +726,7 @@ def get_validated_options(options, warn=True):
else:
raise
else:
validated_options[lower] = value
validated_options[get_setter_key(normed_key)] = value
return validated_options
@ -814,3 +822,83 @@ class BaseObject(object):
.. versionadded:: 3.2
"""
return self.__read_concern
class _CaseInsensitiveDictionary(abc.MutableMapping):
def __init__(self, *args, **kwargs):
self.__casedkeys = {}
self.__data = {}
self.update(dict(*args, **kwargs))
def __contains__(self, key):
return key.lower() in self.__data
def __len__(self):
return len(self.__data)
def __iter__(self):
return (key for key in self.__casedkeys)
def __repr__(self):
return str({self.__casedkeys[k]: self.__data[k] for k in self})
def __setitem__(self, key, value):
lc_key = key.lower()
self.__casedkeys[lc_key] = key
self.__data[lc_key] = value
def __getitem__(self, key):
return self.__data[key.lower()]
def __delitem__(self, key):
lc_key = key.lower()
del self.__casedkeys[lc_key]
del self.__data[lc_key]
def __eq__(self, other):
if not isinstance(other, abc.Mapping):
return NotImplemented
if len(self) != len(other):
return False
for key in other:
if self[key] != other[key]:
return False
return True
def get(self, key, default=None):
return self.__data.get(key.lower(), default)
def pop(self, key, *args, **kwargs):
lc_key = key.lower()
self.__casedkeys.pop(lc_key, None)
return self.__data.pop(lc_key, *args, **kwargs)
def popitem(self):
lc_key, cased_key = self.__casedkeys.popitem()
value = self.__data.pop(lc_key)
return cased_key, value
def clear(self):
self.__casedkeys.clear()
self.__data.clear()
def setdefault(self, key, default=None):
lc_key = key.lower()
if key in self:
return self.__data[lc_key]
else:
self.__casedkeys[lc_key] = key
self.__data[lc_key] = default
return default
def update(self, other):
if isinstance(other, _CaseInsensitiveDictionary):
for key in other:
self[other.cased_key(key)] = other[key]
else:
for key in other:
self[key] = other[key]
def cased_key(self, key):
return self.__casedkeys[key.lower()]

View File

@ -39,7 +39,7 @@ import weakref
from collections import defaultdict
from bson.codec_options import DEFAULT_CODEC_OPTIONS, TypeRegistry
from bson.codec_options import DEFAULT_CODEC_OPTIONS
from bson.py3compat import (integer_types,
string_type)
from bson.son import SON
@ -71,8 +71,8 @@ from pymongo.server_type import SERVER_TYPE
from pymongo.topology import Topology
from pymongo.topology_description import TOPOLOGY_TYPE
from pymongo.settings import TopologySettings
from pymongo.uri_parser import (_CaseInsensitiveDictionary,
_handle_option_deprecations,
from pymongo.uri_parser import (_handle_option_deprecations,
_handle_security_options,
_normalize_options)
from pymongo.write_concern import DEFAULT_WRITE_CONCERN
@ -588,7 +588,7 @@ class MongoClient(common.BaseObject):
for entity in host:
if "://" in entity:
res = uri_parser.parse_uri(
entity, port, validate=True, warn=True)
entity, port, validate=True, warn=True, normalize=False)
seeds.update(res["nodelist"])
username = res["username"] or username
password = res["password"] or password
@ -605,7 +605,7 @@ class MongoClient(common.BaseObject):
monitor_class = kwargs.pop('_monitor_class', None)
condition_class = kwargs.pop('_condition_class', None)
keyword_opts = kwargs
keyword_opts = common._CaseInsensitiveDictionary(kwargs)
keyword_opts['document_class'] = document_class
if type_registry is not None:
keyword_opts['type_registry'] = type_registry
@ -616,15 +616,19 @@ class MongoClient(common.BaseObject):
keyword_opts['tz_aware'] = tz_aware
keyword_opts['connect'] = connect
# Validate kwargs options.
keyword_opts = _CaseInsensitiveDictionary(
dict(common.validate(k, v) for k, v in keyword_opts.items()))
# Handle deprecated options in kwarg list.
# Handle deprecated options in kwarg options.
keyword_opts = _handle_option_deprecations(keyword_opts)
# Change kwarg option names to those used internally.
keyword_opts = _normalize_options(keyword_opts)
# Augment URI options with kwarg options, overriding the former.
# Validate kwarg options.
keyword_opts = common._CaseInsensitiveDictionary(
dict(common.validate(k, v) for k, v in keyword_opts.items()))
# Override connection string options with kwarg options.
opts.update(keyword_opts)
# Handle security-option conflicts in combined options.
opts = _handle_security_options(opts)
# Normalize combined options.
opts = _normalize_options(opts)
# Username and password passed as kwargs override user info in URI.
username = opts.get("username", username)
password = opts.get("password", password)

View File

@ -23,7 +23,7 @@ try:
except ImportError:
_HAVE_DNSPYTHON = False
from bson.py3compat import abc, iteritems, string_type, PY3
from bson.py3compat import string_type, PY3
if PY3:
from urllib.parse import unquote_plus
@ -31,7 +31,8 @@ else:
from urllib import unquote_plus
from pymongo.common import (
get_validated_options, URI_OPTIONS_DEPRECATION_MAP, INTERNAL_URI_OPTION_NAME_MAP)
get_validated_options, INTERNAL_URI_OPTION_NAME_MAP,
URI_OPTIONS_DEPRECATION_MAP, _CaseInsensitiveDictionary)
from pymongo.errors import ConfigurationError, InvalidURI
@ -42,80 +43,6 @@ SRV_SCHEME_LEN = len(SRV_SCHEME)
DEFAULT_PORT = 27017
class _CaseInsensitiveDictionary(abc.MutableMapping):
def __init__(self, *args, **kwargs):
self.__casedkeys = {}
self.__data = {}
self.update(dict(*args, **kwargs))
def __contains__(self, key):
return key.lower() in self.__data
def __len__(self):
return len(self.__data)
def __iter__(self):
return (self.__casedkeys[key] for key in self.__casedkeys)
def __repr__(self):
return str(self.__data)
def __setitem__(self, key, value):
lc_key = key.lower()
self.__casedkeys[lc_key] = key
self.__data[lc_key] = value
def __getitem__(self, key):
return self.__data[key.lower()]
def __delitem__(self, key):
lc_key = key.lower()
del self.__casedkeys[lc_key]
del self.__data[lc_key]
def get(self, key, default=None):
lc_key = key.lower()
if lc_key in self:
return self.__data[lc_key]
return default
def pop(self, key, *args, **kwargs):
lc_key = key.lower()
self.__casedkeys.pop(lc_key, None)
return self.__data.pop(lc_key, *args, **kwargs)
def popitem(self):
lc_key, cased_key = self.__casedkeys.popitem()
value = self.__data.pop(lc_key)
return cased_key, value
def clear(self):
self.__casedkeys.clear()
self.__data.clear()
def setdefault(self, key, default=None):
lc_key = key.lower()
if key in self:
return self.__data[lc_key]
else:
self.__casedkeys[lc_key] = key
self.__data[lc_key] = default
return default
def update(self, other):
for key in other:
self[key] = other[key]
def cased_key(self, key):
return self.__casedkeys[key.lower()]
def as_dict(self):
lc_data = {}
for lc_key in self.__data:
lc_data[lc_key] = self.__data[lc_key]
return lc_data
def parse_userinfo(userinfo):
"""Validates the format of user information in a MongoDB URI.
Reserved characters like ':', '/', '+' and '@' must be escaped
@ -207,11 +134,15 @@ def parse_host(entity, default_port=DEFAULT_PORT):
_IMPLICIT_TLSINSECURE_OPTS = {"tlsallowinvalidcertificates",
"tlsallowinvalidhostnames"}
_TLSINSECURE_EXCLUDE_OPTS = (_IMPLICIT_TLSINSECURE_OPTS |
{INTERNAL_URI_OPTION_NAME_MAP[k] for k in
_IMPLICIT_TLSINSECURE_OPTS})
def _parse_options(opts, delim):
"""Helper method for split_options which creates the options dict.
Also handles the creation of a list for the URI tag_sets/
readpreferencetags portion and the use of the tlsInsecure option."""
readpreferencetags portion, and the use of a unicode options string."""
options = _CaseInsensitiveDictionary()
for uriopt in opts.split(delim):
key, value = uriopt.split("=")
@ -222,14 +153,38 @@ def _parse_options(opts, delim):
warnings.warn("Duplicate URI option '%s'." % (key,))
options[key] = unquote_plus(value)
if 'tlsInsecure' in options:
for implicit_option in _IMPLICIT_TLSINSECURE_OPTS:
if implicit_option in options:
warn_msg = "URI option '%s' overrides value implied by '%s'."
warnings.warn(warn_msg % (options.cased_key(implicit_option),
options.cased_key('tlsInsecure')))
continue
options[implicit_option] = options['tlsInsecure']
return options
def _handle_security_options(options):
"""Raise appropriate errors when conflicting TLS options are present in
the options dictionary.
:Parameters:
- `options`: Instance of _CaseInsensitiveDictionary containing
MongoDB URI options.
"""
tlsinsecure = options.get('tlsinsecure')
if tlsinsecure is not None:
for opt in _TLSINSECURE_EXCLUDE_OPTS:
if opt in options:
err_msg = ("URI options %s and %s cannot be specified "
"simultaneously.")
raise InvalidURI(err_msg % (
options.cased_key('tlsinsecure'), options.cased_key(opt)))
if 'ssl' in options and 'tls' in options:
def truth_value(val):
if val in ('true', 'false'):
return val == 'true'
if isinstance(val, bool):
return val
return val
if truth_value(options.get('ssl')) != truth_value(options.get('tls')):
err_msg = ("Can not specify conflicting values for URI options %s "
"and %s.")
raise InvalidURI(err_msg % (
options.cased_key('ssl'), options.cased_key('tls')))
return options
@ -237,31 +192,49 @@ def _parse_options(opts, delim):
def _handle_option_deprecations(options):
"""Issue appropriate warnings when deprecated options are present in the
options dictionary. Removes deprecated option key, value pairs if the
options dictionary is found to also have the renamed option."""
undeprecated_options = _CaseInsensitiveDictionary()
for key, value in iteritems(options):
optname = str(key).lower()
options dictionary is found to also have the renamed option.
:Parameters:
- `options`: Instance of _CaseInsensitiveDictionary containing
MongoDB URI options.
"""
for optname in list(options):
if optname in URI_OPTIONS_DEPRECATION_MAP:
renamed_key = URI_OPTIONS_DEPRECATION_MAP[optname]
if renamed_key.lower() in options:
warnings.warn("Deprecated option '%s' ignored in favor of "
"'%s'." % (str(key), renamed_key))
newoptname = URI_OPTIONS_DEPRECATION_MAP[optname]
if newoptname in options:
warn_msg = "Deprecated option '%s' ignored in favor of '%s'."
warnings.warn(warn_msg % (options.cased_key(optname),
options.cased_key(newoptname)))
options.pop(optname)
continue
warnings.warn("Option '%s' is deprecated, use '%s' instead." % (
str(key), renamed_key))
undeprecated_options[str(key)] = value
return undeprecated_options
warn_msg = "Option '%s' is deprecated, use '%s' instead."
warnings.warn(warn_msg % (options.cased_key(optname),
newoptname))
return options
def _normalize_options(options):
"""Renames keys in the options dictionary to their internally-used
names."""
normalized_options = {}
for key, value in iteritems(options):
optname = str(key).lower()
intname = INTERNAL_URI_OPTION_NAME_MAP.get(optname, key)
normalized_options[intname] = options[key]
return normalized_options
"""Normalizes option names in the options dictionary by converting them to
their internally-used names. Also handles use of the tlsInsecure option.
:Parameters:
- `options`: Instance of _CaseInsensitiveDictionary containing
MongoDB URI options.
"""
tlsinsecure = options.get('tlsinsecure')
if tlsinsecure is not None:
for opt in _IMPLICIT_TLSINSECURE_OPTS:
intname = INTERNAL_URI_OPTION_NAME_MAP.get(opt, None)
# Internal options are logical inverse of public options.
options[intname] = not tlsinsecure
for optname in list(options):
intname = INTERNAL_URI_OPTION_NAME_MAP.get(optname, None)
if intname is not None:
options[intname] = options.pop(optname)
return options
def validate_options(opts, warn=False):
@ -309,6 +282,8 @@ def split_options(opts, validate=True, warn=False, normalize=True):
except ValueError:
raise InvalidURI("MongoDB URI options are key=value pairs.")
options = _handle_security_options(options)
options = _handle_option_deprecations(options)
if validate:
@ -390,7 +365,8 @@ def _get_dns_txt_options(hostname):
b'&'.join([b''.join(res.strings) for res in results])).decode('utf-8')
def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False):
def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False,
normalize=True):
"""Parse and validate a MongoDB URI.
Returns a dict of the form::
@ -411,15 +387,20 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False):
- `uri`: The MongoDB URI to parse.
- `default_port`: The port number to use when one wasn't specified
for a host in the URI.
- `validate`: If ``True`` (the default), validate and normalize all
options.
- `validate` (optional): If ``True`` (the default), validate and
normalize all options. Default: ``True``.
- `warn` (optional): When validating, if ``True`` then will warn
the user then ignore any invalid options or values. If ``False``,
validation will error when options are unsupported or values are
invalid.
invalid. Default: ``False``.
- `normalize` (optional): If ``True``, convert names of URI options
to their internally-used names. Default: ``True``.
.. versionchanged:: 3.9
Added the ``normalize`` parameter.
.. versionchanged:: 3.6
Added support for mongodb+srv:// URIs
Added support for mongodb+srv:// URIs.
.. versionchanged:: 3.5
Return the original value of the ``readPreference`` MongoDB URI option
@ -448,7 +429,7 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False):
passwd = None
dbase = None
collection = None
options = {}
options = _CaseInsensitiveDictionary()
host_part, _, path_part = scheme_free.partition('/')
if not host_part:
@ -500,7 +481,7 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False):
dns_options = _get_dns_txt_options(fqdn)
if dns_options:
options = split_options(dns_options, validate, warn)
options = split_options(dns_options, validate, warn, normalize)
if set(options) - _ALLOWED_TXT_OPTS:
raise ConfigurationError(
"Only authSource and replicaSet are supported from DNS")
@ -520,7 +501,7 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False):
raise InvalidURI('Bad database name "%s"' % dbase)
if opts:
options.update(split_options(opts, validate, warn))
options.update(split_options(opts, validate, warn, normalize))
if dbase is not None:
dbase = unquote_plus(dbase)

View File

@ -344,6 +344,41 @@ class ClientUnitTest(unittest.TestCase):
c.codec_options.unicode_decode_error_handler,
unicode_decode_error_handler)
def test_uri_option_precedence(self):
# Ensure kwarg options override connection string options.
uri = ("mongodb://localhost/?ssl=true&replicaSet=name"
"&readPreference=primary")
c = MongoClient(uri, ssl=False, replicaSet="newname",
readPreference="secondaryPreferred")
clopts = c._MongoClient__options
opts = clopts._options
self.assertEqual(opts['ssl'], False)
self.assertEqual(clopts.replica_set_name, "newname")
self.assertEqual(
clopts.read_preference, ReadPreference.SECONDARY_PREFERRED)
def test_uri_security_options(self):
# Ensure that we don't silently override security-related options.
with self.assertRaises(InvalidURI):
MongoClient('mongodb://localhost/?ssl=true', tls=False,
connect=False)
# Matching SSL and TLS options should not cause errors.
c = MongoClient('mongodb://localhost/?ssl=false', tls=False,
connect=False)
self.assertEqual(c._MongoClient__options._options['ssl'], False)
# Conflicting tlsInsecure options should raise an error.
with self.assertRaises(InvalidURI):
MongoClient('mongodb://localhost/?tlsInsecure=true',
connect=False, tlsAllowInvalidHostnames=True)
# Conflicting legacy tlsInsecure options should also raise an error.
with self.assertRaises(InvalidURI):
MongoClient('mongodb://localhost/?tlsInsecure=true',
connect=False, ssl_cert_reqs=True)
class TestClient(IntegrationTest):

View File

@ -24,13 +24,12 @@ from pymongo.uri_parser import (parse_userinfo,
split_hosts,
split_options,
parse_uri)
from pymongo.common import get_validated_options
from pymongo.errors import ConfigurationError, InvalidURI
from pymongo.ssl_support import ssl
from pymongo import ReadPreference
from bson.binary import JAVA_LEGACY
from bson.py3compat import string_type, _unicode
from test import clear_warning_registry, unittest
from test import unittest
class TestURI(unittest.TestCase):
@ -435,14 +434,6 @@ class TestURI(unittest.TestCase):
"mongodb://user%40domain.com:password"
"@localhost/foo?uuidrepresentation=notAnOption")
def test_parse_uri_unicode(self):
# Ensure parsing a unicode returns option names that can be passed
# as kwargs. In Python 2.4, keyword argument names must be ASCII.
# In all Pythons, str is the type of valid keyword arg names.
res = parse_uri(_unicode("mongodb://localhost/?fsync=true"))
for key in res['options']:
self.assertTrue(isinstance(key, str))
def test_parse_ssl_paths(self):
# Turn off "validate" since these paths don't exist on filesystem.
self.assertEqual(
@ -467,50 +458,29 @@ class TestURI(unittest.TestCase):
'mongodb://jesse:foo%2Fbar@%2FMongoDB.sock/?ssl_certfile=a/b',
validate=False))
def test_parse_tls_insecure_options(self):
# tlsInsecure is expanded correctly.
def test_tlsinsecure_simple(self):
# check that tlsInsecure is expanded correctly.
uri = "mongodb://example.com/?tlsInsecure=true"
res = get_validated_options(
{"ssl_match_hostname": False, "ssl_cert_reqs": ssl.CERT_NONE,
"tlsinsecure": True}, warn=False)
res = {
"ssl_match_hostname": False, "ssl_cert_reqs": ssl.CERT_NONE,
"tlsinsecure": True}
self.assertEqual(res, parse_uri(uri)["options"])
# tlsAllow* specified AFTER tlsInsecure.
# tlsAllow* options warns and overrides values implied by tlsInsecure.
uri = ("mongodb://example.com/?tlsInsecure=true"
"&tlsAllowInvalidCertificates=false"
"&tlsAllowInvalidHostnames=false")
res = get_validated_options(
{"ssl_match_hostname": True, "ssl_cert_reqs": ssl.CERT_REQUIRED,
"tlsinsecure": True}, warn=False)
with warnings.catch_warnings(record=True) as ctx:
warnings.simplefilter('always')
self.assertEqual(res, parse_uri(uri)["options"])
for warning in ctx:
self.assertRegexpMatches(
warning.message.args[0],
".*tlsAllowInvalid.*overrides.*tlsInsecure.*")
clear_warning_registry()
# tlsAllow* specified BEFORE tlsInsecure.
# tlsAllow* options warns and overrides values implied by tlsInsecure.
uri = ("mongodb://example.com/"
"?tlsAllowInvalidCertificates=false"
"&tlsAllowInvalidHostnames=false"
"&tlsInsecure=true")
res = get_validated_options(
{"ssl_match_hostname": True, "ssl_cert_reqs": ssl.CERT_REQUIRED,
"tlsinsecure": True}, warn=False)
with warnings.catch_warnings(record=True) as ctx:
warnings.simplefilter('always')
self.assertEqual(res, parse_uri(uri)["options"])
for warning in ctx:
self.assertRegexpMatches(
warning.message.args[0],
".*tlsAllowInvalid.*overrides.*tlsInsecure.*")
def test_tlsinsecure_legacy_conflict(self):
# must not allow use of tlsinsecure alongside legacy TLS options.
# same check for modern TLS options is performed in the spec-tests.
uri = "mongodb://srv.com/?tlsInsecure=true&ssl_match_hostname=true"
with self.assertRaises(InvalidURI):
parse_uri(uri, validate=False, warn=False, normalize=False)
def test_normalize_options(self):
# check that options are converted to their internal names correctly.
uri = ("mongodb://example.com/?tls=true&appname=myapp&maxPoolSize=10&"
"fsync=true&wtimeout=10")
res = {
"ssl": True, "appname": "myapp", "maxpoolsize": 10,
"fsync": True, "wtimeoutms": 10}
self.assertEqual(res, parse_uri(uri)["options"])
if __name__ == "__main__":

View File

@ -121,7 +121,7 @@ def create_test(test, test_workdir):
# Compare URI options.
err_msg = "For option %s expected %s but got %s"
if test['options'] is not None:
if test['options']:
opts = options['options']
for opt in test['options']:
lopt = opt.lower()

View File

@ -84,19 +84,145 @@
"options": {}
},
{
"description": "tlsInsecure=true and tlsAllowInvalidCertificates=false warns",
"uri": "mongodb://example.com/?tlsInsecure=true&tlsAllowInvalidCertificates=false",
"valid": true,
"warning": true,
"description": "tlsInsecure and tlsAllowInvalidCertificates both present (and true) raises an error",
"uri": "mongodb://example.com/?tlsInsecure=true&tlsAllowInvalidCertificates=true",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "tlsInsecure=true and tlsAllowInvalidHostnames=false warns",
"uri": "mongodb://example.com/?tlsInsecure=true&tlsAllowInvalidHostnames=false",
"description": "tlsInsecure and tlsAllowInvalidCertificates both present (and false) raises an error",
"uri": "mongodb://example.com/?tlsInsecure=false&tlsAllowInvalidCertificates=false",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "tlsAllowInvalidCertificates and tlsInsecure both present (and true) raises an error",
"uri": "mongodb://example.com/?tlsAllowInvalidCertificates=true&tlsInsecure=true",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "tlsAllowInvalidCertificates and tlsInsecure both present (and false) raises an error",
"uri": "mongodb://example.com/?tlsAllowInvalidCertificates=false&tlsInsecure=false",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "tlsInsecure and tlsAllowInvalidHostnames both present (and true) raises an error",
"uri": "mongodb://example.com/?tlsInsecure=true&tlsAllowInvalidHostnames=true",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "tlsInsecure and tlsAllowInvalidHostnames both present (and false) raises an error",
"uri": "mongodb://example.com/?tlsInsecure=false&tlsAllowInvalidHostnames=false",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "tlsAllowInvalidHostnames and tlsInsecure both present (and true) raises an error",
"uri": "mongodb://example.com/?tlsAllowInvalidHostnames=true&tlsInsecure=true",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "tlsAllowInvalidHostnames and tlsInsecure both present (and false) raises an error",
"uri": "mongodb://example.com/?tlsAllowInvalidHostnames=false&tlsInsecure=false",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "tls=true and ssl=true doesn't warn",
"uri": "mongodb://example.com/?tls=true&ssl=true",
"valid": true,
"warning": true,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "tls=false and ssl=false doesn't warn",
"uri": "mongodb://example.com/?tls=false&ssl=false",
"valid": true,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "ssl=true and tls=true doesn't warn",
"uri": "mongodb://example.com/?ssl=true&tls=true",
"valid": true,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "ssl=false and tls=false doesn't warn",
"uri": "mongodb://example.com/?ssl=false&tls=false",
"valid": true,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "tls=false and ssl=true raises error",
"uri": "mongodb://example.com/?tls=false&ssl=true",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "tls=true and ssl=false raises error",
"uri": "mongodb://example.com/?tls=true&ssl=false",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "ssl=false and tls=true raises error",
"uri": "mongodb://example.com/?ssl=false&tls=true",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}
},
{
"description": "ssl=true and tls=false raises error",
"uri": "mongodb://example.com/?ssl=true&tls=false",
"valid": false,
"warning": false,
"hosts": null,
"auth": null,
"options": {}