Skip to content

Commit 996cf3d

Browse files
authored
Merge pull request #231 from adobe-apiplatform/issue182
Fixes for #182, #129, and #233 - all the requested LDAP improvements
2 parents a82c15a + 9350b60 commit 996cf3d

File tree

11 files changed

+122
-175
lines changed

11 files changed

+122
-175
lines changed

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,26 @@ require_tls_cert: False
5959
all_users_filter: "(&(objectClass=user)(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"
6060

6161
# (optional) group_filter_format (default value given below)
62-
# group_filter_format specifies the format string used to construct a group query,
63-
# as needed by the --users groups or --users mapped command-line arguments.
62+
# group_filter_format specifies the format string used to get the distinguished
63+
# name of a group given its common name (as specified in the directory to Adobe
64+
# group mapping, or in the --users group "name1,name2" command-line argument).
6465
# {group} is replaced with the name of the group to find. The default value here is
6566
# complex, because it's meant to work for both AD-style and OpenLDAP-style directories.
6667
# You will likely want to replace it with a simpler query customized for your directory,
6768
# such as this one for Active Directory: "(&(objectCategory=group)(cn={group}))"
6869
# or this one for OpenLDAP: "(&(|(objectClass=groupOfNames)(objectClass=posixGroup))(cn={group}))"
6970
group_filter_format: "(&(|(objectCategory=group)(objectClass=groupOfNames)(objectClass=posixGroup))(cn={group}))"
7071

72+
# (optional) group_member_filter_format (default value given below)
73+
# group_users_filter specifies the query used to find all members of a group,
74+
# where the string {group_dn} is replaced with the group distinguished name.
75+
# The default value just finds users who are immediate members of the group,
76+
# not those who are "indirectly" members by virtue of membership in a group
77+
# that is contained in the group. If you want indirect containment, then
78+
# use this value instead of the default:
79+
# group_member_filter_format: "(memberOf:1.2.840.113556.1.4.1941:={group_dn})"
80+
group_member_filter_format: "(memberOf={group_dn})"
81+
7182
# (optional) string_encoding (default value given below)
7283
# string_encoding specifies the Unicode string encoding used by the directory.
7384
# All values retrieved from the directory are converted to Unicode before being

tests/connector/directory_csv_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def test_normal(self):
3232
}
3333
directory_connector.initialize(options)
3434

35-
actual_users = directory_connector.load_users_and_groups(all_groups)
35+
actual_users = directory_connector.load_users_and_groups(groups=all_groups)
3636

3737
tests.helper.assert_equal_users(self, all_users, actual_users)
3838

tests/connector/directory_ldap_test.py

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,11 @@
99
class LDAPDirectoryTest(unittest.TestCase):
1010

1111
def test_normal(self):
12-
user1 = tests.helper.create_test_user(['Acrobat1', 'Acrobat2'])
13-
user2 = tests.helper.create_test_user(['Acrobat3'])
12+
user1 = tests.helper.create_test_user([])
13+
user2 = tests.helper.create_test_user([])
1414
user3 = tests.helper.create_test_user([])
1515
all_users = [user1, user2, user3]
1616

