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:
parent
bc8a145de1
commit
a7b393b1f6
@ -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::
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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):
|
||||
|
@ -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,9 +383,6 @@ 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
|
||||
|
||||
@ -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
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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/
|
||||
|
@ -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]
|
||||
|
@ -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."""
|
||||
|
@ -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:
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user