Merge "Support nested groups in Active Directory"
This commit is contained in:
commit
3047689ce3
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
9
releasenotes/notes/bug-1638603-354ee4167e6e.yaml
Normal file
9
releasenotes/notes/bug-1638603-354ee4167e6e.yaml
Normal 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.
|
Loading…
Reference in New Issue
Block a user