diff --git a/doc/changelog.rst b/doc/changelog.rst index 7b0d81211..a9396072a 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -23,6 +23,8 @@ Breaking Changes in 4.0 :attr:`pymongo.mongo_client.MongoClient.is_locked`. - Removed :meth:`pymongo.mongo_client.MongoClient.database_names`. - Removed :meth:`pymongo.database.Database.collection_names`. +- Removed :meth:`pymongo.database.Database.authenticate` and + :meth:`pymongo.database.Database.logout`. - Removed :meth:`pymongo.database.Database.error`, :meth:`pymongo.database.Database.last_status`, :meth:`pymongo.database.Database.previous_error`, diff --git a/doc/migrate-to-pymongo4.rst b/doc/migrate-to-pymongo4.rst index 059483b09..fa847a83b 100644 --- a/doc/migrate-to-pymongo4.rst +++ b/doc/migrate-to-pymongo4.rst @@ -121,6 +121,27 @@ can be changed to this:: Database -------- +Database.authenticate and Database.logout are removed +..................................................... + +Removed :meth:`pymongo.database.Database.authenticate` and +:meth:`pymongo.database.Database.logout`. Authenticating multiple users +on the same client conflicts with support for logical sessions in MongoDB 3.6+. +To authenticate as multiple users, create multiple instances of +:class:`~pymongo.mongo_client.MongoClient`. Code like this:: + + client = MongoClient() + client.admin.authenticate('user1', 'pass1') + client.admin.authenticate('user2', 'pass2') + +can be changed to this:: + + client1 = MongoClient(username='user1', password='pass1') + client2 = MongoClient(username='user2', password='pass2') + +Alternatively, create a single user that contains all the authentication privileges +required by your application. + Database.collection_names is removed .................................... diff --git a/pymongo/auth.py b/pymongo/auth.py index 1e31e2594..4d22717c2 100644 --- a/pymongo/auth.py +++ b/pymongo/auth.py @@ -580,7 +580,3 @@ def authenticate(credentials, sock_info): auth_func = _AUTH_MAP.get(mechanism) auth_func(credentials, sock_info) - -def logout(source, sock_info): - """Log out from a database.""" - sock_info.command(source, {'logout': 1}) diff --git a/pymongo/client_session.py b/pymongo/client_session.py index 245ba5d45..5e3f08194 100644 --- a/pymongo/client_session.py +++ b/pymongo/client_session.py @@ -351,12 +351,11 @@ class ClientSession(object): :class:`ClientSession`, call :meth:`~pymongo.mongo_client.MongoClient.start_session`. """ - def __init__(self, client, server_session, options, authset, implicit): + def __init__(self, client, server_session, options, implicit): # A MongoClient, a _ServerSession, a SessionOptions, and a set. self._client = client self._server_session = server_session self._options = options - self._authset = authset self._cluster_time = None self._operation_time = None # Is this an implicitly created session? diff --git a/pymongo/database.py b/pymongo/database.py index b1ae52d93..a810c67a6 100644 --- a/pymongo/database.py +++ b/pymongo/database.py @@ -980,116 +980,6 @@ class Database(common.BaseObject): next = __next__ - def authenticate(self, name=None, password=None, - source=None, mechanism='DEFAULT', **kwargs): - """**DEPRECATED**: Authenticate to use this database. - - .. warning:: Starting in MongoDB 3.6, calling :meth:`authenticate` - invalidates all existing cursors. It may also leave logical sessions - open on the server for up to 30 minutes until they time out. - - Authentication lasts for the life of the underlying client - instance, or until :meth:`logout` is called. - - Raises :class:`TypeError` if (required) `name`, (optional) `password`, - or (optional) `source` is not an instance of :class:`basestring` - (:class:`str` in python 3). - - .. note:: - - This method authenticates the current connection, and - will also cause all new :class:`~socket.socket` connections - in the underlying client instance to be authenticated automatically. - - - Authenticating more than once on the same database with different - credentials is not supported. You must call :meth:`logout` before - authenticating with new credentials. - - - When sharing a client instance between multiple threads, all - threads will share the authentication. If you need different - authentication profiles for different purposes you must use - distinct client instances. - - :Parameters: - - `name`: the name of the user to authenticate. Optional when - `mechanism` is MONGODB-X509 and the MongoDB server version is - >= 3.4. - - `password` (optional): the password of the user to authenticate. - Not used with GSSAPI or MONGODB-X509 authentication. - - `source` (optional): the database to authenticate on. If not - specified the current database is used. - - `mechanism` (optional): See :data:`~pymongo.auth.MECHANISMS` for - options. If no mechanism is specified, PyMongo automatically uses - MONGODB-CR when connected to a pre-3.0 version of MongoDB, - SCRAM-SHA-1 when connected to MongoDB 3.0 through 3.6, and - negotiates the mechanism to use (SCRAM-SHA-1 or SCRAM-SHA-256) when - connected to MongoDB 4.0+. - - `authMechanismProperties` (optional): Used to specify - authentication mechanism specific options. To specify the service - name for GSSAPI authentication pass - ``authMechanismProperties='SERVICE_NAME:'``. - To specify the session token for MONGODB-AWS authentication pass - ``authMechanismProperties='AWS_SESSION_TOKEN:'``. - - .. versionchanged:: 3.7 - Added support for SCRAM-SHA-256 with MongoDB 4.0 and later. - - .. versionchanged:: 3.5 - Deprecated. Authenticating multiple users conflicts with support for - logical sessions in MongoDB 3.6. To authenticate as multiple users, - create multiple instances of MongoClient. - - .. versionadded:: 2.8 - Use SCRAM-SHA-1 with MongoDB 3.0 and later. - - .. versionchanged:: 2.5 - Added the `source` and `mechanism` parameters. :meth:`authenticate` - now raises a subclass of :class:`~pymongo.errors.PyMongoError` if - authentication fails due to invalid credentials or configuration - issues. - - .. mongodoc:: authenticate - """ - if name is not None and not isinstance(name, str): - raise TypeError("name must be an instance of str") - if password is not None and not isinstance(password, str): - raise TypeError("password must be an instance of str") - if source is not None and not isinstance(source, str): - raise TypeError("source must be an instance of str") - common.validate_auth_mechanism('mechanism', mechanism) - - validated_options = common._CaseInsensitiveDictionary() - for option, value in kwargs.items(): - normalized, val = common.validate_auth_option(option, value) - validated_options[normalized] = val - - credentials = auth._build_credentials_tuple( - mechanism, - source, - name, - password, - validated_options, - self.name) - - self.client._cache_credentials( - self.name, - credentials, - connect=True) - - return True - - def logout(self): - """**DEPRECATED**: Deauthorize use of this database. - - .. warning:: Starting in MongoDB 3.6, calling :meth:`logout` - invalidates all existing cursors. It may also leave logical sessions - open on the server for up to 30 minutes until they time out. - """ - warnings.warn("Database.logout() is deprecated", - DeprecationWarning, stacklevel=2) - - # Sockets will be deauthenticated as they are used. - self.client._purge_credentials(self.name) - def dereference(self, dbref, session=None, **kwargs): """Dereference a :class:`~bson.dbref.DBRef`, getting the document it points to. diff --git a/pymongo/mongo_client.py b/pymongo/mongo_client.py index a9fa28228..35aa0a622 100644 --- a/pymongo/mongo_client.py +++ b/pymongo/mongo_client.py @@ -708,7 +708,7 @@ class MongoClient(common.BaseObject): self.__all_credentials = {} creds = options.credentials if creds: - self._cache_credentials(creds.source, creds) + self.__all_credentials[creds.source] = creds self._topology_settings = TopologySettings( seeds=seeds, @@ -753,38 +753,6 @@ class MongoClient(common.BaseObject): self._encrypter = _Encrypter.create( self, self.__options.auto_encryption_opts) - def _cache_credentials(self, source, credentials, connect=False): - """Save a set of authentication credentials. - - The credentials are used to login a socket whenever one is created. - If `connect` is True, verify the credentials on the server first. - """ - # Don't let other threads affect this call's data. - all_credentials = self.__all_credentials.copy() - - if source in all_credentials: - # Nothing to do if we already have these credentials. - if credentials == all_credentials[source]: - return - raise OperationFailure('Another user is already authenticated ' - 'to this database. You must logout first.') - - if connect: - server = self._get_topology().select_server( - writable_preferred_server_selector) - - # get_socket() logs out of the database if logged in with old - # credentials, and logs in with new ones. - with server.get_socket(all_credentials) as sock_info: - sock_info.authenticate(credentials) - - # If several threads run _cache_credentials at once, last one wins. - self.__all_credentials[source] = credentials - - def _purge_credentials(self, source): - """Purge credentials from the authentication cache.""" - self.__all_credentials.pop(source, None) - def _server_property(self, attr_name): """An attribute of the current server's description. @@ -1594,19 +1562,11 @@ class MongoClient(common.BaseObject): helpers._handle_exception() def __start_session(self, implicit, **kwargs): - # Driver Sessions Spec: "If startSession is called when multiple users - # are authenticated drivers MUST raise an error with the error message - # 'Cannot call startSession when multiple users are authenticated.'" - authset = set(self.__all_credentials.values()) - if len(authset) > 1: - raise InvalidOperation("Cannot call start_session when" - " multiple users are authenticated") - # Raises ConfigurationError if sessions are not supported. server_session = self._get_server_session() opts = client_session.SessionOptions(**kwargs) return client_session.ClientSession( - self, server_session, opts, authset, implicit) + self, server_session, opts, implicit) def start_session(self, causal_consistency=True, @@ -1617,9 +1577,7 @@ class MongoClient(common.BaseObject): :class:`~pymongo.client_session.SessionOptions`. See the :mod:`~pymongo.client_session` module for details and examples. - Requires MongoDB 3.6. It is an error to call :meth:`start_session` - if this client has been authenticated to multiple databases using the - deprecated method :meth:`~pymongo.database.Database.authenticate`. + Requires MongoDB 3.6. A :class:`~pymongo.client_session.ClientSession` may only be used with the MongoClient that started it. :class:`ClientSession` instances are @@ -1655,7 +1613,7 @@ class MongoClient(common.BaseObject): # should always opt-in. return self.__start_session(True, causal_consistency=False) except (ConfigurationError, InvalidOperation): - # Sessions not supported, or multiple users authenticated. + # Sessions not supported. return None @contextlib.contextmanager diff --git a/pymongo/pool.py b/pymongo/pool.py index 33a93105c..5b5f60a33 100644 --- a/pymongo/pool.py +++ b/pymongo/pool.py @@ -771,17 +771,10 @@ class SocketInfo(object): :Parameters: - `all_credentials`: dict, maps auth source to MongoCredential. """ - if all_credentials or self.authset: - cached = set(all_credentials.values()) - authset = self.authset.copy() - - # Logout any credentials that no longer exist in the cache. - for credentials in authset - cached: - auth.logout(credentials.source, self) - self.authset.discard(credentials) - - for credentials in cached - authset: - self.authenticate(credentials) + if all_credentials: + for credentials in all_credentials.values(): + if credentials not in self.authset: + self.authenticate(credentials) # CMAP spec says to publish the ready event only after authenticating # the connection. @@ -807,18 +800,13 @@ class SocketInfo(object): def validate_session(self, client, session): """Validate this session before use with client. - Raises error if this session is logged in as a different user or - the client is not the one that created the session. + Raises error if the client is not the one that created the session. """ if session: if session._client is not client: raise InvalidOperation( 'Can only use session with the MongoClient that' ' started it') - if session._authset != self.authset: - raise InvalidOperation( - 'Cannot use session after authenticating with different' - ' credentials') def close_socket(self, reason): """Close this connection with a reason.""" diff --git a/test/__init__.py b/test/__init__.py index e6a3c208d..063a634b4 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -222,6 +222,17 @@ class ClientContext(object): server_api = ServerApi(MONGODB_API_VERSION) self.default_client_options["server_api"] = server_api + @property + def client_options(self): + """Return the MongoClient options for creating a duplicate client.""" + opts = client_context.default_client_options.copy() + if client_context.auth_enabled: + opts['username'] = db_user + opts['password'] = db_pwd + if self.replica_set_name: + opts['replicaSet'] = self.replica_set_name + return opts + @property def ismaster(self): return self.client.admin.command('isMaster') diff --git a/test/test_auth.py b/test/test_auth.py index 8e178189b..49081c7cc 100644 --- a/test/test_auth.py +++ b/test/test_auth.py @@ -73,31 +73,6 @@ class AutoAuthenticateThread(threading.Thread): self.success = True -class DBAuthenticateThread(threading.Thread): - """Used in testing threaded authentication. - - This does db.test.find_one() with a 1-second delay to ensure it must - check out and authenticate multiple sockets from the pool concurrently. - - :Parameters: - `db`: An auth-protected db with a 'test' collection containing one - document. - """ - - def __init__(self, db, username, password): - super(DBAuthenticateThread, self).__init__() - self.db = db - self.username = username - self.password = password - self.success = False - - def run(self): - self.db.authenticate(self.username, self.password) - assert self.db.test.find_one({'$where': delay(1)}) is not None - self.success = True - - - class TestGSSAPI(unittest.TestCase): @classmethod @@ -314,24 +289,6 @@ class TestSASLPlain(unittest.TestCase): client.ldap.test.find_one() def test_sasl_plain_bad_credentials(self): - - with ignore_deprecations(): - client = MongoClient(SASL_HOST, SASL_PORT) - - # Bad username - self.assertRaises(OperationFailure, client.ldap.authenticate, - 'not-user', SASL_PASS, SASL_DB, 'PLAIN') - self.assertRaises(OperationFailure, client.ldap.test.find_one) - self.assertRaises(OperationFailure, client.ldap.test.insert_one, - {"failed": True}) - - # Bad password - self.assertRaises(OperationFailure, client.ldap.authenticate, - SASL_USER, 'not-pwd', SASL_DB, 'PLAIN') - self.assertRaises(OperationFailure, client.ldap.test.find_one) - self.assertRaises(OperationFailure, client.ldap.test.insert_one, - {"failed": True}) - def auth_string(user, password): uri = ('mongodb://%s:%s@%s:%d/?authMechanism=PLAIN;' 'authSource=%s' % (quote_plus(user), @@ -368,12 +325,6 @@ class TestSCRAMSHA1(unittest.TestCase): def test_scram_sha1(self): host, port = client_context.host, client_context.port - with ignore_deprecations(): - client = rs_or_single_client_noauth() - self.assertTrue(client.pymongo_test.authenticate( - 'user', 'pass', mechanism='SCRAM-SHA-1')) - client.pymongo_test.command('dbstats') - client = rs_or_single_client_noauth( 'mongodb://user:pass@%s:%d/pymongo_test?authMechanism=SCRAM-SHA-1' % (host, port)) @@ -391,6 +342,7 @@ class TestSCRAMSHA1(unittest.TestCase): db.command('dbstats') +# https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst#scram-sha-256-and-mechanism-negotiation class TestSCRAM(unittest.TestCase): @client_context.require_auth @@ -432,148 +384,50 @@ class TestSCRAM(unittest.TestCase): self.assertEqual( started, ['saslStart', 'saslContinue', 'saslContinue']) - @ignore_deprecations def test_scram(self): - host, port = client_context.host, client_context.port - + # Step 1: create users client_context.create_user( - 'testscram', - 'sha1', - 'pwd', - roles=['dbOwner'], + 'testscram', 'sha1', 'pwd', roles=['dbOwner'], mechanisms=['SCRAM-SHA-1']) - client_context.create_user( - 'testscram', - 'sha256', - 'pwd', - roles=['dbOwner'], + 'testscram', 'sha256', 'pwd', roles=['dbOwner'], mechanisms=['SCRAM-SHA-256']) - client_context.create_user( - 'testscram', - 'both', - 'pwd', - roles=['dbOwner'], + 'testscram', 'both', 'pwd', roles=['dbOwner'], mechanisms=['SCRAM-SHA-1', 'SCRAM-SHA-256']) + # Step 2: verify auth success cases client = rs_or_single_client_noauth( - event_listeners=[self.listener]) - self.assertTrue( - client.testscram.authenticate('sha1', 'pwd')) + username='sha1', password='pwd', authSource='testscram') client.testscram.command('dbstats') - client.testscram.logout() - self.assertTrue( - client.testscram.authenticate( - 'sha1', 'pwd', mechanism='SCRAM-SHA-1')) + + client = rs_or_single_client_noauth( + username='sha1', password='pwd', authSource='testscram', + authMechanism='SCRAM-SHA-1') client.testscram.command('dbstats') - client.testscram.logout() - self.assertRaises( - OperationFailure, - client.testscram.authenticate, - 'sha1', 'pwd', mechanism='SCRAM-SHA-256') - self.assertTrue( - client.testscram.authenticate('sha256', 'pwd')) + client = rs_or_single_client_noauth( + username='sha256', password='pwd', authSource='testscram') client.testscram.command('dbstats') - client.testscram.logout() - self.assertTrue( - client.testscram.authenticate( - 'sha256', 'pwd', mechanism='SCRAM-SHA-256')) + + client = rs_or_single_client_noauth( + username='sha256', password='pwd', authSource='testscram', + authMechanism='SCRAM-SHA-256') client.testscram.command('dbstats') - client.testscram.logout() - self.assertRaises( - OperationFailure, - client.testscram.authenticate, - 'sha256', 'pwd', mechanism='SCRAM-SHA-1') - self.listener.results.clear() - self.assertTrue( - client.testscram.authenticate('both', 'pwd')) - started = self.listener.results['started'][0] - self.assertEqual(started.command.get('mechanism'), 'SCRAM-SHA-256') + # Step 2: SCRAM-SHA-1 and SCRAM-SHA-256 + client = rs_or_single_client_noauth( + username='both', password='pwd', authSource='testscram', + authMechanism='SCRAM-SHA-1') client.testscram.command('dbstats') - client.testscram.logout() - self.assertTrue( - client.testscram.authenticate( - 'both', 'pwd', mechanism='SCRAM-SHA-256')) + client = rs_or_single_client_noauth( + username='both', password='pwd', authSource='testscram', + authMechanism='SCRAM-SHA-256') client.testscram.command('dbstats') - client.testscram.logout() - self.assertTrue( - client.testscram.authenticate( - 'both', 'pwd', mechanism='SCRAM-SHA-1')) - client.testscram.command('dbstats') - client.testscram.logout() - - self.assertRaises( - OperationFailure, - client.testscram.authenticate, - 'not-a-user', 'pwd') - - if HAVE_STRINGPREP: - # Test the use of SASLprep on passwords. For example, - # saslprep('\u2136') becomes 'IV' and saslprep('I\u00ADX') - # becomes 'IX'. SASLprep is only supported when the standard - # library provides stringprep. - client_context.create_user( - 'testscram', - '\u2168', - '\u2163', - roles=['dbOwner'], - mechanisms=['SCRAM-SHA-256']) - - client_context.create_user( - 'testscram', - 'IX', - 'IX', - roles=['dbOwner'], - mechanisms=['SCRAM-SHA-256']) - - self.assertTrue( - client.testscram.authenticate('\u2168', '\u2163')) - client.testscram.command('dbstats') - client.testscram.logout() - self.assertTrue( - client.testscram.authenticate( - '\u2168', '\u2163', mechanism='SCRAM-SHA-256')) - client.testscram.command('dbstats') - client.testscram.logout() - self.assertTrue( - client.testscram.authenticate('\u2168', 'IV')) - client.testscram.command('dbstats') - client.testscram.logout() - - self.assertTrue( - client.testscram.authenticate('IX', 'I\u00ADX')) - client.testscram.command('dbstats') - client.testscram.logout() - self.assertTrue( - client.testscram.authenticate( - 'IX', 'I\u00ADX', mechanism='SCRAM-SHA-256')) - client.testscram.command('dbstats') - client.testscram.logout() - self.assertTrue( - client.testscram.authenticate('IX', 'IX')) - client.testscram.command('dbstats') - client.testscram.logout() - - client = rs_or_single_client_noauth( - 'mongodb://\u2168:\u2163@%s:%d/testscram' % (host, port)) - client.testscram.command('dbstats') - client = rs_or_single_client_noauth( - 'mongodb://\u2168:IV@%s:%d/testscram' % (host, port)) - client.testscram.command('dbstats') - - client = rs_or_single_client_noauth( - 'mongodb://IX:I\u00ADX@%s:%d/testscram' % (host, port)) - client.testscram.command('dbstats') - client = rs_or_single_client_noauth( - 'mongodb://IX:IX@%s:%d/testscram' % (host, port)) - client.testscram.command('dbstats') self.listener.results.clear() client = rs_or_single_client_noauth( - 'mongodb://both:pwd@%s:%d/testscram' % (host, port), + username='both', password='pwd', authSource='testscram', event_listeners=[self.listener]) client.testscram.command('dbstats') if client_context.version.at_least(4, 4, -1): @@ -584,17 +438,26 @@ class TestSCRAM(unittest.TestCase): started = self.listener.results['started'][0] self.assertEqual(started.command.get('mechanism'), 'SCRAM-SHA-256') + # Step 3: verify auth failure conditions client = rs_or_single_client_noauth( - 'mongodb://both:pwd@%s:%d/testscram?authMechanism=SCRAM-SHA-1' - % (host, port)) - client.testscram.command('dbstats') + username='sha1', password='pwd', authSource='testscram', + authMechanism='SCRAM-SHA-256') + with self.assertRaises(OperationFailure): + client.testscram.command('dbstats') client = rs_or_single_client_noauth( - 'mongodb://both:pwd@%s:%d/testscram?authMechanism=SCRAM-SHA-256' - % (host, port)) - client.testscram.command('dbstats') + username='sha256', password='pwd', authSource='testscram', + authMechanism='SCRAM-SHA-1') + with self.assertRaises(OperationFailure): + client.testscram.command('dbstats') + + client = rs_or_single_client_noauth( + username='not-a-user', password='pwd', authSource='testscram') + with self.assertRaises(OperationFailure): + client.testscram.command('dbstats') if client_context.is_rs: + host, port = client_context.host, client_context.port uri = ('mongodb://both:pwd@%s:%d/testscram' '?replicaSet=%s' % (host, port, client_context.replica_set_name)) @@ -604,6 +467,62 @@ class TestSCRAM(unittest.TestCase): 'testscram', read_preference=ReadPreference.SECONDARY) db.command('dbstats') + @unittest.skipUnless(HAVE_STRINGPREP, 'Cannot test without stringprep') + def test_scram_saslprep(self): + # Step 4: test SASLprep + host, port = client_context.host, client_context.port + # Test the use of SASLprep on passwords. For example, + # saslprep('\u2136') becomes 'IV' and saslprep('I\u00ADX') + # becomes 'IX'. SASLprep is only supported when the standard + # library provides stringprep. + client_context.create_user( + 'testscram', '\u2168', '\u2163', roles=['dbOwner'], + mechanisms=['SCRAM-SHA-256']) + client_context.create_user( + 'testscram', 'IX', 'IX', roles=['dbOwner'], + mechanisms=['SCRAM-SHA-256']) + + client = rs_or_single_client_noauth( + username='\u2168', password='\u2163', authSource='testscram') + client.testscram.command('dbstats') + + client = rs_or_single_client_noauth( + username='\u2168', password='\u2163', authSource='testscram', + authMechanism='SCRAM-SHA-256') + client.testscram.command('dbstats') + + client = rs_or_single_client_noauth( + username='\u2168', password='IV', authSource='testscram') + client.testscram.command('dbstats') + + client = rs_or_single_client_noauth( + username='IX', password='I\u00ADX', authSource='testscram') + client.testscram.command('dbstats') + + client = rs_or_single_client_noauth( + username='IX', password='I\u00ADX', authSource='testscram', + authMechanism='SCRAM-SHA-256') + client.testscram.command('dbstats') + + client = rs_or_single_client_noauth( + username='IX', password='IX', authSource='testscram', + authMechanism='SCRAM-SHA-256') + client.testscram.command('dbstats') + + client = rs_or_single_client_noauth( + 'mongodb://\u2168:\u2163@%s:%d/testscram' % (host, port)) + client.testscram.command('dbstats') + client = rs_or_single_client_noauth( + 'mongodb://\u2168:IV@%s:%d/testscram' % (host, port)) + client.testscram.command('dbstats') + + client = rs_or_single_client_noauth( + 'mongodb://IX:I\u00ADX@%s:%d/testscram' % (host, port)) + client.testscram.command('dbstats') + client = rs_or_single_client_noauth( + 'mongodb://IX:IX@%s:%d/testscram' % (host, port)) + client.testscram.command('dbstats') + def test_cache(self): client = single_client() # Force authentication. @@ -651,38 +570,6 @@ class TestSCRAM(unittest.TestCase): thread.join() self.assertTrue(thread.success) -class TestThreadedAuth(unittest.TestCase): - - @client_context.require_auth - def test_db_authenticate_threaded(self): - - db = client_context.client.db - coll = db.test - coll.drop() - coll.insert_one({'_id': 1}) - - client_context.create_user( - 'db', - 'user', - 'pass', - roles=['dbOwner']) - self.addCleanup(db.command, 'dropUser', 'user') - - db = rs_or_single_client_noauth().db - db.authenticate('user', 'pass') - # No error. - db.authenticate('user', 'pass') - - db = rs_or_single_client_noauth().db - threads = [] - for _ in range(4): - threads.append(DBAuthenticateThread(db, 'user', 'pass')) - for thread in threads: - thread.start() - for thread in threads: - thread.join() - self.assertTrue(thread.success) - class TestAuthURIOptions(unittest.TestCase): diff --git a/test/test_client.py b/test/test_client.py index b5add1df7..9f8135dc7 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -35,7 +35,7 @@ from bson.codec_options import CodecOptions, TypeEncoder, TypeRegistry from bson.son import SON from bson.tz_util import utc import pymongo -from pymongo import auth, message +from pymongo import message from pymongo.common import CONNECT_TIMEOUT, _UUID_REPRESENTATIONS from pymongo.command_cursor import CommandCursor from pymongo.compression_support import _HAVE_SNAPPY, _HAVE_ZSTD @@ -80,7 +80,6 @@ from test.utils import (assertRaisesExactly, FunctionCallRecorder, get_pool, gevent_monkey_patched, - ignore_deprecations, is_greenthread_patched, lazy_client_trial, NTHREADS, @@ -908,37 +907,6 @@ class TestClient(IntegrationTest): with self.assertRaises(OperationFailure): rs_or_single_client(username="ad min", password="foo").server_info() - @client_context.require_auth - @ignore_deprecations - def test_multiple_logins(self): - client_context.create_user( - 'pymongo_test', 'user1', 'pass', roles=['readWrite']) - client_context.create_user( - 'pymongo_test', 'user2', 'pass', roles=['readWrite']) - self.addCleanup(remove_all_users, self.client.pymongo_test) - - client = rs_or_single_client_noauth( - "mongodb://user1:pass@%s:%d/pymongo_test" % ( - client_context.host, client_context.port)) - - client.pymongo_test.test.find_one() - with self.assertRaises(OperationFailure): - # Can't log in to the same database with multiple users. - client.pymongo_test.authenticate('user2', 'pass') - - client.pymongo_test.test.find_one() - client.pymongo_test.logout() - with self.assertRaises(OperationFailure): - client.pymongo_test.test.find_one() - - client.pymongo_test.authenticate('user2', 'pass') - client.pymongo_test.test.find_one() - - with self.assertRaises(OperationFailure): - client.pymongo_test.authenticate('user1', 'pass') - - client.pymongo_test.test.find_one() - @client_context.require_auth def test_lazy_auth_raises_operation_failure(self): lazy_client = rs_or_single_client_noauth( @@ -1306,12 +1274,6 @@ class TestClient(IntegrationTest): waitQueueTimeoutMS=1, retryReads=False)) - # Simulate an authenticate() call on a different socket. - credentials = auth._build_credentials_tuple( - 'DEFAULT', 'admin', db_user, db_pwd, {}, None) - - c._cache_credentials('test', credentials, connect=False) - # Cause a network error on the actual socket. pool = get_pool(c) socket_info = one(pool.sockets) diff --git a/test/test_database.py b/test/test_database.py index ef91aebac..e910c4b9c 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -17,7 +17,6 @@ import datetime import re import sys -import warnings sys.path[0:0] = [""] @@ -43,17 +42,12 @@ from pymongo.errors import (CollectionInvalid, from pymongo.mongo_client import MongoClient from pymongo.read_concern import ReadConcern from pymongo.read_preferences import ReadPreference -from pymongo.saslprep import HAVE_STRINGPREP from pymongo.write_concern import WriteConcern from test import (client_context, SkipTest, unittest, IntegrationTest) -from test.utils import (EventListener, - ignore_deprecations, - remove_all_users, - rs_or_single_client_noauth, - rs_or_single_client, +from test.utils import (rs_or_single_client, server_started_with_auth, wait_until, IMPOSSIBLE_WRITE_CONCERN, @@ -469,87 +463,6 @@ class TestDatabase(IntegrationTest): self.assertEqual(auth._password_digest("Gustave", "Dor\xe9"), "81e0e2364499209f466e75926a162d73") - @client_context.require_auth - @ignore_deprecations - def test_authenticate_multiple(self): - # "self.client" is logged in as root. - self.client.drop_database("pymongo_test") - self.client.drop_database("pymongo_test1") - users_db_auth = self.client.pymongo_test - - client_context.create_user( - 'admin', - 'ro-admin', - 'pass', - roles=["userAdmin", "readAnyDatabase"]) - - self.addCleanup(client_context.drop_user, 'admin', 'ro-admin') - client_context.create_user( - 'pymongo_test', 'user', 'pass', roles=["userAdmin", "readWrite"]) - self.addCleanup(remove_all_users, users_db_auth) - - # Non-root client. - listener = EventListener() - client = rs_or_single_client_noauth(event_listeners=[listener]) - admin_db = client.admin - users_db = client.pymongo_test - other_db = client.pymongo_test1 - - self.assertRaises(OperationFailure, users_db.test.find_one) - self.assertEqual(listener.started_command_names(), ['find']) - listener.reset() - - # Regular user should be able to query its own db, but - # no other. - users_db.authenticate('user', 'pass') - if client_context.version.at_least(3, 0): - self.assertEqual(listener.started_command_names()[0], 'saslStart') - else: - self.assertEqual(listener.started_command_names()[0], 'getnonce') - - self.assertEqual(0, users_db.test.count_documents({})) - self.assertRaises(OperationFailure, other_db.test.find_one) - - listener.reset() - # Admin read-only user should be able to query any db, - # but not write. - admin_db.authenticate('ro-admin', 'pass') - if client_context.version.at_least(3, 0): - self.assertEqual(listener.started_command_names()[0], 'saslStart') - else: - self.assertEqual(listener.started_command_names()[0], 'getnonce') - self.assertEqual(None, other_db.test.find_one()) - self.assertRaises(OperationFailure, - other_db.test.insert_one, {}) - - # Close all sockets. - client.close() - - listener.reset() - # We should still be able to write to the regular user's db. - self.assertTrue(users_db.test.delete_many({})) - names = listener.started_command_names() - if client_context.version.at_least(4, 4, -1): - # No speculation with multiple users (but we do skipEmptyExchange). - self.assertEqual( - names, ['saslStart', 'saslContinue', 'saslStart', - 'saslContinue', 'delete']) - elif client_context.version.at_least(3, 0): - self.assertEqual( - names, ['saslStart', 'saslContinue', 'saslContinue', - 'saslStart', 'saslContinue', 'saslContinue', 'delete']) - else: - self.assertEqual( - names, ['getnonce', 'authenticate', - 'getnonce', 'authenticate', 'delete']) - - # And read from other dbs... - self.assertEqual(0, other_db.test.count_documents({})) - - # But still not write to other dbs. - self.assertRaises(OperationFailure, - other_db.test.insert_one, {}) - def test_id_ordering(self): # PyMongo attempts to have _id show up first # when you iterate key/value pairs in a document. diff --git a/test/test_legacy_api.py b/test/test_legacy_api.py index 333aaeb89..a28238b95 100644 --- a/test/test_legacy_api.py +++ b/test/test_legacy_api.py @@ -15,13 +15,9 @@ """Test various legacy / deprecated API features.""" import sys -import uuid sys.path[0:0] = [""] -from bson.binary import PYTHON_LEGACY, STANDARD -from bson.code import Code -from bson.codec_options import CodecOptions from bson.son import SON from pymongo import ASCENDING, GEOHAYSTACK from pymongo.common import partition_node @@ -1113,10 +1109,9 @@ class TestLegacyBulkAuthorization(BulkAuthorizationTestBase): def test_readonly(self): # We test that an authorization failure aborts the batch and is raised # as OperationFailure. - cli = rs_or_single_client_noauth() - db = cli.pymongo_test - coll = db.test - db.authenticate('readonly', 'pw') + cli = rs_or_single_client_noauth( + username='readonly', password='pw', authSource='pymongo_test') + coll = cli.pymongo_test.test bulk = coll.initialize_ordered_bulk_op() bulk.insert({'x': 1}) self.assertRaises(OperationFailure, bulk.execute) @@ -1124,10 +1119,9 @@ class TestLegacyBulkAuthorization(BulkAuthorizationTestBase): def test_no_remove(self): # We test that an authorization failure aborts the batch and is raised # as OperationFailure. - cli = rs_or_single_client_noauth() - db = cli.pymongo_test - coll = db.test - db.authenticate('noremove', 'pw') + cli = rs_or_single_client_noauth( + username='noremove', password='pw', authSource='pymongo_test') + coll = cli.pymongo_test.test bulk = coll.initialize_ordered_bulk_op() bulk.insert({'x': 1}) bulk.find({'x': 2}).upsert().replace_one({'x': 2}) diff --git a/test/test_read_preferences.py b/test/test_read_preferences.py index 9515099a0..93a282a6d 100644 --- a/test/test_read_preferences.py +++ b/test/test_read_preferences.py @@ -19,7 +19,6 @@ import copy import pickle import random import sys -import warnings sys.path[0:0] = [""] @@ -39,9 +38,7 @@ from pymongo.write_concern import WriteConcern from test import (SkipTest, client_context, IntegrationTest, - unittest, - db_user, - db_pwd) + unittest) from test.utils import (connected, ignore_deprecations, one, @@ -312,7 +309,7 @@ class TestReadPreferences(TestReadPreferencesBase): class ReadPrefTester(MongoClient): def __init__(self, *args, **kwargs): self.has_read_from = set() - client_options = client_context.default_client_options.copy() + client_options = client_context.client_options client_options.update(kwargs) super(ReadPrefTester, self).__init__(*args, **client_options) @@ -354,11 +351,8 @@ class TestCommandAndReadPreference(IntegrationTest): super(TestCommandAndReadPreference, cls).setUpClass() cls.c = ReadPrefTester( client_context.pair, - replicaSet=client_context.replica_set_name, # Ignore round trip times, to test ReadPreference modes only. localThresholdMS=1000*1000) - if client_context.auth_enabled: - cls.c.admin.authenticate(db_user, db_pwd) cls.client_version = Version.from_client(cls.c) # mapReduce fails if the collection does not exist. coll = cls.c.pymongo_test.get_collection( diff --git a/test/test_session.py b/test/test_session.py index e183338e4..6f32dac81 100644 --- a/test/test_session.py +++ b/test/test_session.py @@ -29,7 +29,7 @@ from pymongo.errors import (ConfigurationError, InvalidOperation, OperationFailure) from pymongo.read_concern import ReadConcern -from test import IntegrationTest, client_context, db_user, db_pwd, unittest, SkipTest +from test import IntegrationTest, client_context, unittest, SkipTest from test.utils import (ignore_deprecations, rs_or_single_client, EventListener, @@ -1040,112 +1040,6 @@ class TestCausalConsistency(unittest.TestCase): self.assertIsNone(after_cluster_time) -class TestSessionsMultiAuth(IntegrationTest): - @client_context.require_auth - @client_context.require_sessions - def setUp(self): - super(TestSessionsMultiAuth, self).setUp() - - client_context.create_user( - 'pymongo_test', 'second-user', 'pass', roles=['readWrite']) - self.addCleanup(client_context.drop_user, 'pymongo_test','second-user') - - @ignore_deprecations - def test_session_authenticate_multiple(self): - listener = SessionTestListener() - # Logged in as root. - client = rs_or_single_client(event_listeners=[listener]) - db = client.pymongo_test - db.authenticate('second-user', 'pass') - - with self.assertRaises(InvalidOperation): - client.start_session() - - # No implicit sessions. - listener.results.clear() - db.collection.find_one() - event = listener.first_command_started() - self.assertNotIn( - 'lsid', event.command, - "find_one with multi-auth shouldn't have sent lsid with %s" % ( - event.command_name)) - - @ignore_deprecations - def test_explicit_session_logout(self): - listener = SessionTestListener() - - # Changing auth invalidates the session. Start as root. - client = rs_or_single_client(event_listeners=[listener]) - db = client.pymongo_test - db.collection.insert_many([{} for _ in range(10)]) - self.addCleanup(db.collection.drop) - - with client.start_session() as s: - listener.results.clear() - cursor = db.collection.find(session=s).batch_size(2) - next(cursor) - event = listener.first_command_started() - self.assertEqual(event.command_name, 'find') - self.assertEqual( - s.session_id, event.command.get('lsid'), - "find() sent wrong lsid with %s cmd" % (event.command_name,)) - - client.admin.logout() - db.authenticate('second-user', 'pass') - - err = ('Cannot use session after authenticating with different' - ' credentials') - - with self.assertRaisesRegex(InvalidOperation, err): - # Auth has changed between find and getMore. - list(cursor) - - with self.assertRaisesRegex(InvalidOperation, err): - db.collection.bulk_write([InsertOne({})], session=s) - - with self.assertRaisesRegex(InvalidOperation, err): - db.list_collection_names(session=s) - - with self.assertRaisesRegex(InvalidOperation, err): - db.collection.find_one(session=s) - - with self.assertRaisesRegex(InvalidOperation, err): - list(db.collection.aggregate([], session=s)) - - @ignore_deprecations - def test_implicit_session_logout(self): - listener = SessionTestListener() - - # Changing auth doesn't invalidate the session. Start as root. - client = rs_or_single_client(event_listeners=[listener]) - db = client.pymongo_test - - for name, f in [ - ('bulk_write', lambda: db.collection.bulk_write([InsertOne({})])), - ('list_collection_names', db.list_collection_names), - ('find_one', db.collection.find_one), - ('aggregate', lambda: list(db.collection.aggregate([]))) - ]: - def sub_test(): - listener.results.clear() - f() - for event in listener.results['started']: - self.assertIn( - 'lsid', event.command, - "%s sent no lsid with %s" % ( - name, event.command_name)) - - # We switch auth without clearing the pool of session ids. The - # server considers these to be new sessions since it's a new user. - # The old sessions time out on the server after 30 minutes. - client.admin.logout() - db.authenticate('second-user', 'pass') - sub_test() - db.logout() - client.admin.authenticate(db_user, db_pwd) - sub_test() - - class TestSessionsNotSupported(IntegrationTest): @client_context.require_version_max(3, 5, 10) def test_sessions_not_supported(self): diff --git a/test/test_ssl.py b/test/test_ssl.py index 82d99d5d1..71ea142a2 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -30,8 +30,6 @@ from pymongo.ssl_support import HAVE_SSL, get_ssl_context, validate_cert_reqs, _ from pymongo.write_concern import WriteConcern from test import (IntegrationTest, client_context, - db_pwd, - db_user, SkipTest, unittest, HAVE_IPADDRESS) @@ -548,14 +546,7 @@ class TestSSL(IntegrationTest): @client_context.require_ssl_certfile def test_mongodb_x509_auth(self): host, port = client_context.host, client_context.port - ssl_client = MongoClient( - client_context.pair, - ssl=True, - ssl_cert_reqs=ssl.CERT_NONE, - ssl_certfile=CLIENT_PEM) - self.addCleanup(remove_all_users, ssl_client['$external']) - - ssl_client.admin.authenticate(db_user, db_pwd) + self.addCleanup(remove_all_users, client_context.client['$external']) # Give x509 user all necessary privileges. client_context.create_user('$external', MONGODB_X509_USERNAME, roles=[ diff --git a/test/test_threads.py b/test/test_threads.py index 21d24a6b4..854d3cab0 100644 --- a/test/test_threads.py +++ b/test/test_threads.py @@ -17,13 +17,10 @@ import threading from test import (client_context, - db_user, - db_pwd, IntegrationTest, unittest) -from test.utils import rs_or_single_client_noauth, rs_or_single_client +from test.utils import rs_or_single_client from test.utils import joinall -from pymongo.errors import OperationFailure @client_context.require_connection @@ -203,34 +200,5 @@ class TestThreads(IntegrationTest): self.assertTrue(t.passed) -class TestThreadsAuth(IntegrationTest): - @classmethod - @client_context.require_auth - def setUpClass(cls): - super(TestThreadsAuth, cls).setUpClass() - - def test_auto_auth_login(self): - # Create the database upfront to workaround SERVER-39167. - self.client.auth_test.test.insert_one({}) - self.addCleanup(self.client.drop_database, "auth_test") - client = rs_or_single_client_noauth() - self.assertRaises(OperationFailure, client.auth_test.test.find_one) - - # Admin auth - client.admin.authenticate(db_user, db_pwd) - - nthreads = 10 - threads = [] - for _ in range(nthreads): - t = AutoAuthenticateThreads(client.auth_test.test, 10) - t.start() - threads.append(t) - - joinall(threads) - - for t in threads: - self.assertTrue(t.success) - - if __name__ == "__main__": unittest.main()