From d3e88ee8ed1e2f75988781e648c24740104f836c Mon Sep 17 00:00:00 2001 From: Bernie Hackett Date: Wed, 29 Oct 2014 13:37:05 -0700 Subject: [PATCH] PYTHON-768 - Support authMechanismProperties. This change also deprecates the gssapiServiceName option, which is replaced by authMechanismProperties=SERVICE_NAME:. --- doc/examples/authentication.rst | 6 +++--- pymongo/auth.py | 13 ++++++++++++- pymongo/common.py | 25 ++++++++++++++++++++++++- test/test_auth.py | 27 +++++++++++++++++++-------- 4 files changed, 58 insertions(+), 13 deletions(-) diff --git a/doc/examples/authentication.rst b/doc/examples/authentication.rst index 422d187c2..a6b5d2f21 100644 --- a/doc/examples/authentication.rst +++ b/doc/examples/authentication.rst @@ -144,15 +144,15 @@ or using :meth:`~pymongo.database.Database.authenticate`:: True The default service name used by MongoDB and PyMongo is `mongodb`. You can -specify a custom service name with the ``gssapiServiceName`` option:: +specify a custom service name with the ``authMechanismProperties`` option:: >>> from pymongo import MongoClient - >>> uri = "mongodb://mongodbuser%40EXAMPLE.COM@example.com/?authMechanism=GSSAPI&gssapiServiceName=myservicename" + >>> uri = "mongodb://mongodbuser%40EXAMPLE.COM@example.com/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:myservicename" >>> client = MongoClient(uri) >>> >>> client = MongoClient('example.com') >>> db = client.test - >>> db.authenticate('mongodbuser@EXAMPLE.COM', mechanism='GSSAPI', gssapiServiceName='myservicename') + >>> db.authenticate('mongodbuser@EXAMPLE.COM', mechanism='GSSAPI', authMechanismProperties='SERVICE_NAME:myservicename') True .. note:: diff --git a/pymongo/auth.py b/pymongo/auth.py index 9a75eb85f..214bb4071 100644 --- a/pymongo/auth.py +++ b/pymongo/auth.py @@ -15,6 +15,7 @@ """Authentication helpers.""" import hmac +import warnings try: import hashlib _MD5 = hashlib.md5 @@ -52,7 +53,17 @@ def _build_credentials_tuple(mech, source, user, passwd, extra): """Build and return a mechanism specific credentials tuple. """ if mech == 'GSSAPI': - gsn = extra.get('gssapiservicename', 'mongodb') + gsn = 'mongodb' + if "gssapiservicename" in extra: + gsn = extra.get('gssapiservicename') + msg = ('The gssapiServiceName option is deprecated. Use ' + '"authMechanismProperties=SERVICE_NAME:%s" instead.' % gsn) + warnings.warn(msg, DeprecationWarning, stacklevel=3) + # SERVICE_NAME overrides gssapiServiceName. + if 'authmechanismproperties' in extra: + props = extra['authmechanismproperties'] + if 'SERVICE_NAME' in props: + gsn = props.get('SERVICE_NAME') # No password, source is always $external. return (mech, '$external', user, gsn) elif mech == 'MONGODB-X509': diff --git a/pymongo/common.py b/pymongo/common.py index 9341ed99c..953435c50 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -265,6 +265,28 @@ def validate_uuid_subtype(dummy, value): return value +_MECHANISM_PROPS = frozenset(['SERVICE_NAME']) + + +def validate_auth_mechanism_properties(option, value): + """Validate authMechanismProperties.""" + value = validate_basestring(option, value) + props = {} + for opt in value.split(','): + try: + key, val = opt.split(':') + if key not in _MECHANISM_PROPS: + raise ConfigurationError("%s is not a supported auth " + "mechanism property. Must be one of " + "%s." % (key, tuple(_MECHANISM_PROPS))) + props[key] = val + except ValueError: + raise ConfigurationError("auth mechanism properties must be " + "key:value pairs like SERVICE_NAME:" + "mongodb, not %s." % (opt,)) + return props + + # jounal is an alias for j, # wtimeoutms is an alias for wtimeout, # readpreferencetags is an alias for tag_sets. @@ -299,12 +321,13 @@ VALIDATORS = { 'authmechanism': validate_auth_mechanism, 'authsource': validate_basestring, 'gssapiservicename': validate_basestring, + 'authmechanismproperties': validate_auth_mechanism_properties, 'uuidrepresentation': validate_uuid_representation, 'socketkeepalive': validate_boolean } -_AUTH_OPTIONS = frozenset(['gssapiservicename']) +_AUTH_OPTIONS = frozenset(['gssapiservicename', 'authmechanismproperties']) def validate_auth_option(option, value): diff --git a/test/test_auth.py b/test/test_auth.py index a566a0a29..64d216261 100644 --- a/test/test_auth.py +++ b/test/test_auth.py @@ -107,22 +107,27 @@ class TestGSSAPI(unittest.TestCase): # Without gssapiServiceName self.assertTrue(client.test.authenticate(PRINCIPAL, mechanism='GSSAPI')) - self.assertTrue(client.database_names()) + client.database_names() uri = ('mongodb://%s@%s:%d/?authMechanism=' 'GSSAPI' % (quote_plus(PRINCIPAL), GSSAPI_HOST, GSSAPI_PORT)) client = MongoClient(uri) - self.assertTrue(client.database_names()) + client.database_names() # With gssapiServiceName self.assertTrue(client.test.authenticate(PRINCIPAL, mechanism='GSSAPI', gssapiServiceName='mongodb')) - self.assertTrue(client.database_names()) + client.database_names() uri = ('mongodb://%s@%s:%d/?authMechanism=' 'GSSAPI;gssapiServiceName=mongodb' % (quote_plus(PRINCIPAL), GSSAPI_HOST, GSSAPI_PORT)) client = MongoClient(uri) - self.assertTrue(client.database_names()) + client.database_names() + uri = ('mongodb://%s@%s:%d/?authMechanism=' + 'GSSAPI;authMechanismProperties=SERVICE_NAME:mongodb' % ( + quote_plus(PRINCIPAL), GSSAPI_HOST, GSSAPI_PORT)) + client = MongoClient(uri) + client.database_names() set_name = client.admin.command('ismaster').get('setName') if set_name: @@ -132,25 +137,31 @@ class TestGSSAPI(unittest.TestCase): # Without gssapiServiceName self.assertTrue(client.test.authenticate(PRINCIPAL, mechanism='GSSAPI')) - self.assertTrue(client.database_names()) + client.database_names() uri = ('mongodb://%s@%s:%d/?authMechanism=GSSAPI;replicaSet' '=%s' % (quote_plus(PRINCIPAL), GSSAPI_HOST, GSSAPI_PORT, str(set_name))) client = MongoReplicaSetClient(uri) - self.assertTrue(client.database_names()) + client.database_names() # With gssapiServiceName self.assertTrue(client.test.authenticate(PRINCIPAL, mechanism='GSSAPI', gssapiServiceName='mongodb')) - self.assertTrue(client.database_names()) + client.database_names() uri = ('mongodb://%s@%s:%d/?authMechanism=GSSAPI;replicaSet' '=%s;gssapiServiceName=mongodb' % (quote_plus(PRINCIPAL), GSSAPI_HOST, GSSAPI_PORT, str(set_name))) client = MongoReplicaSetClient(uri) - self.assertTrue(client.database_names()) + client.database_names() + uri = ('mongodb://%s@%s:%d/?authMechanism=GSSAPI;replicaSet=%s;' + 'authMechanismProperties=SERVICE_NAME:mongodb' % ( + quote_plus(PRINCIPAL), + GSSAPI_HOST, GSSAPI_PORT, str(set_name))) + client = MongoReplicaSetClient(uri) + client.database_names() def test_gssapi_threaded(self):