diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index 9649166009..574b26be17 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -1637,9 +1637,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 @@ -1651,6 +1651,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 a9156dd8cc..8e5ea13baa 100644 --- a/etc/keystone.conf.sample +++ b/etc/keystone.conf.sample @@ -1019,6 +1019,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 @@ -1118,6 +1122,10 @@ # Its value may be silently ignored in the future. #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 107fbbf555..81f64b0070 100644 --- a/keystone/common/config.py +++ b/keystone/common/config.py @@ -668,6 +668,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 ' @@ -759,6 +763,11 @@ FILE_OPTIONS = { deprecated_for_removal=True, 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 0bb3830c82..6386ae2af2 100644 --- a/keystone/common/ldap/core.py +++ b/keystone/common/ldap/core.py @@ -1771,19 +1771,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 @@ -1791,6 +1795,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 @@ -1807,7 +1823,7 @@ class EnabledEmuMixIn(BaseLdap): def _get_enabled(self, object_id, conn): dn = self._id_to_dn(object_id) - query = '(member=%s)' % dn + query = '(%s=%s)' % (self.member_attribute, dn) try: enabled_value = conn.search_s(self.enabled_emulation_dn, ldap.SCOPE_BASE, @@ -1821,13 +1837,14 @@ class EnabledEmuMixIn(BaseLdap): with self.get_connection() as conn: if not self._get_enabled(object_id, conn): modlist = [(ldap.MOD_ADD, - 'member', + self.member_attribute, [self._id_to_dn(object_id)])] 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 6015107d2a..6891c57249 100644 --- a/keystone/resource/core.py +++ b/keystone/resource/core.py @@ -841,6 +841,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 16d54973e4..d96ec37683 100644 --- a/keystone/tests/unit/test_backend_ldap.py +++ b/keystone/tests/unit/test_backend_ldap.py @@ -2181,6 +2181,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.ldapdb.clear() + 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)