Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/sync single group #603

Merged
merged 3 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/integrations/aws/identity_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ def list_groups_with_memberships(
"""Retrieves groups with their members from the AWS Identity Center (identitystore)

Args:
group_members (bool): Include group members in the response. Default is True.
members_details (bool): Include the details of the members. Default is True.
include_empty_groups (bool): Include groups without members. Default is True.
groups_filters (list): A list of filters to apply to the groups. Default is None.
**kwargs: Additional keyword arguments for the API call. (passed to list_groups)

Returns:
Expand All @@ -309,6 +313,7 @@ def list_groups_with_memberships(
if groups_filters is not None:
for groups_filter in groups_filters:
groups = filters.filter_by_condition(groups, groups_filter)
logger.info(f"Founds {len(groups)} groups in AWS Identity Store.")
if not group_members:
return groups

Expand Down
9 changes: 7 additions & 2 deletions app/integrations/google_workspace/google_directory.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Google Directory module to interact with the Google Workspace Directory API."""

from logging import getLogger
from integrations.google_workspace.google_service import (
handle_google_api_errors,
execute_google_api_call,
Expand All @@ -9,6 +10,8 @@
from integrations.utils.api import convert_string_to_camel_case
from utils import filters

logger = getLogger(__name__)


@handle_google_api_errors
def get_user(user_key, delegated_user_email=None):
Expand Down Expand Up @@ -181,8 +184,10 @@ def list_groups_with_members(
if not groups:
return []

for filter in groups_filters:
groups = filters.filter_by_condition(groups, filter)
if groups_filters is not None:
for groups_filter in groups_filters:
groups = filters.filter_by_condition(groups, groups_filter)
logger.info(f"Found {len(groups)} groups.")
if not group_members:
return groups

Expand Down
19 changes: 14 additions & 5 deletions app/modules/aws/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"""

import os
from slack_bolt import App
from slack_bolt import App, Ack, Respond
from slack_sdk.web import WebClient
from logging import Logger

from integrations.aws.organizations import get_account_id_by_name
from integrations.aws import identity_store
Expand All @@ -20,20 +22,25 @@
AWS_ADMIN_GROUPS = os.environ.get("AWS_ADMIN_GROUPS", "[email protected]").split(",")

help_text = """
\n `/aws user <operation> <user1> <user2> ...`
\n `/aws users <operation> <user1> <user2> ...`
\n - Provision or deprovision AWS users | Provisionner ou déprovisionner des utilisateurs AWS
\n Supports multiple users for a single operation | Supporte plusieurs utilisateurs pour l'opération
\n `<operation>`: `create` or/ou `delete`
\n `<user>`: email address or Slack username of the user | adresse courriel ou identifiant Slack de l'utilisateur
\n Usage: `/aws user create @username [email protected]`
\n `/aws groups <operation> <group1> <group2> ...`
\n - Manage AWS groups | Gérer les groupes AWS
\n `<operation>`: `sync`, `list`
\n `<group>`: name of the group | nom du groupe (sync only)
\n Usage: `/aws groups sync`, `/aws groups sync group-name` or/ou `/aws groups list`
\n `/aws help | aide`
\n - Show this help text | montre le dialogue d'aide
\n
\n (currently disabled)
\n `/aws access`
\n - starts the process to access an AWS account | débute le processus pour accéder à un compte AWS
\n `/aws health`
\n - Query the health of an AWS account | Demander l'état de santé d'un compte AWS
\n `/aws access`
\n - starts the process to access an AWS account | débute le processus pour accéder à un compte AWS
"""


Expand All @@ -48,7 +55,9 @@ def register(bot: App) -> None:
bot.view("aws_health_view")(aws_account_health.health_view_handler)


def aws_command(ack, command, logger, respond, client, body) -> None:
def aws_command(
ack: Ack, command, logger: Logger, respond: Respond, client: WebClient, body
) -> None:
"""AWS command handler.

This function handles the `/aws` command by parsing the command text and executing the appropriate action.
Expand Down
24 changes: 23 additions & 1 deletion app/modules/aws/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,37 @@ def command_handler(client, body, respond, args, logger):


def request_groups_sync(client, body, respond, args, logger):
"""Sync groups from AWS Identity Center."""
"""Sync groups from AWS Identity Center.

If additional arguments are provided, they will be used to filter the groups to sync.

Args:
client (Slack WebClient): The Slack client.
body (dict): The request body.
respond (function): The function to respond to the request.
args (list[str]): The list of arguments.
logger (Logger): The logger.
"""
requestor_email = slack_users.get_user_email_from_body(client, body)
if permissions.is_user_member_of_groups(requestor_email, AWS_ADMIN_GROUPS):
pre_processing_filters = (
[
lambda group, arg=arg: arg.lower()
in group.get("DisplayName", "").lower()
or arg.lower() in group.get("name", "").lower()
for arg in args
]
if args
else []
)
logger.info("Synchronizing AWS Identity Center Groups.")
respond("AWS Groups Memberships Synchronization Initiated.")
identity_center.synchronize(
enable_users_sync=False,
enable_user_create=False,
enable_membership_create=True,
enable_membership_delete=True,
pre_processing_filters=pre_processing_filters,
)
else:
logger.error(f"User {requestor_email} does not have permission to sync groups.")
Expand Down
10 changes: 8 additions & 2 deletions app/modules/aws/identity_center.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,26 @@ def synchronize(**kwargs):
enable_membership_create = kwargs.pop("enable_membership_create", True)
enable_membership_delete = kwargs.pop("enable_membership_delete", False)
query = kwargs.pop("query", "email:aws-*")
pre_processing_filters = kwargs.pop("pre_processing_filters", [])

users_sync_status = None
groups_sync_status = None

source_groups_filters = [lambda group: "AWS-" in group["name"]]
source_groups = groups.get_groups_from_integration(
"google_groups", query=query, post_processing_filters=source_groups_filters
"google_groups",
query=query,
pre_processing_filters=pre_processing_filters,
post_processing_filters=source_groups_filters,
)
source_users = filters.get_unique_nested_dicts(source_groups, "members")
logger.info(
f"synchronize:Found {len(source_groups)} Groups and {len(source_users)} Users from Source"
)

target_groups = groups.get_groups_from_integration("aws_identity_center")
target_groups = groups.get_groups_from_integration(
"aws_identity_center", pre_processing_filters=pre_processing_filters
)
target_users = identity_store.list_users()

logger.info(
Expand Down
37 changes: 36 additions & 1 deletion app/tests/modules/aws/test_aws_groups.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from unittest.mock import patch, MagicMock, call
from unittest.mock import patch, MagicMock, call, ANY
from modules.aws import groups


Expand Down Expand Up @@ -86,9 +86,44 @@ def test_request_groups_sync_synchronizes_groups(
)
logger.info.assert_called_once_with("Synchronizing AWS Identity Center Groups.")
mock_identity_center.synchronize.assert_called_once_with(
enable_users_sync=False,
enable_user_create=False,
enable_membership_create=True,
enable_membership_delete=True,
pre_processing_filters=[],
)
respond.assert_called_once_with("AWS Groups Memberships Synchronization Initiated.")


@patch("modules.aws.groups.slack_users")
@patch("modules.aws.groups.permissions")
@patch("modules.aws.groups.identity_center")
def test_request_groups_sync_synchronizes_groups_with_args(
mock_identity_center, mock_permissions, mock_slack_users
):
client = MagicMock()
body = MagicMock()
respond = MagicMock()
args = ["group1", "group2"]
logger = MagicMock()

mock_slack_users.get_user_email_from_body.return_value = "[email protected]"
mock_permissions.is_user_member_of_groups.return_value = True
mock_identity_center.synchronize.return_value = None

groups.request_groups_sync(client, body, respond, args, logger)

mock_slack_users.get_user_email_from_body.assert_called_once_with(client, body)
mock_permissions.is_user_member_of_groups.assert_called_once_with(
"[email protected]", groups.AWS_ADMIN_GROUPS
)
logger.info.assert_called_once_with("Synchronizing AWS Identity Center Groups.")
mock_identity_center.synchronize.assert_called_once_with(
enable_users_sync=False,
enable_user_create=False,
enable_membership_create=True,
enable_membership_delete=True,
pre_processing_filters=ANY,
)
respond.assert_called_once_with("AWS Groups Memberships Synchronization Initiated.")

Expand Down
30 changes: 22 additions & 8 deletions app/tests/modules/aws/test_sync_identity_center.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,13 @@ def test_synchronize_sync_users_and_groups_with_defaults(

assert mock_groups.get_groups_from_integration.call_count == 2
assert mock_groups.get_groups_from_integration.call_args_list == [
call("google_groups", query="email:aws-*", post_processing_filters=ANY),
call("aws_identity_center"),
call(
"google_groups",
query="email:aws-*",
pre_processing_filters=[],
post_processing_filters=ANY,
),
call("aws_identity_center", pre_processing_filters=[]),
]

assert mock_identity_store.list_users.call_count == 2
Expand Down Expand Up @@ -284,9 +289,12 @@ def test_synchronize_sync_skip_users_if_false(
assert mock_groups.get_groups_from_integration.call_count == 2

google_groups_call = call(
"google_groups", query="email:aws-*", post_processing_filters=ANY
"google_groups",
query="email:aws-*",
pre_processing_filters=[],
post_processing_filters=ANY,
)
aws_identity_center_call = call("aws_identity_center")
aws_identity_center_call = call("aws_identity_center", pre_processing_filters=[])
assert mock_groups.get_groups_from_integration.call_args_list == [
google_groups_call,
aws_identity_center_call,
Expand Down Expand Up @@ -351,9 +359,12 @@ def test_synchronize_sync_skip_groups_false_if_false(
assert mock_groups.get_groups_from_integration.call_count == 2

google_groups_call = call(
"google_groups", query="email:aws-*", post_processing_filters=ANY
"google_groups",
query="email:aws-*",
pre_processing_filters=[],
post_processing_filters=ANY,
)
aws_identity_center_call = call("aws_identity_center")
aws_identity_center_call = call("aws_identity_center", pre_processing_filters=[])
assert mock_groups.get_groups_from_integration.call_args_list == [
google_groups_call,
aws_identity_center_call,
Expand Down Expand Up @@ -419,9 +430,12 @@ def test_synchronize_sync_skip_users_and_groups_if_false(
assert mock_groups.get_groups_from_integration.call_count == 2

google_groups_call = call(
"google_groups", query="email:aws-*", post_processing_filters=ANY
"google_groups",
query="email:aws-*",
pre_processing_filters=[],
post_processing_filters=ANY,
)
aws_identity_center_call = call("aws_identity_center")
aws_identity_center_call = call("aws_identity_center", pre_processing_filters=[])
assert mock_groups.get_groups_from_integration.call_args_list == [
google_groups_call,
aws_identity_center_call,
Expand Down
Loading