Skip to content

Commit b5431ba

Browse files
committed
Complete work to fix #90, fix #46, and fix #101.
This ended up being a major overhaul of the code that processes Adobe users who don't match users on the customer side. The old code was overly complicated, because it was trying to carefully sequence deleting membership in groups with membership in organizations. But in fact this is not necessary, because removing a user from an organization will remove that user from all the groups in the organization. So the new code just has to be careful to remove users from accessor (aka trustee) orgs before removing or deleting them in the owning org. Another major piece of cleanup/overhaul had to do with enumerating users in accessor organizations. Since the conceptual framework for accessors vs. owners is that the owner org owns the domains that the users are in, whereas the accessors are just granting entitlements to the users in those domains, the whole structure assumes that all users must be in the owner org and only some of them are in the accessor org. This means that, when we go to enumerate accessors, we want to ignore any dashboard users we find that weren't in the owner org. In addition, we never want to update attributes in the accessor orgs, because trustees are not allowed to do that. Finally, when we find an unmatched user that we want to operate on, we need to keep track which accessors we've actually seen that user in, so that we don't just go trying to remove every user from every accessor. (We could do this, of course, but it's overkill, and currently the server gives errors if you try to delete a user who is not actually in the org.) The work done here included (in no particular order): * move to umapi_client 2.1, because it has compatibility fixes for changes in the wire protocol around removing users from organizations and deleting them. * take advantage of the umapi_client upgrade to correctly delete user accounts. * update the format of the unmatched users list, so that we also track the org of the unmatched user as well as the user key. This allows being precise about which users need to be removed from which orgs. * rename "nonexistent" users to be "unmatched" users in all public contexts. @phil-levy may or may not like this change. * rename "orphan users" (the internal word in the code for unmatched) to be "strays". * make sure that the default new account type is inherited and used by the ldap and csv connectors. * fix the stats to make sense in all cases * rework the way we track excluded and included users so it works for accessors as well as the owner org. * rework the way we track unmatched users by keeping a global map from organizations to the user keys that were deleted in those organizations. This works even when you haven't loaded the directory or dashboard, as when you are removing users on an input list. * changed input delete list handling so we don't bother loading directory or dashboard users. The code now seems dead stable and works with multiple organizations quite well. The one problem is a strange umapi crash on an unexpected server response when you do --remove-entitlements-for-non-existent-users on multiple orgs. The crash happens *after* all the server-side processing has been done, so essentially the run completes successfully and then umapi_client immediately throws a ServerError. I'll have to figure this out for the GM build, but for now testing can proceed on RC2.
1 parent 0feeb13 commit b5431ba

File tree

9 files changed

+239
-200
lines changed

9 files changed

+239
-200
lines changed

docs/success-guide/setup_config_files.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ A more realistic example is:
112112

113113
![](images/setup_config_group_map.png)
114114

115-
#### Delete Limits
115+
#### Unmatched User Limits
116116

117117
Limits on deletion prevent accidental account deletion in the event of misconfiguration or some other problem that results in User Sync not getting proper data from the directory system.
118118

@@ -121,8 +121,8 @@ Limits on deletion prevent accidental account deletion in the event of misconfig
121121
☐ If you expect the number of directory users to drop by more than 200 between User Sync runs, then you will need to raise the max\_missing\_users value. These config file entries are to prevent runaway deletion in case of misconfiguration or other problems.
122122

123123
limits:
124-
    max_strays_to_process: 10 # ceiling on disable/remove/delete
125-
    max_strays_hard_limit: 200      # abort if this many directory users disappear
124+
    max_removed_users: 10 # ceiling on disable/remove/delete
125+
    max_unmatched_users: 200      # abort if this many directory users disappear
126126

127127

128128

docs/user-manual/index.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -561,13 +561,13 @@ groups:
561561
User accounts are removed from the Adobe dashboard when
562562
corresponding users are not present in the directory and the tool
563563
is invoked with the `--remove-nonexistent-users` option. The
564-
`max_strays_to_process` and `max_strays_hard_limit` values in
564+
`max_removed_users` and `max_unmatched_users` values in
565565
the `limits` section of the configuration file set limits on
566566
how many users can be removed at any one time. These limits
567567
prevent accidental removal of a large number of accounts in case
568568
of misconfiguration or other errors:
569569

570-
- The value of `max_strays_to_process` sets a limit on the number
570+
- The value of `max_removed_users` sets a limit on the number
571571
of account removals in a single run. If more users are flagged
572572
for removal, they are left for the next run.
573573

@@ -577,7 +577,7 @@ raise this value.
577577
- If your organization has a large number of users in the
578578
enterprise directory and the number of users read during a sync
579579
is suddenly small, this could indicate a misconfiguration or
580-
error situation. The value of `max_strays_hard_limit` is a threshold
580+
error situation. The value of `max_unmatched_users` is a threshold
581581
which causes the run to exit and report an error if there are
582582
this many fewer users in the enterprise directory than in the
583583
Adobe admin console.
@@ -589,8 +589,8 @@ For example:
589589

590590
```YAML
591591
limits:
592-
max_strays_to_process: 10
593-
max_strays_hard_limit: 200
592+
max_removed_users: 10
593+
max_unmatched_users: 200
594594
```
595595

596596
This configuration causes User Sync to remove no more than 10
@@ -693,8 +693,8 @@ directory:
693693
- "Default Adobe Enterprise Support Program configuration"
694694
695695
limits:
696-
max_strays_to_process: 10
697-
max_strays_hard_limit: 200
696+
max_removed_users: 10
697+
max_unmatched_users: 200
698698
699699
logging:
700700
log_to_file: True

examples/config files - basic/1 user-sync-config.yml

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ dashboard:
1919
# accessor_config_filename_format: "dashboard-accessor-{organization_name}-config.yml"
2020

2121
directory:
22-
# (optional) Default country code to use if directory doesn't provide one for a user [Must be two-letter ISO-3166 code - see https://en.wikipedia.org/wiki/ISO_3166-1]
22+
# (optional) Default country code to use if directory doesn't provide one for a user
23+
# [Must be a two-letter ISO-3166 code - see https://en.wikipedia.org/wiki/ISO_3166-1]
2324
#
2425
# example:
2526
# default_country_code: US
@@ -87,14 +88,14 @@ directory:
8788
# - enterpriseID
8889
# - federatedID
8990

90-
limits: # provide processing controls over stray Adobe users (that is, that are unmatched on customer side)
91-
# these controls only apply when strays are being processed for removal or deletion from the Adobe side;
92-
# if strays are just being output to a file for later processing, these settings do not apply.
93-
max_strays_hard_limit: 200 # If more than this many stray users are found, no processing of them is done.
94-
# Instead, user sync will abort at the point it would remove or delete them.
95-
max_strays_to_process: 10 # If more than this many stray users are found, only this many of them will be
96-
# removed or deleted, and the the run will terminate normally. Later runs
97-
# can be used to process the next batch of them.
91+
limits: # provide processing controls over Adobe users that do not match users on the customer side
92+
# these controls only apply when unmatched users are being removed or deleted from the Adobe side;
93+
# if the users are just being output to a file for later processing, these settings do not apply.
94+
max_unmatched_users: 200 # If more than this many unmatched users are found, no processing of them is done.
95+
# Instead, user sync will abort at the point it would remove or delete them.
96+
max_removed_users: 10 # If more than this many stray users are found, only this many of them will be
97+
# removed or deleted, and the the run will terminate normally. Later runs
98+
# can be used to process the next batch of them.
9899

99100
logging:
100101
# specifies whether you wish to generate a log file

examples/config files - custom attributes and mappings/1 user-sync-config.yml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ dashboard:
1919
# accessor_config_filename_format: "dashboard-accessor-{organization_name}-config.yml"
2020

2121
directory:
22-
# (optional) Default country code to use if directory doesn't provide one for a user [Must be two-letter ISO-3166 code - see https://en.wikipedia.org/wiki/ISO_3166-1]
22+
# (optional) Default country code to use if directory doesn't provide one for a user
23+
# [Must be a two-letter ISO-3166 code - see https://en.wikipedia.org/wiki/ISO_3166-1]
2324
#
2425
# example:
2526
# default_country_code: US
@@ -121,9 +122,14 @@ extensions:
121122
elif subco == 'Company 2':
122123
target_groups.add('Company 2 Users')
123124
124-
limits:
125-
max_strays_to_process: 10 # if --remove-nonexistent-users is specified, this is the most users that will be removed. Others will be left for a later run. A critical message will be logged.
126-
max_strays_hard_limit: 200 # if more than this number of user accounts are not found in the directory, user sync will abort with an error and a critical message will be logged.
125+
limits: # provide processing controls over Adobe users that do not match users on the customer side
126+
# these controls only apply when unmatched users are being removed or deleted from the Adobe side;
127+
# if the users are just being output to a file for later processing, these settings do not apply.
128+
max_unmatched_users: 200 # If more than this many unmatched users are found, no processing of them is done.
129+
# Instead, user sync will abort at the point it would remove or delete them.
130+
max_removed_users: 10 # If more than this many stray users are found, only this many of them will be
131+
# removed or deleted, and the the run will terminate normally. Later runs
132+
# can be used to process the next batch of them.
127133

128134
logging:
129135
# specifies whether you wish to generate a log file

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
'pycrypto',
4444
'python-ldap==2.4.25',
4545
'PyYAML',
46-
'umapi-client>=2.0.2',
46+
'umapi-client>=2.1',
4747
'psutil',
4848
],
4949
setup_requires=['nose>=1.0'],

