Skip to content

Commit 3520590

Browse files
committed
Added Feature: Two-Steps lookup
1 parent 21fe4ce commit 3520590

File tree

2 files changed

+79
-18
lines changed

2 files changed

+79
-18
lines changed

examples/config files - basic/3 connector-ldap.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,23 @@ group_filter_format: "(&(|(objectCategory=group)(objectClass=groupOfNames)(objec
8181
# group_member_filter_format: "(memberOf:1.2.840.113556.1.4.1941:={group_dn})"
8282
group_member_filter_format: "(memberOf={group_dn})"
8383

84+
# (optional) two_steps_group_member_lookup (default value given below)
85+
# two steps group member lookup should only be used for the directory system that
86+
# does not support memberOf or equivalent virtual attribute. Two steps lookup allows
87+
# user sync tool pull group membership from group object and does a secondary
88+
# lookup of user object based on group membership value. When enabled,
89+
# group_member_filter_format will no longer be used.
90+
# Depending on the size of your directory, this process might take longer.
91+
# Set the value to True to enable two-steps lookup.
92+
#two_steps_group_member_lookup: False
93+
94+
# (optional) two_steps_group_member_attribute_name: (default value given below)
95+
# two-step group member attribute name is required for two steps lookup to know which
96+
# attribute within group object contained group membership value.
97+
# In OpenLDAP, this value can be uniqueMember or member depending on objectClass.
98+
# the value of the specified attribute should return a distinguishName of a user.
99+
#two_steps_group_member_attribute_name: "member"
100+
84101
# (optional) string_encoding (default value given below)
85102
# string_encoding specifies the Unicode string encoding used by the directory.
86103
# All values retrieved from the directory are converted to Unicode before being

user_sync/connector/directory_ldap.py

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ def __init__(self, caller_options):
7171
builder.set_string_value('group_member_filter_format', six.text_type(
7272
'(memberOf={group_dn})'))
7373
builder.set_bool_value('require_tls_cert', False)
74+
builder.set_bool_value('two_steps_group_member_lookup', False)
75+
builder.set_string_value('two_steps_group_member_attribute_name', 'member')
76+
builder.set_string_value('user_identity_type_format', None)
7477
builder.set_string_value('string_encoding', 'utf8')
7578
builder.set_string_value('user_identity_type_format', None)
7679
builder.set_string_value('user_email_format', six.text_type('{mail}'))
@@ -132,28 +135,42 @@ def load_users_and_groups(self, groups, extended_attributes, all_users):
132135
options = self.options
133136
all_users_filter = six.text_type(options['all_users_filter'])
134137
group_member_filter_format = six.text_type(options['group_member_filter_format'])
138+
two_steps_group_member_lookup = six.text_type(options['two_steps_group_member_lookup'])
139+
two_steps_group_member_attribute_name = six.text_type(options['two_steps_group_member_attribute_name'])
140+
141+
# save all the users to memory for faster 2-steps lookup process
142+
if two_steps_group_member_lookup:
143+
all_users_records = dict(self.iter_users(all_users_filter, extended_attributes))
135144

136145
# for each group that's required, do one search for the users of that group
137146
for group in groups:
138-
group_dn = self.find_ldap_group_dn(group)
139-
if not group_dn:
140-
self.logger.warning("No group found for: %s", group)
141-
continue
142-
group_member_subfilter = self.format_ldap_query_string(group_member_filter_format, group_dn=group_dn)
143-
if not group_member_subfilter.startswith('('):
144-
group_member_subfilter = six.text_type('(') + group_member_subfilter + six.text_type(')')
145-
user_subfilter = all_users_filter
146-
if not user_subfilter.startswith('('):
147-
user_subfilter = six.text_type('(') + user_subfilter + six.text_type(')')
148-
group_user_filter = six.text_type('(&') + group_member_subfilter + user_subfilter + six.text_type(')')
149147
group_users = 0
150-
try:
151-
for user_dn, user in self.iter_users(group_user_filter, extended_attributes):
152-
user['groups'].append(group)
153-
group_users += 1
154-
self.logger.debug('Count of users in group "%s": %d', group, group_users)
155-
except Exception as e:
156-
raise AssertionException('Unexpected LDAP failure reading group members: %s' % e)
148+
if not two_steps_group_member_lookup:
149+
group_dn = self.find_ldap_group_dn(group)
150+
if not group_dn:
151+
self.logger.warning("No group found for: %s", group)
152+
continue
153+
group_member_subfilter = self.format_ldap_query_string(group_member_filter_format, group_dn=group_dn)
154+
if not group_member_subfilter.startswith('('):
155+
group_member_subfilter = six.text_type('(') + group_member_subfilter + six.text_type(')')
156+
user_subfilter = all_users_filter
157+
if not user_subfilter.startswith('('):
158+
user_subfilter = six.text_type('(') + user_subfilter + six.text_type(')')
159+
group_user_filter = six.text_type('(&') + group_member_subfilter + user_subfilter + six.text_type(')')
160+
try:
161+
for user_dn, user in self.iter_users(group_user_filter, extended_attributes):
162+
user['groups'].append(group)
163+
group_users += 1
164+
self.logger.debug('Count of users in group "%s": %d', group, group_users)
165+
except Exception as e:
166+
raise AssertionException('Unexpected LDAP failure reading group members: %s' % e)
167+
else:
168+
members = self.find_ldap_group_member_dn(group, two_steps_group_member_attribute_name)
169+
for member in members:
170+
if member in all_users_records:
171+
user = all_users_records[member]
172+
user['groups'].append(group)
173+
group_users += 1
157174

158175
# if all users are requested, do an additional search for all of them
159176
if all_users:
@@ -197,6 +214,33 @@ def find_ldap_group_dn(self, group):
197214
group_dn = current_tuple[0]
198215
return group_dn
199216

217+
def find_ldap_group_member_dn(self, group, member_attribute):
218+
"""
219+
:type group: str
220+
:type member_attribute: str
221+
:rtype list
222+
"""
223+
connection = self.connection
224+
options = self.options
225+
base_dn = six.text_type(options['base_dn'])
226+
group_filter_format = six.text_type(options['group_filter_format'])
227+
try:
228+
filter_string = self.format_ldap_query_string(group_filter_format, group=group)
229+
res = connection.search_s(base_dn, ldap.SCOPE_SUBTREE,
230+
filterstr=filter_string, attrlist=[member_attribute])
231+
except Exception as e:
232+
raise AssertionException('Unexpected LDAP failure reading group info: %s' % e)
233+
group_members = []
234+
group_dn = None
235+
for current_tuple in res:
236+
if current_tuple[0]:
237+
if group_dn:
238+
raise AssertionException("Multiple LDAP groups found for: %s" % group)
239+
group_dn = current_tuple[0]
240+
if member_attribute in current_tuple[1]:
241+
group_members = current_tuple[1][member_attribute]
242+
return group_members
243+
200244
def iter_users(self, users_filter, extended_attributes):
201245
options = self.options
202246
base_dn = six.text_type(options['base_dn'])

0 commit comments

Comments
 (0)