PYTHON-768 - Support authMechanismProperties.

This change also deprecates the gssapiServiceName option, which
is replaced by authMechanismProperties=SERVICE_NAME:<service name>.
This commit is contained in:
Bernie Hackett 2014-10-29 13:37:05 -07:00
parent c101ed036c
commit d3e88ee8ed
4 changed files with 58 additions and 13 deletions

View File

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

View File

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

View File

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

View File

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