Merge "Allow additional attribute mappings in ldap"
This commit is contained in:
commit
a78bc2e427
@ -226,6 +226,18 @@
|
||||
# tls_cacertdir =
|
||||
# tls_req_cert = demand
|
||||
|
||||
# Additional attribute mappings can be used to map ldap attributes to internal
|
||||
# keystone attributes. This allows keystone to fulfill ldap objectclass
|
||||
# requirements. An example to map the description and gecos attributes to a
|
||||
# user's name would be:
|
||||
# user_additional_attribute_mapping = description:name, gecos:name
|
||||
#
|
||||
# domain_additional_attribute_mapping =
|
||||
# group_additional_attribute_mapping =
|
||||
# role_additional_attribute_mapping =
|
||||
# project_additional_attribute_mapping =
|
||||
# user_additional_attribute_mapping =
|
||||
|
||||
[auth]
|
||||
methods = password,token
|
||||
password = keystone.auth.plugins.password.Password
|
||||
|
@ -314,6 +314,8 @@ def configure():
|
||||
register_bool('user_allow_delete', group='ldap', default=True)
|
||||
register_bool('user_enabled_emulation', group='ldap', default=False)
|
||||
register_str('user_enabled_emulation_dn', group='ldap', default=None)
|
||||
register_list(
|
||||
'user_additional_attribute_mapping', group='ldap', default=None)
|
||||
|
||||
register_str('tenant_tree_dn', group='ldap', default=None)
|
||||
register_str('tenant_filter', group='ldap', default=None)
|
||||
@ -331,6 +333,8 @@ def configure():
|
||||
register_bool('tenant_allow_delete', group='ldap', default=True)
|
||||
register_bool('tenant_enabled_emulation', group='ldap', default=False)
|
||||
register_str('tenant_enabled_emulation_dn', group='ldap', default=None)
|
||||
register_list(
|
||||
'tenant_additional_attribute_mapping', group='ldap', default=None)
|
||||
|
||||
register_str('role_tree_dn', group='ldap', default=None)
|
||||
register_str('role_filter', group='ldap', default=None)
|
||||
@ -343,6 +347,8 @@ def configure():
|
||||
register_bool('role_allow_create', group='ldap', default=True)
|
||||
register_bool('role_allow_update', group='ldap', default=True)
|
||||
register_bool('role_allow_delete', group='ldap', default=True)
|
||||
register_list(
|
||||
'role_additional_attribute_mapping', group='ldap', default=None)
|
||||
|
||||
register_str('group_tree_dn', group='ldap', default=None)
|
||||
register_str('group_filter', group='ldap', default=None)
|
||||
@ -357,6 +363,8 @@ def configure():
|
||||
register_bool('group_allow_create', group='ldap', default=True)
|
||||
register_bool('group_allow_update', group='ldap', default=True)
|
||||
register_bool('group_allow_delete', group='ldap', default=True)
|
||||
register_list(
|
||||
'group_additional_attribute_mapping', group='ldap', default=None)
|
||||
|
||||
register_str('domain_tree_dn', group='ldap', default=None)
|
||||
register_str('domain_filter', group='ldap', default=None)
|
||||
@ -372,6 +380,9 @@ def configure():
|
||||
register_bool('domain_allow_delete', group='ldap', default=True)
|
||||
register_bool('domain_enabled_emulation', group='ldap', default=False)
|
||||
register_str('domain_enabled_emulation_dn', group='ldap', default=None)
|
||||
register_list(
|
||||
'domain_additional_attribute_mapping', group='ldap', default=None)
|
||||
|
||||
register_str('tls_cacertfile', group='ldap', default=None)
|
||||
register_str('tls_cacertdir', group='ldap', default=None)
|
||||
register_bool('use_tls', group='ldap', default=False)
|
||||
|
@ -104,6 +104,7 @@ class BaseLdap(object):
|
||||
DEFAULT_ID_ATTR = 'cn'
|
||||
DEFAULT_OBJECTCLASS = None
|
||||
DEFAULT_FILTER = None
|
||||
DEFAULT_EXTRA_ATTR_MAPPING = []
|
||||
DUMB_MEMBER_DN = 'cn=dumb,dc=nonexistent'
|
||||
NotFound = None
|
||||
notfound_arg = None
|
||||
@ -140,6 +141,12 @@ class BaseLdap(object):
|
||||
self.object_class = (getattr(conf.ldap, objclass)
|
||||
or self.DEFAULT_OBJECTCLASS)
|
||||
|
||||
attr_mapping_opt = ('%s_additional_attribute_mapping' %
|
||||
self.options_name)
|
||||
attr_mapping = (getattr(conf.ldap, attr_mapping_opt)
|
||||
or self.DEFAULT_EXTRA_ATTR_MAPPING)
|
||||
self.extra_attr_mapping = self._parse_extra_attrs(attr_mapping)
|
||||
|
||||
filter = '%s_filter' % self.options_name
|
||||
self.filter = getattr(conf.ldap, filter) or self.DEFAULT_FILTER
|
||||
|
||||
@ -169,6 +176,25 @@ class BaseLdap(object):
|
||||
else:
|
||||
return self.NotFound(**{self.notfound_arg: object_id})
|
||||
|
||||
def _parse_extra_attrs(self, option_list):
|
||||
mapping = {}
|
||||
for item in option_list:
|
||||
try:
|
||||
ldap_attr, attr_map = item.split(':')
|
||||
except Exception:
|
||||
LOG.warn(_('Invalid additional attribute mapping: "%s". '
|
||||
'Format must be ' +
|
||||
'<ldap_attribute>:<keystone_attribute>') % item)
|
||||
continue
|
||||
if attr_map not in self.attribute_mapping:
|
||||
LOG.warn(_('Invalid additional attribute mapping: "%(item)s". '
|
||||
'Value "%(attr_map)s" must use one of %(keys)s.') %
|
||||
{'item': item, 'attr_map': attr_map,
|
||||
'keys': ', '.join(self.attribute_mapping.keys())})
|
||||
continue
|
||||
mapping[ldap_attr] = attr_map
|
||||
return mapping
|
||||
|
||||
def get_connection(self, user=None, password=None):
|
||||
if self.LDAP_URL.startswith('fake://'):
|
||||
conn = fakeldap.FakeLdap(self.LDAP_URL)
|
||||
@ -272,6 +298,11 @@ class BaseLdap(object):
|
||||
if v is not None:
|
||||
attr_type = self.attribute_mapping.get(k, k)
|
||||
attrs.append((attr_type, [v]))
|
||||
extra_attrs = [attr for attr, name
|
||||
in self.extra_attr_mapping.iteritems()
|
||||
if name == k]
|
||||
for attr in extra_attrs:
|
||||
attrs.append((attr, [v]))
|
||||
|
||||
if 'groupOfNames' in object_classes and self.use_dumb_member:
|
||||
attrs.append(('member', [self.dumb_member]))
|
||||
@ -289,8 +320,9 @@ class BaseLdap(object):
|
||||
'filter': (filter or self.filter or ''),
|
||||
'object_class': self.object_class})
|
||||
try:
|
||||
res = conn.search_s(self.tree_dn, self.LDAP_SCOPE, query,
|
||||
self.attribute_mapping.values())
|
||||
attrs = list(set((self.attribute_mapping.values() +
|
||||
self.extra_attr_mapping.keys())))
|
||||
res = conn.search_s(self.tree_dn, self.LDAP_SCOPE, query, attrs)
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
return None
|
||||
try:
|
||||
|
@ -362,6 +362,26 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests):
|
||||
'Invalid LDAP deref option: %s\.' % CONF.ldap.alias_dereferencing,
|
||||
identity.backends.ldap.Identity)
|
||||
|
||||
def test_user_extra_attribute_mapping(self):
|
||||
CONF.ldap.user_additional_attribute_mapping = ['description:name']
|
||||
self.identity_api = identity.backends.ldap.Identity()
|
||||
user = {
|
||||
'id': 'extra_attributes',
|
||||
'name': 'EXTRA_ATTRIBUTES',
|
||||
'password': 'extra',
|
||||
}
|
||||
self.identity_api.create_user(user['id'], user)
|
||||
dn, attrs = self.identity_api.user._ldap_get(user['id'])
|
||||
self.assertTrue(user['name'] in attrs['description'])
|
||||
|
||||
def test_parse_extra_attribute_mapping(self):
|
||||
option_list = ['description:name', 'gecos:password',
|
||||
'fake:invalid', 'invalid1', 'invalid2:',
|
||||
'description:name:something']
|
||||
mapping = self.identity_api.user._parse_extra_attrs(option_list)
|
||||
expected_dict = {'description': 'name', 'gecos': 'password'}
|
||||
self.assertDictEqual(expected_dict, mapping)
|
||||
|
||||
# TODO (henry-nash) These need to be removed when the full LDAP implementation
|
||||
# is submitted - see Bugs 1092187, 1101287, 1101276, 1101289
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user