Skip to content

Commit 8d0c11b

Browse files
committed
improve fix of #236: only push has remove-group semantics
* Changed the fix of #236 so that the semantics of sync is unchanged from before (for better backward compatibility). Only push does remove of groups, because it's operating in the blind. * Updated the nosetests so they work in py2 and py3 * Updated the noestests to add testing the rules with push strategy
1 parent b5c7689 commit 8d0c11b

File tree

5 files changed

+125
-36
lines changed

5 files changed

+125
-36
lines changed

tests/config_file_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import os
2424
import os.path
2525
import yaml
26-
import six
26+
import six.moves
2727

2828
import mock
2929
import tests.helper
@@ -43,7 +43,7 @@ def assert_eq(self, result, expected, error_message):
4343
'''
4444
self.assertEqual(result, expected, error_message + '\nexpected: %s, got: %s' % (expected, result))
4545

46-
@mock.patch('builtins.open')
46+
@mock.patch('six.moves.builtins.open')
4747
@mock.patch('yaml.load')
4848
@mock.patch('os.path.isfile')
4949
def test_load_root_config(self, mock_isfile, mock_yaml, mock_open):
@@ -93,7 +93,7 @@ def test_load_root_config(self, mock_isfile, mock_yaml, mock_open):
9393
self.assert_eq(yml['other']['test-list'][2]['test-string-3'], 'xyz',
9494
'/other/test-list/[2] value should not change')
9595

96-
@mock.patch('builtins.open')
96+
@mock.patch('six.moves.builtins.open')
9797
@mock.patch('yaml.load')
9898
@mock.patch('os.path.isfile')
9999
def test_load_root_default_config(self, mock_isfile, mock_yaml, mock_open):
@@ -123,7 +123,7 @@ def test_load_root_default_config(self, mock_isfile, mock_yaml, mock_open):
123123
self.assert_eq(yml['adobe_users']['connectors']['umapi'], os.path.abspath('config-test-2/test-123'),
124124
'default primary umapi configuration path is incorrect')
125125

126-
@mock.patch('builtins.open')
126+
@mock.patch('six.moves.builtins.open')
127127
@mock.patch('yaml.load')
128128
@mock.patch('os.path.isfile')
129129
def test_load_sub_config(self, mock_isfile, mock_yaml, mock_open):

tests/connector/directory_ldap_test.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import unittest
33

44
import mock.mock
5+
import six
56

67
import tests.helper
78
import user_sync.connector.directory
@@ -46,11 +47,11 @@ def mock_result3(*args, **kwargs):
4647
rtype = ldap.RES_SEARCH_RESULT
4748
rmsgid = None
4849
serverctrls = []
49-
rdata = [(user['firstname'], {
50-
'givenName': [user['firstname']],
51-
'sn': [user['lastname']],
52-
'c': [user['country']],
53-
'mail': [user['email']],
50+
rdata = [(user['firstname'].encode('utf8', 'strict'), {
51+
'givenName': [user['firstname'].encode('utf8', 'strict')],
52+
'sn': [user['lastname'].encode('utf8', 'strict')],
53+
'c': [user['country'].encode('utf8', 'strict')],
54+
'mail': [user['email'].encode('utf8', 'strict')],
5455
}) for user in all_users]
5556
return rtype, rdata, rmsgid, serverctrls
5657

tests/rules_test.py

Lines changed: 100 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,75 +27,156 @@
2727
import tests.helper
2828

2929
class RulesTest(unittest.TestCase):
30-
31-
def test_normal(self):
30+
31+
def test_sync(self):
3232
primary_umapi_name = user_sync.rules.PRIMARY_UMAPI_NAME
3333
secondary_1_umapi_name = "secondary1"
34-
directory_group_1 = 'acrobat1'
35-
directory_group_2 = 'acrobat2'
34+
directory_group_1 = 'acrobat1'
35+
directory_group_2 = 'acrobat2'
3636
primary_group_11 = 'acrobat11'
37-
primary_group_12 = 'acrobat12'
37+
secondary_group_12 = 'acrobat12'
3838
primary_group_21 = 'acrobat21'
3939
directory_groups = {
40-
directory_group_1: [user_sync.rules.AdobeGroup(primary_group_11, primary_umapi_name), user_sync.rules.AdobeGroup('acrobat12', secondary_1_umapi_name)],
40+
directory_group_1: [user_sync.rules.AdobeGroup(primary_group_11, primary_umapi_name),
41+
user_sync.rules.AdobeGroup(secondary_group_12, secondary_1_umapi_name)],
4142
directory_group_2: [user_sync.rules.AdobeGroup(primary_group_21, primary_umapi_name)]
4243
}
4344
everybody = [tests.helper.create_test_user([directory_group_1]),
4445
tests.helper.create_test_user([directory_group_2]),
4546
tests.helper.create_test_user([])]
46-
47+
4748
for user in everybody:
4849
user['username'] = user['email']
4950
user['domain'] = None
50-
51+
5152
primary_users = []
5253
primary_user_1 = everybody[1].copy()
5354
primary_user_1['groups'] = [primary_group_11]
5455
primary_users.append(primary_user_1)
55-
56+
5657
def mock_load_users_and_groups(groups=None, extended_attributes=None, all_users=True):
5758
return list(everybody)
5859
mock_directory_connector = mock.mock.create_autospec(user_sync.connector.directory.DirectoryConnector)
5960
mock_directory_connector.load_users_and_groups = mock_load_users_and_groups
60-
61+
6162
primary_commands_list = []
6263
mock_primary_umapi_connector = self.create_mock_umapi_connector(primary_users, primary_commands_list)
6364

6465
secondary_commands_list = []
6566
mock_secondary_umapi_connector = self.create_mock_umapi_connector([], secondary_commands_list)
66-
67+
6768
umapi_connectors = user_sync.rules.UmapiConnectors(mock_primary_umapi_connector, {
6869
secondary_1_umapi_name: mock_secondary_umapi_connector
6970
})
70-
71+
7172
rule_processor = user_sync.rules.RuleProcessor({'manage_groups': True})
7273
rule_processor.run(directory_groups, mock_directory_connector, umapi_connectors)
7374

7475
rule_options = rule_processor.options
7576

7677
expected_primary_commands_list = []
77-
78+
# when syncing, the existing Adobe user is processsed first
7879
user = everybody[1]
7980
commands = tests.helper.create_umapi_commands(user)
8081
commands.add_groups(set([primary_group_21]))
8182
commands.remove_groups(set([primary_group_11]))
8283
expected_primary_commands_list.append(commands)
83-
8484
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)
89-
9089
user = everybody[2]
9190
commands = tests.helper.create_umapi_commands(user)
9291
commands.add_user(self.create_user_attributes_for_commands(user, rule_options['update_user_info']))
9392
expected_primary_commands_list.append(commands)
94-
93+
94+
expected_secondary_commands_list = []
95+
user = everybody[0]
96+
commands = tests.helper.create_umapi_commands(user)
97+
commands.add_groups(set([secondary_group_12]))
98+
expected_secondary_commands_list.append(commands)
99+
100+
tests.helper.assert_equal_umapi_commands_list(self, expected_primary_commands_list, primary_commands_list)
101+
tests.helper.assert_equal_umapi_commands_list(self, expected_secondary_commands_list, secondary_commands_list)
102+
103+
def test_push(self):
104+
primary_umapi_name = user_sync.rules.PRIMARY_UMAPI_NAME
105+
secondary_1_umapi_name = "secondary1"
106+
directory_group_1 = 'acrobat1'
107+
directory_group_2 = 'acrobat2'
108+
primary_group_11 = 'acrobat11'
109+
secondary_group_12 = 'acrobat12'
110+
primary_group_21 = 'acrobat21'
111+
directory_groups = {
112+
directory_group_1: [user_sync.rules.AdobeGroup(primary_group_11, primary_umapi_name),
113+
user_sync.rules.AdobeGroup(secondary_group_12, secondary_1_umapi_name)],
114+
directory_group_2: [user_sync.rules.AdobeGroup(primary_group_21, primary_umapi_name)]
115+
}
116+
everybody = [tests.helper.create_test_user([directory_group_1]),
117+
tests.helper.create_test_user([directory_group_2]),
118+
tests.helper.create_test_user([])]
119+
120+
for user in everybody:
121+
user['username'] = user['email']
122+
user['domain'] = None
123+
124+
primary_users = []
125+
primary_user_1 = everybody[1].copy()
126+
primary_user_1['groups'] = [primary_group_11]
127+
primary_users.append(primary_user_1)
128+
129+
def mock_load_users_and_groups(groups=None, extended_attributes=None, all_users=True):
130+
return list(everybody)
131+
132+
mock_directory_connector = mock.mock.create_autospec(user_sync.connector.directory.DirectoryConnector)
133+
mock_directory_connector.load_users_and_groups = mock_load_users_and_groups
134+
135+
primary_commands_list = []
136+
mock_primary_umapi_connector = self.create_mock_umapi_connector(primary_users, primary_commands_list)
137+
138+
secondary_commands_list = []
139+
mock_secondary_umapi_connector = self.create_mock_umapi_connector([], secondary_commands_list)
140+
141+
umapi_connectors = user_sync.rules.UmapiConnectors(mock_primary_umapi_connector, {
142+
secondary_1_umapi_name: mock_secondary_umapi_connector
143+
})
144+
145+
rule_processor = user_sync.rules.RuleProcessor({'manage_groups': True,
146+
'strategy': 'push',
147+
'update_user_info': True,
148+
})
149+
rule_processor.run(directory_groups, mock_directory_connector, umapi_connectors)
150+
151+
rule_options = rule_processor.options
152+
153+
expected_primary_commands_list = []
154+
# when pushing, they are in directory order
155+
user = everybody[0]
156+
commands = tests.helper.create_umapi_commands(user)
157+
commands.add_user(self.create_user_attributes_for_commands(user, rule_options['update_user_info']))
158+
commands.add_groups(set([primary_group_11]))
159+
commands.remove_groups(set([primary_group_21]))
160+
expected_primary_commands_list.append(commands)
161+
162+
user = everybody[1]
163+
commands = tests.helper.create_umapi_commands(user)
164+
commands.add_user(self.create_user_attributes_for_commands(user, rule_options['update_user_info']))
165+
commands.add_groups(set([primary_group_21]))
166+
commands.remove_groups(set([primary_group_11]))
167+
expected_primary_commands_list.append(commands)
168+
169+
user = everybody[2]
170+
commands = tests.helper.create_umapi_commands(user)
171+
commands.add_user(self.create_user_attributes_for_commands(user, rule_options['update_user_info']))
172+
commands.remove_groups(set([primary_group_11, primary_group_21]))
173+
expected_primary_commands_list.append(commands)
174+
95175
expected_secondary_commands_list = []
96176
user = everybody[0]
97177
commands = tests.helper.create_umapi_commands(user)
98-
commands.add_groups(set([primary_group_12]))
178+
commands.add_user(self.create_user_attributes_for_commands(user, False))
179+
commands.add_groups(set([secondary_group_12]))
99180
expected_secondary_commands_list.append(commands)
100181

101182
tests.helper.assert_equal_umapi_commands_list(self, expected_primary_commands_list, primary_commands_list)
@@ -105,7 +186,7 @@ def mock_load_users_and_groups(groups=None, extended_attributes=None, all_users=
105186
@mock.patch('logging.getLogger')
106187
def _do_country_code_test(self, mock_umapi_commands, mock_connectors, identity_type, default_country_code, user_country_code, expected_country_code, mock_logger):
107188
user_key = identity_type + ',[email protected],'
108-
expected_result = {'lastname': 'User1', 'email': '[email protected]', 'firstname': '!Openldap CCE', 'option': 'updateIfAlreadyExists'}
189+
expected_result = {'lastname': 'User1', 'email': '[email protected]', 'firstname': '!Openldap CCE', 'option': 'ignoreIfAlreadyExists'}
109190
if (expected_country_code):
110191
expected_result['country'] = expected_country_code
111192

@@ -124,7 +205,7 @@ def _do_country_code_test(self, mock_umapi_commands, mock_connectors, identity_t
124205
mock_rules.add_umapi_user(user_key, set(), mock_connectors)
125206

126207
if (identity_type == 'federatedID' and default_country_code == None and user_country_code == None):
127-
mock_rules.logger.error.assert_called_with('User %s cannot be added as it has a blank country code and no default has been specified.', user_key)
208+
mock_rules.logger.error.assert_called_with('Federated user cannot be added without a specified country code: %s', user_key)
128209
else:
129210
mock_umapi_commands.return_value.add_user.assert_called_with(expected_result)
130211

user_sync/connector/directory_ldap.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ def generate_value(self, record):
366366
@classmethod
367367
def get_attribute_value(cls, attributes, attribute_name):
368368
"""
369+
The attribute value type must be decodable (str in py2, bytes in py3)
369370
:type attributes: dict
370371
:type attribute_name: unicode
371372
"""

user_sync/rules.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ def sync_umapi_users(self, umapi_connectors):
407407
# Handle creates for new users. This also drives adding the new user to the secondaries,
408408
# but the secondary adobe groups will be managed below in the usual way.
409409
for user_key, groups_to_add in six.iteritems(primary_adds_by_user_key):
410-
self.add_umapi_user(user_key, groups_to_add, umapi_connectors, manage_secondary_groups=False)
410+
self.add_umapi_user(user_key, groups_to_add, umapi_connectors)
411411
# we just did a bunch of adds, we need to flush the connections before we can sync groups
412412
umapi_connectors.execute_actions()
413413

@@ -434,7 +434,7 @@ def push_umapi_users(self, umapi_connectors):
434434
primary_umapi_info = self.get_umapi_info(PRIMARY_UMAPI_NAME)
435435
# Create all the users, putting them in their groups
436436
for user_key, groups_to_add in six.iteritems(primary_umapi_info.get_desired_groups_by_user_key()):
437-
self.add_umapi_user(user_key, groups_to_add, umapi_connectors, manage_secondary_groups=True)
437+
self.add_umapi_user(user_key, groups_to_add, umapi_connectors)
438438

439439
def is_selected_user_key(self, user_key):
440440
"""
@@ -604,11 +604,13 @@ def create_commands_from_directory_user(self, directory_user, identity_type=None
604604
directory_user['username'], directory_user['domain'])
605605
return commands
606606

607-
def add_umapi_user(self, user_key, groups_to_add, umapi_connectors, manage_secondary_groups=True):
607+
def add_umapi_user(self, user_key, groups_to_add, umapi_connectors):
608608
"""
609609
Add the user to the primary umapi with the given groups, and create the user in any secondaries
610-
in which he should be in a group. If directed, also add the user to those groups in the secondary.
611-
If we are managing groups, we also remove the user from any mapped groups he shouldn't be in.
610+
in which he should be in a group. If we are syncing, that's all we do. If we are pushing rather
611+
than syncing, we also add the user to the correct group in the secondaries, and we remove
612+
the user from any mapped groups he shouldn't be in both in the primary and in the secondaries.
613+
(This way, when we push blindly, we manage his entire set of mapped groups rather than just some.)
612614
:type user_key: str
613615
:type groups_to_add: list
614616
:type umapi_connectors: UmapiConnectors
@@ -617,6 +619,7 @@ def add_umapi_user(self, user_key, groups_to_add, umapi_connectors, manage_secon
617619
options = self.options
618620
update_user_info = options['update_user_info']
619621
manage_groups = self.will_manage_groups()
622+
doing_push = not self.sync_umapi
620623

621624
# put together the user's attributes
622625
directory_user = self.directory_user_by_user_key[user_key]
@@ -646,7 +649,9 @@ def add_umapi_user(self, user_key, groups_to_add, umapi_connectors, manage_secon
646649
primary_commands.add_user(attributes)
647650
if manage_groups:
648651
primary_commands.add_groups(groups_to_add)
649-
primary_commands.remove_groups(self.get_umapi_info(PRIMARY_UMAPI_NAME).get_mapped_groups() - groups_to_add)
652+
if doing_push:
653+
groups_to_remove = self.get_umapi_info(PRIMARY_UMAPI_NAME).get_mapped_groups() - groups_to_add
654+
primary_commands.remove_groups(groups_to_remove)
650655
umapi_connectors.get_primary_connector().send_commands(primary_commands)
651656
# add the user to secondaries, maybe with groups
652657
attributes['option'] = 'ignoreIfAlreadyExists' # can only update in the owning org
@@ -658,9 +663,10 @@ def add_umapi_user(self, user_key, groups_to_add, umapi_connectors, manage_secon
658663
self.logger.info('Adding directory user to %s with user key: %s', umapi_name, user_key)
659664
secondary_commands = self.create_commands_from_directory_user(directory_user, identity_type)
660665
secondary_commands.add_user(attributes)
661-
if manage_secondary_groups and manage_groups:
666+
if manage_groups and doing_push:
662667
secondary_commands.add_groups(groups_to_add)
663-
secondary_commands.remove_groups(secondary_umapi_info.get_mapped_groups() - groups_to_add)
668+
groups_to_remove = secondary_umapi_info.get_mapped_groups() - groups_to_add
669+
secondary_commands.remove_groups(groups_to_remove)
664670
umapi_connector.send_commands(secondary_commands)
665671

666672
def update_umapi_user(self, umapi_info, user_key, umapi_connector,

0 commit comments

Comments
 (0)