PYTHON-1314 Remove Database.authenticate and Database.logout (#568)

This commit is contained in:
Shane Harvey 2021-02-17 14:16:07 -08:00 committed by GitHub
parent c15028a6c7
commit 95974617bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 153 additions and 685 deletions

View File

@ -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`,

View File

@ -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
....................................

View File

@ -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})

View File

@ -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?

View File

@ -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:<service name>'``.
To specify the session token for MONGODB-AWS authentication pass
``authMechanismProperties='AWS_SESSION_TOKEN:<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.

View File

@ -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

View File

@ -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."""

View File

@ -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')

View File

@ -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):

View File

@ -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)

View File

@ -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.

View File

@ -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})

View File

@ -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(

View File

@ -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):

View File

@ -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=[

View File

@ -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()