Remove LDAP write support

Removed LDAP write support and removed the configuration options
*_allow_create, use_dumb_member, dumb_member, allow_subtree_delete.

Also removed the driver logic related to dumb_members, tree deletion
and their respective tests.

Write functionality is still present because our tests depend on it,
but it's hidden behind a toggle which the tests set to enable it.

Co-Authored-By: Gage Hugo <gagehugo@gmail.com>
Co-Authored-By: Steve Martinelli <s.martinelli@gmail.com>

Implements: bp removed-as-of-ocata

Change-Id: I13eada3d5c3a166223c3e3ce70b7054eaed1003a
This commit is contained in:
Kristi Nikolla 2017-01-20 20:47:37 -05:00
parent bc8a145de1
commit a7b393b1f6
13 changed files with 151 additions and 554 deletions

View File

@ -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::

View File

@ -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,

View File

@ -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):

View File

@ -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

View File

@ -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',

View File

@ -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

View File

@ -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
auth_pool_connection_lifetime=300

View File

@ -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/

View File

@ -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]

View File

@ -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."""

View File

@ -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:

View File

@ -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',

View File

@ -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