From 407ef644eddc9ce68c5bcc651126b51f5c3233fe Mon Sep 17 00:00:00 2001 From: Guillaume Charest <1690085+gcharest@users.noreply.github.com> Date: Tue, 7 May 2024 16:11:24 -0400 Subject: [PATCH] Feat/aws identity center sync (#495) * feat: improved logging messages * feat: added support to preformat group for specific keys * feat: AWS module to synchronize the Identity Store with specific Google Groups * chore: cleanup the dev module testing the group sync feature * chore: fmt * fix: modify function to use regex for more control * fix: use regex pattern for AWS groups * fix: update logging of groups and users found * feat: make the output of the sync command formatted for the slack bot --- app/modules/aws/identity_center.py | 331 +++ app/modules/dev/aws_dev.py | 93 +- app/modules/provisioning/groups.py | 16 + app/modules/provisioning/users.py | 4 +- .../modules/aws/test_sync_identity_center.py | 1927 +++++++++++++++++ .../provisioning/test_provisioning_groups.py | 90 + 6 files changed, 2398 insertions(+), 63 deletions(-) create mode 100644 app/modules/aws/identity_center.py create mode 100644 app/tests/modules/aws/test_sync_identity_center.py diff --git a/app/modules/aws/identity_center.py b/app/modules/aws/identity_center.py new file mode 100644 index 00000000..2818a19f --- /dev/null +++ b/app/modules/aws/identity_center.py @@ -0,0 +1,331 @@ +"""Module to sync the AWS Identity Center with the Google Workspace.""" +from logging import getLogger +from integrations.aws import identity_store +from modules.provisioning import users, groups +from utils import filters + + +logger = getLogger(__name__) +DRY_RUN = True + + +def synchronize(**kwargs): + """Sync the AWS Identity Center with the Google Workspace. + + Args: + enable_users_sync (bool): Toggle to sync users. + enable_groups_sync (bool): Toggle to sync groups. + query (str): The query to filter the Google Groups. + + Returns: + tuple: A tuple containing the users sync status and groups sync status. + """ + enable_users_sync = kwargs.pop("enable_users_sync", True) + enable_groups_sync = kwargs.pop("enable_groups_sync", True) + query = kwargs.pop("query", "email:aws-*") + + users_sync_status = None + groups_sync_status = None + + source_groups_filters = [lambda group: "AWS-" in group["name"]] + source_groups = groups.get_groups_with_members_from_integration( + "google_groups", query=query, filters=source_groups_filters + ) + source_users = users.get_unique_users_from_groups(source_groups, "members") + + logger.info( + f"synchronize:Found {len(source_groups)} Source Groups and {len(source_users)} Users" + ) + + for group in source_groups: + logger.info( + f"synchronize:Source:Group {group['name']} has {len(group['members'])} members" + ) + for user in source_users: + logger.info(f"synchronize:Source:User {user['primaryEmail']}") + + target_groups = groups.get_groups_with_members_from_integration( + "aws_identity_center" + ) + target_users = identity_store.list_users() + + logger.info( + f"synchronize:Found {len(target_groups)} Target Groups and {len(target_users)} Users" + ) + + for group in target_groups: + logger.info( + f"synchronize:Target:Group {group['DisplayName']} has {len(group['GroupMemberships'])} members" + ) + + for user in target_users: + logger.info(f"synchronize:Target:User {user['UserName']}") + + if enable_users_sync: + logger.info("synchronize:users:Syncing Users") + users_sync_status = sync_users(source_users, target_users, **kwargs) + target_users = identity_store.list_users() + + if enable_groups_sync: + logger.info("synchronize:groups:Syncing Groups") + + logger.info("synchronize:groups:Formatting Source Groups") + source_groups = groups.preformat_groups( + source_groups, "name", "DisplayName", pattern=r"^AWS-", replace="" + ) + groups_sync_status = sync_groups( + source_groups, target_groups, target_users, **kwargs + ) + + return { + "users": users_sync_status, + "groups": groups_sync_status, + } + + +def create_aws_users(users_to_create): + """Create the users in the identity store. + + Args: + users_to_create (list): A list of users to create from the source system. + + Returns: + list: A list of user ID of the users created. + """ + logger.info(f"create_aws_users:Starting creation of {len(users_to_create)} users.") + users_created = [] + for user in users_to_create: + if not DRY_RUN: + response = identity_store.create_user( + user["primaryEmail"], + user["name"]["givenName"], + user["name"]["familyName"], + ) + if response: + logger.info( + f"create_aws_users:Successfully created user {user['primaryEmail']}" + ) + users_created.append(response) + else: + logger.error( + f"create_aws_users:Failed to create user {user['primaryEmail']}" + ) + else: + logger.info( + f"create_aws_users:DRY_RUN:Successfully created user {user['primaryEmail']}" + ) + users_created.append(user["primaryEmail"]) + logger.info(f"create_aws_users:Finished creation of {len(users_created)} users.") + return users_created + + +def delete_aws_users(users_to_delete, enable_delete=False): + """Delete the users in the identity store. + + Args: + users_to_delete (list): A list of users to delete from the target system. + + Returns: + list: A list of user name of the users deleted. + """ + logger.info(f"delete_aws_users:Starting deletion of {len(users_to_delete)} users.") + users_deleted = [] + for user in users_to_delete: + if enable_delete and not DRY_RUN: + response = identity_store.delete_user(user["UserId"]) + if response: + logger.info( + f"delete_aws_users:Successfully deleted user {user['UserName']}" + ) + users_deleted.append(user["UserName"]) + else: + logger.error( + f"delete_aws_users:Failed to delete user {user['UserName']}" + ) + else: + logger.info( + f"delete_aws_users:DRY_RUN:Successfully deleted user {user['UserName']}" + ) + users_deleted.append(user["UserName"]) + logger.info(f"delete_aws_users:Finished deletion of {len(users_deleted)} users.") + return users_deleted + + +def sync_users(source_users, target_users, **kwargs): + """Sync the users in the identity store. + + Args: + + source_users (list): A list of users from the source system. + target_users (list): A list of users in the identity store. + enable_delete (bool): Enable deletion of users. + delete_target_all (bool): Mark all target users for deletion. + + Returns: + tuple: A tuple containing the users created and deleted. + """ + enable_delete = kwargs.get("enable_delete", False) + delete_target_all = kwargs.get("delete_target_all", False) + + if delete_target_all: + users_to_delete = target_users + users_to_create = [] + else: + users_to_create, users_to_delete = filters.compare_lists( + {"values": source_users, "key": "primaryEmail"}, + {"values": target_users, "key": "UserName"}, + mode="sync", + ) + logger.info( + f"synchronize:users:Found {len(users_to_create)} Users to Create and {len(users_to_delete)} Users to Delete" + ) + + created_users = create_aws_users(users_to_create) + deleted_users = delete_aws_users(users_to_delete, enable_delete=enable_delete) + + return created_users, deleted_users + + +def create_group_memberships(target_group, users_to_add, target_users): + """Create group memberships for the users in the identity store. + + Args: + group (dict): The group to add the users to. + users_to_add (list): A list of users to add to the group. + target_users (list): A list of users in the identity store. + + Returns: + list: A list of group membership ID for memberships created. + """ + memberships_created = [] + logger.info( + f"create_group_memberships:Adding {len(users_to_add)} users to group {target_group['DisplayName']}" + ) + for user in users_to_add: + matching_target_users = filters.filter_by_condition( + target_users, + lambda target_user: target_user["UserName"] == user["primaryEmail"], + ) + if matching_target_users: + matching_target_user = matching_target_users[0] + else: + logger.info( + f"create_group_memberships:Failed to find user {user['primaryEmail']} in target system" + ) + continue + if not DRY_RUN: + response = identity_store.create_group_membership( + target_group["GroupId"], matching_target_user["UserId"] + ) + if response: + logger.info( + f"create_group_memberships:Successfully added user {matching_target_user['UserName']} to group {target_group['DisplayName']}" + ) + memberships_created.append(response) + else: + logger.error( + f"create_group_memberships:Failed to add user {matching_target_user['UserName']} to group {target_group['DisplayName']}" + ) + else: + logger.info( + f"create_group_memberships:DRY_RUN:Successfully added user {matching_target_user['UserName']} to group {target_group['DisplayName']}" + ) + memberships_created.append( + target_group["GroupId"] + "-" + user["primaryEmail"] + ) + logger.info( + f"create_group_memberships:Finished adding {len(memberships_created)} users to group {target_group['DisplayName']}." + ) + return memberships_created + + +def delete_group_memberships(group, users_to_remove, enable_delete=False): + """Delete group memberships for the users in the identity store. + + Args: + group (dict): The group to remove the users from. + users_to_remove (list): A list of users to remove from the group. + enable_delete (bool): Enable deletion of group memberships. + + Returns: + list: A list of group membership ID for memberships deleted. + """ + memberships_deleted = [] + logger.info( + f"delete_group_memberships:Removing {len(users_to_remove)} users from group {group['DisplayName']}" + ) + for user in users_to_remove: + if enable_delete and not DRY_RUN: + response = identity_store.delete_group_membership(user["MembershipId"]) + + if response: + memberships_deleted.append(response) + logger.info( + f"delete_group_memberships:Successfully removed user {user['MemberId']['UserName']} from group {group['DisplayName']}" + ) + else: + logger.error( + f"delete_group_memberships:Failed to remove user {user['MemberId']['UserName']} from group {group['DisplayName']}" + ) + else: + logger.info( + f"delete_group_memberships:DRY_RUN:Successfully removed user {user['MemberId']['UserName']} from group {group['DisplayName']}" + ) + memberships_deleted.append(user["MembershipId"]) + logger.info( + f"delete_group_memberships:Finished removing {len(memberships_deleted)} users from group {group['DisplayName']}" + ) + return memberships_deleted + + +def sync_groups(source_groups, target_groups, target_users, **kwargs): + """Sync the groups in the identity store. + + Args: + source_groups (list): A list of groups from the source system. + target_groups (list): A list of groups in the identity store. + target_users (list): A list of users in the identity store. + enable_delete (bool): Enable deletion of group memberships. + + Returns: + tuple: A tuple containing the groups memberships created and deleted. + """ + enable_delete = kwargs.get("enable_delete", False) + + source_groups_to_sync, target_groups_to_sync = filters.compare_lists( + {"values": source_groups, "key": "DisplayName"}, + {"values": target_groups, "key": "DisplayName"}, + mode="match", + ) + logger.info( + f"synchronize:groups:Found {len(source_groups_to_sync)} Source Groups and {len(target_groups_to_sync)} Target Groups" + ) + + groups_memberships_created = [] + groups_memberships_deleted = [] + for i in range(len(source_groups_to_sync)): + if ( + source_groups_to_sync[i]["DisplayName"] + == target_groups_to_sync[i]["DisplayName"] + ): + logger.info( + f"synchronize:groups:Syncing group {source_groups_to_sync[i]['name']} with {target_groups_to_sync[i]['DisplayName']}" + ) + users_to_add, users_to_remove = filters.compare_lists( + {"values": source_groups_to_sync[i]["members"], "key": "primaryEmail"}, + { + "values": target_groups_to_sync[i]["GroupMemberships"], + "key": "MemberId.UserName", + }, + mode="sync", + ) + memberships_created = create_group_memberships( + target_groups_to_sync[i], users_to_add, target_users + ) + groups_memberships_created.extend(memberships_created) + memberships_deleted = delete_group_memberships( + target_groups_to_sync[i], users_to_remove, enable_delete=enable_delete + ) + groups_memberships_deleted.extend(memberships_deleted) + + return groups_memberships_created, groups_memberships_deleted diff --git a/app/modules/dev/aws_dev.py b/app/modules/dev/aws_dev.py index d42df095..10afa499 100644 --- a/app/modules/dev/aws_dev.py +++ b/app/modules/dev/aws_dev.py @@ -1,68 +1,37 @@ """Testing AWS service (will be removed)""" -from integrations.aws import identity_store +import logging +from modules.aws import identity_center -# from modules.aws import sync_groups - -# from integrations.aws import identity_store from dotenv import load_dotenv load_dotenv() - -def aws_dev_command(client, body, respond): - # user = identity_store.create_user("test.user@test_email.com", "Test", "User") - # if not user: - # respond("There was an error creating the user.") - # return - # respond(f"Created user with user_id: {user}") - user_id = identity_store.get_user_id("test.user@test_email.com") - if not user_id: - respond("No user found.") - return - respond(f"Found user_id: {user_id}") - # result = identity_store.delete_user(user_id) - # if not result: - # respond("There was an error deleting the user.") - # return - # if result: - # respond("User deleted.") - # groups = identity_store.list_groups_with_membership() - # if not groups: - # respond("There was an error retrieving the groups.") - # return - # respond(f"Found {len(groups)} groups.") - # for k, v in groups[0].items(): - # print(f"{k}: {v}") - # users = identity_store.list_users() - # if not users: - # respond("There was an error retrieving the users.") - # return - # respond(f"Found {len(users)} users.") - - # user = identity_store.get_user_id("guillaume.charest@cds-snc.ca") - # if not user: - # respond("There was an error retrieving the user.") - # return - # respond(f"Found user: {user}") - - # groups = identity_store.list_groups() - # if not groups: - # respond("There was an error retrieving the groups.") - # return - # respond(f"Found {len(groups)} groups.") - - # matching_groups = sync_groups.get_aws_google_groups() - # if not matching_groups: - # respond("There was an error retrieving the groups.") - # return - # print(f"Found {len(matching_groups[0])} AWS matching groups.") - # print(f"Found {len(matching_groups[1])} Google matching groups.") - # for group in matching_groups[0]: - # print(group) - # # join each group in a multiline string - # aws_groups = "\n".join(str(group) for group in matching_groups[0]) - # respond(f"aws_groups:\n{aws_groups}") - # for group in matching_groups[1]: - # print(group) - # for i in range(5): - # respond(f"google_group: {matching_groups[1][i]}") +logger = logging.getLogger(__name__) + + +def aws_dev_command(ack, client, body, respond): + ack() + response = identity_center.synchronize(enable_groups_sync=False) + if not response: + respond("No groups found.") + else: + message = "" + if identity_center.DRY_RUN: + message += "Dry run mode enabled.\n" + if response["users"]: + users_created, users_deleted = response["users"] + message += "Users created:\n- " + "\n- ".join(users_created) + "\n" + message += "Users deleted:\n- " + "\n- ".join(users_deleted) + "\n" + else: + message += "Users Sync Disabled.\n" + if response["groups"]: + groups_created, groups_deleted = response["groups"] + message += ( + "Groups memberships created:\n- " + "\n- ".join(groups_created) + "\n" + ) + message += ( + "Groups memberships deleted:\n- " + "\n- ".join(groups_deleted) + "\n" + ) + else: + message += "Groups Sync Disabled.\n" + respond(message) diff --git a/app/modules/provisioning/groups.py b/app/modules/provisioning/groups.py index a1fe09b5..03f44624 100644 --- a/app/modules/provisioning/groups.py +++ b/app/modules/provisioning/groups.py @@ -1,8 +1,13 @@ +from logging import getLogger +import re from integrations.google_workspace import google_directory from integrations.aws import identity_store from utils import filters as filter_tools +logger = getLogger(__name__) + + def get_groups_with_members_from_integration(integration_source, **kwargs): """Retrieve the users from an integration group source. Supported sources are: @@ -27,10 +32,12 @@ def get_groups_with_members_from_integration(integration_source, **kwargs): groups = [] match integration_source: case "google_groups": + logger.info("Getting Google Groups with members.") groups = google_directory.list_groups_with_members( query=query, members_details=members_details ) case "aws_identity_center": + logger.info("Getting AWS Identity Center Groups with members.") groups = identity_store.list_groups_with_memberships( members_details=members_details ) @@ -40,3 +47,12 @@ def get_groups_with_members_from_integration(integration_source, **kwargs): for filter in filters: groups = filter_tools.filter_by_condition(groups, filter) return groups + + +def preformat_groups(groups, lookup_key, new_key, pattern="", replace=""): + for group in groups: + if lookup_key not in group: + raise KeyError(f"Group {group} does not have {lookup_key} key") + group[new_key] = re.sub(pattern, replace, group[lookup_key]) + + return groups diff --git a/app/modules/provisioning/users.py b/app/modules/provisioning/users.py index 7916bf7d..af9cfc02 100644 --- a/app/modules/provisioning/users.py +++ b/app/modules/provisioning/users.py @@ -19,13 +19,15 @@ def get_unique_users_from_groups(groups, key): """ users_dict = {} if isinstance(groups, list): + logger.info(f"Getting unique users from {len(groups)} groups.") for group in groups: for user in filter_tools.get_nested_value(group, key): if user: users_dict[str(user)] = user elif isinstance(groups, dict): + logger.info("Getting unique users from a single group.") for user in filter_tools.get_nested_value(groups, key): if user: users_dict[str(user)] = user - + logger.info(f"Found {len(users_dict)} unique users.") return list(users_dict.values()) diff --git a/app/tests/modules/aws/test_sync_identity_center.py b/app/tests/modules/aws/test_sync_identity_center.py new file mode 100644 index 00000000..c40c71cf --- /dev/null +++ b/app/tests/modules/aws/test_sync_identity_center.py @@ -0,0 +1,1927 @@ +from unittest.mock import patch, call, ANY +from modules.aws import identity_center + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.create_user") +@patch("modules.aws.identity_center.identity_store.delete_user") +@patch("modules.aws.identity_center.identity_store.create_group_membership") +@patch("modules.aws.identity_center.identity_store.delete_group_membership") +@patch("modules.aws.identity_center.identity_store.list_users") +@patch("modules.aws.identity_center.groups.get_groups_with_members_from_integration") +def test_synchronize_defaults_dry_run_false( + mock_get_groups_with_members_from_integration, + mock_list_users, + mock_delete_group_membership, + mock_create_group_membership, + mock_delete_user, + mock_create_user, + mock_logger, + google_groups_w_users, + aws_groups_w_users, + aws_users, +): + # 3 groups, with 9 users in each group + source_groups = google_groups_w_users(3, 9, prefix="AWS-") + # only keep first 6 users in groups + for group in source_groups: + group["members"] = group["members"][:6] + for member in group["members"]: + member["primaryEmail"] = member["primaryEmail"].replace("AWS-", "") + + # 3 groups, with 9 users in each group + target_groups = aws_groups_w_users(3, 9) + # only keep last 6 users in groups + for group in target_groups: + group["GroupMemberships"] = group["GroupMemberships"][3:] + + mock_get_groups_with_members_from_integration.side_effect = [ + source_groups, + target_groups, + ] + + # keep last 6 users + target_users = aws_users(9) + mock_list_users.side_effect = [target_users[3:], target_users] + mock_create_user.side_effect = [ + "user-email1@test.com", + "user-email2@test.com", + "user-email3@test.com", + ] + mock_create_group_membership.side_effect = [ + "aws-group_id1-user-email1@test.com", + "aws-group_id1-user-email2@test.com", + "aws-group_id1-user-email3@test.com", + "aws-group_id2-user-email1@test.com", + "aws-group_id2-user-email2@test.com", + "aws-group_id2-user-email3@test.com", + "aws-group_id3-user-email1@test.com", + "aws-group_id3-user-email2@test.com", + "aws-group_id3-user-email3@test.com", + ] + mock_delete_group_membership.side_effect = [ + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + ] + expected_target_users_to_create = [ + "user-email1@test.com", + "user-email2@test.com", + "user-email3@test.com", + ] + expected_target_users_to_delete = [ + "user-email7@test.com", + "user-email8@test.com", + "user-email9@test.com", + ] + + expected_group_memberships_to_create = [ + "aws-group_id1-user-email1@test.com", + "aws-group_id1-user-email2@test.com", + "aws-group_id1-user-email3@test.com", + "aws-group_id2-user-email1@test.com", + "aws-group_id2-user-email2@test.com", + "aws-group_id2-user-email3@test.com", + "aws-group_id3-user-email1@test.com", + "aws-group_id3-user-email2@test.com", + "aws-group_id3-user-email3@test.com", + ] + + expected_group_memberships_to_delete = [ + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + ] + + result = identity_center.synchronize( + enable_users_sync=True, enable_groups_sync=True, enable_delete=True + ) + + assert result == { + "users": (expected_target_users_to_create, expected_target_users_to_delete), + "groups": ( + expected_group_memberships_to_create, + expected_group_memberships_to_delete, + ), + } + + assert mock_logger.info.call_count == 68 + assert ( + call("synchronize:Found 3 Source Groups and 6 Users") + in mock_logger.info.call_args_list + ) + assert ( + call("synchronize:Found 3 Target Groups and 6 Users") + in mock_logger.info.call_args_list + ) + + assert ( + call("synchronize:users:Found 3 Users to Create and 3 Users to Delete") + in mock_logger.info.call_args_list + ) + + for user in expected_target_users_to_create: + assert ( + call(f"create_aws_users:Successfully created user {user}") + in mock_logger.info.call_args_list + ) + for user in expected_target_users_to_delete: + assert ( + call(f"delete_aws_users:Successfully deleted user {user}") + in mock_logger.info.call_args_list + ) + for group in target_groups: + for user in expected_target_users_to_create: + assert ( + call( + f"create_group_memberships:Successfully added user {user} to group {group['DisplayName']}" + ) + in mock_logger.info.call_args_list + ) + + for group in target_groups: + for user in expected_target_users_to_delete: + assert ( + call( + f"delete_group_memberships:Successfully removed user {user} from group {group['DisplayName']}" + ) + in mock_logger.info.call_args_list + ) + + assert mock_create_user.call_count == 3 + assert mock_delete_user.call_count == 3 + assert mock_create_group_membership.call_count == 9 + assert mock_delete_group_membership.call_count == 9 + + +@patch("modules.aws.identity_center.DRY_RUN", True) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.create_user") +@patch("modules.aws.identity_center.identity_store.delete_user") +@patch("modules.aws.identity_center.identity_store.create_group_membership") +@patch("modules.aws.identity_center.identity_store.delete_group_membership") +@patch("modules.aws.identity_center.identity_store.list_users") +@patch("modules.aws.identity_center.groups.get_groups_with_members_from_integration") +def test_synchronize_defaults_dry_run_true( + mock_get_groups_with_members_from_integration, + mock_list_users, + mock_delete_group_membership, + mock_create_group_membership, + mock_delete_user, + mock_create_user, + mock_logger, + google_groups_w_users, + aws_groups_w_users, + aws_users, +): + # 3 groups, with 9 users in each group + source_groups = google_groups_w_users(3, 9, prefix="AWS-") + # only keep first 6 users in groups + for group in source_groups: + group["members"] = group["members"][:6] + for member in group["members"]: + member["primaryEmail"] = member["primaryEmail"].replace("AWS-", "") + + # 3 groups, with 9 users in each group + target_groups = aws_groups_w_users(3, 9) + # only keep last 6 users in groups + for group in target_groups: + group["GroupMemberships"] = group["GroupMemberships"][3:] + + mock_get_groups_with_members_from_integration.side_effect = [ + source_groups, + target_groups, + ] + + # keep last 6 users + target_users = aws_users(9) + mock_list_users.side_effect = [target_users[3:], target_users] + + expected_target_users_to_create = [ + "user-email1@test.com", + "user-email2@test.com", + "user-email3@test.com", + ] + expected_target_users_to_delete = [ + "user-email7@test.com", + "user-email8@test.com", + "user-email9@test.com", + ] + + expected_group_memberships_to_create = [ + "aws-group_id1-user-email1@test.com", + "aws-group_id1-user-email2@test.com", + "aws-group_id1-user-email3@test.com", + "aws-group_id2-user-email1@test.com", + "aws-group_id2-user-email2@test.com", + "aws-group_id2-user-email3@test.com", + "aws-group_id3-user-email1@test.com", + "aws-group_id3-user-email2@test.com", + "aws-group_id3-user-email3@test.com", + ] + + expected_group_memberships_to_delete = [ + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + ] + + result = identity_center.synchronize( + enable_users_sync=True, enable_groups_sync=True + ) + + assert mock_logger.info.call_count == 68 + assert ( + call("synchronize:Found 3 Source Groups and 6 Users") + in mock_logger.info.call_args_list + ) + assert ( + call("synchronize:Found 3 Target Groups and 6 Users") + in mock_logger.info.call_args_list + ) + + assert ( + call("synchronize:users:Found 3 Users to Create and 3 Users to Delete") + in mock_logger.info.call_args_list + ) + + assert result == { + "users": (expected_target_users_to_create, expected_target_users_to_delete), + "groups": ( + expected_group_memberships_to_create, + expected_group_memberships_to_delete, + ), + } + + assert mock_create_user.call_count == 0 + assert mock_delete_user.call_count == 0 + assert mock_create_group_membership.call_count == 0 + assert mock_delete_group_membership.call_count == 0 + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.create_user") +@patch("modules.aws.identity_center.identity_store.delete_user") +@patch("modules.aws.identity_center.identity_store.create_group_membership") +@patch("modules.aws.identity_center.identity_store.delete_group_membership") +@patch("modules.aws.identity_center.identity_store.list_users") +@patch("modules.aws.identity_center.groups.get_groups_with_members_from_integration") +def test_synchronize_enable_delete_dry_run_false( + mock_get_groups_with_members_from_integration, + mock_list_users, + mock_delete_group_membership, + mock_create_group_membership, + mock_delete_user, + mock_create_user, + mock_logger, + google_groups_w_users, + aws_groups_w_users, + aws_users, +): + # 3 groups, with 9 users in each group + source_groups = google_groups_w_users(3, 9, prefix="AWS-") + # only keep first 6 users in groups + for group in source_groups: + group["members"] = group["members"][:6] + for member in group["members"]: + member["primaryEmail"] = member["primaryEmail"].replace("AWS-", "") + + # 3 groups, with 9 users in each group + target_groups = aws_groups_w_users(3, 9) + # only keep last 6 users in groups + for group in target_groups: + group["GroupMemberships"] = group["GroupMemberships"][3:] + + mock_get_groups_with_members_from_integration.side_effect = [ + source_groups, + target_groups, + ] + + # keep last 6 users + target_users = aws_users(9) + mock_list_users.side_effect = [target_users[3:], target_users] + mock_create_user.side_effect = [ + "user-email1@test.com", + "user-email2@test.com", + "user-email3@test.com", + ] + mock_create_group_membership.side_effect = [ + "aws-group_id1-user-email1@test.com", + "aws-group_id1-user-email2@test.com", + "aws-group_id1-user-email3@test.com", + "aws-group_id2-user-email1@test.com", + "aws-group_id2-user-email2@test.com", + "aws-group_id2-user-email3@test.com", + "aws-group_id3-user-email1@test.com", + "aws-group_id3-user-email2@test.com", + "aws-group_id3-user-email3@test.com", + ] + mock_delete_group_membership.side_effect = [ + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + ] + expected_target_users_to_create = [ + "user-email1@test.com", + "user-email2@test.com", + "user-email3@test.com", + ] + expected_target_users_to_delete = [ + "user-email7@test.com", + "user-email8@test.com", + "user-email9@test.com", + ] + + expected_group_memberships_to_create = [ + "aws-group_id1-user-email1@test.com", + "aws-group_id1-user-email2@test.com", + "aws-group_id1-user-email3@test.com", + "aws-group_id2-user-email1@test.com", + "aws-group_id2-user-email2@test.com", + "aws-group_id2-user-email3@test.com", + "aws-group_id3-user-email1@test.com", + "aws-group_id3-user-email2@test.com", + "aws-group_id3-user-email3@test.com", + ] + + expected_group_memberships_to_delete = [ + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + ] + + result = identity_center.synchronize( + enable_users_sync=True, enable_groups_sync=True, enable_delete=True + ) + + assert result == { + "users": (expected_target_users_to_create, expected_target_users_to_delete), + "groups": ( + expected_group_memberships_to_create, + expected_group_memberships_to_delete, + ), + } + + assert mock_logger.info.call_count == 68 + assert ( + call("synchronize:Found 3 Source Groups and 6 Users") + in mock_logger.info.call_args_list + ) + assert ( + call("synchronize:Found 3 Target Groups and 6 Users") + in mock_logger.info.call_args_list + ) + + assert ( + call("synchronize:users:Found 3 Users to Create and 3 Users to Delete") + in mock_logger.info.call_args_list + ) + + for user in expected_target_users_to_create: + assert ( + call(f"create_aws_users:Successfully created user {user}") + in mock_logger.info.call_args_list + ) + for user in expected_target_users_to_delete: + assert ( + call(f"delete_aws_users:Successfully deleted user {user}") + in mock_logger.info.call_args_list + ) + for group in target_groups: + for user in expected_target_users_to_create: + assert ( + call( + f"create_group_memberships:Successfully added user {user} to group {group['DisplayName']}" + ) + in mock_logger.info.call_args_list + ) + + for group in target_groups: + for user in expected_target_users_to_delete: + assert ( + call( + f"delete_group_memberships:Successfully removed user {user} from group {group['DisplayName']}" + ) + in mock_logger.info.call_args_list + ) + + assert mock_create_user.call_count == 3 + assert mock_delete_user.call_count == 3 + assert mock_create_group_membership.call_count == 9 + assert mock_delete_group_membership.call_count == 9 + + +@patch("modules.aws.identity_center.DRY_RUN", True) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.create_user") +@patch("modules.aws.identity_center.identity_store.delete_user") +@patch("modules.aws.identity_center.identity_store.create_group_membership") +@patch("modules.aws.identity_center.identity_store.delete_group_membership") +@patch("modules.aws.identity_center.identity_store.list_users") +@patch("modules.aws.identity_center.groups.get_groups_with_members_from_integration") +def test_synchronize_enable_delete_dry_run_true( + mock_get_groups_with_members_from_integration, + mock_list_users, + mock_delete_group_membership, + mock_create_group_membership, + mock_delete_user, + mock_create_user, + mock_logger, + google_groups_w_users, + aws_groups_w_users, + aws_users, +): + # 3 groups, with 9 users in each group + source_groups = google_groups_w_users(3, 9, prefix="AWS-") + # only keep first 6 users in groups + for group in source_groups: + group["members"] = group["members"][:6] + for member in group["members"]: + member["primaryEmail"] = member["primaryEmail"].replace("AWS-", "") + + # 3 groups, with 9 users in each group + target_groups = aws_groups_w_users(3, 9) + # only keep last 6 users in groups + for group in target_groups: + group["GroupMemberships"] = group["GroupMemberships"][3:] + + mock_get_groups_with_members_from_integration.side_effect = [ + source_groups, + target_groups, + ] + + # keep last 6 users + target_users = aws_users(9) + mock_list_users.side_effect = [target_users[3:], target_users] + mock_create_user.side_effect = [ + "user-email1@test.com", + "user-email2@test.com", + "user-email3@test.com", + ] + mock_create_group_membership.side_effect = [ + "aws-group_id1-user-email1@test.com", + "aws-group_id1-user-email2@test.com", + "aws-group_id1-user-email3@test.com", + "aws-group_id2-user-email1@test.com", + "aws-group_id2-user-email2@test.com", + "aws-group_id2-user-email3@test.com", + "aws-group_id3-user-email1@test.com", + "aws-group_id3-user-email2@test.com", + "aws-group_id3-user-email3@test.com", + ] + mock_delete_group_membership.side_effect = [ + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + ] + expected_target_users_to_create = [ + "user-email1@test.com", + "user-email2@test.com", + "user-email3@test.com", + ] + expected_target_users_to_delete = [ + "user-email7@test.com", + "user-email8@test.com", + "user-email9@test.com", + ] + + expected_group_memberships_to_create = [ + "aws-group_id1-user-email1@test.com", + "aws-group_id1-user-email2@test.com", + "aws-group_id1-user-email3@test.com", + "aws-group_id2-user-email1@test.com", + "aws-group_id2-user-email2@test.com", + "aws-group_id2-user-email3@test.com", + "aws-group_id3-user-email1@test.com", + "aws-group_id3-user-email2@test.com", + "aws-group_id3-user-email3@test.com", + ] + + expected_group_memberships_to_delete = [ + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + "membership_id_7", + "membership_id_8", + "membership_id_9", + ] + + result = identity_center.synchronize( + enable_users_sync=True, enable_groups_sync=True, enable_delete=True + ) + + assert result == { + "users": (expected_target_users_to_create, expected_target_users_to_delete), + "groups": ( + expected_group_memberships_to_create, + expected_group_memberships_to_delete, + ), + } + + assert mock_logger.info.call_count == 68 + assert ( + call("synchronize:Found 3 Source Groups and 6 Users") + in mock_logger.info.call_args_list + ) + assert ( + call("synchronize:Found 3 Target Groups and 6 Users") + in mock_logger.info.call_args_list + ) + + assert ( + call("synchronize:users:Found 3 Users to Create and 3 Users to Delete") + in mock_logger.info.call_args_list + ) + + for user in expected_target_users_to_create: + assert ( + call(f"create_aws_users:DRY_RUN:Successfully created user {user}") + in mock_logger.info.call_args_list + ) + for user in expected_target_users_to_delete: + assert ( + call(f"delete_aws_users:DRY_RUN:Successfully deleted user {user}") + in mock_logger.info.call_args_list + ) + for group in target_groups: + for user in expected_target_users_to_create: + assert ( + call( + f"create_group_memberships:DRY_RUN:Successfully added user {user} to group {group['DisplayName']}" + ) + in mock_logger.info.call_args_list + ) + + for group in target_groups: + for user in expected_target_users_to_delete: + assert ( + call( + f"delete_group_memberships:DRY_RUN:Successfully removed user {user} from group {group['DisplayName']}" + ) + in mock_logger.info.call_args_list + ) + + assert mock_create_user.call_count == 0 + assert mock_delete_user.call_count == 0 + assert mock_create_group_membership.call_count == 0 + assert mock_delete_group_membership.call_count == 0 + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.groups.preformat_groups") +@patch("modules.aws.identity_center.sync_groups") +@patch("modules.aws.identity_center.sync_users") +@patch("modules.aws.identity_center.identity_store.list_users") +@patch("modules.aws.identity_center.users.get_unique_users_from_groups") +@patch("modules.aws.identity_center.groups.get_groups_with_members_from_integration") +def test_synchronize_sync_skip_users_if_false( + mock_get_groups_with_members_from_integration, + mock_get_unique_users_from_groups, + mock_list_users, + mock_sync_identity_center_users, + mock_sync_identity_center_groups, + mock_preformat_groups, + mock_logger, + aws_groups_w_users, + aws_users, + google_groups_w_users, + google_users, +): + source_groups = google_groups_w_users(3, 6) + source_users = google_users(6) + target_groups = aws_groups_w_users(3, 6) + target_users = aws_users(6) + mock_get_groups_with_members_from_integration.side_effect = [ + source_groups, + target_groups, + ] + mock_preformat_groups.return_value = source_groups + mock_get_unique_users_from_groups.return_value = source_users + mock_list_users.return_value = target_users + mock_sync_identity_center_users.return_value = ("users_created", "users_deleted") + mock_sync_identity_center_groups.return_value = ( + "memberships_created", + "memberships_deleted", + ) + + response = identity_center.synchronize(enable_users_sync=False) + + assert response == { + "users": None, + "groups": ("memberships_created", "memberships_deleted"), + } + + assert mock_get_groups_with_members_from_integration.call_count == 2 + + google_groups_call = call("google_groups", query="email:aws-*", filters=ANY) + aws_identity_center_call = call("aws_identity_center") + assert mock_get_groups_with_members_from_integration.call_args_list == [ + google_groups_call, + aws_identity_center_call, + ] + + assert mock_get_unique_users_from_groups.call_count == 1 + assert mock_list_users.call_count == 1 + assert mock_sync_identity_center_users.call_count == 0 + assert mock_sync_identity_center_groups.call_count == 1 + + assert ( + call(source_groups, target_groups, target_users) + in mock_sync_identity_center_groups.call_args_list + ) + + assert mock_logger.info.call_count == 22 + logger_calls = [call("synchronize:Found 3 Source Groups and 6 Users")] + for group in source_groups: + logger_calls.append( + call( + f"synchronize:Source:Group {group['name']} has {len(group['members'])} members" + ) + ) + for user in source_users: + logger_calls.append(call(f"synchronize:Source:User {user['primaryEmail']}")) + logger_calls.append(call("synchronize:Found 3 Target Groups and 6 Users")) + for group in target_groups: + logger_calls.append( + call( + f"synchronize:Target:Group {group['DisplayName']} has {len(group['GroupMemberships'])} members" + ) + ) + for user in target_users: + logger_calls.append(call(f"synchronize:Target:User {user['UserName']}")) + logger_calls.append(call("synchronize:groups:Syncing Groups")) + logger_calls.append(call("synchronize:groups:Formatting Source Groups")) + + assert mock_logger.info.call_args_list == logger_calls + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.groups.preformat_groups") +@patch("modules.aws.identity_center.sync_groups") +@patch("modules.aws.identity_center.sync_users") +@patch("modules.aws.identity_center.identity_store.list_users") +@patch("modules.aws.identity_center.users.get_unique_users_from_groups") +@patch("modules.aws.identity_center.groups.get_groups_with_members_from_integration") +def test_synchronize_sync_skip_groups_false_if_false( + mock_get_groups_with_members_from_integration, + mock_get_unique_users_from_groups, + mock_list_users, + mock_sync_identity_center_users, + mock_sync_identity_center_groups, + mock_preformat_groups, + mock_logger, + aws_groups_w_users, + aws_users, + google_groups_w_users, + google_users, +): + source_groups = google_groups_w_users(3, 6) + source_users = google_users(6) + target_groups = aws_groups_w_users(3, 6) + target_users = aws_users(6) + mock_get_groups_with_members_from_integration.side_effect = [ + source_groups, + target_groups, + ] + mock_preformat_groups.return_value = source_groups + mock_get_unique_users_from_groups.return_value = source_users + mock_list_users.return_value = target_users + mock_sync_identity_center_users.return_value = ("users_created", "users_deleted") + mock_sync_identity_center_groups.return_value = ( + "memberships_created", + "memberships_deleted", + ) + + response = identity_center.synchronize(enable_groups_sync=False) + + assert response == {"users": ("users_created", "users_deleted"), "groups": None} + + assert mock_get_groups_with_members_from_integration.call_count == 2 + + google_groups_call = call("google_groups", query="email:aws-*", filters=ANY) + aws_identity_center_call = call("aws_identity_center") + assert mock_get_groups_with_members_from_integration.call_args_list == [ + google_groups_call, + aws_identity_center_call, + ] + + assert mock_get_unique_users_from_groups.call_count == 1 + assert mock_list_users.call_count == 2 + assert mock_sync_identity_center_users.call_count == 1 + assert mock_sync_identity_center_groups.call_count == 0 + + assert ( + call(source_users, target_users) + in mock_sync_identity_center_users.call_args_list + ) + + assert mock_logger.info.call_count == 21 + logger_calls = [call("synchronize:Found 3 Source Groups and 6 Users")] + for group in source_groups: + logger_calls.append( + call( + f"synchronize:Source:Group {group['name']} has {len(group['members'])} members" + ) + ) + for user in source_users: + logger_calls.append(call(f"synchronize:Source:User {user['primaryEmail']}")) + logger_calls.append(call("synchronize:Found 3 Target Groups and 6 Users")) + for group in target_groups: + logger_calls.append( + call( + f"synchronize:Target:Group {group['DisplayName']} has {len(group['GroupMemberships'])} members" + ) + ) + for user in target_users: + logger_calls.append(call(f"synchronize:Target:User {user['UserName']}")) + logger_calls.append(call("synchronize:users:Syncing Users")) + + assert mock_logger.info.call_args_list == logger_calls + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.sync_groups") +@patch("modules.aws.identity_center.sync_users") +@patch("modules.aws.identity_center.identity_store.list_users") +@patch("modules.aws.identity_center.users.get_unique_users_from_groups") +@patch("modules.aws.identity_center.groups.get_groups_with_members_from_integration") +def test_synchronize_sync_skip_users_and_groups_if_false( + mock_get_groups_with_members_from_integration, + mock_get_unique_users_from_groups, + mock_list_users, + mock_sync_identity_center_users, + mock_sync_identity_center_groups, + mock_logger, + aws_groups_w_users, + aws_users, + google_groups_w_users, + google_users, +): + source_groups = google_groups_w_users(3, 6) + source_users = google_users(6) + target_groups = aws_groups_w_users(3, 6) + target_users = aws_users(6) + mock_get_groups_with_members_from_integration.side_effect = [ + source_groups, + target_groups, + ] + mock_get_unique_users_from_groups.return_value = source_users + mock_list_users.return_value = target_users + mock_sync_identity_center_users.return_value = ("users_created", "users_deleted") + mock_sync_identity_center_groups.return_value = ( + "memberships_created", + "memberships_deleted", + ) + + response = identity_center.synchronize( + enable_groups_sync=False, enable_users_sync=False + ) + + assert response == {"users": None, "groups": None} + + assert mock_get_groups_with_members_from_integration.call_count == 2 + + google_groups_call = call("google_groups", query="email:aws-*", filters=ANY) + aws_identity_center_call = call("aws_identity_center") + assert mock_get_groups_with_members_from_integration.call_args_list == [ + google_groups_call, + aws_identity_center_call, + ] + + assert mock_get_unique_users_from_groups.call_count == 1 + assert mock_list_users.call_count == 1 + + assert mock_sync_identity_center_users.call_count == 0 + assert mock_sync_identity_center_groups.call_count == 0 + assert mock_logger.info.call_count == 20 + logger_calls = [call("synchronize:Found 3 Source Groups and 6 Users")] + for group in source_groups: + logger_calls.append( + call( + f"synchronize:Source:Group {group['name']} has {len(group['members'])} members" + ) + ) + for user in source_users: + logger_calls.append(call(f"synchronize:Source:User {user['primaryEmail']}")) + logger_calls.append(call("synchronize:Found 3 Target Groups and 6 Users")) + for group in target_groups: + logger_calls.append( + call( + f"synchronize:Target:Group {group['DisplayName']} has {len(group['GroupMemberships'])} members" + ) + ) + for user in target_users: + logger_calls.append(call(f"synchronize:Target:User {user['UserName']}")) + + assert mock_logger.info.call_args_list == logger_calls + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.create_user") +def test_create_aws_users( + mock_create_user, mock_logger, google_users, google_groups_w_users +): + users_to_create = google_users(3) + # groups_to_sync = google_groups_w_users(3, 9) + mock_create_user.side_effect = users_to_create + + result = identity_center.create_aws_users(users_to_create) + + assert result == users_to_create + assert mock_create_user.call_count == 3 + mock_logger.info.assert_any_call( + f"create_aws_users:Starting creation of {len(users_to_create)} users." + ) + for user in users_to_create: + mock_create_user.assert_has_calls( + [ + call( + user["primaryEmail"], + user["name"]["givenName"], + user["name"]["familyName"], + ) + for user in users_to_create + ] + ) + mock_logger.info.assert_any_call( + f"create_aws_users:Successfully created user {user['primaryEmail']}" + ) + assert ( + call("create_aws_users:Finished creation of 3 users.") + in mock_logger.info.call_args_list + ) + + +@patch("modules.aws.identity_center.DRY_RUN", True) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.create_user") +def test_create_aws_users_dry_run(mock_create_user, mock_logger, google_users): + users_to_create = google_users(3) + expected_output = [user["primaryEmail"] for user in users_to_create] + + result = identity_center.create_aws_users(users_to_create) + + assert result == expected_output + assert mock_create_user.call_count == 0 + mock_logger.info.assert_any_call( + f"create_aws_users:Starting creation of {len(users_to_create)} users." + ) + for user in users_to_create: + mock_logger.info.assert_any_call( + f"create_aws_users:DRY_RUN:Successfully created user {user['primaryEmail']}" + ) + assert ( + call("create_aws_users:Finished creation of 3 users.") + in mock_logger.info.call_args_list + ) + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.create_user") +def test_create_aws_users_handles_failure( + mock_create_user, mock_logger, google_users, aws_users +): + users_to_create = google_users(3) + created_users = aws_users(3) + expected_output = [user["UserId"] for user in created_users] + mock_create_user.side_effect = [expected_output[0], None, expected_output[2]] + del expected_output[1] + + result = identity_center.create_aws_users(users_to_create) + + assert result == expected_output + assert mock_create_user.call_count == 3 + assert mock_logger.info.call_count == 4 + assert mock_logger.error.call_count == 1 + mock_logger.info.assert_any_call( + f"create_aws_users:Starting creation of {len(users_to_create)} users." + ) + for i in range(len(users_to_create)): + mock_create_user.assert_any_call( + users_to_create[i]["primaryEmail"], + users_to_create[i]["name"]["givenName"], + users_to_create[i]["name"]["familyName"], + ) + if i == 1: + mock_logger.error.assert_any_call( + f"create_aws_users:Failed to create user {users_to_create[i]['primaryEmail']}" + ) + else: + mock_logger.info.assert_any_call( + f"create_aws_users:Successfully created user {users_to_create[i]['primaryEmail']}" + ) + assert ( + call("create_aws_users:Finished creation of 2 users.") + in mock_logger.info.call_args_list + ) + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.create_user") +def test_create_aws_users_empty_list(mock_create_user, mock_logger): + users_to_create = [] + mock_create_user.side_effect = users_to_create + + result = identity_center.create_aws_users(users_to_create) + + assert result == users_to_create + assert mock_create_user.call_count == 0 + assert mock_logger.info.call_count == 2 + assert ( + call("create_aws_users:Starting creation of 0 users.") + in mock_logger.info.call_args_list + ) + assert call("create_aws_users:Finished creation of 0 users.") + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.delete_user") +def test_delete_aws_users_default(mock_delete_user, mock_logger, aws_users): + users_to_delete = aws_users(3) + expected_output = [ + "user-email1@test.com", + "user-email2@test.com", + "user-email3@test.com", + ] + + result = identity_center.delete_aws_users(users_to_delete) + + assert result == expected_output + assert mock_logger.info.call_count == 5 + assert ( + call("delete_aws_users:Starting deletion of 3 users.") + in mock_logger.info.call_args_list + ) + for user in users_to_delete: + mock_logger.info.assert_any_call( + f"delete_aws_users:DRY_RUN:Successfully deleted user {user['UserName']}" + ) + assert ( + call("delete_aws_users:Finished deletion of 3 users.") + in mock_logger.info.call_args_list + ) + mock_delete_user.call_count == 0 + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.delete_user") +def test_delete_aws_users_enable_delete_true(mock_delete_user, mock_logger, aws_users): + users_to_delete = aws_users(3) + expected_output = [ + "user-email1@test.com", + "user-email2@test.com", + "user-email3@test.com", + ] + mock_delete_user.return_value = True + + result = identity_center.delete_aws_users(users_to_delete, enable_delete=True) + + assert result == expected_output + assert mock_logger.info.call_count == 5 + assert ( + call("delete_aws_users:Starting deletion of 3 users.") + in mock_logger.info.call_args_list + ) + for user in users_to_delete: + mock_logger.info.assert_any_call( + f"delete_aws_users:Successfully deleted user {user['UserName']}" + ) + mock_delete_user.assert_any_call(user["UserId"]) + assert ( + call("delete_aws_users:Finished deletion of 3 users.") + in mock_logger.info.call_args_list + ) + mock_delete_user.call_count == 3 + + +@patch("modules.aws.identity_center.DRY_RUN", True) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.delete_user") +def test_delete_aws_users_enable_delete_true_dry_run( + mock_delete_user, mock_logger, aws_users +): + users_to_delete = aws_users(3) + expected_output = [ + "user-email1@test.com", + "user-email2@test.com", + "user-email3@test.com", + ] + mock_delete_user.return_value = True + + result = identity_center.delete_aws_users(users_to_delete, enable_delete=True) + + assert result == expected_output + assert mock_logger.info.call_count == 5 + assert ( + call("delete_aws_users:Starting deletion of 3 users.") + in mock_logger.info.call_args_list + ) + for user in users_to_delete: + mock_logger.info.assert_any_call( + f"delete_aws_users:DRY_RUN:Successfully deleted user {user['UserName']}" + ) + assert ( + call("delete_aws_users:Finished deletion of 3 users.") + in mock_logger.info.call_args_list + ) + mock_delete_user.call_count == 0 + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.delete_user") +def test_delete_aws_users_handles_failure(mock_delete_user, mock_logger, aws_users): + users_to_delete = aws_users(3) + expected_output = ["user-email1@test.com", "user-email3@test.com"] + mock_delete_user.side_effect = [True, False, True] + + result = identity_center.delete_aws_users(users_to_delete, enable_delete=True) + + assert result == expected_output + assert mock_logger.info.call_count == 4 + assert mock_logger.error.call_count == 1 + assert ( + call("delete_aws_users:Starting deletion of 3 users.") + in mock_logger.info.call_args_list + ) + for i in range(len(users_to_delete)): + mock_delete_user.assert_any_call(users_to_delete[i]["UserId"]) + if i == 1: + mock_logger.error.assert_any_call( + f"delete_aws_users:Failed to delete user {users_to_delete[i]['UserName']}" + ) + else: + mock_logger.info.assert_any_call( + f"delete_aws_users:Successfully deleted user {users_to_delete[i]['UserName']}" + ) + assert ( + call("delete_aws_users:Finished deletion of 2 users.") + in mock_logger.info.call_args_list + ) + mock_delete_user.call_count == 3 + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.delete_user") +def test_delete_aws_users_empty_list(mock_delete_user, mock_logger): + users_to_delete = [] + mock_delete_user.side_effect = users_to_delete + + result = identity_center.delete_aws_users(users_to_delete) + + assert result == users_to_delete + assert mock_delete_user.call_count == 0 + + +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.delete_aws_users") +@patch("modules.aws.identity_center.create_aws_users") +@patch("modules.aws.identity_center.filters.compare_lists") +def test_sync_identity_center_users_default( + mock_compare_lists, + mock_create_aws_users, + mock_delete_aws_users, + mock_logger, + google_users, + aws_users, +): + source_users = google_users(3) + target_users = aws_users(6)[:3] + mock_compare_lists.return_value = source_users, target_users + mock_create_aws_users.return_value = source_users + mock_delete_aws_users.return_value = [] + + result = identity_center.sync_users(source_users, target_users) + + assert result == (source_users, []) + + assert ( + call( + {"values": source_users, "key": "primaryEmail"}, + {"values": target_users, "key": "UserName"}, + mode="sync", + ) + in mock_compare_lists.call_args_list + ) + assert call(source_users) in mock_create_aws_users.call_args_list + assert ( + call(target_users, enable_delete=False) in mock_delete_aws_users.call_args_list + ) + assert mock_logger.info.call_count == 1 + assert ( + call("synchronize:users:Found 3 Users to Create and 3 Users to Delete") + in mock_logger.info.call_args_list + ) + + +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.delete_aws_users") +@patch("modules.aws.identity_center.create_aws_users") +@patch("modules.aws.identity_center.filters.compare_lists") +def test_sync_identity_center_users_enable_delete_true( + mock_compare_lists, + mock_create_aws_users, + mock_delete_aws_users, + mock_logger, + google_users, + aws_users, +): + source_users = google_users(3) + target_users = aws_users(6)[:3] + mock_compare_lists.return_value = source_users, target_users + mock_create_aws_users.return_value = source_users + mock_delete_aws_users.return_value = target_users + + result = identity_center.sync_users(source_users, target_users, enable_delete=True) + + assert result == (source_users, target_users) + assert ( + call( + {"values": source_users, "key": "primaryEmail"}, + {"values": target_users, "key": "UserName"}, + mode="sync", + ) + in mock_compare_lists.call_args_list + ) + assert call(source_users) in mock_create_aws_users.call_args_list + assert ( + call(target_users, enable_delete=True) in mock_delete_aws_users.call_args_list + ) + assert mock_logger.info.call_count == 1 + assert ( + call("synchronize:users:Found 3 Users to Create and 3 Users to Delete") + in mock_logger.info.call_args_list + ) + + +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.delete_aws_users") +@patch("modules.aws.identity_center.create_aws_users") +@patch("modules.aws.identity_center.filters.compare_lists") +def test_sync_identity_center_users_delete_target_all_disable_delete( + mock_compare_lists, + mock_create_aws_users, + mock_delete_aws_users, + mock_logger, + google_users, + aws_users, +): + source_users = google_users(3) + target_users = aws_users(6) + mock_create_aws_users.return_value = [] + mock_delete_aws_users.return_value = target_users + + result = identity_center.sync_users( + source_users, target_users, delete_target_all=True + ) + + assert result == ([], target_users) + assert mock_compare_lists.call_count == 0 + + assert call([]) in mock_create_aws_users.call_args_list + assert ( + call(target_users, enable_delete=False) in mock_delete_aws_users.call_args_list + ) + + assert mock_logger.info.call_count == 1 + assert ( + call("synchronize:users:Found 0 Users to Create and 6 Users to Delete") + in mock_logger.info.call_args_list + ) + + +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.delete_aws_users") +@patch("modules.aws.identity_center.create_aws_users") +@patch("modules.aws.identity_center.filters.compare_lists") +def test_sync_identity_center_users_delete_target_all_enable_delete( + mock_compare_lists, + mock_create_aws_users, + mock_delete_aws_users, + mock_logger, + google_users, + aws_users, +): + source_users = google_users(3) + target_users = aws_users(6) + mock_create_aws_users.return_value = [] + mock_delete_aws_users.return_value = target_users + + result = identity_center.sync_users( + source_users, target_users, enable_delete=True, delete_target_all=True + ) + + assert result == ([], target_users) + assert mock_compare_lists.call_count == 0 + + assert call([]) in mock_create_aws_users.call_args_list + assert ( + call(target_users, enable_delete=True) in mock_delete_aws_users.call_args_list + ) + + assert mock_logger.info.call_count == 1 + assert ( + call("synchronize:users:Found 0 Users to Create and 6 Users to Delete") + in mock_logger.info.call_args_list + ) + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.filters.filter_by_condition") +@patch("modules.aws.identity_center.identity_store.create_group_membership") +def test_create_group_memberships( + mock_create_group_membership, + mock_filter_by_condition, + mock_logger, + aws_groups_w_users, + aws_users, + google_users, +): + group = aws_groups_w_users(1, 3)[0] + target_users = aws_users(3) + users_to_add = google_users(3) + mock_filter_by_condition.side_effect = [ + [target_users[0]], + [target_users[1]], + [target_users[2]], + ] + + mock_create_group_membership.side_effect = [ + "user-email1@test.com", + "user-email2@test.com", + "user-email3@test.com", + ] + + result = identity_center.create_group_memberships(group, users_to_add, target_users) + + assert result == [ + "user-email1@test.com", + "user-email2@test.com", + "user-email3@test.com", + ] + assert mock_create_group_membership.call_count == 3 + assert mock_filter_by_condition.call_count == 3 + for user in target_users: + assert ( + call( + f"create_group_memberships:Successfully added user {user['UserName']} to group {group['DisplayName']}" + ) + in mock_logger.info.call_args_list + ) + mock_create_group_membership.assert_any_call(group["GroupId"], user["UserId"]) + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.filters.filter_by_condition") +@patch("modules.aws.identity_center.identity_store.create_group_membership") +def test_create_group_memberships_handles_failure( + mock_create_group_membership, + mock_filter_by_condition, + mock_logger, + aws_groups_w_users, + aws_users, + google_users, +): + group = aws_groups_w_users(1, 3)[0] + target_users = aws_users(3) + users_to_add = google_users(3) + mock_filter_by_condition.side_effect = [ + [target_users[0]], + [target_users[1]], + [target_users[2]], + ] + + mock_create_group_membership.side_effect = [ + "user-email1@test.com", + None, + "user-email3@test.com", + ] + + result = identity_center.create_group_memberships(group, users_to_add, target_users) + + assert result == [ + "user-email1@test.com", + "user-email3@test.com", + ] + assert mock_create_group_membership.call_count == 3 + assert mock_filter_by_condition.call_count == 3 + for i in range(len(target_users)): + if i == 1: + assert ( + call( + f"create_group_memberships:Failed to add user {target_users[i]['UserName']} to group {group['DisplayName']}" + ) + in mock_logger.error.call_args_list + ) + else: + assert ( + call( + f"create_group_memberships:Successfully added user {target_users[i]['UserName']} to group {group['DisplayName']}" + ) + in mock_logger.info.call_args_list + ) + mock_create_group_membership.assert_any_call( + group["GroupId"], target_users[i]["UserId"] + ) + + +@patch("modules.aws.identity_center.DRY_RUN", True) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.filters.filter_by_condition") +@patch("modules.aws.identity_center.identity_store.create_group_membership") +def test_create_group_memberships_dry_run( + mock_create_group_membership, + mock_filter_by_condition, + mock_logger, + aws_groups_w_users, + aws_users, + google_users, +): + group = aws_groups_w_users(1, 3)[0] + target_users = aws_users(3) + users_to_add = google_users(3) + + mock_filter_by_condition.side_effect = [ + [target_users[0]], + [target_users[1]], + [target_users[2]], + ] + result = identity_center.create_group_memberships(group, users_to_add, target_users) + + assert result == [ + "aws-group_id1-user-email1@test.com", + "aws-group_id1-user-email2@test.com", + "aws-group_id1-user-email3@test.com", + ] + assert mock_create_group_membership.call_count == 0 + assert mock_filter_by_condition.call_count == 3 + for user in target_users: + mock_logger.info.assert_any_call( + f"create_group_memberships:DRY_RUN:Successfully added user {user['UserName']} to group {group['DisplayName']}" + ) + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.filters.filter_by_condition") +@patch("modules.aws.identity_center.identity_store.create_group_membership") +def test_create_group_memberships_empty_list( + mock_create_group_membership, + mock_filter_by_condition, + mock_logger, + aws_groups, + aws_users, +): + group = aws_groups(1)[0] + target_users = aws_users(3) + users_to_add = [] + + result = identity_center.create_group_memberships(group, users_to_add, target_users) + + assert result == [] + assert mock_create_group_membership.call_count == 0 + assert mock_filter_by_condition.call_count == 0 + assert mock_logger.info.call_count == 2 + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.filters.filter_by_condition") +@patch("modules.aws.identity_center.identity_store.create_group_membership") +def test_create_group_memberships_matching_user_not_found( + mock_create_group_membership, + mock_filter_by_condition, + mock_logger, + aws_groups_w_users, + aws_users, + google_users, +): + group = aws_groups_w_users(1, 3)[0] + target_users = aws_users(2) + users_to_add = google_users(3) + mock_filter_by_condition.side_effect = [ + [target_users[0]], + [target_users[1]], + [], + ] + + mock_create_group_membership.side_effect = [ + "user-email1@test.com", + "user-email2@test.com", + ] + + result = identity_center.create_group_memberships(group, users_to_add, target_users) + + assert result == [ + "user-email1@test.com", + "user-email2@test.com", + ] + assert mock_create_group_membership.call_count == 2 + + for user in target_users: + mock_logger.info.assert_any_call( + f"create_group_memberships:Successfully added user {user['UserName']} to group {group['DisplayName']}" + ) + mock_create_group_membership.assert_any_call( + group["GroupId"], target_users[0]["UserId"] + ) + mock_create_group_membership.assert_any_call( + group["GroupId"], target_users[1]["UserId"] + ) + mock_logger.info.assert_any_call( + f"create_group_memberships:Failed to find user {users_to_add[2]['primaryEmail']} in target system" + ) + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.delete_group_membership") +def test_delete_group_memberships_defaults_not_deleting( + mock_delete_group_membership, + mock_logger, + aws_groups_w_users, +): + group = aws_groups_w_users(1, 6)[0] + users_to_remove = group["GroupMemberships"] + + result = identity_center.delete_group_memberships(group, users_to_remove) + + assert result == [ + "membership_id_1", + "membership_id_2", + "membership_id_3", + "membership_id_4", + "membership_id_5", + "membership_id_6", + ] + assert mock_delete_group_membership.call_count == 0 + mock_logger.info.assert_any_call( + f"delete_group_memberships:Removing {len(users_to_remove)} users from group {group['DisplayName']}" + ) + for user in users_to_remove: + mock_logger.info.assert_any_call( + f"delete_group_memberships:DRY_RUN:Successfully removed user {user['MemberId']['UserName']} from group {group['DisplayName']}" + ) + + assert mock_logger.info.call_count == 8 + + +@patch("modules.aws.identity_center.DRY_RUN", True) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.delete_group_membership") +def test_delete_group_memberships_enable_delete_dry_run( + mock_delete_group_membership, + mock_logger, + aws_groups_w_users, +): + group = aws_groups_w_users(1, 6)[0] + users_to_remove = group["GroupMemberships"] + + mock_delete_group_membership.side_effect = [ + "membership_id_1", + "membership_id_2", + "membership_id_3", + "membership_id_4", + "membership_id_5", + "membership_id_6", + ] + + result = identity_center.delete_group_memberships( + group, users_to_remove, enable_delete=True + ) + + assert result == [ + "membership_id_1", + "membership_id_2", + "membership_id_3", + "membership_id_4", + "membership_id_5", + "membership_id_6", + ] + assert mock_delete_group_membership.call_count == 0 + mock_logger.info.assert_any_call( + f"delete_group_memberships:Removing {len(users_to_remove)} users from group {group['DisplayName']}" + ) + for user in users_to_remove: + mock_logger.info.assert_any_call( + f"delete_group_memberships:DRY_RUN:Successfully removed user {user['MemberId']['UserName']} from group {group['DisplayName']}" + ) + + assert mock_logger.info.call_count == 8 + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.delete_group_membership") +def test_delete_group_memberships_enable_delete_and_not_dry_run( + mock_delete_group_membership, + mock_logger, + aws_groups_w_users, +): + group = aws_groups_w_users(1, 6)[0] + users_to_remove = group["GroupMemberships"] + + mock_delete_group_membership.side_effect = [ + "membership_id_1", + "membership_id_2", + "membership_id_3", + "membership_id_4", + "membership_id_5", + "membership_id_6", + ] + + result = identity_center.delete_group_memberships( + group, users_to_remove, enable_delete=True + ) + + assert result == [ + "membership_id_1", + "membership_id_2", + "membership_id_3", + "membership_id_4", + "membership_id_5", + "membership_id_6", + ] + assert mock_delete_group_membership.call_count == 6 + mock_logger.info.assert_any_call( + f"delete_group_memberships:Removing {len(users_to_remove)} users from group {group['DisplayName']}" + ) + for user in users_to_remove: + mock_logger.info.assert_any_call( + f"delete_group_memberships:Successfully removed user {user['MemberId']['UserName']} from group {group['DisplayName']}" + ) + + assert mock_logger.info.call_count == 8 + + +@patch("modules.aws.identity_center.DRY_RUN", False) +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.identity_store.delete_group_membership") +def test_delete_group_memberships_handles_failure( + mock_delete_group_membership, + mock_logger, + aws_groups_w_users, +): + group = aws_groups_w_users(1, 6)[0] + users_to_remove = group["GroupMemberships"] + + mock_delete_group_membership.side_effect = [ + "membership_id_1", + None, + "membership_id_3", + None, + "membership_id_5", + "membership_id_6", + ] + + result = identity_center.delete_group_memberships( + group, users_to_remove, enable_delete=True + ) + + assert result == [ + "membership_id_1", + "membership_id_3", + "membership_id_5", + "membership_id_6", + ] + assert mock_delete_group_membership.call_count == 6 + for i in range(len(users_to_remove)): + if i == 1 or i == 3: + mock_logger.error.assert_any_call( + f"delete_group_memberships:Failed to remove user {users_to_remove[i]['MemberId']['UserName']} from group {group['DisplayName']}" + ) + else: + mock_logger.info.assert_any_call( + f"delete_group_memberships:Successfully removed user {users_to_remove[i]['MemberId']['UserName']} from group {group['DisplayName']}" + ) + + +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.filters.compare_lists") +@patch("modules.aws.identity_center.create_group_memberships") +@patch("modules.aws.identity_center.delete_group_memberships") +def test_sync_identity_center_groups_defaults( + mock_delete_group_memberships, + mock_create_group_memberships, + mock_compare_lists, + mock_logger, + aws_groups_w_users, + aws_users, + google_groups_w_users, +): + source_groups = google_groups_w_users(3, 6, prefix="target-") + for group in source_groups: + group["members"] = group["members"][:3] + group["DisplayName"] = group["name"] + target_groups = aws_groups_w_users(3, 6, prefix="target-") + for group in target_groups: + group["GroupMemberships"] = group["GroupMemberships"][3:] + target_users = aws_users(6) + + side_effects = [ + (source_groups, target_groups), + (source_groups[0]["members"], target_groups[0]["GroupMemberships"]), + (source_groups[1]["members"], target_groups[1]["GroupMemberships"]), + (source_groups[2]["members"], target_groups[2]["GroupMemberships"]), + ] + mock_compare_lists.side_effect = side_effects + mock_create_group_memberships.side_effect = [ + ["target-membership_id_1", "target-membership_id_2", "target-membership_id_3"], + ["target-membership_id_1", "target-membership_id_2", "target-membership_id_3"], + ["target-membership_id_1", "target-membership_id_2", "target-membership_id_3"], + ] + mock_delete_group_memberships.side_effect = [ + [], + [], + [], + ] + result = identity_center.sync_groups(source_groups, target_groups, target_users) + + assert result == ( + [ + "target-membership_id_1", + "target-membership_id_2", + "target-membership_id_3", + "target-membership_id_1", + "target-membership_id_2", + "target-membership_id_3", + "target-membership_id_1", + "target-membership_id_2", + "target-membership_id_3", + ], + [], + ) + assert ( + call( + {"values": source_groups, "key": "DisplayName"}, + {"values": target_groups, "key": "DisplayName"}, + mode="match", + ) + in mock_compare_lists.call_args_list + ) + assert mock_compare_lists.call_count == 4 + assert mock_create_group_memberships.call_count == 3 + assert mock_delete_group_memberships.call_count == 3 + assert mock_logger.info.call_count == 4 + assert ( + call("synchronize:groups:Found 3 Source Groups and 3 Target Groups") + in mock_logger.info.call_args_list + ) + + +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.filters.compare_lists") +@patch("modules.aws.identity_center.create_group_memberships") +@patch("modules.aws.identity_center.delete_group_memberships") +def test_sync_identity_center_groups_enable_delete_true( + mock_delete_group_memberships, + mock_create_group_memberships, + mock_compare_lists, + mock_logger, + aws_groups_w_users, + aws_users, + google_groups_w_users, +): + source_groups = google_groups_w_users(3, 6, prefix="target-") + for group in source_groups: + group["members"] = group["members"][:3] + group["DisplayName"] = group["name"] + target_groups = aws_groups_w_users(3, 6, prefix="target-") + for group in target_groups: + group["GroupMemberships"] = group["GroupMemberships"][3:] + target_users = aws_users(6) + + side_effects = [ + (source_groups, target_groups), + (source_groups[0]["members"], target_groups[0]["GroupMemberships"]), + (source_groups[1]["members"], target_groups[1]["GroupMemberships"]), + (source_groups[2]["members"], target_groups[2]["GroupMemberships"]), + ] + mock_compare_lists.side_effect = side_effects + mock_create_group_memberships.side_effect = [ + ["target-membership_id_1", "target-membership_id_2", "target-membership_id_3"], + ["target-membership_id_1", "target-membership_id_2", "target-membership_id_3"], + ["target-membership_id_1", "target-membership_id_2", "target-membership_id_3"], + ] + mock_delete_group_memberships.side_effect = [ + ["target-membership_id_4", "target-membership_id_5", "target-membership_id_6"], + ["target-membership_id_4", "target-membership_id_5", "target-membership_id_6"], + ["target-membership_id_4", "target-membership_id_5", "target-membership_id_6"], + ] + result = identity_center.sync_groups( + source_groups, target_groups, target_users, enable_delete=True + ) + + assert result == ( + [ + "target-membership_id_1", + "target-membership_id_2", + "target-membership_id_3", + "target-membership_id_1", + "target-membership_id_2", + "target-membership_id_3", + "target-membership_id_1", + "target-membership_id_2", + "target-membership_id_3", + ], + [ + "target-membership_id_4", + "target-membership_id_5", + "target-membership_id_6", + "target-membership_id_4", + "target-membership_id_5", + "target-membership_id_6", + "target-membership_id_4", + "target-membership_id_5", + "target-membership_id_6", + ], + ) + assert ( + call( + {"values": source_groups, "key": "DisplayName"}, + {"values": target_groups, "key": "DisplayName"}, + mode="match", + ) + in mock_compare_lists.call_args_list + ) + assert mock_compare_lists.call_count == 4 + assert mock_create_group_memberships.call_count == 3 + assert mock_delete_group_memberships.call_count == 3 + assert mock_logger.info.call_count == 4 + assert ( + call("synchronize:groups:Found 3 Source Groups and 3 Target Groups") + in mock_logger.info.call_args_list + ) + + +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.filters.compare_lists") +@patch("modules.aws.identity_center.create_group_memberships") +@patch("modules.aws.identity_center.delete_group_memberships") +def test_sync_identity_center_groups_empty_source_groups( + mock_delete_group_memberships, + mock_create_group_memberships, + mock_compare_lists, + mock_logger, + aws_groups_w_users, + aws_users, +): + source_groups = [] + target_groups = aws_groups_w_users(3, 3, prefix="target-") + target_users = aws_users(3) + mock_compare_lists.return_value = [], [] + + result = identity_center.sync_groups( + source_groups, target_groups, target_users, enable_delete=True + ) + + assert result == ([], []) + assert ( + call( + {"values": source_groups, "key": "DisplayName"}, + {"values": target_groups, "key": "DisplayName"}, + mode="match", + ) + in mock_compare_lists.call_args_list + ) + assert mock_compare_lists.call_count == 1 + assert mock_create_group_memberships.call_count == 0 + assert mock_delete_group_memberships.call_count == 0 + assert mock_logger.info.call_count == 1 + assert ( + call("synchronize:groups:Found 0 Source Groups and 0 Target Groups") + in mock_logger.info.call_args_list + ) + + +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.filters.compare_lists") +@patch("modules.aws.identity_center.create_group_memberships") +@patch("modules.aws.identity_center.delete_group_memberships") +def test_sync_identity_center_groups_empty_target_groups( + mock_delete_group_memberships, + mock_create_group_memberships, + mock_compare_lists, + mock_logger, + aws_users, + google_groups_w_users, +): + source_groups = google_groups_w_users(3, 3, prefix="source-") + target_groups = [] + target_users = aws_users(3) + mock_compare_lists.return_value = [], [] + + result = identity_center.sync_groups( + source_groups, target_groups, target_users, enable_delete=True + ) + + assert result == ([], []) + assert ( + call( + {"values": source_groups, "key": "DisplayName"}, + {"values": target_groups, "key": "DisplayName"}, + mode="match", + ) + in mock_compare_lists.call_args_list + ) + assert mock_compare_lists.call_count == 1 + assert mock_create_group_memberships.call_count == 0 + assert mock_delete_group_memberships.call_count == 0 + assert mock_logger.info.call_count == 1 + assert ( + call("synchronize:groups:Found 0 Source Groups and 0 Target Groups") + in mock_logger.info.call_args_list + ) + + +@patch("modules.aws.identity_center.logger") +@patch("modules.aws.identity_center.filters.compare_lists") +@patch("modules.aws.identity_center.create_group_memberships") +@patch("modules.aws.identity_center.delete_group_memberships") +def test_sync_identity_center_groups_no_matching_groups_to_sync( + mock_delete_group_memberships, + mock_create_group_memberships, + mock_compare_lists, + mock_logger, + aws_groups_w_users, + aws_users, + google_groups_w_users, +): + source_groups = google_groups_w_users(3, 3, prefix="source-") + target_groups = aws_groups_w_users(3, 3, prefix="target-") + target_users = aws_users(3) + mock_compare_lists.return_value = [], [] + + result = identity_center.sync_groups( + source_groups, target_groups, target_users, enable_delete=True + ) + + assert result == ([], []) + assert ( + call( + {"values": source_groups, "key": "DisplayName"}, + {"values": target_groups, "key": "DisplayName"}, + mode="match", + ) + in mock_compare_lists.call_args_list + ) + assert mock_compare_lists.call_count == 1 + assert mock_create_group_memberships.call_count == 0 + assert mock_delete_group_memberships.call_count == 0 + assert mock_logger.info.call_count == 1 + assert ( + call("synchronize:groups:Found 0 Source Groups and 0 Target Groups") + in mock_logger.info.call_args_list + ) diff --git a/app/tests/modules/provisioning/test_provisioning_groups.py b/app/tests/modules/provisioning/test_provisioning_groups.py index b99a4bab..62039e32 100644 --- a/app/tests/modules/provisioning/test_provisioning_groups.py +++ b/app/tests/modules/provisioning/test_provisioning_groups.py @@ -1,3 +1,4 @@ +import pytest from unittest.mock import patch from modules.provisioning import groups @@ -177,3 +178,92 @@ def test_get_groups_with_members_from_integration_filters_returns_subset( assert mock_filter_tools.called_once_with(aws_groups_prefix, filters) assert mock_aws_list_groups_with_memberships.called_once_with(members_details=True) assert not mock_google_list_groups_with_members.called + + +def test_preformat_groups(): + groups_to_format = [ + { + "id": "PREFIX-google_group_id1", + "name": "PREFIX-group-name1", + "members": [ + { + "id": "PREFIX-user_id1", + "primaryEmail": "PREFIX-user-email1@test.com", + } + ], + } + ] + + lookup_key = "name" + new_key = "DisplayName" + pattern = r"^PREFIX-" + replace = "new-" + response = groups.preformat_groups( + groups_to_format, lookup_key, new_key, pattern, replace + ) + + assert response == [ + { + "id": "PREFIX-google_group_id1", + "name": "PREFIX-group-name1", + "members": [ + { + "id": "PREFIX-user_id1", + "primaryEmail": "PREFIX-user-email1@test.com", + } + ], + "DisplayName": "new-group-name1", + } + ] + + +def test_preformat_groups_returns_value_if_no_matching_pattern(google_groups_w_users): + groups_to_format = [ + { + "id": "PREFIX-google_group_id1", + "name": "not-PREFIX-group-name1", + "members": [ + { + "id": "PREFIX-user_id1", + "primaryEmail": "PREFIX-user-email1@test.com", + } + ], + } + ] + lookup_key = "name" + new_key = "DisplayName" + pattern = r"^PREFIX-" + replace = "new-" + response = groups.preformat_groups( + groups_to_format, lookup_key, new_key, pattern, replace + ) + + assert response == [ + { + "id": "PREFIX-google_group_id1", + "name": "not-PREFIX-group-name1", + "members": [ + { + "id": "PREFIX-user_id1", + "primaryEmail": "PREFIX-user-email1@test.com", + } + ], + "DisplayName": "not-PREFIX-group-name1", + } + ] + + +def test_preformat_groups_lookup_key_not_found_raise_error(google_groups_w_users): + groups_to_format = google_groups_w_users(n_groups=1, n_users=1, prefix="PREFIX-") + lookup_key = "invalid_key" + new_key = "DisplayName" + pattern = "PREFIX-" + replace = "new-" + + with pytest.raises(KeyError) as exc: + groups.preformat_groups(groups_to_format, lookup_key, new_key, pattern, replace) + + expected_error_message = ( + f'"Group {groups_to_format[0]} does not have {lookup_key} key"' + ) + assert str(exc.value) == expected_error_message