"""Test users script file.""" import os from datetime import datetime from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from unittest.mock import Mock, call, mock_open, patch from aws_user_audit import users_script import boto3 from freezegun import freeze_time import pytest LINK_TO_POST = os.getenv('LINK_TO_POST') @pytest.fixture def users_with_policies(): """Fixture for the users with policies list.""" return [{'Path': '/', 'UserName': 'user1', 'UserId': 'ID1', 'Arn': 'arn:aws:iam::437795906767:user/user1', 'CreateDate': '2022-01-01', 'PasswordLastUsed': datetime(2022, 1, 1), 'Directly Attached Policies': '=HYPERLINK("https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users/user1"; "6")', # noqa: E501 'Inline Policies': 3, 'Console Access': 'Y', 'Access Key First': datetime(2022, 1, 1), 'Access Key Second': 'Never used'}, {'Path': '/', 'UserName': 'user2', 'UserId': 'ID2', 'Arn': 'arn:aws:iam::437795906767:user/user2', 'CreateDate': '2022-01-01', 'PasswordLastUsed': datetime(2022, 3, 4), 'Directly Attached Policies': '=HYPERLINK("https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users/user2"; "6")', # noqa: E501 'Inline Policies': 3, 'Console Access': 'N', 'Access Key First': datetime(2022, 1, 1), 'Access Key Second': 'Never used' }, {'Path': '/', 'UserName': 'user3', 'UserId': 'ID3', 'Arn': 'arn:aws:iam::437795906767:user/user3', 'CreateDate': '2022-01-01', 'Role': 'service-user', 'PasswordLastUsed': 'Never used', 'Console Access': 'N', 'Access Key First': 'Inactive', 'Access Key Second': 'No Access Key', 'Directly Attached Policies': '=HYPERLINK("https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users/user3"; "6")', # noqa: E501 'Inline Policies': 3}] @pytest.fixture def iam_users(): """Fixture for the not updated users list.""" return([{'Path': '/', 'UserName': 'user1', 'UserId': 'ID1', 'Arn': 'arn:aws:iam::437795906767:user/user1', 'CreateDate': '2022-01-01', 'PasswordLastUsed': datetime(2022, 1, 1), 'Console Access': 'Y', 'Access Key First': datetime(2022, 1, 1), 'Access Key Second': 'Never used' }, {'Path': '/', 'UserName': 'user2', 'UserId': 'ID2', 'Arn': 'arn:aws:iam::437795906767:user/user2', 'CreateDate': '2022-01-01', 'PasswordLastUsed': datetime(2022, 3, 4), 'Console Access': 'N', 'Access Key First': datetime(2022, 1, 1), 'Access Key Second': 'Never used' }, {'Path': '/', 'UserName': 'user3', 'UserId': 'ID3', 'Arn': 'arn:aws:iam::437795906767:user/user3', 'CreateDate': '2022-01-01', 'Role': 'service-user', 'PasswordLastUsed': 'Never used', 'Console Access': 'N', 'Access Key First': 'Inactive', 'Access Key Second': 'No Access Key' }]) @pytest.fixture def iam_user(): """Fixture for the not updated user.""" return([{'Path': '/', 'UserName': 'user1', 'UserId': 'ID1', 'Arn': 'arn:aws:iam::437795906767:user/user1', 'CreateDate': '2022-01-01'}]) @pytest.fixture def updated_iam_users(): """Fixture for the updated users list.""" return([{'UserName': 'user1', 'CreateDate': '2022-01-01', 'PasswordLastUsed': datetime(2022, 1, 1), 'Console Access': 'Y', 'Access Key First': datetime(2022, 1, 1), 'Access Key Second': 'Never used' }, {'UserName': 'user2', 'CreateDate': '2022-01-01', 'PasswordLastUsed': datetime(2022, 3, 4), 'Console Access': 'N', 'Access Key First': datetime(2022, 1, 1), 'Access Key Second': 'Never used' }, {'UserName': 'user3', 'CreateDate': '2022-01-01', 'Role': 'service-user', 'PasswordLastUsed': 'Never used', 'Console Access': 'N', 'Access Key First': 'Inactive', 'Access Key Second': 'No Access Key' }]) def test_delete_unnecessary_user_info(iam_users, updated_iam_users): """Test delete_unnecessary_user_info function to correctly delete info.""" users_script.delete_unnecessary_user_info(iam_users) assert iam_users == updated_iam_users @patch('botocore.client.BaseClient._make_api_call') def test_get_iam_user_list(mock_client): """Test get_iam_user_list function to make right api call.""" users_script.get_iam_user_list() mock_client.assert_called_once_with('List' 'Users', {'MaxItems': 700}) @patch('botocore.client.BaseClient._make_api_call', return_value={ 'Groups': [{'GroupName': 'A'}, {'GroupName': 'B'}, {'GroupName': 'C'}], 'Else': [] }) def test_add_groups_to_iam_user(mock_client, iam_users): """Test add_groups_to_iam_user function. to make right api call && return right data. """ users_script.add_groups_to_iam_user(iam_users) mock_client.assert_any_call('ListGroupsForUser', {'UserName': 'user3'}) mock_client.assert_any_call('ListGroupsForUser', {'UserName': 'user2'}) mock_client.assert_any_call('ListGroupsForUser', {'UserName': 'user1'}) @patch('botocore.client.BaseClient._make_api_call', return_value={ 'PolicyNames': ['policy1', 'policy2', 'policy3'], 'AttachedPolicies': [{'PolicyName': 'attached_policy1'}, {'PolicyName': 'attached_policy2'}, {'PolicyName': 'attached_policy3'}], }) def test_add_inline_and_direct_policies_to_users(mock_client, iam_users, users_with_policies): """Test add_inline_and_direct_policies_to_users function. to make right api call && return right data. """ users_script.add_inline_and_direct_policies_to_users(iam_users) mock_client.assert_any_call('ListUserPolicies', {'UserName': 'user3'}) mock_client.assert_any_call('ListUserPolicies', {'UserName': 'user2'}) mock_client.assert_any_call('ListUserPolicies', {'UserName': 'user1'}) mock_client.assert_any_call('ListAttachedUserPolicies', {'UserName': 'user3'}) mock_client.assert_any_call('ListAttachedUserPolicies', {'UserName': 'user2'}) mock_client.assert_any_call('ListAttachedUserPolicies', {'UserName': 'user1'}) assert iam_users == users_with_policies @patch('botocore.client.BaseClient._make_api_call', return_value={ 'AccessKeyMetadata': [{'AccessKeyId': 'id12345'}], 'AccessKeyLastUsed': '02-12-2021', }) def test_add_access_keys(mock_client, iam_user): """Test to add access keys to users.""" users_script.add_access_keys(iam_user) mock_client.assert_has_calls([ call('ListAccessKeys', {'UserName': 'user1'}), call('GetAccessKeyLastUsed', {'AccessKeyId': 'id12345'})]) @patch('botocore.client.BaseClient._make_api_call', return_value={ 'Tags': [{'Key': 'name', 'Value': 'user'}, {'Key': 'email', 'Value': 'user@theorchard.com'}, {'Key': 'role', 'Value': 'tech'}, {'Key': 'manager', 'Value': 'manager'}, {'Key': 'company', 'Value': 'orchard'}], }) def test_add_user_tags(mock_client, iam_user): """Test to add metadata to users.""" users_script.add_user_tags(iam_user) mock_client.assert_called_once_with('ListUserTags', {'UserName': 'user1'}) @patch('botocore.client.BaseClient._make_api_call') def test_add_console_access(mock_botocore): """Test to check users console access.""" users_console_access = [{'UserName': 'user1'}] users_script.add_console_access(users_console_access) user = users_console_access.pop() assert user['Console Access'] == 'Y' # test without console access def get_login_profile_side_effect(*args, **kwargs): client = boto3.client('iam') raise client.exceptions.NoSuchEntityException({'test': 'test1'}, 'test') mock_botocore.side_effect = get_login_profile_side_effect users_console_access = [{'UserName': 'user1'}] users_script.add_console_access(users_console_access) mock_botocore.assert_called_with('GetLoginProfile', {'UserName': 'user1'}) user = users_console_access.pop() assert user['Console Access'] == 'N' def test_update_password_last_used(iam_user): """Test to check password last used column updating.""" users_script.update_password_last_used(iam_user) assert iam_user == [{'Path': '/', 'UserName': 'user1', 'UserId': 'ID1', 'Arn': 'arn:aws:iam::437795906767:user/user1', 'CreateDate': '2022-01-01', 'PasswordLastUsed': 'Never used'}] @patch('aws_user_audit.users_script.json.dumps') @patch('aws_user_audit.users_script.requests.post') def test_post_to_slack(requests_mock, json_mock): """Check posting notification to slack.""" post_mock = Mock() json_dumps_mock = Mock() json_mock.return_value = json_dumps_mock requests_mock.post.return_value = post_mock users_script.post_to_slack() requests_mock.assert_called_with(LINK_TO_POST, data=json_dumps_mock, headers={'Content-Type': 'application/json'}) @patch('aws_user_audit.users_script.recipient', 'recipient') @patch('aws_user_audit.users_script.source', 'source') @patch('aws_user_audit.users_script.file_name', 'file_name_placeholder') @patch('builtins.open', new_callable=mock_open, read_data='data') @patch('aws_user_audit.users_script.MIMEText', spec=MIMEText) @patch('aws_user_audit.users_script.MIMEApplication', spec=MIMEApplication) @patch('aws_user_audit.users_script.MIMEMultipart', spec=MIMEMultipart) @patch('aws_user_audit.users_script.boto3.client') def test_send_email( mock_client, mock_mime_multi_part, mock_mime_app, mock_mime_text, mock_file): """Check sending email with attachment.""" users_script.send_email() mock_mime_app.assert_called_once_with('data') mock_mime_app().add_header.assert_called_once_with( 'Content-Disposition', 'attachment', filename='file_name_placeholder') mock_msg = mock_mime_multi_part() mock_msg.__setitem__.assert_called_with('Subject', 'AWS User Audit') mock_msg.attach.assert_any_call(mock_mime_app()) mock_msg.attach.assert_any_call(mock_mime_text()) mock_client().send_raw_email.assert_called_with( Destinations=['recipient'], RawMessage={'Data': mock_msg.as_string()}, Source='source' ) def test_check_if_deactivated(users_with_policies): """Check check_if_deactivated function to work correctly.""" users_script.check_if_deactivated(users_with_policies) assert users_with_policies[0]['Deactivated?'] == 'N' assert users_with_policies[1]['Deactivated?'] == 'N' assert users_with_policies[2]['Deactivated?'] == 'Y' @freeze_time('2022-07-18') def test_get_last_user_activity(iam_users): """Check get_last_user_activity function to work correctly.""" users_script.get_last_user_activity(iam_users) assert iam_users[0]['Last Activity'] == '198' assert iam_users[1]['Last Activity'] == '136' assert iam_users[2]['Last Activity'] == 'Never' def test_filter_service_users(iam_users): """Check filter_service_users function to filter service users.""" filtered_users = users_script.filter_service_users(iam_users) assert filtered_users == iam_users[:-1] def test_filter_out_deactivated_users(users_with_policies): """Check filter_out_deactivated_users function to filter deactivated users.""" # noqa: E501 users_script.check_if_deactivated(users_with_policies) filtered_users = users_script.filter_out_deactivated_users(users_with_policies) # noqa: E501 assert filtered_users == users_with_policies[:-1]