From 5b9257644f96cc377fb2239f8df6de8eed6a128b Mon Sep 17 00:00:00 2001 From: Bernie Hackett Date: Tue, 20 Feb 2018 21:55:22 -0800 Subject: [PATCH] PYTHON-1464 - Implement SCRAM-SHA-256 --- pymongo/auth.py | 94 +++++++++++++++++--------- pymongo/database.py | 13 +++- pymongo/saslprep.py | 2 + test/__init__.py | 14 ++-- test/test_auth.py | 152 +++++++++++++++++++++++++++++++++++++++++- test/test_database.py | 73 ++++++++------------ test/test_session.py | 8 +-- test/utils.py | 4 +- 8 files changed, 261 insertions(+), 99 deletions(-) diff --git a/pymongo/auth.py b/pymongo/auth.py index a7a69aea5..6f2586adc 100644 --- a/pymongo/auth.py +++ b/pymongo/auth.py @@ -14,6 +14,7 @@ """Authentication helpers.""" +import functools import hmac import socket @@ -36,17 +37,24 @@ except ImportError: from base64 import standard_b64decode, standard_b64encode from collections import namedtuple -from hashlib import md5, sha1 +from hashlib import md5, sha1, sha256 from random import SystemRandom from bson.binary import Binary from bson.py3compat import b, string_type, _unicode, PY3 from bson.son import SON from pymongo.errors import ConfigurationError, OperationFailure +from pymongo.saslprep import saslprep MECHANISMS = frozenset( - ['GSSAPI', 'MONGODB-CR', 'MONGODB-X509', 'PLAIN', 'SCRAM-SHA-1', 'DEFAULT']) + ['GSSAPI', + 'MONGODB-CR', + 'MONGODB-X509', + 'PLAIN', + 'SCRAM-SHA-1', + 'SCRAM-SHA-256', + 'DEFAULT']) """The authentication mechanisms supported by PyMongo.""" @@ -66,8 +74,6 @@ GSSAPIProperties = namedtuple('GSSAPIProperties', def _build_credentials_tuple(mech, source, user, passwd, extra): """Build and return a mechanism specific credentials tuple. """ - user = _unicode(user) if user is not None else None - password = passwd if passwd is None else _unicode(passwd) if mech == 'GSSAPI': properties = extra.get('authmechanismproperties', {}) service_name = properties.get('SERVICE_NAME', 'mongodb') @@ -77,14 +83,14 @@ def _build_credentials_tuple(mech, source, user, passwd, extra): canonicalize_host_name=canonicalize, service_realm=service_realm) # Source is always $external. - return MongoCredential(mech, '$external', user, password, props) + return MongoCredential(mech, '$external', user, passwd, props) elif mech == 'MONGODB-X509': # user can be None. return MongoCredential(mech, '$external', user, None, None) else: if passwd is None: raise ConfigurationError("A password is required.") - return MongoCredential(mech, source, user, password, None) + return MongoCredential(mech, source, user, passwd, None) if PY3: @@ -110,31 +116,32 @@ else: return int(_hexlify(value), 16) - def _to_bytes(value, dummy0, dummy1, _unhexlify=_unhexlify): + def _to_bytes(value, length, dummy, _unhexlify=_unhexlify): """An implementation of int.to_bytes for python 2.x.""" - return _unhexlify('%040x' % value) + fmt = '%%0%dx' % (2 * length,) + return _unhexlify(fmt % value) try: # The fastest option, if it's been compiled to use OpenSSL's HMAC. from backports.pbkdf2 import pbkdf2_hmac - def _hi(data, salt, iterations): - return pbkdf2_hmac('sha1', data, salt, iterations) + def _hi(data, salt, iterations, digestmod): + return pbkdf2_hmac(digestmod().name, data, salt, iterations) except ImportError: try: # Python 2.7.8+, or Python 3.4+. from hashlib import pbkdf2_hmac - def _hi(data, salt, iterations): - return pbkdf2_hmac('sha1', data, salt, iterations) + def _hi(data, salt, iterations, digestmod): + return pbkdf2_hmac(digestmod().name, data, salt, iterations) except ImportError: - def _hi(data, salt, iterations): - """A simple implementation of PBKDF2.""" - mac = hmac.HMAC(data, None, sha1) + def _hi(data, salt, iterations, digestmod): + """A simple implementation of PBKDF2-HMAC.""" + mac = hmac.HMAC(data, None, digestmod) def _digest(msg, mac=mac): """Get a digest for msg.""" @@ -150,7 +157,7 @@ except ImportError: for _ in range(iterations - 1): _u1 = _digest(_u1) _ui ^= from_bytes(_u1, 'big') - return to_bytes(_ui, 20, 'big') + return to_bytes(_ui, mac.digest_size, 'big') try: from hmac import compare_digest @@ -187,15 +194,21 @@ def _parse_scram_response(response): return dict(item.split(b"=", 1) for item in response.split(b",")) -def _authenticate_scram_sha1(credentials, sock_info): - """Authenticate using SCRAM-SHA-1.""" - username = credentials.username - password = credentials.password +def _authenticate_scram(credentials, sock_info, mechanism): + """Authenticate using SCRAM.""" + + if mechanism == 'SCRAM-SHA-256': + digestmod = sha256 + username = saslprep(credentials.username) + data = saslprep(credentials.password).encode("utf-8") + else: + digestmod = sha1 + username = credentials.username + data = _password_digest(username, credentials.password).encode("utf-8") source = credentials.source # Make local _hmac = hmac.HMAC - _sha1 = sha1 user = username.encode("utf-8").replace(b"=", b"=3D").replace(b",", b"=2C") nonce = standard_b64encode( @@ -203,7 +216,7 @@ def _authenticate_scram_sha1(credentials, sock_info): first_bare = b"n=" + user + b",r=" + nonce cmd = SON([('saslStart', 1), - ('mechanism', 'SCRAM-SHA-1'), + ('mechanism', mechanism), ('payload', Binary(b"n,," + first_bare)), ('autoAuthorize', 1)]) res = sock_info.command(source, cmd) @@ -211,25 +224,26 @@ def _authenticate_scram_sha1(credentials, sock_info): server_first = res['payload'] parsed = _parse_scram_response(server_first) iterations = int(parsed[b'i']) + if iterations < 4096: + raise OperationFailure("Server returned an invalid iteration count.") salt = parsed[b's'] rnonce = parsed[b'r'] if not rnonce.startswith(nonce): raise OperationFailure("Server returned an invalid nonce.") without_proof = b"c=biws,r=" + rnonce - salted_pass = _hi(_password_digest(username, password).encode("utf-8"), - standard_b64decode(salt), - iterations) - client_key = _hmac(salted_pass, b"Client Key", _sha1).digest() - stored_key = _sha1(client_key).digest() + salted_pass = _hi( + data, standard_b64decode(salt), iterations, digestmod) + client_key = _hmac(salted_pass, b"Client Key", digestmod).digest() + stored_key = digestmod(client_key).digest() auth_msg = b",".join((first_bare, server_first, without_proof)) - client_sig = _hmac(stored_key, auth_msg, _sha1).digest() + client_sig = _hmac(stored_key, auth_msg, digestmod).digest() client_proof = b"p=" + standard_b64encode(_xor(client_key, client_sig)) client_final = b",".join((without_proof, client_proof)) - server_key = _hmac(salted_pass, b"Server Key", _sha1).digest() + server_key = _hmac(salted_pass, b"Server Key", digestmod).digest() server_sig = standard_b64encode( - _hmac(server_key, auth_msg, _sha1).digest()) + _hmac(server_key, auth_msg, digestmod).digest()) cmd = SON([('saslContinue', 1), ('conversationId', res['conversationId']), @@ -462,8 +476,19 @@ def _authenticate_mongo_cr(credentials, sock_info): def _authenticate_default(credentials, sock_info): - if sock_info.max_wire_version >= 3: - return _authenticate_scram_sha1(credentials, sock_info) + if sock_info.max_wire_version >= 7: + source = credentials.source + cmd = SON([ + ('ismaster', 1), + ('saslSupportedMechs', source + '.' + credentials.username)]) + mechs = sock_info.command( + source, cmd, publish_events=False).get('saslSupportedMechs', []) + if 'SCRAM-SHA-256' in mechs: + return _authenticate_scram(credentials, sock_info, 'SCRAM-SHA-256') + else: + return _authenticate_scram(credentials, sock_info, 'SCRAM-SHA-1') + elif sock_info.max_wire_version >= 3: + return _authenticate_scram(credentials, sock_info, 'SCRAM-SHA-1') else: return _authenticate_mongo_cr(credentials, sock_info) @@ -474,7 +499,10 @@ _AUTH_MAP = { 'MONGODB-CR': _authenticate_mongo_cr, 'MONGODB-X509': _authenticate_x509, 'PLAIN': _authenticate_plain, - 'SCRAM-SHA-1': _authenticate_scram_sha1, + 'SCRAM-SHA-1': functools.partial( + _authenticate_scram, mechanism='SCRAM-SHA-1'), + 'SCRAM-SHA-256': functools.partial( + _authenticate_scram, mechanism='SCRAM-SHA-256'), 'DEFAULT': _authenticate_default, } diff --git a/pymongo/database.py b/pymongo/database.py index 54a39429f..a80f4a231 100644 --- a/pymongo/database.py +++ b/pymongo/database.py @@ -939,14 +939,12 @@ class Database(common.BaseObject): ">= 2.6, use 'roles' instead", DeprecationWarning) if password is not None: - # We always salt and hash client side. if "digestPassword" in kwargs: raise ConfigurationError("The digestPassword option is not " "supported via add_user. Please use " "db.command('createUser', ...) " "instead for this option.") - opts["pwd"] = auth._password_digest(name, password) - opts["digestPassword"] = False + opts["pwd"] = password # Don't send {} as writeConcern. if self.write_concern.acknowledged and self.write_concern.document: @@ -994,6 +992,9 @@ class Database(common.BaseObject): .. _updateUser: https://docs.mongodb.com/manual/reference/command/updateUser/ .. _dropUser: https://docs.mongodb.com/manual/reference/command/createUser/ + .. warning:: Never create or modify users over an insecure network without + the use of TLS. See :doc:`/examples/tls` for more information. + :Parameters: - `name`: the name of the user to create - `password` (optional): the password of the user to create. Can not @@ -1006,6 +1007,9 @@ class Database(common.BaseObject): - `session` (optional): a :class:`~pymongo.client_session.ClientSession`. + .. versionchanged:: 3.7 + Added support for SCRAM-SHA-256 users with MongoDB 4.0 and later. + .. versionchanged:: 3.6 Added ``session`` parameter. Deprecated add_user. @@ -1122,6 +1126,9 @@ class Database(common.BaseObject): name for GSSAPI authentication pass authMechanismProperties='SERVICE_NAME:' + .. 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, diff --git a/pymongo/saslprep.py b/pymongo/saslprep.py index 3160b6524..baa1a4066 100644 --- a/pymongo/saslprep.py +++ b/pymongo/saslprep.py @@ -19,6 +19,7 @@ from bson.py3compat import text_type as _text_type try: import stringprep except ImportError: + HAVE_STRINGPREP = False def saslprep(data): """SASLprep dummy""" if isinstance(data, _text_type): @@ -27,6 +28,7 @@ except ImportError: "passwords must be ASCII strings.") return data else: + HAVE_STRINGPREP = True import unicodedata # RFC4013 section 2.3 prohibited output. _PROHIBITED = ( diff --git a/test/__init__.py b/test/__init__.py index ff119db2b..d0a52a689 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -50,14 +50,12 @@ if HAVE_SSL: import ssl # The host and port of a single mongod or mongos, or the seed host -# for a replica set. Hostnames retrieved from isMaster will be of -# unicode type in Python 2, so ensure these hostnames are unicodes, -# too. It makes tests like `test_repr` predictable. -host = _unicode(os.environ.get("DB_IP", 'localhost')) +# for a replica set. +host = os.environ.get("DB_IP", 'localhost') port = int(os.environ.get("DB_PORT", 27017)) -db_user = _unicode(os.environ.get("DB_USER", "user")) -db_pwd = _unicode(os.environ.get("DB_PASSWORD", "password")) +db_user = os.environ.get("DB_USER", "user") +db_pwd = os.environ.get("DB_PASSWORD", "password") CERT_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'certificates') @@ -234,7 +232,7 @@ class ClientContext(object): self.sessions_enabled = 'logicalSessionTimeoutMinutes' in ismaster if 'setName' in ismaster: - self.replica_set_name = ismaster['setName'] + self.replica_set_name = str(ismaster['setName']) self.is_rs = True if self.auth_enabled: # It doesn't matter which member we use as the seed here. @@ -314,7 +312,7 @@ class ClientContext(object): def host(self): if self.is_rs: primary = self.client.primary - return primary[0] if primary is not None else host + return str(primary[0]) if primary is not None else host return host @property diff --git a/test/test_auth.py b/test/test_auth.py index 2d383a8e1..53648df26 100644 --- a/test/test_auth.py +++ b/test/test_auth.py @@ -26,15 +26,17 @@ except ImportError: sys.path[0:0] = [""] -from pymongo import MongoClient +from pymongo import MongoClient, monitoring from pymongo.auth import HAVE_KERBEROS, _build_credentials_tuple from pymongo.errors import OperationFailure from pymongo.read_preferences import ReadPreference +from pymongo.saslprep import saslprep, HAVE_STRINGPREP from test import client_context, SkipTest, unittest, Version from test.utils import (delay, ignore_deprecations, rs_or_single_client_noauth, - single_client_noauth) + single_client_noauth, + WhiteListEventListener) # YOU MUST RUN KINIT BEFORE RUNNING GSSAPI TESTS ON UNIX. GSSAPI_HOST = os.environ.get('GSSAPI_HOST') @@ -366,6 +368,152 @@ class TestSCRAMSHA1(unittest.TestCase): db.command('dbstats') +class TestSCRAM(unittest.TestCase): + + @client_context.require_auth + @client_context.require_version_min(3, 7, 2) + def setUp(self): + self._SENSITIVE_COMMANDS = monitoring._SENSITIVE_COMMANDS + monitoring._SENSITIVE_COMMANDS = set([]) + self.listener = WhiteListEventListener("saslStart") + + def tearDown(self): + monitoring._SENSITIVE_COMMANDS = self._SENSITIVE_COMMANDS + client_context.client.testscram.command("dropAllUsersFromDatabase") + client_context.client.drop_database("testscram") + + def test_scram(self): + host, port = client_context.host, client_context.port + + client_context.create_user( + 'testscram', + 'sha1', + 'pwd', + roles=['dbOwner'], + mechanisms=['SCRAM-SHA-1']) + + client_context.create_user( + 'testscram', + 'sha256', + 'pwd', + roles=['dbOwner'], + mechanisms=['SCRAM-SHA-256']) + + client_context.create_user( + 'testscram', + 'both', + 'pwd', + roles=['dbOwner'], + mechanisms=['SCRAM-SHA-1', 'SCRAM-SHA-256']) + + client = rs_or_single_client_noauth( + event_listeners=[self.listener]) + self.assertTrue( + client.testscram.authenticate('sha1', 'pwd')) + client.testscram.command('dbstats') + client.testscram.logout() + self.assertTrue( + client.testscram.authenticate( + 'sha1', 'pwd', mechanism='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.testscram.command('dbstats') + client.testscram.logout() + self.assertTrue( + client.testscram.authenticate( + 'sha256', 'pwd', mechanism='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') + client.testscram.command('dbstats') + client.testscram.logout() + self.assertTrue( + client.testscram.authenticate( + 'both', 'pwd', mechanism='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: + client_context.create_user( + 'testscram', + saslprep(u'\u2168'), + u'\u2168', + roles=['dbOwner'], + mechanisms=['SCRAM-SHA-256']) + + self.assertTrue( + client.testscram.authenticate(u'\u2168', u'\u2168')) + client.testscram.command('dbstats') + client.testscram.logout() + self.assertTrue( + client.testscram.authenticate( + u'\u2168', u'\u2168', mechanism='SCRAM-SHA-256')) + client.testscram.command('dbstats') + client.testscram.logout() + self.assertRaises( + OperationFailure, + client.testscram.authenticate, + u'\u2168', u'\u2168', mechanism='SCRAM-SHA-1') + + client = rs_or_single_client_noauth( + u'mongodb://\u2168:\u2168@%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), + event_listeners=[self.listener]) + client.testscram.command('dbstats') + started = self.listener.results['started'][0] + self.assertEqual(started.command.get('mechanism'), 'SCRAM-SHA-256') + + client = rs_or_single_client_noauth( + 'mongodb://both:pwd@%s:%d/testscram?authMechanism=SCRAM-SHA-1' + % (host, port)) + 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') + + if client_context.is_rs: + uri = ('mongodb://both:pwd@%s:%d/testscram' + '?replicaSet=%s' % (host, port, + client_context.replica_set_name)) + client = single_client_noauth(uri) + client.testscram.command('dbstats') + db = client.get_database( + 'testscram', read_preference=ReadPreference.SECONDARY) + db.command('dbstats') + + class TestAuthURIOptions(unittest.TestCase): @client_context.require_auth diff --git a/test/test_database.py b/test/test_database.py index 17ea9ba94..c70329916 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -45,6 +45,7 @@ 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, @@ -503,12 +504,8 @@ class TestDatabase(IntegrationTest): self.assertRaises(ConfigurationError, auth_db.add_user, "user", "password", digestPassword=True) - extra = {} - if client_context.version.at_least(3, 7, 2): - extra['mechanisms'] = ['SCRAM-SHA-1'] - # Add / authenticate / remove - auth_db.add_user("mike", "password", roles=["read"], **extra) + auth_db.add_user("mike", "password", roles=["read"]) self.addCleanup(remove_all_users, auth_db) self.assertRaises(TypeError, check_auth, 5, "password") self.assertRaises(TypeError, check_auth, "mike", 5) @@ -517,35 +514,34 @@ class TestDatabase(IntegrationTest): self.assertRaises(OperationFailure, check_auth, "faker", "password") check_auth("mike", "password") - # Unicode name and password. - check_auth(u"mike", u"password") + if not client_context.version.at_least(3, 7, 2) or HAVE_STRINGPREP: + # Unicode name and password. + check_auth(u"mike", u"password") - auth_db.remove_user("mike") - self.assertRaises(OperationFailure, - check_auth, "mike", "password") + auth_db.remove_user("mike") + self.assertRaises( + OperationFailure, check_auth, "mike", "password") - # Add / authenticate / change password - self.assertRaises(OperationFailure, check_auth, "Gustave", u"Dor\xe9") - auth_db.add_user("Gustave", u"Dor\xe9", roles=["read"], **extra) - check_auth("Gustave", u"Dor\xe9") + # Add / authenticate / change password + self.assertRaises( + OperationFailure, check_auth, "Gustave", u"Dor\xe9") + auth_db.add_user("Gustave", u"Dor\xe9", roles=["read"]) + check_auth("Gustave", u"Dor\xe9") - # Change password. - auth_db.add_user("Gustave", "password", roles=["read"], **extra) - self.assertRaises(OperationFailure, check_auth, "Gustave", u"Dor\xe9") - check_auth("Gustave", u"password") + # Change password. + auth_db.add_user("Gustave", "password", roles=["read"]) + self.assertRaises( + OperationFailure, check_auth, "Gustave", u"Dor\xe9") + check_auth("Gustave", u"password") @client_context.require_auth @ignore_deprecations def test_make_user_readonly(self): - extra = {} - if client_context.version.at_least(3, 7, 2): - extra['mechanisms'] = ['SCRAM-SHA-1'] - # "self.client" is logged in as root. auth_db = self.client.pymongo_test # Make a read-write user. - auth_db.add_user('jesse', 'pw', **extra) + auth_db.add_user('jesse', 'pw') self.addCleanup(remove_all_users, auth_db) # Check that we're read-write by default. @@ -556,7 +552,7 @@ class TestDatabase(IntegrationTest): c.pymongo_test.collection.insert_one({}) # Make the user read-only. - auth_db.add_user('jesse', 'pw', read_only=True, **extra) + auth_db.add_user('jesse', 'pw', read_only=True) c = rs_or_single_client_noauth(username='jesse', password='pw', @@ -569,13 +565,9 @@ class TestDatabase(IntegrationTest): @client_context.require_auth @ignore_deprecations def test_default_roles(self): - extra = {} - if client_context.version.at_least(3, 7, 2): - extra['mechanisms'] = ['SCRAM-SHA-1'] - # "self.client" is logged in as root. auth_admin = self.client.admin - auth_admin.add_user('test_default_roles', 'pass', **extra) + auth_admin.add_user('test_default_roles', 'pass') self.addCleanup(client_context.drop_user, 'admin', 'test_default_roles') info = auth_admin.command( 'usersInfo', 'test_default_roles')['users'][0] @@ -583,33 +575,29 @@ class TestDatabase(IntegrationTest): self.assertEqual("root", info['roles'][0]['role']) # Read only "admin" user - auth_admin.add_user('ro-admin', 'pass', read_only=True, **extra) + auth_admin.add_user('ro-admin', 'pass', read_only=True) self.addCleanup(client_context.drop_user, 'admin', 'ro-admin') info = auth_admin.command('usersInfo', 'ro-admin')['users'][0] self.assertEqual("readAnyDatabase", info['roles'][0]['role']) # "Non-admin" user auth_db = self.client.pymongo_test - auth_db.add_user('user', 'pass', **extra) + auth_db.add_user('user', 'pass') self.addCleanup(remove_all_users, auth_db) info = auth_db.command('usersInfo', 'user')['users'][0] self.assertEqual("dbOwner", info['roles'][0]['role']) # Read only "Non-admin" user - auth_db.add_user('ro-user', 'pass', read_only=True, **extra) + auth_db.add_user('ro-user', 'pass', read_only=True) info = auth_db.command('usersInfo', 'ro-user')['users'][0] self.assertEqual("read", info['roles'][0]['role']) @client_context.require_auth @ignore_deprecations def test_new_user_cmds(self): - extra = {} - if client_context.version.at_least(3, 7, 2): - extra['mechanisms'] = ['SCRAM-SHA-1'] - # "self.client" is logged in as root. auth_db = self.client.pymongo_test - auth_db.add_user("amalia", "password", roles=["userAdmin"], **extra) + auth_db.add_user("amalia", "password", roles=["userAdmin"]) self.addCleanup(client_context.drop_user, "pymongo_test", "amalia") db = rs_or_single_client_noauth(username="amalia", @@ -618,7 +606,7 @@ class TestDatabase(IntegrationTest): # This tests the ability to update user attributes. db.add_user("amalia", "new_password", - customData={"secret": "koalas"}, **extra) + customData={"secret": "koalas"}) user_info = db.command("usersInfo", "amalia") self.assertTrue(user_info["users"]) @@ -629,10 +617,6 @@ class TestDatabase(IntegrationTest): @client_context.require_auth @ignore_deprecations def test_authenticate_multiple(self): - extra = {} - if client_context.version.at_least(3, 7, 2): - extra['mechanisms'] = ['SCRAM-SHA-1'] - # "self.client" is logged in as root. self.client.drop_database("pymongo_test") self.client.drop_database("pymongo_test1") @@ -650,12 +634,11 @@ class TestDatabase(IntegrationTest): admin_db_auth.add_user( 'ro-admin', 'pass', - roles=["userAdmin", "readAnyDatabase"], - **extra) + roles=["userAdmin", "readAnyDatabase"]) self.addCleanup(client_context.drop_user, 'admin', 'ro-admin') users_db_auth.add_user( - 'user', 'pass', roles=["userAdmin", "readWrite"], **extra) + 'user', 'pass', roles=["userAdmin", "readWrite"]) self.addCleanup(remove_all_users, users_db_auth) # Regular user should be able to query its own db, but diff --git a/test/test_session.py b/test/test_session.py index 006c91fc0..9dd48c36d 100644 --- a/test/test_session.py +++ b/test/test_session.py @@ -273,15 +273,11 @@ class TestSession(IntegrationTest): client = self.client db = client.pymongo_test - extra = {'roles': ['read']} - if client_context.version.at_least(3, 7, 2): - extra['mechanisms'] = ['SCRAM-SHA-1'] - self._test_ops( client, - (db.add_user, ['session-test', 'pass'], extra), + (db.add_user, ['session-test', 'pass'], {'roles': ['read']}), # Do it again to test updateUser command. - (db.add_user, ['session-test', 'pass'], extra), + (db.add_user, ['session-test', 'pass'], {'roles': ['read']}), (db.remove_user, ['session-test'], {})) def test_collection(self): diff --git a/test/utils.py b/test/utils.py index d97469f48..79582fc6d 100644 --- a/test/utils.py +++ b/test/utils.py @@ -111,9 +111,9 @@ def _connection_string(h, p, authenticate): if h.startswith("mongodb://"): return h elif client_context.auth_enabled and authenticate: - return "mongodb://%s:%s@%s:%d" % (db_user, db_pwd, h, p) + return "mongodb://%s:%s@%s:%d" % (db_user, db_pwd, str(h), p) else: - return "mongodb://%s:%d" % (h, p) + return "mongodb://%s:%d" % (str(h), p) def _mongo_client(host, port, authenticate=True, direct=False, **kwargs):