Skip to content

Commit 1419b71

Browse files
authored
Merge pull request #238 from adobe-apiplatform/issue236
fix #236: allow users to be pushed rather than sync'd to Adobe.
2 parents e8d935e + b235367 commit 1419b71

File tree

3 files changed

+73
-27
lines changed

3 files changed

+73
-27
lines changed

user_sync/app.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ def process_args():
107107
"specify the encoding of your configuration files with this argument. "
108108
"All encoding names understood by Python are allowed.",
109109
dest='encoding_name', default='ascii')
110+
parser.add_argument('--strategy',
111+
help="whether to fetch and sync the Adobe directory against the customer directory "
112+
"or just to push each customer user to the Adobe side. Default is to fetch and sync.",
113+
dest='strategy', metavar='sync|push', default='sync')
110114
return parser.parse_args()
111115

112116

@@ -238,6 +242,7 @@ def create_config_loader_options(args):
238242
'exclude_strays': False,
239243
'manage_groups': args.manage_groups,
240244
'remove_strays': False,
245+
'strategy': 'sync',
241246
'stray_list_input_path': None,
242247
'stray_list_output_path': None,
243248
'test_mode': args.test_mode,
@@ -308,6 +313,12 @@ def create_config_loader_options(args):
308313
logger.info('--adobe-only-user-list specified, so not reading or comparing directory and Adobe users')
309314
config_options['stray_list_input_path'] = stray_list_input_path
310315

316+
# --strategy
317+
if user_sync.helper.normalize_string(args.strategy) == 'push':
318+
config_options['strategy'] = 'push'
319+
if stray_list_input_path or adobe_action_args is not None:
320+
raise AssertionException("You cannot specify '--strategy push' and any '--adobe-only-user' options")
321+
311322
return config_options
312323

313324

user_sync/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def __init__(self, caller_options):
5656
'main_config_filename': DEFAULT_MAIN_CONFIG_FILENAME,
5757
'manage_groups': False,
5858
'remove_strays': False,
59+
'strategy': 'sync',
5960
'stray_list_input_path': None,
6061
'stray_list_output_path': None,
6162
'test_mode': False,
@@ -343,6 +344,7 @@ def get_rule_options(self):
343344
'manage_groups': options['manage_groups'],
344345
'max_adobe_only_users': max_adobe_only_users,
345346
'new_account_type': new_account_type,
347+
'strategy': options['strategy'],
346348
'remove_strays': options['remove_strays'],
347349
'stray_list_input_path': options['stray_list_input_path'],
348350
'stray_list_output_path': options['stray_list_output_path'],

user_sync/rules.py

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def __init__(self, caller_options):
5353
'max_adobe_only_users': 200,
5454
'new_account_type': user_sync.identity_type.ENTERPRISE_IDENTITY_TYPE,
5555
'remove_strays': False,
56+
'strategy': 'sync',
5657
'stray_list_input_path': None,
5758
'stray_list_output_path': None,
5859
'test_mode': False,
@@ -111,6 +112,13 @@ def __init__(self, caller_options):
111112
self.will_process_strays = (not options['exclude_strays']) and (options['stray_list_output_path'] or
112113
self.will_manage_strays)
113114

