diff --git a/AUTHORS b/AUTHORS index 21b0ec64..45a754cc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -251,3 +251,4 @@ that much better: * Gleb Voropaev (https://github.com/buggyspace) * Paulo Amaral (https://github.com/pauloAmaral) * Gaurav Dadhania (https://github.com/GVRV) + * Yurii Andrieiev (https://github.com/yandrieiev) diff --git a/docs/changelog.rst b/docs/changelog.rst index dfed5f59..b88c2ce6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,6 +12,7 @@ Development - POTENTIAL BREAKING CHANGE: Aggregate gives wrong results when used with a queryset having limit and skip #2029 - mongoengine now requires pymongo>=3.5 #2017 - Generate Unique Indices for SortedListField and EmbeddedDocumentListFields #2020 +- connect() fails immediately when db name contains invalid characters (e. g. when user mistakenly puts 'mongodb://127.0.0.1:27017' as db name, happened in #1718) or is if db name is of an invalid type - (Fill this out as you fix issues and develop your features). Changes in 0.17.0 diff --git a/mongoengine/connection.py b/mongoengine/connection.py index 8902bbf6..67374d01 100644 --- a/mongoengine/connection.py +++ b/mongoengine/connection.py @@ -1,4 +1,5 @@ from pymongo import MongoClient, ReadPreference, uri_parser +from pymongo.database import _check_name import six from mongoengine.pymongo_support import IS_PYMONGO_3 @@ -32,6 +33,16 @@ _connections = {} _dbs = {} +def check_db_name(name): + """Check if a database name is valid. + This functionality is copied from pymongo Database class constructor. + """ + if not isinstance(name, six.string_types): + raise TypeError('name must be an instance of %s' % six.string_types) + elif name != '$external': + _check_name(name) + + def _get_connection_settings( db=None, name=None, host=None, port=None, read_preference=READ_PREFERENCE, @@ -41,21 +52,21 @@ def _get_connection_settings( **kwargs): """Get the connection settings as a dict - :param db: the name of the database to use, for compatibility with connect - :param name: the name of the specific database to use - :param host: the host name of the :program:`mongod` instance to connect to - :param port: the port that the :program:`mongod` instance is running on - :param read_preference: The read preference for the collection + : param db: the name of the database to use, for compatibility with connect + : param name: the name of the specific database to use + : param host: the host name of the: program: `mongod` instance to connect to + : param port: the port that the: program: `mongod` instance is running on + : param read_preference: The read preference for the collection ** Added pymongo 2.1 - :param username: username to authenticate with - :param password: password to authenticate with - :param authentication_source: database to authenticate against - :param authentication_mechanism: database authentication mechanisms. + : param username: username to authenticate with + : param password: password to authenticate with + : param authentication_source: database to authenticate against + : param authentication_mechanism: database authentication mechanisms. By default, use SCRAM-SHA-1 with MongoDB 3.0 and later, MONGODB-CR (MongoDB Challenge Response protocol) for older servers. - :param is_mock: explicitly use mongomock for this connection - (can also be done by using `mongomock://` as db host prefix) - :param kwargs: ad-hoc parameters to be passed into the pymongo driver, + : param is_mock: explicitly use mongomock for this connection + (can also be done by using `mongomock: // ` as db host prefix) + : param kwargs: ad-hoc parameters to be passed into the pymongo driver, for example maxpoolsize, tz_aware, etc. See the documentation for pymongo's `MongoClient` for a full list. @@ -72,6 +83,7 @@ def _get_connection_settings( 'authentication_mechanism': authentication_mechanism } + check_db_name(conn_settings['name']) conn_host = conn_settings['host'] # Host can be a list or a string, so if string, force to a list. @@ -139,23 +151,23 @@ def register_connection(alias, db=None, name=None, host=None, port=None, **kwargs): """Register the connection settings. - :param alias: the name that will be used to refer to this connection + : param alias: the name that will be used to refer to this connection throughout MongoEngine - :param name: the name of the specific database to use - :param db: the name of the database to use, for compatibility with connect - :param host: the host name of the :program:`mongod` instance to connect to - :param port: the port that the :program:`mongod` instance is running on - :param read_preference: The read preference for the collection + : param name: the name of the specific database to use + : param db: the name of the database to use, for compatibility with connect + : param host: the host name of the: program: `mongod` instance to connect to + : param port: the port that the: program: `mongod` instance is running on + : param read_preference: The read preference for the collection ** Added pymongo 2.1 - :param username: username to authenticate with - :param password: password to authenticate with - :param authentication_source: database to authenticate against - :param authentication_mechanism: database authentication mechanisms. + : param username: username to authenticate with + : param password: password to authenticate with + : param authentication_source: database to authenticate against + : param authentication_mechanism: database authentication mechanisms. By default, use SCRAM-SHA-1 with MongoDB 3.0 and later, MONGODB-CR (MongoDB Challenge Response protocol) for older servers. - :param is_mock: explicitly use mongomock for this connection - (can also be done by using `mongomock://` as db host prefix) - :param kwargs: ad-hoc parameters to be passed into the pymongo driver, + : param is_mock: explicitly use mongomock for this connection + (can also be done by using `mongomock: // ` as db host prefix) + : param kwargs: ad-hoc parameters to be passed into the pymongo driver, for example maxpoolsize, tz_aware, etc. See the documentation for pymongo's `MongoClient` for a full list. @@ -319,7 +331,7 @@ def connect(db=None, alias=DEFAULT_CONNECTION_NAME, **kwargs): provide username and password arguments as well. Multiple databases are supported by using aliases. Provide a separate - `alias` to connect to a different instance of :program:`mongod`. + `alias` to connect to a different instance of: program: `mongod`. In order to replace a connection identified by a given alias, you'll need to call ``disconnect`` first diff --git a/tests/test_connection.py b/tests/test_connection.py index 5ff22e06..e5e10479 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1,7 +1,7 @@ import datetime from pymongo import MongoClient -from pymongo.errors import OperationFailure +from pymongo.errors import OperationFailure, InvalidName try: import unittest2 as unittest @@ -170,6 +170,36 @@ class ConnectionTest(unittest.TestCase): connect(host='mongodb://localhost:27017/mongoenginetest02', alias='test02') self.assertEqual(len(mongoengine.connection._connections), 3) + def test_connect_with_invalid_db_name(self): + """Ensure that connect() method fails fast if db name is invalid + """ + with self.assertRaises(InvalidName): + connect('mongomock://localhost') + + def test_connect_with_db_name_external(self): + """Ensure that connect() works if db name is $external + """ + """Ensure that the connect() method works properly.""" + connect('$external') + + conn = get_connection() + self.assertIsInstance(conn, pymongo.mongo_client.MongoClient) + + db = get_db() + self.assertIsInstance(db, pymongo.database.Database) + self.assertEqual(db.name, '$external') + + connect('$external', alias='testdb') + conn = get_connection('testdb') + self.assertIsInstance(conn, pymongo.mongo_client.MongoClient) + + def test_connect_with_invalid_db_name_type(self): + """Ensure that connect() method fails fast if db name has invalid type + """ + with self.assertRaises(TypeError): + non_string_db_name = ['e. g. list instead of a string'] + connect(non_string_db_name) + def test_connect_in_mocking(self): """Ensure that the connect() method works properly in mocking. """