tests/config_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,11 @@ def test_get_rule_options(self, mock_id_type,mock_get_dict,mock_get_list,mock_ge
108108
'exclude_users': [],
109109
'extended_attributes': None,
110110
'manage_groups': False,
111-
'max_strays_hard_limit': 1,
112-
'max_strays_to_process': 1,
111+
'max_removed_users': 1,
112+
'max_unmatched_users': 1,
113113
'new_account_type': 'new_acc',
114114
'remove_strays': False,
115-
'stray_key_list': None,
115+
'stray_key_map': None,
116116
'stray_list_output_path': None,
117117
'update_user_info': True,
118118
'username_filter_regex': None,

user_sync/app.py

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -71,33 +71,35 @@ def process_args():
7171
'the group membership is updated on the Adobe side so that the memberships in mapped '
7272
'groups matches the customer side.',
7373
action='store_true', dest='manage_groups')
74-
parser.add_argument('--remove-entitlements-for-strays',
75-
help="any 'stray' Adobe users (that is, Adobe users that don't match users on the customer "
76-
"side) are removed from all user groups and product configurations on the Adobe side, "
74+
parser.add_argument('--remove-entitlements-for-unmatched-users',
75+
help="any Adobe users that don't match users on the customer "
76+
"side are removed from all user groups and product configurations, "
7777
"but they are left visible in the Users list in the Adobe console.",
7878
action='store_true', dest='disentitle_strays')
79-
parser.add_argument('--remove-strays',
80-
help='like --remove-entitlements-for-strays, but additionally removes stray users '
79+
parser.add_argument('--remove-unmatched-users',
80+
help='like --remove-entitlements-for-unmatched-users, but additionally removes unmatched users '
8181
'from the Users list in the Adobe console. The user account is left intact, with all'
8282
'of its associated storage, and can be re-added to the Users list if desired.',
8383
action='store_true', dest='remove_strays')
84-
parser.add_argument('--delete-strays',
85-
help='like --remove-strays, but additionally deletes the '
84+
parser.add_argument('--delete-unmatched-users',
85+
help='like --remove-unmatched-users, but additionally deletes the '
8686
'(Enterprise or Federated ID) user account for any '
87-
'stray users, so that all of their associated storage is reclaimed and their email '
87+
'unmatched users, so that all of their associated storage is reclaimed and their email '
8888
'address is freed up for re-allocation to a new user.',
8989
action='store_true', dest='delete_strays')
90-
parser.add_argument('--output-stray-list',
91-
help="any 'stray' Adobe users (that is, Adobe users that don't match users on the customer "
92-
"side) are written to a file with the given pathname. "
93-
"This file can then be given in the --stray-list argument in a subsequent run.",
90+
parser.add_argument('--output-unmatched-users',
91+
help="all Adobe users that don't match users on the customer "
92+
"side are written to a file with the given pathname, "
93+
"but are not otherwise processed. "
94+
"The output file can then be used with --input-unmatched-users in a subsequent run.",
9495
metavar='output_path', dest='stray_list_output_path')
95-
parser.add_argument('--input-stray-list',
96-
help='causes stray Adobe users to be read from a file with the given pathname rather than'
97-
'being computed by matching Adobe users with customer users, '
98-
'see --output-stray-list. When using this option, you must also specify the type'
99-
'of stray processing you want done by specifying one of the options '
100-
'--remove-entitlements-for-strays, --remove-strays or --delete-strays.',
96+
parser.add_argument('--input-unmatched-users',
97+
help='instead of computing unmatched users by comparing Adobe users with directory users, '
98+
'the list of unmatched Adobe users is read from a file (see --output-unmatched-users). '
99+
'When using this option, you must also specify what you want done with unmatched users by'
100+
'specifying one of the arguments '
101+
'--remove-entitlements-for-unmatched-users, --remove-unmatched-users '
102+
'or --delete-unmatched-users.',
101103
metavar='input_path', dest='stray_list_input_path')
102104
return parser.parse_args()
103105

@@ -251,63 +253,61 @@ def create_config_loader_options(args):
251253
raise user_sync.error.AssertionException("Bad regular expression for --user-filter: %s reason: %s" % (username_filter_pattern, e.message))
252254
config_options['username_filter_regex'] = compiled_expression
253255

254-
# --input-stray-list
256+
# --input-unmatched-users
255257
stray_list_input_path = args.stray_list_input_path
256258
if (stray_list_input_path != None):
257259
if users_args is not None:
258-
raise user_sync.error.AssertionException('You cannot specify both --users and --input-stray-list')
260+
raise user_sync.error.AssertionException('You cannot specify both --users and --input-unmatched-users')
259261
# don't read the directory when processing from the stray list
260262
config_options['directory_connector_module_name'] = None
261-
logger.info('--input-stray-list specified, so not reading and comparing directory and Adobe users')
262-
logger.info('Reading stray list from: %s', stray_list_input_path)
263-
stray_key_list = user_sync.rules.RuleProcessor.read_remove_list(stray_list_input_path, logger = logger)
264-
logger.info('Total users in stray list: %d', len(stray_key_list))
265-
config_options['stray_key_list'] = stray_key_list
263+
logger.info('--input-unmatched-users specified, so not reading and comparing directory and Adobe users')
264+
stray_key_map = user_sync.rules.RuleProcessor.read_stray_key_map(stray_list_input_path, logger = logger)
265+
config_options['stray_key_map'] = stray_key_map
266266

267-
# --output-stray-list
267+
# --output-unmatched-users
268268
stray_list_output_path = args.stray_list_output_path
269269
if (stray_list_output_path != None):
270270
if stray_list_input_path:
271271
raise user_sync.error.AssertionException('You cannot specify both '
272-
'--input-stray-list and --output-stray-list')
273-
logger.info('Writing stray list to: %s', stray_list_output_path)
272+
'--input-unmatched-users and --output-unmatched-users')
273+
logger.info('Writing unmatched users to: %s', stray_list_output_path)
274274
config_options['stray_list_output_path'] = stray_list_output_path
275275

276276
# keep track of the number user removal type commands
277277
stray_processing_command_count = 0
278278

279-
# --remove-strays
279+
# --remove-unmatched-users
280280
remove_strays = args.remove_strays
281281
if remove_strays:
282282
if stray_list_output_path:
283283
remove_strays = False
284-
logger.warn('--remove-strays ignored when --output-stray-list is specified')
284+
logger.warn('--remove-unmatched-users ignored when --output-unmatched-users is specified')
285285
stray_processing_command_count += 1
286286
config_options['remove_strays'] = remove_strays
287287

288-
# --delete-strays
288+
# --delete-unmatched-users
289289
delete_strays = args.delete_strays
290290
if delete_strays:
291291
if stray_list_output_path:
292292
delete_strays = False
293-
logger.warn('--delete-strays ignored when --output-stray-list is specified')
293+
logger.warn('--delete-unmatched-users ignored when --output-unmatched-users is specified')
294294
stray_processing_command_count += 1
295295
config_options['delete_strays'] = delete_strays
296296

297-
# --remove-entitlements-for-strays
297+
# --remove-entitlements-for-unmatched-users
298298
disentitle_strays = args.disentitle_strays
299299
if disentitle_strays:
300300
if stray_list_output_path:
301301
disentitle_strays = False
302-
logger.warn('--remove-entitlements-for-strays ignored when --output-stray-list is specified')
302+
logger.warn('--remove-entitlements-for-unmatched-users ignored when --output-unmatched-users is specified')
303303
stray_processing_command_count += 1
304304
config_options['disentitle_strays'] = disentitle_strays
305305

306306
# ensure the user has only entered one "remove user" type command.
307307
if stray_processing_command_count > 1:
308308
raise user_sync.error.AssertionException('You cannot specify more than one of '
309-
'--remove-entitlements-for-strays, --remove-strays '
310-
'and --delete-strays')
309+
'--remove-entitlements-for-unmatched-users, --remove-unmatched-users '
310+
'and --delete-unmatched-users')
311311

312312
source_filter_args = args.source_filter_args
313313
if (source_filter_args != None):

user_sync/config.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def __init__(self, caller_options):
6969
'main_config_filename': DEFAULT_MAIN_CONFIG_FILENAME,
7070
'manage_groups': False,
7171
'remove_strays': False,
72-
'stray_key_list': None,
72+
'stray_key_map': None,
7373
'stray_list_output_path': None,
7474
'test_mode': False,
7575
'update_user_info': True,
@@ -334,8 +334,8 @@ def get_rule_options(self):
334334
exclude_groups.append(group.get_group_name())
335335

336336
limits_config = self.main_config.get_dict_config('limits')
337-
max_strays_hard_limit = limits_config.get_int('max_strays_hard_limit')
338-
max_strays_to_process = limits_config.get_int('max_strays_to_process')
337+
max_unmatched_users = limits_config.get_int('max_unmatched_users')
338+
max_removed_users = limits_config.get_int('max_removed_users')
339339

340340
after_mapping_hook = None
341341
extended_attributes = None
@@ -383,11 +383,11 @@ def get_rule_options(self):
383383
'exclude_users': exclude_users,
384384
'extended_attributes': extended_attributes,
385385
'manage_groups': options['manage_groups'],
386-
'max_strays_hard_limit': max_strays_hard_limit,
387-
'max_strays_to_process': max_strays_to_process,
386+
'max_removed_users': max_removed_users,
387+
'max_unmatched_users': max_unmatched_users,
388388
'new_account_type': new_account_type,
389389
'remove_strays': options['remove_strays'],
390-
'stray_key_list': options['stray_key_list'],
390+
'stray_key_map': options['stray_key_map'],
391391
'stray_list_output_path': options['stray_list_output_path'],
392392
'update_user_info': options['update_user_info'],
393393
'username_filter_regex': options['username_filter_regex'],

0 commit comments

Comments
 (0)