From 0bce43a466aa3ee89c6c956fede2292e457146d7 Mon Sep 17 00:00:00 2001 From: Nathan Kinder Date: Wed, 11 Nov 2015 07:56:48 -0800 Subject: [PATCH] Remove hardcoded LDAP group schema from emulated enabled mix-in The emulated enabled mix-in uses hard-coded LDAP schema for the group objectclass and membership attributes. This patch makes the mix-in optionally use the LDAP group configuration settings. Conflicts: keystone/common/ldap/core.py keystone/tests/unit/test_backend_ldap.py Change-Id: I5ed9d552ec140f83578398fd29e2130ebf827662 Closes-Bug: #1515302 (cherry picked from commit e465de5b44ffb1da65eab77ba7c81d1c0ced367f) --- doc/source/configuration.rst | 12 ++++++--- etc/keystone.conf.sample | 8 ++++++ keystone/common/config.py | 9 +++++++ keystone/common/ldap/core.py | 31 ++++++++++++++++++------ keystone/resource/core.py | 1 + keystone/tests/unit/test_backend_ldap.py | 20 +++++++++++++++ 6 files changed, 71 insertions(+), 10 deletions(-) diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index 5eff8a9cdc..fb939e24b3 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -1631,9 +1631,9 @@ have been created. They are enabled by setting their respective flags to True. Then the attributes ``user_enabled_emulation_dn`` and ``project_enabled_emulation_dn`` may be set to specify how the enabled users and projects (tenants) are selected. These attributes work by using a -``groupOfNames`` and adding whichever users or projects (tenants) that you want -enabled to the respective group. For example, this will mark any user who is a -member of ``enabled_users`` as enabled: +``groupOfNames`` entry and adding whichever users or projects (tenants) that +you want enabled to the respective group with the ``member`` attribute. For +example, this will mark any user who is a member of ``enabled_users`` as enabled: .. code-block:: ini @@ -1645,6 +1645,12 @@ The default values for user and project (tenant) enabled emulation DN is ``cn=enabled_users,$user_tree_dn`` and ``cn=enabled_tenants,$project_tree_dn`` respectively. +If a different LDAP schema is used for group membership, it is possible to use +the ``group_objectclass`` and ``group_member_attribute`` attributes to +determine membership in the enabled emulation group by setting the +``user_enabled_emulation_use_group_config`` and +``project_enabled_emulation_use_group_config`` attributes to True. + Secure Connection ----------------- diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample index 1c2298bf8a..7b7d168eaf 100644 --- a/etc/keystone.conf.sample +++ b/etc/keystone.conf.sample @@ -895,6 +895,10 @@ # (string value) #user_enabled_emulation_dn = +# Use the "group_member_attribute" and "group_objectclass" settings to +# determine membership in the emulated enabled group. (boolean value) +#user_enabled_emulation_use_group_config = false + # List of additional LDAP attributes used for mapping additional attribute # mappings for users. Attribute mapping format is :, # where ldap_attr is the attribute in the LDAP entry and user_attr is the @@ -964,6 +968,10 @@ # Deprecated group/name - [ldap]/tenant_enabled_emulation_dn #project_enabled_emulation_dn = +# Use the "group_member_attribute" and "group_objectclass" settings to +# determine membership in the emulated enabled group. (boolean value) +#project_enabled_emulation_use_group_config = false + # Additional attribute mappings for projects. Attribute mapping format is # :, where ldap_attr is the attribute in the LDAP entry # and user_attr is the Identity API attribute. (list value) diff --git a/keystone/common/config.py b/keystone/common/config.py index c725026216..2e578a143c 100644 --- a/keystone/common/config.py +++ b/keystone/common/config.py @@ -649,6 +649,10 @@ FILE_OPTIONS = { cfg.StrOpt('user_enabled_emulation_dn', help='DN of the group entry to hold enabled users when ' 'using enabled emulation.'), + cfg.BoolOpt('user_enabled_emulation_use_group_config', default=False, + help='Use the "group_member_attribute" and ' + '"group_objectclass" settings to determine ' + 'membership in the emulated enabled group.'), cfg.ListOpt('user_additional_attribute_mapping', default=[], help='List of additional LDAP attributes used for mapping ' @@ -724,6 +728,11 @@ FILE_OPTIONS = { 'tenant_enabled_emulation_dn', group='ldap')], help='DN of the group entry to hold enabled projects when ' 'using enabled emulation.'), + cfg.BoolOpt('project_enabled_emulation_use_group_config', + default=False, + help='Use the "group_member_attribute" and ' + '"group_objectclass" settings to determine ' + 'membership in the emulated enabled group.'), cfg.ListOpt('project_additional_attribute_mapping', deprecated_opts=[cfg.DeprecatedOpt( 'tenant_additional_attribute_mapping', group='ldap')], diff --git a/keystone/common/ldap/core.py b/keystone/common/ldap/core.py index 8a3576b809..8a577ada09 100644 --- a/keystone/common/ldap/core.py +++ b/keystone/common/ldap/core.py @@ -1770,19 +1770,23 @@ class BaseLdap(object): class EnabledEmuMixIn(BaseLdap): """Emulates boolean 'enabled' attribute if turned on. - Creates groupOfNames holding all enabled objects of this class, all missing + Creates a group holding all enabled objects of this class, all missing objects are considered disabled. Options: * $name_enabled_emulation - boolean, on/off - * $name_enabled_emulation_dn - DN of that groupOfNames, default is + * $name_enabled_emulation_dn - DN of that group, default is cn=enabled_${name}s,${tree_dn} + * $name_enabled_emulation_use_group_config - boolean, on/off Where ${name}s is the plural of self.options_name ('users' or 'tenants'), ${tree_dn} is self.tree_dn. """ + DEFAULT_GROUP_OBJECTCLASS = 'groupOfNames' + DEFAULT_MEMBER_ATTRIBUTE = 'member' + def __init__(self, conf): super(EnabledEmuMixIn, self).__init__(conf) enabled_emulation = '%s_enabled_emulation' % self.options_name @@ -1790,6 +1794,18 @@ class EnabledEmuMixIn(BaseLdap): enabled_emulation_dn = '%s_enabled_emulation_dn' % self.options_name self.enabled_emulation_dn = getattr(conf.ldap, enabled_emulation_dn) + + use_group_config = ('%s_enabled_emulation_use_group_config' % + self.options_name) + self.use_group_config = getattr(conf.ldap, use_group_config) + + if not self.use_group_config: + self.member_attribute = self.DEFAULT_MEMBER_ATTRIBUTE + self.group_objectclass = self.DEFAULT_GROUP_OBJECTCLASS + else: + self.member_attribute = conf.ldap.group_member_attribute + self.group_objectclass = conf.ldap.group_objectclass + if not self.enabled_emulation_dn: naming_attr_name = 'cn' naming_attr_value = 'enabled_%ss' % self.options_name @@ -1806,7 +1822,7 @@ class EnabledEmuMixIn(BaseLdap): def _get_enabled(self, object_id): dn = self._id_to_dn(object_id) - query = '(member=%s)' % dn + query = '(%s=%s)' % (self.member_attribute, dn) with self.get_connection() as conn: try: enabled_value = conn.search_s(self.enabled_emulation_dn, @@ -1820,14 +1836,15 @@ class EnabledEmuMixIn(BaseLdap): def _add_enabled(self, object_id): if not self._get_enabled(object_id): modlist = [(ldap.MOD_ADD, - 'member', + self.member_attribute, [self._id_to_dn(object_id)])] with self.get_connection() as conn: try: conn.modify_s(self.enabled_emulation_dn, modlist) except ldap.NO_SUCH_OBJECT: - attr_list = [('objectClass', ['groupOfNames']), - ('member', [self._id_to_dn(object_id)]), + attr_list = [('objectClass', [self.group_objectclass]), + (self.member_attribute, + [self._id_to_dn(object_id)]), self.enabled_emulation_naming_attr] if self.use_dumb_member: attr_list[1][1].append(self.dumb_member) @@ -1835,7 +1852,7 @@ class EnabledEmuMixIn(BaseLdap): def _remove_enabled(self, object_id): modlist = [(ldap.MOD_DELETE, - 'member', + self.member_attribute, [self._id_to_dn(object_id)])] with self.get_connection() as conn: try: diff --git a/keystone/resource/core.py b/keystone/resource/core.py index 0559b68d10..6b6150b382 100644 --- a/keystone/resource/core.py +++ b/keystone/resource/core.py @@ -808,6 +808,7 @@ class DomainConfigManager(manager.Manager): 'user_attribute_ignore', 'user_default_project_id_attribute', 'user_allow_create', 'user_allow_update', 'user_allow_delete', 'user_enabled_emulation', 'user_enabled_emulation_dn', + 'user_enabled_emulation_use_group_config', 'user_additional_attribute_mapping', 'group_tree_dn', 'group_filter', 'group_objectclass', 'group_id_attribute', 'group_name_attribute', 'group_member_attribute', diff --git a/keystone/tests/unit/test_backend_ldap.py b/keystone/tests/unit/test_backend_ldap.py index eca8817a1e..94216e842d 100644 --- a/keystone/tests/unit/test_backend_ldap.py +++ b/keystone/tests/unit/test_backend_ldap.py @@ -2019,6 +2019,26 @@ class LDAPIdentityEnabledEmulation(LDAPIdentity): self.skipTest( "Enabled emulation conflicts with enabled mask") + def test_user_enabled_use_group_config(self): + self.config_fixture.config( + group='ldap', + user_enabled_emulation_use_group_config=True, + group_member_attribute='uniqueMember', + group_objectclass='groupOfUniqueNames') + self.clear_database() + self.load_backends() + self.load_fixtures(default_fixtures) + + # Create a user and ensure they are enabled. + user1 = {'name': u'fäké1', 'enabled': True, + 'domain_id': CONF.identity.default_domain_id} + user_ref = self.identity_api.create_user(user1) + self.assertIs(True, user_ref['enabled']) + + # Get a user and ensure they are enabled. + user_ref = self.identity_api.get_user(user_ref['id']) + self.assertIs(True, user_ref['enabled']) + def test_user_enabled_invert(self): self.config_fixture.config(group='ldap', user_enabled_invert=True, user_enabled_default=False)