PYTHON-1827 Follow-on work for unifying URI options
This commit is contained in:
parent
820d884ef7
commit
8dbf41a5ff
@ -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()]
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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):
|
||||
|
||||
|
||||
@ -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__":
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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": {}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user