def test_list_user_pagination(self, mock_cache, mock_boto3_client, mock_creds): # Arrange UserFactory.create(username='******') UserFactory.create(username='******') SourceTableFactory(dataset=MasterDataSetFactory.create( user_access_type='REQUIRES_AUTHENTICATION')) mock_user_client = mock.Mock() mock_user_client.list_users.side_effect = [ { "UserList": [{ "Arn": "Arn", "Email": "*****@*****.**", "Role": "AUTHOR", "UserName": "******", }], "NextToken": "foo", }, { "UserList": [{ "Arn": "Arn2", "Email": "*****@*****.**", "Role": "AUTHOR", "UserName": "******", }] }, ] mock_data_client = mock.Mock() mock_sts_client = mock.Mock() mock_boto3_client.side_effect = [ mock_user_client, mock_data_client, mock_sts_client, ] mock_creds.return_value = [mock.Mock()] # Act sync_quicksight_permissions() # Assert assert mock_user_client.update_user.call_args_list == [ mock.call( AwsAccountId=mock.ANY, Namespace='default', Role='AUTHOR', CustomPermissionsName='author-custom-permissions', UserName='******', Email='*****@*****.**', ), mock.call( AwsAccountId=mock.ANY, Namespace='default', Role='AUTHOR', CustomPermissionsName='author-custom-permissions', UserName='******', Email='*****@*****.**', ), ]
def test_missing_user_handled_gracefully(self, mock_cache, mock_boto3_client, mock_creds): # Arrange user = UserFactory.create(username='******') user2 = UserFactory.create(username='******') SourceTableFactory(dataset=MasterDataSetFactory.create( user_access_type='REQUIRES_AUTHENTICATION')) mock_user_client = mock.Mock() mock_user_client.describe_user.side_effect = [ botocore.exceptions.ClientError( { "Error": { "Code": "ResourceNotFoundException", "Message": "User not found", } }, 'DescribeUser', ), { "User": { "Arn": "Arn", "Email": "*****@*****.**", "Role": "AUTHOR" } }, ] mock_data_client = mock.Mock() mock_sts_client = mock.Mock() mock_boto3_client.side_effect = [ mock_user_client, mock_data_client, mock_sts_client, ] # Act sync_quicksight_permissions(user_sso_ids_to_update=[ str(user.profile.sso_id), str(user2.profile.sso_id) ]) # Assert assert mock_user_client.describe_user.call_args_list == [ mock.call( AwsAccountId=mock.ANY, Namespace='default', UserName=f'quicksight_federation/{user.profile.sso_id}', ), mock.call( AwsAccountId=mock.ANY, Namespace='default', UserName=f'quicksight_federation/{user2.profile.sso_id}', ), ] assert len(mock_data_client.create_data_source.call_args_list) == 1 assert len(mock_data_client.update_data_source.call_args_list) == 0
def test_quicksight_link(self, mocker): user = UserFactory.create() vis = VisualisationCatalogueItemFactory.create( user_access_type='REQUIRES_AUTHENTICATION' ) link = VisualisationLinkFactory.create( visualisation_type='QUICKSIGHT', identifier='5d75e131-20f4-48f8-b0eb-f4ebf36434f4', visualisation_catalogue_item=vis, ) quicksight = mocker.patch( 'dataworkspace.apps.applications.views.get_quicksight_dashboard_name_url' ) quicksight.return_value = ( 'my-dashboard', 'https://my.dashboard.quicksight.amazonaws.com', ) client = Client(**get_http_sso_data(user)) response = client.get(link.get_absolute_url()) assert response.status_code == 200 assert ( 'https://my.dashboard.quicksight.amazonaws.com' in response.content.decode(response.charset) ) assert ( 'frame-src https://eu-west-2.quicksight.aws.amazon.com' in response['content-security-policy'] )
def test_user_needs_access_via_catalogue_item(self, mocker): user = UserFactory.create() vis = VisualisationCatalogueItemFactory.create( user_access_type='REQUIRES_AUTHORIZATION') link = VisualisationLinkFactory.create( visualisation_type='QUICKSIGHT', identifier=str(uuid4()), visualisation_catalogue_item=vis, ) quicksight = mocker.patch( 'dataworkspace.apps.applications.views.get_quicksight_dashboard_name_url' ) quicksight.return_value = ( 'my-dashboard', 'https://my.dashboard.quicksight.amazonaws.com', ) client = Client(**get_http_sso_data(user)) response = client.get(link.get_absolute_url()) assert response.status_code == 403 VisualisationUserPermissionFactory.create(visualisation=vis, user=user) response = client.get(link.get_absolute_url()) assert response.status_code == 200
def test_sync_doesnt_create_role_if_user_cant_access_tools( self, mock_hawk_request, create_tools_access_iam_role_task): user = UserFactory.create(email='*****@*****.**') user.profile.sso_id = '00000000-0000-0000-0000-000000000000' user.save() with open( os.path.join( os.path.dirname(__file__), 'test_fixture_activity_stream_sso_john_smith.json', ), 'r', ) as file: user_john_smith = (200, file.read()) with open( os.path.join( os.path.dirname(__file__), 'test_fixture_activity_stream_sso_empty.json', ), 'r', ) as file: empty_result = (200, file.read()) mock_hawk_request.side_effect = [user_john_smith, empty_result] _do_sync_activity_stream_sso_users() User = get_user_model() all_users = User.objects.all() assert len(all_users) == 1 assert not create_tools_access_iam_role_task.delay.called
def test_sync_updates_existing_users_email(self, mock_hawk_request): # set the email to something different to what the activity stream # will return to test that it gets updated user = UserFactory.create(email='*****@*****.**') user.profile.sso_id = '00000000-0000-0000-0000-000000000000' user.save() with open( os.path.join( os.path.dirname(__file__), 'test_fixture_activity_stream_sso_john_smith.json', ), 'r', ) as file: user_john_smith = (200, file.read()) with open( os.path.join( os.path.dirname(__file__), 'test_fixture_activity_stream_sso_empty.json', ), 'r', ) as file: empty_result = (200, file.read()) mock_hawk_request.side_effect = [user_john_smith, empty_result] _do_sync_activity_stream_sso_users() User = get_user_model() all_users = User.objects.all() assert len(all_users) == 1 assert str(all_users[0].email) == '*****@*****.**'
def test_metabase_link(self, mocker): user = UserFactory.create() vis = VisualisationCatalogueItemFactory.create( user_access_type='REQUIRES_AUTHENTICATION' ) link = VisualisationLinkFactory.create( visualisation_type='METABASE', identifier='123456789', visualisation_catalogue_item=vis, ) jwt_encode = mocker.patch('dataworkspace.apps.applications.views.jwt.encode') jwt_encode.return_value = b'my-token' client = Client(**get_http_sso_data(user)) response = client.get(link.get_absolute_url()) assert response.status_code == 200 assert ( '//metabase.dataworkspace.test:8000/embed/dashboard/my-token#bordered=false&titled=false' in response.content.decode(response.charset) ) assert ( 'frame-src metabase.dataworkspace.test' in response['content-security-policy'] )
def test_invalid_link_404s(self): user = UserFactory.create() client = Client(**get_http_sso_data(user)) response = client.get( reverse( 'visualisations:link', kwargs={"link_id": "2af5890a-bbcc-4e7d-8b2d-2a63139b3e4f"}, )) assert response.status_code == 404
def test_appstream_link_only_shown_to_user_with_permission( has_appstream_update, expected_href, expected_text): user = UserFactory.create(is_staff=False, is_superuser=False) if has_appstream_update: perm = Permission.objects.get(codename='access_appstream') user.user_permissions.add(perm) user.save() client = Client(**get_http_sso_data(user)) response = client.get(reverse("applications:tools")) soup = BeautifulSoup(response.content.decode(response.charset)) quicksight_link = soup.find('a', href=True, text=expected_text) assert quicksight_link.get('href') == expected_href
def test_datastudio_link(self): user = UserFactory.create() vis = VisualisationCatalogueItemFactory.create( user_access_type='REQUIRES_AUTHENTICATION') link = VisualisationLinkFactory.create( visualisation_type='DATASTUDIO', identifier='https://www.data.studio', visualisation_catalogue_item=vis, ) client = Client(**get_http_sso_data(user)) response = client.get(link.get_absolute_url()) assert response.status_code == 302 assert response['location'] == 'https://www.data.studio'
def test_unauthorised_visualisation(self, has_access): user = UserFactory.create() vis = VisualisationCatalogueItemFactory.create( visualisation_template__user_access_type='REQUIRES_AUTHORIZATION') if has_access: ApplicationTemplateUserPermissionFactory.create( application_template=vis.visualisation_template, user=user) client = Client(**get_http_sso_data(user)) response = client.get(vis.get_absolute_url()) assert response.status_code == 200 assert vis.name in response.content.decode(response.charset) assert ("You do not have permission to access this data visualisation." in response.content.decode(response.charset)) is not has_access
def test_task_creates_iam_role(self, mock_create_tools_access_iam_role): user = UserFactory.create(username='******') user.profile.sso_id = '00000000-0000-0000-0000-000000000001' user.profile.home_directory_efs_access_point_id = 'some-access-point-id' user.save() _do_create_tools_access_iam_role(user.id) assert mock_create_tools_access_iam_role.call_args_list == [ mock.call( '*****@*****.**', '00000000-0000-0000-0000-000000000001', 'some-access-point-id', ) ]
def test_task_creates_iam_role(self, mock_create_tools_access_iam_role): user = UserFactory.create(username="******") user.profile.sso_id = "00000000-0000-0000-0000-000000000001" user.profile.home_directory_efs_access_point_id = "some-access-point-id" user.save() _do_create_tools_access_iam_role(user.id) assert mock_create_tools_access_iam_role.call_args_list == [ mock.call( "*****@*****.**", "00000000-0000-0000-0000-000000000001", "some-access-point-id", ) ]
def test_user_needs_access_via_catalogue_item(self): user = UserFactory.create() vis = VisualisationCatalogueItemFactory.create( user_access_type='REQUIRES_AUTHORIZATION') link = VisualisationLinkFactory.create( visualisation_type='METABASE', identifier='123', visualisation_catalogue_item=vis, ) client = Client(**get_http_sso_data(user)) response = client.get(link.get_absolute_url()) assert response.status_code == 403 VisualisationUserPermissionFactory.create(visualisation=vis, user=user) response = client.get(link.get_absolute_url()) assert response.status_code == 200
def test_sync_creates_role_if_user_can_access_tools( self, mock_hawk_request, create_tools_access_iam_role_task ): can_access_tools_permission = Permission.objects.get( codename="start_all_applications", content_type=ContentType.objects.get_for_model(ApplicationInstance), ) user = UserFactory.create(email="*****@*****.**") user.profile.sso_id = "00000000-0000-0000-0000-000000000000" user.save() user.user_permissions.add(can_access_tools_permission) with open( os.path.join( os.path.dirname(__file__), "test_fixture_activity_stream_sso_john_smith.json", ), "r", ) as file: user_john_smith = (200, file.read()) with open( os.path.join( os.path.dirname(__file__), "test_fixture_activity_stream_sso_empty.json", ), "r", ) as file: empty_result = (200, file.read()) mock_hawk_request.side_effect = [user_john_smith, empty_result] _do_sync_activity_stream_sso_users() User = get_user_model() all_users = User.objects.all() assert len(all_users) == 1 assert create_tools_access_iam_role_task.delay.call_args_list == [ mock.call( user.id, ) ]
def test_sync_doesnt_create_role_if_user_already_has_role( self, mock_hawk_request, create_tools_access_iam_role_task): can_access_tools_permission = Permission.objects.get( codename='start_all_applications', content_type=ContentType.objects.get_for_model( ApplicationInstance), ) user = UserFactory.create(email='*****@*****.**') user.user_permissions.add(can_access_tools_permission) user.profile.sso_id = '00000000-0000-0000-0000-000000000000' user.profile.tools_access_role_arn = 'some-arn' user.save() with open( os.path.join( os.path.dirname(__file__), 'test_fixture_activity_stream_sso_john_smith.json', ), 'r', ) as file: user_john_smith = (200, file.read()) with open( os.path.join( os.path.dirname(__file__), 'test_fixture_activity_stream_sso_empty.json', ), 'r', ) as file: empty_result = (200, file.read()) mock_hawk_request.side_effect = [user_john_smith, empty_result] _do_sync_activity_stream_sso_users() User = get_user_model() all_users = User.objects.all() assert len(all_users) == 1 assert not create_tools_access_iam_role_task.delay.called
def test_sync_updates_existing_users_sso_id_and_email( self, mock_hawk_request): # set the sso id to something different to what the activity stream # will return and set the email to the third email in the list that # the activity stream will return to test that it is able to look up # the user and update both their email and sso id user = UserFactory.create(email='*****@*****.**') user.profile.sso_id = '00000000-0000-0000-0000-111111111111' user.save() with open( os.path.join( os.path.dirname(__file__), 'test_fixture_activity_stream_sso_john_smith_multiple_emails.json', ), 'r', ) as file: user_john_smith = (200, file.read()) with open( os.path.join( os.path.dirname(__file__), 'test_fixture_activity_stream_sso_empty.json', ), 'r', ) as file: empty_result = (200, file.read()) mock_hawk_request.side_effect = [user_john_smith, empty_result] _do_sync_activity_stream_sso_users() User = get_user_model() all_users = User.objects.all() assert len(all_users) == 1 assert (str(all_users[0].profile.sso_id) == '00000000-0000-0000-0000-000000000000') assert str(all_users[0].email) == '*****@*****.**'
def test_create_new_data_source(self, mock_cache, mock_boto3_client, mock_creds): # Arrange UserFactory.create(username='******') SourceTableFactory(dataset=MasterDataSetFactory.create( user_access_type='REQUIRES_AUTHENTICATION')) mock_user_client = mock.Mock() mock_user_client.list_users.return_value = { "UserList": [{ "Arn": "Arn", "Email": "*****@*****.**", "Role": "AUTHOR", "UserName": "******", }] } mock_data_client = mock.Mock() mock_sts_client = mock.Mock() mock_boto3_client.side_effect = [ mock_user_client, mock_data_client, mock_sts_client, ] mock_creds.return_value = [mock.Mock()] # Act sync_quicksight_permissions() # Assert assert mock_user_client.update_user.call_args_list == [ mock.call( AwsAccountId=mock.ANY, Namespace='default', Role='AUTHOR', CustomPermissionsName='author-custom-permissions', UserName='******', Email='*****@*****.**', ) ] assert mock_data_client.create_data_source.call_args_list == [ mock.call( AwsAccountId=mock.ANY, DataSourceId=mock.ANY, Name=mock.ANY, DataSourceParameters={ 'AuroraPostgreSqlParameters': { 'Host': mock.ANY, 'Port': mock.ANY, 'Database': mock.ANY, } }, Credentials={ 'CredentialPair': { 'Username': mock.ANY, 'Password': mock.ANY } }, VpcConnectionProperties={'VpcConnectionArn': mock.ANY}, Type='AURORA_POSTGRESQL', Permissions=[{ 'Principal': 'Arn', 'Actions': [ 'quicksight:DescribeDataSource', 'quicksight:DescribeDataSourcePermissions', 'quicksight:PassDataSource', ], }], ) ] assert mock_data_client.update_data_source.call_args_list == [] assert sorted( mock_data_client.delete_data_source.call_args_list, key=lambda x: x.kwargs['DataSourceId'], ) == [ mock.call( AwsAccountId=mock.ANY, DataSourceId='data-workspace-dev-my_database-88f3887d', ), mock.call( AwsAccountId=mock.ANY, DataSourceId='data-workspace-dev-test_external_db2-88f3887d', ), ]
def test_poll_until_user_created(self, mock_cache, mock_boto3_client, mock_creds): # Arrange user = UserFactory.create(username='******') SourceTableFactory(dataset=MasterDataSetFactory.create( user_access_type='REQUIRES_AUTHENTICATION')) mock_user_client = mock.Mock() mock_user_client.describe_user.side_effect = [ botocore.exceptions.ClientError( { "Error": { "Code": "ResourceNotFoundException", "Message": "User not found", } }, 'DescribeUser', ), ] * 10 + [{ "User": { "Arn": "Arn", "Email": "*****@*****.**", "Role": "AUTHOR", "UserName": "******", } }] mock_data_client = mock.Mock() mock_sts_client = mock.Mock() mock_boto3_client.side_effect = [ mock_user_client, mock_data_client, mock_sts_client, ] # Act with mock.patch('dataworkspace.apps.applications.utils.gevent.sleep'): sync_quicksight_permissions( user_sso_ids_to_update=[str(user.profile.sso_id)], poll_for_user_creation=True, ) # Assert assert mock_user_client.update_user.call_args_list == [ mock.call( AwsAccountId=mock.ANY, Namespace='default', Role='AUTHOR', CustomPermissionsName='author-custom-permissions', UserName='******', Email='*****@*****.**', ) ] assert (mock_user_client.describe_user.call_args_list == [ mock.call( AwsAccountId=mock.ANY, Namespace='default', UserName=f'quicksight_federation/{user.profile.sso_id}', ), ] * 11) assert len(mock_data_client.create_data_source.call_args_list) == 1 assert len(mock_data_client.update_data_source.call_args_list) == 0
def test_create_new_data_source(self, mock_cache, mock_boto3_client, mock_creds): # Arrange UserFactory.create(username="******") SourceTableFactory( dataset=MasterDataSetFactory.create( user_access_type=UserAccessType.REQUIRES_AUTHENTICATION ) ) mock_user_client = mock.Mock() mock_user_client.list_users.return_value = { "UserList": [ { "Arn": "Arn", "Email": "*****@*****.**", "Role": "AUTHOR", "UserName": "******", } ] } mock_data_client = mock.Mock() mock_sts_client = mock.Mock() mock_boto3_client.side_effect = [ mock_user_client, mock_data_client, mock_sts_client, ] mock_creds.return_value = [mock.Mock()] # Act sync_quicksight_permissions() # Assert assert mock_user_client.update_user.call_args_list == [ mock.call( AwsAccountId=mock.ANY, Namespace="default", Role="AUTHOR", CustomPermissionsName="author-custom-permissions", UserName="******", Email="*****@*****.**", ) ] assert mock_data_client.create_data_source.call_args_list == [ mock.call( AwsAccountId=mock.ANY, DataSourceId=mock.ANY, Name=mock.ANY, DataSourceParameters={ "AuroraPostgreSqlParameters": { "Host": mock.ANY, "Port": mock.ANY, "Database": mock.ANY, } }, Credentials={"CredentialPair": {"Username": mock.ANY, "Password": mock.ANY}}, VpcConnectionProperties={"VpcConnectionArn": mock.ANY}, Type="AURORA_POSTGRESQL", Permissions=[ { "Principal": "Arn", "Actions": [ "quicksight:DescribeDataSource", "quicksight:DescribeDataSourcePermissions", "quicksight:PassDataSource", ], } ], ) ] assert mock_data_client.update_data_source.call_args_list == [] assert sorted( mock_data_client.delete_data_source.call_args_list, key=lambda x: x.kwargs["DataSourceId"], ) == [ mock.call( AwsAccountId=mock.ANY, DataSourceId="data-workspace-dev-my_database-88f3887d", ), mock.call( AwsAccountId=mock.ANY, DataSourceId="data-workspace-dev-test_external_db2-88f3887d", ), ]