Allow additional attribute mappings in ldap
This is needed as a work around for objectclasses that require additional attributes other than just what is supplied in user_id_attribute and user_name_attribute. Change-Id: Ie6cdd0534b8389f62f98fdca7d19bc0feb9c131f Fixes: bug #1158077
This commit is contained in:
parent
b4db547d0a
commit
f452c3d6b1
@ -226,6 +226,18 @@
|
|||||||
# tls_cacertdir =
|
# tls_cacertdir =
|
||||||
# tls_req_cert = demand
|
# 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]
|
[auth]
|
||||||
methods = password,token
|
methods = password,token
|
||||||
password = keystone.auth.plugins.password.Password
|
password = keystone.auth.plugins.password.Password
|
||||||
|
@ -314,6 +314,8 @@ def configure():
|
|||||||
register_bool('user_allow_delete', group='ldap', default=True)
|
register_bool('user_allow_delete', group='ldap', default=True)
|
||||||
register_bool('user_enabled_emulation', group='ldap', default=False)
|
register_bool('user_enabled_emulation', group='ldap', default=False)
|
||||||
register_str('user_enabled_emulation_dn', group='ldap', default=None)
|
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_tree_dn', group='ldap', default=None)
|
||||||
register_str('tenant_filter', 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_allow_delete', group='ldap', default=True)
|
||||||
register_bool('tenant_enabled_emulation', group='ldap', default=False)
|
register_bool('tenant_enabled_emulation', group='ldap', default=False)
|
||||||
register_str('tenant_enabled_emulation_dn', group='ldap', default=None)
|
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_tree_dn', group='ldap', default=None)
|
||||||
register_str('role_filter', 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_create', group='ldap', default=True)
|
||||||
register_bool('role_allow_update', group='ldap', default=True)
|
register_bool('role_allow_update', group='ldap', default=True)
|
||||||
register_bool('role_allow_delete', 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_tree_dn', group='ldap', default=None)
|
||||||
register_str('group_filter', 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_create', group='ldap', default=True)
|
||||||
register_bool('group_allow_update', group='ldap', default=True)
|
register_bool('group_allow_update', group='ldap', default=True)
|
||||||
register_bool('group_allow_delete', 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_tree_dn', group='ldap', default=None)
|
||||||
register_str('domain_filter', 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_allow_delete', group='ldap', default=True)
|
||||||
register_bool('domain_enabled_emulation', group='ldap', default=False)
|
register_bool('domain_enabled_emulation', group='ldap', default=False)
|
||||||
register_str('domain_enabled_emulation_dn', group='ldap', default=None)
|
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_cacertfile', group='ldap', default=None)
|
||||||
register_str('tls_cacertdir', group='ldap', default=None)
|
register_str('tls_cacertdir', group='ldap', default=None)
|
||||||
register_bool('use_tls', group='ldap', default=False)
|
register_bool('use_tls', group='ldap', default=False)
|
||||||
|
@ -104,6 +104,7 @@ class BaseLdap(object):
|
|||||||
DEFAULT_ID_ATTR = 'cn'
|
DEFAULT_ID_ATTR = 'cn'
|
||||||
DEFAULT_OBJECTCLASS = None
|
DEFAULT_OBJECTCLASS = None
|
||||||
DEFAULT_FILTER = None
|
DEFAULT_FILTER = None
|
||||||
|
DEFAULT_EXTRA_ATTR_MAPPING = []
|
||||||
DUMB_MEMBER_DN = 'cn=dumb,dc=nonexistent'
|
DUMB_MEMBER_DN = 'cn=dumb,dc=nonexistent'
|
||||||
NotFound = None
|
NotFound = None
|
||||||
notfound_arg = None
|
notfound_arg = None
|
||||||
@ -140,6 +141,12 @@ class BaseLdap(object):
|
|||||||
self.object_class = (getattr(conf.ldap, objclass)
|
self.object_class = (getattr(conf.ldap, objclass)
|
||||||
or self.DEFAULT_OBJECTCLASS)
|
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
|
filter = '%s_filter' % self.options_name
|
||||||
self.filter = getattr(conf.ldap, filter) or self.DEFAULT_FILTER
|
self.filter = getattr(conf.ldap, filter) or self.DEFAULT_FILTER
|
||||||
|
|
||||||
@ -169,6 +176,25 @@ class BaseLdap(object):
|
|||||||
else:
|
else:
|
||||||
return self.NotFound(**{self.notfound_arg: object_id})
|
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):
|
def get_connection(self, user=None, password=None):
|
||||||
if self.LDAP_URL.startswith('fake://'):
|
if self.LDAP_URL.startswith('fake://'):
|
||||||
conn = fakeldap.FakeLdap(self.LDAP_URL)
|
conn = fakeldap.FakeLdap(self.LDAP_URL)
|
||||||
@ -272,6 +298,11 @@ class BaseLdap(object):
|
|||||||
if v is not None:
|
if v is not None:
|
||||||
attr_type = self.attribute_mapping.get(k, k)
|
attr_type = self.attribute_mapping.get(k, k)
|
||||||
attrs.append((attr_type, [v]))
|
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:
|
if 'groupOfNames' in object_classes and self.use_dumb_member:
|
||||||
attrs.append(('member', [self.dumb_member]))
|
attrs.append(('member', [self.dumb_member]))
|
||||||
@ -289,8 +320,9 @@ class BaseLdap(object):
|
|||||||
'filter': (filter or self.filter or ''),
|
'filter': (filter or self.filter or ''),
|
||||||
'object_class': self.object_class})
|
'object_class': self.object_class})
|
||||||
try:
|
try:
|
||||||
res = conn.search_s(self.tree_dn, self.LDAP_SCOPE, query,
|
attrs = list(set((self.attribute_mapping.values() +
|
||||||
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:
|
except ldap.NO_SUCH_OBJECT:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
|
@ -364,6 +364,26 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests):
|
|||||||
'Invalid LDAP deref option: %s\.' % CONF.ldap.alias_dereferencing,
|
'Invalid LDAP deref option: %s\.' % CONF.ldap.alias_dereferencing,
|
||||||
identity.backends.ldap.Identity)
|
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
|
# TODO (henry-nash) These need to be removed when the full LDAP implementation
|
||||||
# is submitted - see Bugs 1092187, 1101287, 1101276, 1101289
|
# is submitted - see Bugs 1092187, 1101287, 1101276, 1101289
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user