17-
users_by_group = {}
18-
for user in all_users:
19-
for group in user['groups']:
20-
users_with_same_group = users_by_group.get(group)
21-
if (users_with_same_group == None):
22-
users_by_group[group] = users_with_same_group = []
23-
users_with_same_group.append(user)
24-
2517
ldap_options = {
2618
'host': 'test_host',
2719
'username': 'test_user',
@@ -43,19 +35,8 @@ def mock_simple_bind_s(*args, **kwargs):
4335
def mock_search_s(*args, **kwargs):
4436
search_result = re.search('cn=(.*?)\)', kwargs['filterstr'])
4537
group_name = search_result.group(1)
46-
users = users_by_group.get(group_name, [])
47-
return [(group_name, {
48-
'member': [user['firstname'] for user in users if group_name in user['groups']]
49-
})]
50-
51-
def mock_result(*args, **kwargs):
52-
rtype = ldap.RES_SEARCH_RESULT
53-
rdata = []
54-
return rtype, rdata
38+
return [(group_name, {})]
5539

56-
def mock_search(*args, **kwargs):
57-
return kwargs['filterstr']
58-
5940
def mock_search_ext(*args, **kwargs):
6041
return kwargs['filterstr']
6142

@@ -76,12 +57,10 @@ def mock_result3(*args, **kwargs):
7657
connection.search_s = mock_search_s
7758
connection.search_ext = mock_search_ext
7859
connection.result3 = mock_result3
79-
connection.search = mock_search
80-
connection.result = mock_result
8160

8261
directory_connector = user_sync.connector.directory.DirectoryConnector(user_sync.connector.directory_ldap)
8362
directory_connector.initialize(ldap_options)
8463

85-
actual_users = directory_connector.load_users_and_groups(users_by_group.iterkeys())
64+
actual_users = directory_connector.load_users_and_groups(None)
8665

8766
tests.helper.assert_equal_users(self, all_users, actual_users)

tests/rules_test.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,21 @@ def test_normal(self):
4040
directory_group_1: [user_sync.rules.AdobeGroup(primary_group_11, primary_umapi_name), user_sync.rules.AdobeGroup('acrobat12', secondary_1_umapi_name)],
4141
directory_group_2: [user_sync.rules.AdobeGroup(primary_group_21, primary_umapi_name)]
4242
}
43-
all_users = [tests.helper.create_test_user([directory_group_1]),
43+
everybody = [tests.helper.create_test_user([directory_group_1]),
4444
tests.helper.create_test_user([directory_group_2]),
4545
tests.helper.create_test_user([])]
4646

47-
for user in all_users:
47+
for user in everybody:
4848
user['username'] = user['email']
4949
user['domain'] = None
5050

5151
primary_users = []
52-
primary_user_1 = all_users[1].copy()
52+
primary_user_1 = everybody[1].copy()
5353
primary_user_1['groups'] = [primary_group_11]
5454
primary_users.append(primary_user_1)
5555

56-
def mock_load_users_and_groups(groups, extended_attributes=None):
57-
return list(all_users)
56+
def mock_load_users_and_groups(groups=None, extended_attributes=None, all_users=True):
57+
return list(everybody)
5858
mock_directory_connector = mock.mock.create_autospec(user_sync.connector.directory.DirectoryConnector)
5959
mock_directory_connector.load_users_and_groups = mock_load_users_and_groups
6060

@@ -75,25 +75,25 @@ def mock_load_users_and_groups(groups, extended_attributes=None):
7575

7676
expected_primary_commands_list = []
7777

78-
user = all_users[1]
78+
user = everybody[1]
7979
commands = tests.helper.create_umapi_commands(user)
8080
commands.add_groups(set([primary_group_21]))
8181
commands.remove_groups(set([primary_group_11]))
8282
expected_primary_commands_list.append(commands)
8383

84-
user = all_users[0]
84+
user = everybody[0]
8585
commands = tests.helper.create_umapi_commands(user)
8686
commands.add_user(self.create_user_attributes_for_commands(user, rule_options['update_user_info']))
8787
commands.add_groups(set([primary_group_11]))
8888
expected_primary_commands_list.append(commands)
8989

90-
user = all_users[2]
90+
user = everybody[2]
9191
commands = tests.helper.create_umapi_commands(user)
9292
commands.add_user(self.create_user_attributes_for_commands(user, rule_options['update_user_info']))
9393
expected_primary_commands_list.append(commands)
9494

9595
expected_secondary_commands_list = []
96-
user = all_users[0]
96+
user = everybody[0]
9797
commands = tests.helper.create_umapi_commands(user)
9898
commands.add_groups(set([primary_group_12]))
9999
expected_secondary_commands_list.append(commands)

user_sync/app.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,11 @@ def main():
359359
if not e.is_reported():
360360
logger.critical(e.message)
361361
e.set_reported()
362+
except KeyboardInterrupt:
363+
try:
364+
logger.critical('Keyboard interrupt, exiting immediately.')
365+
except:
366+
pass
362367
except:
363368
try:
364369
logger.error('Unhandled exception', exc_info=sys.exc_info())

user_sync/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -844,7 +844,7 @@ def set_int_value(self, key, default_value):
844844
def set_string_value(self, key, default_value):
845845
"""
846846
:type key: str
847-
:type default_value: str
847+
:type default_value: Optional(str)
848848
"""
849849
self.set_value(key, types.StringTypes, default_value)
850850

user_sync/connector/directory.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,16 @@ def initialize(self, options=None):
4545
options = {}
4646
self.state = self.implementation.connector_initialize(options)
4747

48-
def load_users_and_groups(self, groups, extended_attributes=None):
48+
def load_users_and_groups(self, groups, extended_attributes=None, all_users=True):
4949
"""
5050
:type groups: list(str)
51-
:type extended_attributes: list(str)
51+
:type extended_attributes: Optional(list(str))
52+
:type all_users: bool
5253
:rtype (bool, iterable(dict))
5354
"""
5455
if extended_attributes is None:
5556
extended_attributes = []
56-
return self.implementation.connector_load_users_and_groups(self.state, groups, extended_attributes)
57+
return self.implementation.connector_load_users_and_groups(self.state,
58+
groups=groups,
59+
extended_attributes=extended_attributes,
60+
all_users=all_users)

user_sync/connector/directory_csv.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,16 @@ def connector_initialize(options):
3939
return state
4040

4141

42-
def connector_load_users_and_groups(state, groups, extended_attributes):
42+
def connector_load_users_and_groups(state, groups=None, extended_attributes=None, all_users=True):
4343
"""
4444
:type state: CSVDirectoryConnector
45-
:type groups: list(str)
46-
:type extended_attributes: list(str)
45+
:type groups: Optional(list(str))
46+
:type extended_attributes: Optional(list(str))
47+
:type all_users: bool
4748
:rtype (bool, iterable(dict))
4849
"""
49-
50-
# CSV supports arbitrary aka "extended" attrs by default,
51-
# so the value of extended_attributes has no impact on this particular connector
52-
return state.load_users_and_groups(groups, extended_attributes)
50+
# CSV always reads all users, so we don't bother passing the all_users parameter into the implementation
51+
return state.load_users_and_groups(groups or [], extended_attributes or [])
5352

5453

5554
class CSVDirectoryConnector(object):

0 commit comments

Comments
 (0)