Merge "Support nested groups in Active Directory"

This commit is contained in:
Jenkins 2016-11-10 00:30:01 +00:00 committed by Gerrit Code Review
commit 3047689ce3
4 changed files with 110 additions and 6 deletions

View File

@ -437,6 +437,14 @@ object and `group_attr` is the attribute which should appear in the identity
API.
"""))
group_ad_nesting = cfg.BoolOpt(
'group_ad_nesting',
default=False,
help=utils.fmt("""
If enabled, group queries will use Active Directory specific filters for
nested groups.
"""))
tls_cacertfile = cfg.StrOpt(
'tls_cacertfile',
help=utils.fmt("""
@ -608,6 +616,7 @@ ALL_OPTS = [
group_allow_update,
group_allow_delete,
group_additional_attribute_mapping,
group_ad_nesting,
tls_cacertfile,
tls_cacertdir,
use_tls,

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.')
LDAP_MATCHING_RULE_IN_CHAIN = "1.2.840.113556.1.4.1941"
class Identity(base.IdentityDriverBase):
def __init__(self, conf=None):
@ -337,6 +339,7 @@ class GroupApi(common_ldap.BaseLdap):
def __init__(self, conf):
super(GroupApi, self).__init__(conf)
self.group_ad_nesting = conf.ldap.group_ad_nesting
self.member_attribute = (conf.ldap.group_member_attribute
or self.DEFAULT_MEMBER_ATTRIBUTE)
@ -386,15 +389,29 @@ class GroupApi(common_ldap.BaseLdap):
def list_user_groups(self, user_dn):
"""Return a list of groups for which the user is a member."""
user_dn_esc = ldap.filter.escape_filter_chars(user_dn)
query = '(%s=%s)' % (self.member_attribute,
user_dn_esc)
if self.group_ad_nesting:
query = '(%s:%s:=%s)' % (
self.member_attribute,
LDAP_MATCHING_RULE_IN_CHAIN,
user_dn_esc)
else:
query = '(%s=%s)' % (self.member_attribute,
user_dn_esc)
return self.get_all(query)
def list_user_groups_filtered(self, user_dn, hints):
"""Return a filtered list of groups for which the user is a member."""
user_dn_esc = ldap.filter.escape_filter_chars(user_dn)
query = '(%s=%s)' % (self.member_attribute,
user_dn_esc)
if self.group_ad_nesting:
# Hardcoded to member as that is how the Matching Rule in Chain
# Mechanisms expects it. The member_attribute might actually be
# member_of elsewhere, so they are not the same.
query = '(member:%s:=%s)' % (
LDAP_MATCHING_RULE_IN_CHAIN,
user_dn_esc)
else:
query = '(%s=%s)' % (self.member_attribute,
user_dn_esc)
return self.get_all_filtered(hints, query)
def list_group_users(self, group_id):
@ -403,8 +420,20 @@ class GroupApi(common_ldap.BaseLdap):
group_dn = group_ref['dn']
try:
attrs = self._ldap_get_list(group_dn, ldap.SCOPE_BASE,
attrlist=[self.member_attribute])
if self.group_ad_nesting:
# NOTE(ayoung): LDAP_SCOPE is used here instead of hard-
# coding to SCOPE_SUBTREE to get through the unit tests.
# However, it is also probably more correct.
attrs = self._ldap_get_list(
self.tree_dn, self.LDAP_SCOPE,
query_params={
"member:%s:" % LDAP_MATCHING_RULE_IN_CHAIN:
group_dn},
attrlist=[self.member_attribute])
else:
attrs = self._ldap_get_list(group_dn, ldap.SCOPE_BASE,
attrlist=[self.member_attribute])
except ldap.NO_SUCH_OBJECT:
raise self.NotFound(group_id=group_id)

View File

@ -3296,3 +3296,60 @@ class LdapFilterTests(identity_tests.FilterTests, LDAPTestSetup):
# The LDAP identity driver currently does not support filtering on the
# listing users for a given group, so will fail this test.
super(LdapFilterTests, self).test_list_users_in_group_exact_filtered()
class LDAPMatchingRuleInChainTests(LDAPTestSetup):
def setUp(self):
self.useFixture(database.Database())
super(LDAPMatchingRuleInChainTests, self).setUp()
_assert_backends(self, identity='ldap')
group = unit.new_group_ref(domain_id=CONF.identity.default_domain_id)
self.group = self.identity_api.create_group(group)
user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
self.user = self.identity_api.create_user(user)
self.identity_api.add_user_to_group(self.user['id'],
self.group['id'])
def config_overrides(self):
super(LDAPMatchingRuleInChainTests, self).config_overrides()
self.config_fixture.config(group='identity', driver='ldap')
self.config_fixture.config(
group='ldap',
group_ad_nesting=True,
url='fake://memory',
chase_referrals=False,
group_tree_dn='cn=UserGroups,cn=example,cn=com',
query_scope='one')
def config_files(self):
config_files = super(LDAPMatchingRuleInChainTests, self).config_files()
config_files.append(unit.dirs.tests_conf('backend_ldap.conf'))
return config_files
def test_get_group(self):
group_ref = self.identity_api.get_group(self.group['id'])
self.assertDictEqual(self.group, group_ref)
def test_list_user_groups(self):
# tests indirectly by calling delete user
self.identity_api.delete_user(self.user['id'])
def test_list_groups_for_user(self):
groups_ref = self.identity_api.list_groups_for_user(self.user['id'])
self.assertEqual(0, len(groups_ref))
def test_list_groups(self):
groups_refs = self.identity_api.list_groups()
self.assertEqual(1, len(groups_refs))
self.assertEqual(self.group['id'], groups_refs[0]['id'])
self.identity_api.delete_group(self.group['id'])
self.assertRaises(exception.GroupNotFound,
self.identity_api.get_group,
self.group['id'])
groups_refs = self.identity_api.list_groups()
self.assertEqual([], groups_refs)

View File

@ -0,0 +1,9 @@
---
features:
- >
[`bug 1638603 <https://bugs.launchpad.net/keystone/+bug/1638603>`_]
Support nested groups in Active Directory. A new boolean option
``[ldap] group_ad_nesting`` has been added, it defaults to ``False``.
Enable the option is using Active Directory with nested groups. This
option will impact the ``list_users_in_group``, ``list_groups_for_user``,
and ``check_user_in_group`` operations.