Skip to content

Commit 8a43277

Browse files
Merge pull request #414 from adorton-adobe/feature/email-username
Support email-type federated users with different email addresses
2 parents d63876f + b91b2cd commit 8a43277

File tree

2 files changed

+70
-0
lines changed

2 files changed

+70
-0
lines changed

docs/en/user-manual/advanced_configuration.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,37 @@ For domains that use username-based login, the `user_username_format` configurat
450450

451451
If you are using username-based login, you must still provide a unique email address for every user, and that email address must be in a domain that the organization has claimed and owns. User Sync will not add a user to the Adobe organization without an email address.
452452

453+
## Syncing Email-based Users with Different Email Address
454+
455+
Some organizations must authenticate users with an internal-facing
456+
email-type ID such as user principal name, but wish to allow users to
457+
user their public-facing email address to log into Adobe products and
458+
use collaboration features.
459+
460+
Internally, the Adobe Admin Console maintains a distinction between a
461+
user's email-type username and their email address. These fields are
462+
normally set to the same value, but the Admin Console allows the
463+
email address to differ from the username. The User Management API
464+
also supports the creation, update, and deletion of users that have
465+
different usernames and email addresses.
466+
467+
**Note:** Any domain used in the email address field **must** be
468+
claimed and added to an Adobe identity directory.
469+
470+
To use this functionality in the Sync Tool, simply specify both the
471+
`user_email_format` and the `user_username_format` options in
472+
`connector-ldap.yml`.
473+
474+
```yaml
475+
user_email_format: "{mail}"
476+
user_username_format: "{userPrincipalName}"
477+
```
478+
479+
In this scenario, the `user_username_format` option must map to a field
480+
that will always contain an email-type identifier (it does not need
481+
to be a live, working email address). Users with non-email values
482+
will fail to be validated and synced.
483+
453484
## Protecting Specific Accounts from User Sync Deletion
454485

455486
If you drive account creation and removal through User Sync, and want to manually create a few accounts, you may need this feature to keep User Sync from deleting the manually created accounts.

user_sync/rules.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ def __init__(self, caller_options):
150150
'hook_storage': None,
151151
}
152152

153+
# map of username to email address for users that have an email-type username that
154+
# differs from the user's email address
155+
self.email_override = {} # type: dict[str, str]
156+
153157
if logger.isEnabledFor(logging.DEBUG):
154158
options_to_report = options.copy()
155159
username_filter_regex = options_to_report['username_filter_regex']
@@ -612,6 +616,8 @@ def manage_strays(self, umapi_connectors):
612616
def get_commands(key):
613617
"""Given a user key, returns the umapi commands targeting that user"""
614618
id_type, username, domain = self.parse_user_key(key)
619+
if '@' in username and username in self.email_override:
620+
username = self.email_override[username]
615621
return user_sync.connector.umapi.Commands(identity_type=id_type, username=username, domain=domain)
616622

617623
# do the secondary umapis first, in case we are deleting user accounts from the primary umapi at the end
@@ -698,6 +704,13 @@ def create_umapi_commands_for_directory_user(self, directory_user, do_update=Fal
698704
:return user_sync.connector.umapi.Commands (or None if there's an error)
699705
"""
700706
identity_type = self.get_identity_type_from_directory_user(directory_user)
707+
update_username = None
708+
if (identity_type == user_sync.identity_type.FEDERATED_IDENTITY_TYPE and directory_user['username'] and
709+
'@' in directory_user['username'] and
710+
normalize_string(directory_user['email']) != normalize_string(directory_user['username'])):
711+
update_username = directory_user['username']
712+
directory_user['username'] = directory_user['email']
713+
701714
commands = user_sync.connector.umapi.Commands(identity_type, directory_user['email'],
702715
directory_user['username'], directory_user['domain'])
703716
attributes = self.get_user_attributes(directory_user)
@@ -722,6 +735,8 @@ def create_umapi_commands_for_directory_user(self, directory_user, do_update=Fal
722735
else:
723736
attributes['option'] = 'ignoreIfAlreadyExists'
724737
commands.add_user(attributes)
738+
if update_username is not None:
739+
commands.update_user({"email": directory_user['email'], "username": update_username})
725740
return commands
726741

727742
def create_umapi_user(self, user_key, groups_to_add, umapi_info, umapi_connector):
@@ -784,6 +799,16 @@ def update_umapi_user(self, umapi_info, user_key, umapi_connector,
784799
directory_user = umapi_user
785800
identity_type = umapi_user.get('type')
786801

802+
# if user has email-type username and it is different from email address, then we need to
803+
# override the username with email address
804+
if '@' in directory_user['username'] and directory_user['email'] != directory_user['username']:
805+
if groups_to_add or groups_to_remove or attributes_to_update:
806+
directory_user['username'] = directory_user['email']
807+
if attributes_to_update and 'email' in attributes_to_update:
808+
directory_user['email'] = umapi_user['email']
809+
attributes_to_update['username'] = umapi_user['username']
810+
directory_user['username'] = umapi_user['email']
811+
787812
commands = user_sync.connector.umapi.Commands(identity_type, directory_user['email'],
788813
directory_user['username'], directory_user['domain'])
789814
commands.update_user(attributes_to_update)
@@ -844,6 +869,8 @@ def update_umapi_users_for_connector(self, umapi_info, umapi_connector):
844869
if self.is_umapi_user_excluded(in_primary_org, user_key, current_groups):
845870
continue
846871

872+
self.map_email_override(umapi_user)
873+
847874
directory_user = filtered_directory_user_by_user_key.get(user_key)
848875
if directory_user is None:
849876
# There's no selected directory user matching this adobe user
@@ -876,6 +903,18 @@ def update_umapi_users_for_connector(self, umapi_info, umapi_connector):
876903
umapi_info.set_umapi_users_loaded()
877904
return user_to_group_map
878905

906+
def map_email_override(self, umapi_user):
907+
"""
908+
for users with email-type usernames that don't match the email address, we need to add some
909+
special cases to update and disentitle users
910+
:param umapi_user: dict
911+
:return:
912+
"""
913+
email = umapi_user.get('email', '')
914+
username = umapi_user.get('username', '')
915+
if '@' in username and username != email:
916+
self.email_override[username] = email
917+
879918
def is_umapi_user_excluded(self, in_primary_org, user_key, current_groups):
880919
if in_primary_org:
881920
self.primary_user_count += 1

0 commit comments

Comments
 (0)