diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index 53db268bb0..bb2f214a1a 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -1566,8 +1566,6 @@ The corresponding entries in the keystone configuration file are: user = dc=Manager,dc=openstack,dc=org password = badpassword suffix = dc=openstack,dc=org - use_dumb_member = False - allow_subtree_delete = False user_tree_dn = ou=Users,dc=openstack,dc=org user_objectclass = inetOrgPerson @@ -1587,18 +1585,6 @@ entries in the keystone configuration file are: user_id_attribute = uidNumber user_name_attribute = cn - -There is a set of allowed actions per object type that you can modify depending -on your specific deployment. For example, the users are managed by another tool -and you have only read access, in such case the configuration is: - -.. code-block:: ini - - [ldap] - user_allow_create = False - user_allow_update = False - user_allow_delete = False - There are some configuration options for filtering users, tenants and roles, if the backend is providing too much output, in such case the configuration will look like: @@ -1765,18 +1751,7 @@ backend. Also note that if there is an LDAP Identity, and no resource, assignment or role backend is specified, they will default to LDAP. Although this may seem counter intuitive, it is provided for backwards compatibility. Nonetheless, the explicit option will always override the implicit option, so -specifying the options as shown above will always be correct. Finally, it is -also worth noting that whether or not the LDAP accessible directory is to be -considered read only is still configured as described in a previous section -above by setting values such as the following in the ``[ldap]`` configuration -section: - -.. code-block:: ini - - [ldap] - user_allow_create = False - user_allow_update = False - user_allow_delete = False +specifying the options as shown above will always be correct. .. NOTE:: diff --git a/keystone/conf/ldap.py b/keystone/conf/ldap.py index 5f73f503f4..4c40852f5a 100644 --- a/keystone/conf/ldap.py +++ b/keystone/conf/ldap.py @@ -11,17 +11,10 @@ # under the License. from oslo_config import cfg -from oslo_log import versionutils from keystone.conf import utils -_DEPRECATED_LDAP_WRITE = utils.fmt(""" -Write support for the LDAP identity backend has been deprecated in the Mitaka -release and will be removed in the Ocata release. -""") - - url = cfg.StrOpt( 'url', default='ldap://localhost', @@ -54,42 +47,6 @@ The default LDAP server suffix to use, if a DN is not defined via either `[ldap] user_tree_dn` or `[ldap] group_tree_dn`. """)) -use_dumb_member = cfg.BoolOpt( - 'use_dumb_member', - default=False, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATED_LDAP_WRITE, - deprecated_since=versionutils.deprecated.MITAKA, - help=utils.fmt(""" -If true, keystone will add a dummy member based on the `[ldap] dumb_member` -option when creating new groups. This is required if the object class for -groups requires the `member` attribute. This option is only used for write -operations. -""")) - -dumb_member = cfg.StrOpt( - 'dumb_member', - default='cn=dumb,dc=nonexistent', - deprecated_for_removal=True, - deprecated_reason=_DEPRECATED_LDAP_WRITE, - deprecated_since=versionutils.deprecated.MITAKA, - help=utils.fmt(""" -DN of the "dummy member" to use when `[ldap] use_dumb_member` is enabled. This -option is only used for write operations. -""")) - -allow_subtree_delete = cfg.BoolOpt( - 'allow_subtree_delete', - default=False, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATED_LDAP_WRITE, - deprecated_since=versionutils.deprecated.MITAKA, - help=utils.fmt(""" -Delete subtrees using the subtree delete control. Only enable this option if -your LDAP server supports subtree deletion. This option is only used for write -operations. -""")) - query_scope = cfg.StrOpt( 'query_scope', default='one', @@ -259,36 +216,6 @@ The LDAP attribute mapped to a user's default_project_id in keystone. This is most commonly used when keystone has write access to LDAP. """)) -user_allow_create = cfg.BoolOpt( - 'user_allow_create', - default=True, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATED_LDAP_WRITE, - deprecated_since=versionutils.deprecated.MITAKA, - help=utils.fmt(""" -If enabled, keystone is allowed to create users in the LDAP server. -""")) - -user_allow_update = cfg.BoolOpt( - 'user_allow_update', - default=True, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATED_LDAP_WRITE, - deprecated_since=versionutils.deprecated.MITAKA, - help=utils.fmt(""" -If enabled, keystone is allowed to update users in the LDAP server. -""")) - -user_allow_delete = cfg.BoolOpt( - 'user_allow_delete', - default=True, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATED_LDAP_WRITE, - deprecated_since=versionutils.deprecated.MITAKA, - help=utils.fmt(""" -If enabled, keystone is allowed to delete users in the LDAP server. -""")) - user_enabled_emulation = cfg.BoolOpt( 'user_enabled_emulation', default=False, @@ -396,36 +323,6 @@ List of group attributes to ignore on create and update. or whether a specific group attribute should be filtered for list or show group. """)) -group_allow_create = cfg.BoolOpt( - 'group_allow_create', - default=True, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATED_LDAP_WRITE, - deprecated_since=versionutils.deprecated.MITAKA, - help=utils.fmt(""" -If enabled, keystone is allowed to create groups in the LDAP server. -""")) - -group_allow_update = cfg.BoolOpt( - 'group_allow_update', - default=True, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATED_LDAP_WRITE, - deprecated_since=versionutils.deprecated.MITAKA, - help=utils.fmt(""" -If enabled, keystone is allowed to update groups in the LDAP server. -""")) - -group_allow_delete = cfg.BoolOpt( - 'group_allow_delete', - default=True, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATED_LDAP_WRITE, - deprecated_since=versionutils.deprecated.MITAKA, - help=utils.fmt(""" -If enabled, keystone is allowed to delete groups in the LDAP server. -""")) - group_additional_attribute_mapping = cfg.ListOpt( 'group_additional_attribute_mapping', default=[], @@ -583,9 +480,6 @@ ALL_OPTS = [ user, password, suffix, - use_dumb_member, - dumb_member, - allow_subtree_delete, query_scope, page_size, alias_dereferencing, @@ -605,9 +499,6 @@ ALL_OPTS = [ user_enabled_default, 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, @@ -621,9 +512,6 @@ ALL_OPTS = [ group_members_are_ids, group_desc_attribute, group_attribute_ignore, - group_allow_create, - group_allow_update, - group_allow_delete, group_additional_attribute_mapping, group_ad_nesting, tls_cacertfile, diff --git a/keystone/identity/backends/ldap/common.py b/keystone/identity/backends/ldap/common.py index b176e3f8ab..fd6c9637b8 100644 --- a/keystone/identity/backends/ldap/common.py +++ b/keystone/identity/backends/ldap/common.py @@ -55,6 +55,10 @@ DN_ONLY = ['1.1'] _utf8_encoder = codecs.getencoder('utf-8') +# FIXME(knikolla): This enables writing to the LDAP backend +# Only enabled during tests and unsupported +WRITABLE = False + def utf8_encode(value): """Encode a basestring to UTF-8. @@ -1130,7 +1134,6 @@ class BaseLdap(object): DEFAULT_OBJECTCLASS = None DEFAULT_FILTER = None DEFAULT_EXTRA_ATTR_MAPPING = [] - DUMB_MEMBER_DN = 'cn=dumb,dc=nonexistent' NotFound = None notfound_arg = None options_name = None @@ -1195,15 +1198,6 @@ class BaseLdap(object): self.ldap_filter = getattr(conf.ldap, ldap_filter) or self.DEFAULT_FILTER - allow_create = '%s_allow_create' % self.options_name - self.allow_create = getattr(conf.ldap, allow_create) - - allow_update = '%s_allow_update' % self.options_name - self.allow_update = getattr(conf.ldap, allow_update) - - allow_delete = '%s_allow_delete' % self.options_name - self.allow_delete = getattr(conf.ldap, allow_delete) - member_attribute = '%s_member_attribute' % self.options_name self.member_attribute = getattr(conf.ldap, member_attribute, None) @@ -1215,12 +1209,6 @@ class BaseLdap(object): attribute_ignore = '%s_attribute_ignore' % self.options_name self.attribute_ignore = getattr(conf.ldap, attribute_ignore) - self.use_dumb_member = conf.ldap.use_dumb_member - self.dumb_member = (conf.ldap.dumb_member or - self.DUMB_MEMBER_DN) - - self.subtree_delete_enabled = conf.ldap.allow_subtree_delete - def _not_found(self, object_id): if self.NotFound is None: return exception.NotFound(target=object_id) @@ -1242,14 +1230,6 @@ class BaseLdap(object): mapping[ldap_attr] = attr_map return mapping - def _is_dumb_member(self, member_dn): - """Check that member is a dumb member. - - :param member_dn: DN of member to be checked. - """ - return (self.use_dumb_member - and is_dn_equal(member_dn, self.dumb_member)) - def get_connection(self, user=None, password=None, end_user_auth=False): use_pool = self.use_pool pool_size = self.pool_size @@ -1387,21 +1367,6 @@ class BaseLdap(object): return obj - def check_allow_create(self): - if not self.allow_create: - action = _('LDAP %s create') % self.options_name - raise exception.ForbiddenAction(action=action) - - def check_allow_update(self): - if not self.allow_update: - action = _('LDAP %s update') % self.options_name - raise exception.ForbiddenAction(action=action) - - def check_allow_delete(self): - if not self.allow_delete: - action = _('LDAP %s delete') % self.options_name - raise exception.ForbiddenAction(action=action) - def affirm_unique(self, values): if values.get('name') is not None: try: @@ -1446,8 +1411,6 @@ class BaseLdap(object): 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])) with self.get_connection() as conn: conn.add_s(self._id_to_dn(values['id']), attrs) return values @@ -1614,43 +1577,6 @@ class BaseLdap(object): except ldap.NO_SUCH_OBJECT: raise self._not_found(object_id) - def delete_tree(self, object_id): - tree_delete_control = ldap.controls.LDAPControl(CONTROL_TREEDELETE, - 0, - None) - with self.get_connection() as conn: - try: - conn.delete_ext_s(self._id_to_dn(object_id), - serverctrls=[tree_delete_control]) - except ldap.NO_SUCH_OBJECT: - raise self._not_found(object_id) - except ldap.NOT_ALLOWED_ON_NONLEAF: - # Most LDAP servers do not support the tree_delete_control. - # In these servers, the usual idiom is to first perform a - # search to get the entries to delete, then delete them - # in order of child to parent, since LDAP forbids the - # deletion of a parent entry before deleting the children - # of that parent. The simplest way to do that is to delete - # the entries in order of the length of the DN, from longest - # to shortest DN. - dn = self._id_to_dn(object_id) - scope = ldap.SCOPE_SUBTREE - # With some directory servers, an entry with objectclass - # ldapsubentry will not be returned unless it is explicitly - # requested, by specifying the objectclass in the search - # filter. We must specify this, with objectclass=*, in an - # LDAP filter OR clause, in order to return all entries - filt = '(|(objectclass=*)(objectclass=ldapsubentry))' - # We only need the DNs of the entries. Since no attributes - # will be returned, we do not have to specify attrsonly=1. - entries = conn.search_s(dn, scope, filt, attrlist=DN_ONLY) - if entries: - for dn in sorted((e[0] for e in entries), - key=len, reverse=True): - conn.delete_s(dn) - else: - LOG.debug('No entries in LDAP subtree %s', dn) - def add_member(self, member_dn, member_list_dn): """Add member to the member list. @@ -1897,8 +1823,6 @@ class EnabledEmuMixIn(BaseLdap): (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) conn.add_s(self.enabled_emulation_dn, attr_list) def _remove_enabled(self, object_id): diff --git a/keystone/identity/backends/ldap/core.py b/keystone/identity/backends/ldap/core.py index 8eb872b4d0..b5e8824a3a 100644 --- a/keystone/identity/backends/ldap/core.py +++ b/keystone/identity/backends/ldap/core.py @@ -35,6 +35,8 @@ _DEPRECATION_MSG = _('%s for the LDAP identity backend has been deprecated in ' 'the Mitaka release in favor of read-only identity LDAP ' 'access. It will be removed in the "O" release.') +READ_ONLY_LDAP_ERROR_MESSAGE = _("LDAP does not support write operations") + LDAP_MATCHING_RULE_IN_CHAIN = "1.2.840.113556.1.4.1941" @@ -90,66 +92,6 @@ class Identity(base.IdentityDriverBase): # parameter left in so this matches the Driver specification return self.user.filter_attributes(self.user.get_by_name(user_name)) - # CRUD - def create_user(self, user_id, user): - msg = _DEPRECATION_MSG % "create_user" - versionutils.report_deprecated_feature(LOG, msg) - self.user.check_allow_create() - user_ref = self.user.create(user) - return self.user.filter_attributes(user_ref) - - def update_user(self, user_id, user): - msg = _DEPRECATION_MSG % "update_user" - versionutils.report_deprecated_feature(LOG, msg) - self.user.check_allow_update() - old_obj = self.user.get(user_id) - if 'name' in user and old_obj.get('name') != user['name']: - raise exception.Conflict(_('Cannot change user name')) - - if self.user.enabled_mask: - self.user.mask_enabled_attribute(user) - elif self.user.enabled_invert and not self.user.enabled_emulation: - # We need to invert the enabled value for the old model object - # to prevent the LDAP update code from thinking that the enabled - # values are already equal. - user['enabled'] = not user['enabled'] - old_obj['enabled'] = not old_obj['enabled'] - - self.user.update(user_id, user, old_obj) - return self.user.get_filtered(user_id) - - def change_password(self, user_id, new_password): - raise exception.NotImplemented( - _('Self-service user password changes are not implemented for ' - 'LDAP.')) - - def delete_user(self, user_id): - msg = _DEPRECATION_MSG % "delete_user" - versionutils.report_deprecated_feature(LOG, msg) - self.user.check_allow_delete() - user = self.user.get(user_id) - user_dn = user['dn'] - groups = self.group.list_user_groups(user_dn) - for group in groups: - group_ref = self.group.get(group['id'], '*') # unfiltered - group_dn = group_ref['dn'] - try: - super(GroupApi, self.group).remove_member(user_dn, group_dn) - except ldap.NO_SUCH_ATTRIBUTE: - msg = _LW('User %(user)s was not removed from group %(group)s ' - 'because the relationship was not found') - LOG.warning(msg, {'user': user_id, 'group': group['id']}) - - if hasattr(user, 'tenant_id'): - self.project.remove_user(user.tenant_id, user_dn) - self.user.delete(user_id) - - def create_group(self, group_id, group): - msg = _DEPRECATION_MSG % "create_group" - versionutils.report_deprecated_feature(LOG, msg) - self.group.check_allow_create() - return common_ldap.filter_entity(self.group.create(group)) - def get_group(self, group_id): return self.group.get_filtered(group_id) @@ -158,32 +100,6 @@ class Identity(base.IdentityDriverBase): # parameter left in so this matches the Driver specification return self.group.get_filtered_by_name(group_name) - def update_group(self, group_id, group): - msg = _DEPRECATION_MSG % "update_group" - versionutils.report_deprecated_feature(LOG, msg) - self.group.check_allow_update() - return common_ldap.filter_entity(self.group.update(group_id, group)) - - def delete_group(self, group_id): - msg = _DEPRECATION_MSG % "delete_group" - versionutils.report_deprecated_feature(LOG, msg) - self.group.check_allow_delete() - return self.group.delete(group_id) - - def add_user_to_group(self, user_id, group_id): - msg = _DEPRECATION_MSG % "add_user_to_group" - versionutils.report_deprecated_feature(LOG, msg) - user_ref = self._get_user(user_id) - user_dn = user_ref['dn'] - self.group.add_user(user_dn, group_id, user_id) - - def remove_user_from_group(self, user_id, group_id): - msg = _DEPRECATION_MSG % "remove_user_from_group" - versionutils.report_deprecated_feature(LOG, msg) - user_ref = self._get_user(user_id) - user_dn = user_ref['dn'] - self.group.remove_user(user_dn, group_id, user_id) - def list_groups_for_user(self, user_id, hints): user_ref = self._get_user(user_id) if self.conf.ldap.group_members_are_ids: @@ -226,6 +142,121 @@ class Identity(base.IdentityDriverBase): {'user_id': user_id, 'group_id': group_id}) + # Unsupported methods + def _disallow_write(self): + if not common_ldap.WRITABLE: + exception.Forbidden(READ_ONLY_LDAP_ERROR_MESSAGE) + + def create_user(self, user_id, user): + self._disallow_write() + return self._create_user(user_id, user) + + def update_user(self, user_id, user): + self._disallow_write() + return self._update_user(user_id, user) + + def delete_user(self, user_id): + self._disallow_write() + self._delete_user(user_id) + + def change_password(self, user_id, new_password): + raise exception.Forbidden(READ_ONLY_LDAP_ERROR_MESSAGE) + + def add_user_to_group(self, user_id, group_id): + self._disallow_write() + self._add_user_to_group(user_id, group_id) + + def remove_user_from_group(self, user_id, group_id): + self._disallow_write() + self._remove_user_from_group(user_id, group_id) + + def create_group(self, group_id, group): + self._disallow_write() + return self._create_group(group_id, group) + + def update_group(self, group_id, group): + self._disallow_write() + return self._update_group(group_id, group) + + def delete_group(self, group_id): + self._disallow_write() + return self._delete_group(group_id) + + # Test implementations + def _create_user(self, user_id, user): + msg = _DEPRECATION_MSG % "create_user" + versionutils.report_deprecated_feature(LOG, msg) + user_ref = self.user.create(user) + return self.user.filter_attributes(user_ref) + + def _update_user(self, user_id, user): + msg = _DEPRECATION_MSG % "update_user" + versionutils.report_deprecated_feature(LOG, msg) + old_obj = self.user.get(user_id) + if 'name' in user and old_obj.get('name') != user['name']: + raise exception.Conflict(_('Cannot change user name')) + + if self.user.enabled_mask: + self.user.mask_enabled_attribute(user) + elif self.user.enabled_invert and not self.user.enabled_emulation: + # We need to invert the enabled value for the old model object + # to prevent the LDAP update code from thinking that the enabled + # values are already equal. + user['enabled'] = not user['enabled'] + old_obj['enabled'] = not old_obj['enabled'] + + self.user.update(user_id, user, old_obj) + return self.user.get_filtered(user_id) + + def _delete_user(self, user_id): + msg = _DEPRECATION_MSG % "delete_user" + versionutils.report_deprecated_feature(LOG, msg) + user = self.user.get(user_id) + user_dn = user['dn'] + groups = self.group.list_user_groups(user_dn) + for group in groups: + group_ref = self.group.get(group['id'], '*') # unfiltered + group_dn = group_ref['dn'] + try: + super(GroupApi, self.group).remove_member(user_dn, group_dn) + except ldap.NO_SUCH_ATTRIBUTE: + msg = _LW('User %(user)s was not removed from group %(group)s ' + 'because the relationship was not found') + LOG.warning(msg, {'user': user_id, 'group': group['id']}) + + if hasattr(user, 'tenant_id'): + self.project.remove_user(user.tenant_id, user_dn) + self.user.delete(user_id) + + def _create_group(self, group_id, group): + msg = _DEPRECATION_MSG % "create_group" + versionutils.report_deprecated_feature(LOG, msg) + return common_ldap.filter_entity(self.group.create(group)) + + def _update_group(self, group_id, group): + msg = _DEPRECATION_MSG % "update_group" + versionutils.report_deprecated_feature(LOG, msg) + return common_ldap.filter_entity(self.group.update(group_id, group)) + + def _delete_group(self, group_id): + msg = _DEPRECATION_MSG % "delete_group" + versionutils.report_deprecated_feature(LOG, msg) + return self.group.delete(group_id) + + def _add_user_to_group(self, user_id, group_id): + msg = _DEPRECATION_MSG % "add_user_to_group" + versionutils.report_deprecated_feature(LOG, msg) + user_ref = self._get_user(user_id) + user_dn = user_ref['dn'] + self.group.add_user(user_dn, group_id, user_id) + + def _remove_user_from_group(self, user_id, group_id): + msg = _DEPRECATION_MSG % "remove_user_from_group" + versionutils.report_deprecated_feature(LOG, msg) + user_ref = self._get_user(user_id) + user_dn = user_ref['dn'] + self.group.remove_user(user_dn, group_id, user_id) + # TODO(termie): turn this into a data object and move logic to driver class UserApi(common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap): @@ -352,17 +383,14 @@ class GroupApi(common_ldap.BaseLdap): return super(GroupApi, self).create(data) def delete(self, group_id): - if self.subtree_delete_enabled: - super(GroupApi, self).delete_tree(group_id) - else: - # TODO(spzala): this is only placeholder for group and domain - # role support which will be added under bug 1101287 + # TODO(spzala): this is only placeholder for group and domain + # role support which will be added under bug 1101287 - group_ref = self.get(group_id) - group_dn = group_ref['dn'] - if group_dn: - self._delete_tree_nodes(group_dn, ldap.SCOPE_ONELEVEL) - super(GroupApi, self).delete(group_id) + group_ref = self.get(group_id) + group_dn = group_ref['dn'] + if group_dn: + self._delete_tree_nodes(group_dn, ldap.SCOPE_ONELEVEL) + super(GroupApi, self).delete(group_id) def update(self, group_id, values): old_obj = self.get(group_id) @@ -441,8 +469,6 @@ class GroupApi(common_ldap.BaseLdap): for dn, member in attrs: user_dns = member.get(self.member_attribute, []) for user_dn in user_dns: - if self._is_dumb_member(user_dn): - continue users.append(user_dn) return users diff --git a/keystone/resource/core.py b/keystone/resource/core.py index 6211b0224f..be8d0ad7dc 100644 --- a/keystone/resource/core.py +++ b/keystone/resource/core.py @@ -877,8 +877,7 @@ class DomainConfigManager(manager.Manager): whitelisted_options = { 'identity': ['driver', 'list_limit'], 'ldap': [ - 'url', 'user', 'suffix', 'use_dumb_member', 'dumb_member', - 'allow_subtree_delete', 'query_scope', 'page_size', + 'url', 'user', 'suffix', 'query_scope', 'page_size', 'alias_dereferencing', 'debug_level', 'chase_referrals', 'user_tree_dn', 'user_filter', 'user_objectclass', 'user_id_attribute', 'user_name_attribute', 'user_mail_attribute', @@ -886,14 +885,12 @@ class DomainConfigManager(manager.Manager): 'user_enabled_attribute', 'user_enabled_invert', 'user_enabled_mask', 'user_enabled_default', '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', 'group_desc_attribute', 'group_attribute_ignore', - 'group_allow_create', 'group_allow_update', 'group_allow_delete', 'group_additional_attribute_mapping', 'tls_cacertfile', 'tls_cacertdir', 'use_tls', 'tls_req_cert', 'use_pool', 'pool_size', 'pool_retry_max', 'pool_retry_delay', diff --git a/keystone/tests/unit/config_files/backend_liveldap.conf b/keystone/tests/unit/config_files/backend_liveldap.conf index bb9ee08f51..496253fa30 100644 --- a/keystone/tests/unit/config_files/backend_liveldap.conf +++ b/keystone/tests/unit/config_files/backend_liveldap.conf @@ -7,4 +7,3 @@ group_tree_dn = ou=UserGroups,dc=openstack,dc=org user_tree_dn = ou=Users,dc=openstack,dc=org user_enabled_emulation = True user_mail_attribute = mail -use_dumb_member = True diff --git a/keystone/tests/unit/config_files/backend_pool_liveldap.conf b/keystone/tests/unit/config_files/backend_pool_liveldap.conf index c36e05f951..375adf7b67 100644 --- a/keystone/tests/unit/config_files/backend_pool_liveldap.conf +++ b/keystone/tests/unit/config_files/backend_pool_liveldap.conf @@ -7,7 +7,6 @@ group_tree_dn = ou=UserGroups,dc=openstack,dc=org user_tree_dn = ou=Users,dc=openstack,dc=org user_enabled_emulation = True user_mail_attribute = mail -use_dumb_member = True # Connection pooling specific attributes @@ -29,4 +28,4 @@ auth_pool_size=50 # End user auth connection lifetime in seconds. (integer # value) -auth_pool_connection_lifetime=300 \ No newline at end of file +auth_pool_connection_lifetime=300 diff --git a/keystone/tests/unit/config_files/backend_tls_liveldap.conf b/keystone/tests/unit/config_files/backend_tls_liveldap.conf index b66044b728..39b825d0a5 100644 --- a/keystone/tests/unit/config_files/backend_tls_liveldap.conf +++ b/keystone/tests/unit/config_files/backend_tls_liveldap.conf @@ -7,7 +7,6 @@ group_tree_dn = ou=UserGroups,dc=openstack,dc=org user_tree_dn = ou=Users,dc=openstack,dc=org user_enabled_emulation = True user_mail_attribute = mail -use_dumb_member = True use_tls = True tls_cacertfile = /etc/keystone/ssl/certs/cacert.pem tls_cacertdir = /etc/keystone/ssl/certs/ diff --git a/keystone/tests/unit/fakeldap.py b/keystone/tests/unit/fakeldap.py index 1e67a32264..f2ce5c18a9 100644 --- a/keystone/tests/unit/fakeldap.py +++ b/keystone/tests/unit/fakeldap.py @@ -380,13 +380,6 @@ class FakeLdap(common.LDAPHandler): raise ldap.SERVER_DOWN try: - if CONTROL_TREEDELETE in [c.controlType for c in serverctrls]: - LOG.debug('FakeLdap subtree_delete item: dn=%s', - common.utf8_decode(dn)) - children = self._getChildren(dn) - for c in children: - del self.db[c] - key = self.key(dn) LOG.debug('FakeLdap delete item: dn=%s', common.utf8_decode(dn)) del self.db[key] diff --git a/keystone/tests/unit/identity/backends/test_ldap_common.py b/keystone/tests/unit/identity/backends/test_ldap_common.py index 7751fdc220..f7bd7f0ccc 100644 --- a/keystone/tests/unit/identity/backends/test_ldap_common.py +++ b/keystone/tests/unit/identity/backends/test_ldap_common.py @@ -19,7 +19,6 @@ import fixtures import ldap.dn import mock from oslo_config import fixture as config_fixture -from testtools import matchers from keystone.common import driver_hints import keystone.conf @@ -223,62 +222,6 @@ class LDAPDeleteTreeTest(unit.TestCase): config_files.append(unit.dirs.tests_conf('backend_ldap.conf')) return config_files - def test_delete_tree(self): - """Test manually deleting a tree. - - Few LDAP servers support CONTROL_DELETETREE. This test - exercises the alternate code paths in BaseLdap.delete_tree. - - """ - conn = self.identity_api.user.get_connection() - id_attr = self.identity_api.user.id_attr - objclass = self.identity_api.user.object_class.lower() - tree_dn = self.identity_api.user.tree_dn - - def create_entry(name, parent_dn=None): - if not parent_dn: - parent_dn = tree_dn - dn = '%s=%s,%s' % (id_attr, name, parent_dn) - attrs = [('objectclass', [objclass, 'ldapsubentry']), - (id_attr, [name])] - conn.add_s(dn, attrs) - return dn - - # create 3 entries like this: - # cn=base - # cn=child,cn=base - # cn=grandchild,cn=child,cn=base - # then attempt to delete_tree(cn=base) - base_id = 'base' - base_dn = create_entry(base_id) - child_dn = create_entry('child', base_dn) - grandchild_dn = create_entry('grandchild', child_dn) - - # verify that the three entries were created - scope = ldap.SCOPE_SUBTREE - filt = '(|(objectclass=*)(objectclass=ldapsubentry))' - entries = conn.search_s(base_dn, scope, filt, - attrlist=common_ldap.DN_ONLY) - self.assertThat(entries, matchers.HasLength(3)) - sort_ents = sorted([e[0] for e in entries], key=len, reverse=True) - self.assertEqual([grandchild_dn, child_dn, base_dn], sort_ents) - - # verify that a non-leaf node can't be deleted directly by the - # LDAP server - self.assertRaises(ldap.NOT_ALLOWED_ON_NONLEAF, - conn.delete_s, base_dn) - self.assertRaises(ldap.NOT_ALLOWED_ON_NONLEAF, - conn.delete_s, child_dn) - - # call our delete_tree implementation - self.identity_api.user.delete_tree(base_id) - self.assertRaises(ldap.NO_SUCH_OBJECT, - conn.search_s, base_dn, ldap.SCOPE_BASE) - self.assertRaises(ldap.NO_SUCH_OBJECT, - conn.search_s, child_dn, ldap.SCOPE_BASE) - self.assertRaises(ldap.NO_SUCH_OBJECT, - conn.search_s, grandchild_dn, ldap.SCOPE_BASE) - class MultiURLTests(unit.TestCase): """Test for setting multiple LDAP URLs.""" diff --git a/keystone/tests/unit/ksfixtures/ldapdb.py b/keystone/tests/unit/ksfixtures/ldapdb.py index 360e4378c8..fe24fab42a 100644 --- a/keystone/tests/unit/ksfixtures/ldapdb.py +++ b/keystone/tests/unit/ksfixtures/ldapdb.py @@ -26,11 +26,16 @@ class LDAPDatabase(fixtures.Fixture): def setUp(self): super(LDAPDatabase, self).setUp() self.clear() + common_ldap.WRITABLE = True common_ldap._HANDLERS.clear() common_ldap.register_handler('fake://', self._dbclass) # TODO(dstanek): switch the flow here self.addCleanup(self.clear) self.addCleanup(common_ldap._HANDLERS.clear) + self.addCleanup(self.disable_write) + + def disable_write(self): + common_ldap.WRITABLE = False def clear(self): for shelf in fakeldap.FakeShelves: diff --git a/keystone/tests/unit/test_backend_ldap.py b/keystone/tests/unit/test_backend_ldap.py index 97d3518704..015849a4e8 100644 --- a/keystone/tests/unit/test_backend_ldap.py +++ b/keystone/tests/unit/test_backend_ldap.py @@ -379,37 +379,6 @@ class BaseLDAPIdentity(IdentityTests, AssignmentTests, ResourceTests): self.identity_api.get_user, user['id']) - def test_configurable_forbidden_user_actions(self): - driver = self.identity_api._select_identity_driver( - CONF.identity.default_domain_id) - driver.user.allow_create = False - driver.user.allow_update = False - driver.user.allow_delete = False - - user = self.new_user_ref(domain_id=CONF.identity.default_domain_id) - self.assertRaises(exception.ForbiddenAction, - self.identity_api.create_user, - user) - - self.user_foo['password'] = u'fäképass2' - self.assertRaises(exception.ForbiddenAction, - self.identity_api.update_user, - self.user_foo['id'], - self.user_foo) - - self.assertRaises(exception.ForbiddenAction, - self.identity_api.delete_user, - self.user_foo['id']) - - def test_configurable_forbidden_create_existing_user(self): - driver = self.identity_api._select_identity_driver( - CONF.identity.default_domain_id) - driver.user.allow_create = False - - self.assertRaises(exception.ForbiddenAction, - self.identity_api.create_user, - self.user_foo) - def test_user_filter(self): user_ref = self.identity_api.get_user(self.user_foo['id']) self.user_foo.pop('password') @@ -705,47 +674,6 @@ class BaseLDAPIdentity(IdentityTests, AssignmentTests, ResourceTests): after_assignments = len(self.assignment_api.list_role_assignments()) self.assertEqual(existing_assignments + 2, after_assignments) - def test_list_role_assignments_dumb_member(self): - self.config_fixture.config(group='ldap', use_dumb_member=True) - self.ldapdb.clear() - self.load_backends() - self.load_fixtures(default_fixtures) - - new_domain = self._get_domain_fixture() - new_user = self.new_user_ref(domain_id=new_domain['id']) - new_user = self.identity_api.create_user(new_user) - new_project = unit.new_project_ref(domain_id=new_domain['id']) - self.resource_api.create_project(new_project['id'], new_project) - self.assignment_api.create_grant(user_id=new_user['id'], - project_id=new_project['id'], - role_id='other') - - # Read back the list of assignments and ensure - # that the LDAP dumb member isn't listed. - assignment_ids = [a['user_id'] for a in - self.assignment_api.list_role_assignments()] - dumb_id = common_ldap.BaseLdap._dn_to_id(CONF.ldap.dumb_member) - self.assertNotIn(dumb_id, assignment_ids) - - def test_list_user_ids_for_project_dumb_member(self): - self.config_fixture.config(group='ldap', use_dumb_member=True) - self.ldapdb.clear() - self.load_backends() - self.load_fixtures(default_fixtures) - - user = self.new_user_ref(domain_id=CONF.identity.default_domain_id) - - user = self.identity_api.create_user(user) - self.assignment_api.add_user_to_project(self.tenant_baz['id'], - user['id']) - user_ids = self.assignment_api.list_user_ids_for_project( - self.tenant_baz['id']) - - self.assertIn(user['id'], user_ids) - - dumb_id = common_ldap.BaseLdap._dn_to_id(CONF.ldap.dumb_member) - self.assertNotIn(dumb_id, user_ids) - def test_list_group_members_missing_entry(self): """List group members with deleted user. @@ -792,29 +720,6 @@ class BaseLDAPIdentity(IdentityTests, AssignmentTests, ResourceTests): # If this doesn't raise, then the test is successful. self.identity_api.list_users_in_group(group['id']) - def test_list_group_members_dumb_member(self): - self.config_fixture.config(group='ldap', use_dumb_member=True) - self.ldapdb.clear() - self.load_backends() - self.load_fixtures(default_fixtures) - - # Create a group - group = unit.new_group_ref(domain_id=CONF.identity.default_domain_id) - group_id = self.identity_api.create_group(group)['id'] - - # Create a user - user = dict(name=uuid.uuid4().hex, - domain_id=CONF.identity.default_domain_id) - user_id = self.identity_api.create_user(user)['id'] - - # Add user to the group - self.identity_api.add_user_to_group(user_id, group_id) - - user_ids = self.identity_api.list_users_in_group(group_id) - dumb_id = common_ldap.BaseLdap._dn_to_id(CONF.ldap.dumb_member) - - self.assertNotIn(dumb_id, user_ids) - def test_list_domains(self): # We have more domains here than the parent class, check for the # correct number of domains for the multildap backend configs @@ -1181,47 +1086,6 @@ class LDAPIdentity(BaseLDAPIdentity, unit.TestCase): self.resource_api.get_project, project['id']) - def test_configurable_subtree_delete(self): - self.config_fixture.config(group='ldap', allow_subtree_delete=True) - self.load_backends() - - project1 = unit.new_project_ref( - domain_id=CONF.identity.default_domain_id) - self.resource_api.create_project(project1['id'], project1) - - role1 = unit.new_role_ref() - self.role_api.create_role(role1['id'], role1) - - user1 = self.new_user_ref(domain_id=CONF.identity.default_domain_id) - user1 = self.identity_api.create_user(user1) - - self.assignment_api.add_role_to_user_and_project( - user_id=user1['id'], - tenant_id=project1['id'], - role_id=role1['id']) - - self.resource_api.delete_project(project1['id']) - self.assertRaises(exception.ProjectNotFound, - self.resource_api.get_project, - project1['id']) - - self.resource_api.create_project(project1['id'], project1) - - list = self.assignment_api.get_roles_for_user_and_project( - user1['id'], - project1['id']) - self.assertEqual(0, len(list)) - - def test_dumb_member(self): - self.config_fixture.config(group='ldap', use_dumb_member=True) - self.ldapdb.clear() - self.load_backends() - self.load_fixtures(default_fixtures) - dumb_id = common_ldap.BaseLdap._dn_to_id(CONF.ldap.dumb_member) - self.assertRaises(exception.UserNotFound, - self.identity_api.get_user, - dumb_id) - def test_user_enable_attribute_mask(self): self.config_fixture.config(group='ldap', user_enabled_mask=2, user_enabled_default='512') @@ -1474,36 +1338,6 @@ class LDAPIdentity(BaseLDAPIdentity, unit.TestCase): 'Invalid LDAP deref option: %s\.' % CONF.ldap.alias_dereferencing, identity.backends.ldap.Identity) - def test_is_dumb_member(self): - self.config_fixture.config(group='ldap', - use_dumb_member=True) - self.load_backends() - - dn = 'cn=dumb,dc=nonexistent' - self.assertTrue(self.identity_api.driver.user._is_dumb_member(dn)) - - def test_is_dumb_member_upper_case_keys(self): - self.config_fixture.config(group='ldap', - use_dumb_member=True) - self.load_backends() - - dn = 'CN=dumb,DC=nonexistent' - self.assertTrue(self.identity_api.driver.user._is_dumb_member(dn)) - - def test_is_dumb_member_with_false_use_dumb_member(self): - self.config_fixture.config(group='ldap', - use_dumb_member=False) - self.load_backends() - dn = 'cn=dumb,dc=nonexistent' - self.assertFalse(self.identity_api.driver.user._is_dumb_member(dn)) - - def test_is_dumb_member_not_dumb(self): - self.config_fixture.config(group='ldap', - use_dumb_member=True) - self.load_backends() - dn = 'ou=some,dc=example.com' - self.assertFalse(self.identity_api.driver.user._is_dumb_member(dn)) - def test_user_extra_attribute_mapping(self): self.config_fixture.config( group='ldap', diff --git a/releasenotes/notes/removed-as-of-ocata-436bb4b839e74494.yaml b/releasenotes/notes/removed-as-of-ocata-436bb4b839e74494.yaml index 31a6333938..3d6066ab30 100644 --- a/releasenotes/notes/removed-as-of-ocata-436bb4b839e74494.yaml +++ b/releasenotes/notes/removed-as-of-ocata-436bb4b839e74494.yaml @@ -2,9 +2,24 @@ prelude: > - The PKI and PKIz token format has been removed. See ``Other Notes`` for more details. + + - Support for writing to LDAP has been removed. See ``Other Notes`` for more + details. other: - > PKI and PKIz token formats have been removed in favor of Fernet tokens. + - > + Write support for the LDAP has been removed in favor of read-only support. + The following operations are no longer supported for LDAP: + + * ``create user`` + * ``create group`` + * ``delete user`` + * ``delete group`` + * ``update user`` + * ``update group`` + * ``add user to group`` + * ``remove user from group`` - > Routes and SQL backends for the contrib extensions have been removed, they have been incorporated into keystone and are no longer optional. This