PYTHON-1184 - Don't require X.509 user with MongoDB 3.4

This commit is contained in:
Bernie Hackett 2016-11-16 14:31:14 -08:00
parent 807f719405
commit 1600059015
6 changed files with 64 additions and 28 deletions

View File

@ -14,6 +14,8 @@ Highlights include:
- Unicode aware string comparison using :doc:`examples/collations`.
- Support for the new :class:`~bson.decimal128.Decimal128` BSON type.
- A new maxStalenessSeconds read preference option.
- A username is no longer required for the MONGODB-X509 authentication
mechanism when connected to MongoDB >= 3.4.
- :meth:`~pymongo.collection.Collection.parallel_scan` supports maxTimeMS.
- :attr:`~pymongo.write_concern.WriteConcern` is automatically
applied by all helpers for commands that write to the database when

View File

@ -95,10 +95,10 @@ and newer::
>>> import ssl
>>> from pymongo import MongoClient
>>> client = MongoClient('example.com',
... ssl=True,
... ssl_certfile='/path/to/client.pem',
... ssl_cert_reqs=ssl.CERT_REQUIRED,
... ssl_ca_certs='/path/to/ca.pem')
... ssl=True,
... ssl_certfile='/path/to/client.pem',
... ssl_cert_reqs=ssl.CERT_REQUIRED,
... ssl_ca_certs='/path/to/ca.pem')
>>> client.the_database.authenticate("<X.509 derived username>",
... mechanism='MONGODB-X509')
True
@ -109,12 +109,15 @@ do not have to specify a database in the URI::
>>> uri = "mongodb://<X.509 derived username>@example.com/?authMechanism=MONGODB-X509"
>>> client = MongoClient(uri,
... ssl=True,
... ssl_certfile='/path/to/client.pem',
... ssl_cert_reqs=ssl.CERT_REQUIRED,
... ssl_ca_certs='/path/to/ca.pem')
... ssl=True,
... ssl_certfile='/path/to/client.pem',
... ssl_cert_reqs=ssl.CERT_REQUIRED,
... ssl_ca_certs='/path/to/ca.pem')
>>>
.. versionchanged:: 3.4
When connected to MongoDB >= 3.4 the username is no longer required.
.. _use_kerberos:
GSSAPI (Kerberos)

View File

@ -58,7 +58,7 @@ GSSAPIProperties = namedtuple('GSSAPIProperties',
def _build_credentials_tuple(mech, source, user, passwd, extra):
"""Build and return a mechanism specific credentials tuple.
"""
user = _unicode(user)
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', {})
@ -71,6 +71,7 @@ def _build_credentials_tuple(mech, source, user, passwd, extra):
# Source is always $external.
return MongoCredential(mech, '$external', user, password, props)
elif mech == 'MONGODB-X509':
# user can be None.
return MongoCredential(mech, '$external', user, None, None)
else:
if passwd is None:
@ -415,8 +416,13 @@ def _authenticate_x509(credentials, sock_info):
"""Authenticate using MONGODB-X509.
"""
query = SON([('authenticate', 1),
('mechanism', 'MONGODB-X509'),
('user', credentials.username)])
('mechanism', 'MONGODB-X509')])
if credentials.username is not None:
query['user'] = credentials.username
elif sock_info.max_wire_version < 5:
raise ConfigurationError(
"A username is required for MONGODB-X509 authentication "
"when connected to MongoDB versions older than 3.4.")
sock_info.command('$external', query)

View File

@ -29,9 +29,9 @@ from pymongo.write_concern import WriteConcern
def _parse_credentials(username, password, database, options):
"""Parse authentication credentials."""
if username is None:
return None
mechanism = options.get('authmechanism', 'DEFAULT')
if username is None and mechanism != 'MONGODB-X509':
return None
source = options.get('authsource', database or 'admin')
return _build_credentials_tuple(
mechanism, source, username, password, options)

View File

@ -966,7 +966,7 @@ class Database(common.BaseObject):
return
raise
def authenticate(self, name, password=None,
def authenticate(self, name=None, password=None,
source=None, mechanism='DEFAULT', **kwargs):
"""Authenticate to use this database.
@ -992,7 +992,9 @@ class Database(common.BaseObject):
distinct client instances.
:Parameters:
- `name`: the name of the user to authenticate.
- `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
@ -1017,7 +1019,7 @@ class Database(common.BaseObject):
.. mongodoc:: authenticate
"""
if not isinstance(name, string_type):
if name is not None and not isinstance(name, string_type):
raise TypeError("name must be an "
"instance of %s" % (string_type.__name__,))
if password is not None and not isinstance(password, string_type):

View File

@ -518,24 +518,47 @@ class TestSSL(IntegrationTest):
coll = ssl_client.pymongo_test.test
self.assertRaises(OperationFailure, coll.count)
if client_context.version.at_least(3, 3, 12):
self.assertTrue(
ssl_client.admin.authenticate(mechanism='MONGODB-X509'))
# No error
coll.find_one()
# MONGODB_X509_USERNAME and None aren't the same user, so we
# have to log out before continuing.
ssl_client.admin.logout()
else:
# Should require a username
with self.assertRaises(ConfigurationError):
ssl_client.admin.authenticate(mechanism='MONGODB-X509')
self.assertTrue(ssl_client.admin.authenticate(
MONGODB_X509_USERNAME, mechanism='MONGODB-X509'))
coll.drop()
# No error
coll.find_one()
uri = ('mongodb://%s@%s:%d/?authMechanism='
'MONGODB-X509' % (
quote_plus(MONGODB_X509_USERNAME), host, port))
# SSL options aren't supported in the URI...
self.assertTrue(MongoClient(uri, ssl=True,
ssl_cert_reqs=ssl.CERT_NONE,
ssl_certfile=CLIENT_PEM))
client = MongoClient(uri,
ssl=True,
ssl_cert_reqs=ssl.CERT_NONE,
ssl_certfile=CLIENT_PEM)
# No error
client.pymongo_test.test.find_one()
# Should require a username
uri = ('mongodb://%s:%d/?authMechanism=MONGODB-X509' % (host,
port))
client_bad = MongoClient(
uri, ssl=True, ssl_cert_reqs="CERT_NONE", ssl_certfile=CLIENT_PEM)
self.assertRaises(OperationFailure,
client_bad.pymongo_test.test.delete_one, {})
uri = 'mongodb://%s:%d/?authMechanism=MONGODB-X509' % (host, port)
client = MongoClient(uri,
ssl=True,
ssl_cert_reqs=ssl.CERT_NONE,
ssl_certfile=CLIENT_PEM)
if client_context.version.at_least(3, 3, 12):
# No error
client.pymongo_test.test.find_one()
else:
# Should require a username
with self.assertRaises(ConfigurationError):
client.pymongo_test.test.find_one()
# Auth should fail if username and certificate do not match
uri = ('mongodb://%s@%s:%d/?authMechanism='