115+
# specifying a push strategy disables a lot of processing
116+
self.sync_umapi = True
117+
if options['strategy'] == 'push':
118+
self.sync_umapi = False
119+
self.will_manage_strays = False
120+
self.will_process_strays = False
121+
114122
# in/out variables for per-user after-mapping-hook code
115123
self.after_mapping_hook_scope = {
116124
# in: attributes retrieved from customer directory system (eg 'c', 'givenName')
@@ -154,15 +162,14 @@ def run(self, directory_groups, directory_connector, umapi_connectors):
154162
load_directory_stats.log_start(logger)
155163
self.read_desired_user_groups(directory_groups, directory_connector)
156164
load_directory_stats.log_end(logger)
157-
should_sync_umapi_users = True
158-
else:
159-
# no directory users to sync with
160-
should_sync_umapi_users = False
161165

162-
umapi_stats = JobStats("Sync Umapi", divider="-")
166+
umapi_stats = JobStats('Sync with UMAPI' if self.sync_umapi else 'Push to UMAPI', divider="-")
163167
umapi_stats.log_start(logger)
164-
if should_sync_umapi_users:
165-
self.process_umapi_users(umapi_connectors)
168+
if directory_connector is not None:
169+
if self.sync_umapi:
170+
self.sync_umapi_users(umapi_connectors)
171+
else:
172+
self.push_umapi_users(umapi_connectors)
166173
if self.will_process_strays:
167174
self.process_strays(umapi_connectors)
168175
umapi_connectors.execute_actions()
@@ -203,15 +210,22 @@ def log_action_summary(self, umapi_connectors):
203210

204211
# English text description for action summary log.
205212
# The action summary will be shown the same order as they are defined in this list
206-
action_summary_description = [
207-
['directory_users_read', 'Number of directory users read'],
208-
['directory_users_selected', 'Number of directory users selected for input'],
209-
['adobe_users_read', 'Number of Adobe users read'],
210-
['adobe_users_excluded', 'Number of Adobe users excluded from updates'],
211-
['adobe_users_unchanged', 'Number of non-excluded Adobe users with no changes'],
212-
['adobe_users_created', 'Number of new Adobe users added'],
213-
['adobe_users_updated', 'Number of matching Adobe users updated'],
214-
]
213+
if self.sync_umapi:
214+
action_summary_description = [
215+
['directory_users_read', 'Number of directory users read'],
216+
['directory_users_selected', 'Number of directory users selected for input'],
217+
['adobe_users_read', 'Number of Adobe users read'],
218+
['adobe_users_excluded', 'Number of Adobe users excluded from updates'],
219+
['adobe_users_unchanged', 'Number of non-excluded Adobe users with no changes'],
220+
['adobe_users_created', 'Number of new Adobe users added'],
221+
['adobe_users_updated', 'Number of matching Adobe users updated'],
222+
]
223+
else:
224+
action_summary_description = [
225+
['directory_users_read', 'Number of directory users read'],
226+
['directory_users_selected', 'Number of directory users selected for input'],
227+
['adobe_users_created', 'Number of directory users pushed to Adobe'],
228+
]
215229
if self.will_process_strays:
216230
if self.options['delete_strays']:
217231
action = 'deleted'
@@ -368,7 +382,7 @@ def is_directory_user_in_groups(self, directory_user, groups):
368382
return True
369383
return False
370384

371-
def process_umapi_users(self, umapi_connectors):
385+
def sync_umapi_users(self, umapi_connectors):
372386
"""
373387
This is where we actually "do the sync"; that is, where we match users on the two sides.
374388
When we get here, we have loaded all the directory users *and* we have loaded all the adobe users,
@@ -393,7 +407,7 @@ def process_umapi_users(self, umapi_connectors):
393407
# Handle creates for new users. This also drives adding the new user to the secondaries,
394408
# but the secondary adobe groups will be managed below in the usual way.
395409
for user_key, groups_to_add in six.iteritems(primary_adds_by_user_key):
396-
self.add_umapi_user(user_key, groups_to_add, umapi_connectors)
410+
self.add_umapi_user(user_key, groups_to_add, umapi_connectors, manage_secondary_groups=False)
397411
# we just did a bunch of adds, we need to flush the connections before we can sync groups
398412
umapi_connectors.execute_actions()
399413

@@ -408,6 +422,20 @@ def process_umapi_users(self, umapi_connectors):
408422
self.logger.critical("Shouldn't happen! In secondary umapi %s, the following users were not found: %s",
409423
umapi_name, secondary_updates_by_user_key.keys())
410424

425+
def push_umapi_users(self, umapi_connectors):
426+
"""
427+
This is where we push directory users to the Adobe side "as is".
428+
:type umapi_connectors: UmapiConnectors
429+
"""
430+
if umapi_connectors.get_secondary_connectors():
431+
self.logger.debug('Pushing users to primary umapi...')
432+
else:
433+
self.logger.debug('Pushing users to umapi...')
434+
primary_umapi_info = self.get_umapi_info(PRIMARY_UMAPI_NAME)
435+
# Create all the users, putting them in their groups
436+
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)
438+
411439
def is_selected_user_key(self, user_key):
412440
"""
413441
:type user_key: str
@@ -576,10 +604,11 @@ def create_commands_from_directory_user(self, directory_user, identity_type=None
576604
directory_user['username'], directory_user['domain'])
577605
return commands
578606

579-
def add_umapi_user(self, user_key, groups_to_add, umapi_connectors):
607+
def add_umapi_user(self, user_key, groups_to_add, umapi_connectors, manage_secondary_groups=True):
580608
"""
581-
Add the user to the primary umapi with groups, and create in group-using secondaries without groups.
582-
The secondary group mappings should be taken care of by caller when the secondaries are walked.
609+
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.
583612
:type user_key: str
584613
:type groups_to_add: list
585614
:type umapi_connectors: UmapiConnectors
@@ -603,31 +632,35 @@ def add_umapi_user(self, user_key, groups_to_add, umapi_connectors):
603632
# Enterprise users are allowed to have undefined country
604633
country = 'UD'
605634
else:
606-
self.logger.error("User %s cannot be added as it has a blank country code"
607-
" and no default has been specified.", user_key)
635+
self.logger.error("Federated user cannot be added without a specified country code: %s", user_key)
608636
return
609637
attributes['country'] = country
610638
if attributes.get('firstname') is None:
611639
attributes.pop('firstname', None)
612640
if attributes.get('lastname') is None:
613641
attributes.pop('lastname', None)
614-
attributes['option'] = "updateIfAlreadyExists" if update_user_info else 'ignoreIfAlreadyExists'
615-
642+
attributes['option'] = 'updateIfAlreadyExists' if update_user_info else 'ignoreIfAlreadyExists'
616643
# add the user to primary with groups
617644
self.logger.info('Adding directory user with user key: %s', user_key)
618645
self.action_summary['adobe_users_created'] += 1
619646
primary_commands.add_user(attributes)
620647
if manage_groups:
621648
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)
622650
umapi_connectors.get_primary_connector().send_commands(primary_commands)
623-
# add the user to secondaries without groups
651+
# add the user to secondaries, maybe with groups
652+
attributes['option'] = 'ignoreIfAlreadyExists' # can only update in the owning org
624653
for umapi_name, umapi_connector in six.iteritems(umapi_connectors.secondary_connectors):
625654
secondary_umapi_info = self.get_umapi_info(umapi_name)
626655
# only add the user to this secondary if he is in groups in this secondary
627-
if secondary_umapi_info.get_desired_groups(user_key):
656+
groups_to_add = secondary_umapi_info.get_desired_groups(user_key)
657+
if groups_to_add:
628658
self.logger.info('Adding directory user to %s with user key: %s', umapi_name, user_key)
629659
secondary_commands = self.create_commands_from_directory_user(directory_user, identity_type)
630660
secondary_commands.add_user(attributes)
661+
if manage_secondary_groups and manage_groups:
662+
secondary_commands.add_groups(groups_to_add)
663+
secondary_commands.remove_groups(secondary_umapi_info.get_mapped_groups() - groups_to_add)
631664
umapi_connector.send_commands(secondary_commands)
632665

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

0 commit comments

Comments
 (0)