def test_dataset_shows_code_snippets_to_tool_user(): ds = factories.DataSetFactory.create(type=DataSetType.MASTER.value, published=True) user = User.objects.create(is_superuser=False) factories.DataSetUserPermissionFactory.create(user=user, dataset=ds) factories.SourceTableFactory.create(dataset=ds, schema="public", table="MY_LOVELY_TABLE") client = Client(**get_http_sso_data(user)) response = client.get(ds.get_absolute_url()) assert response.status_code == 200 assert ( """SELECT * FROM "public"."MY_LOVELY_TABLE" LIMIT 50""" not in response.content.decode(response.charset)) user.is_superuser = True user.save() client = Client(**get_http_sso_data(user)) response = client.get(ds.get_absolute_url()) assert response.status_code == 200 assert ( """SELECT * FROM "public"."MY_LOVELY_TABLE" LIMIT 50""" in response.content.decode(response.charset))
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_not_developer(self, mock_get_gitlab_project): mock_get_gitlab_project.return_value = { "id": 1, "default_branch": "master", "name": "test-gitlab-project", } develop_visualisations_permission = Permission.objects.get( codename="develop_visualisations", content_type=ContentType.objects.get_for_model(ApplicationInstance), ) user = factories.UserFactory.create( username="******", is_staff=False, is_superuser=False, ) user.user_permissions.add(develop_visualisations_permission) client = Client(**get_http_sso_data(user)) client.post(reverse("admin:index"), follow=True) with mock.patch( "dataworkspace.apps.applications.views.gitlab_has_developer_access" ) as access_mock: access_mock.return_value = False response = client.get(reverse("visualisations:logs", args=(1, "xxx"))) assert response.status_code == 403
def test_approve_visualisation_successfully(self): develop_visualisations_permission = Permission.objects.get( codename='develop_visualisations', content_type=ContentType.objects.get_for_model(ApplicationInstance), ) user = factories.UserFactory.create( username='******', is_staff=False, is_superuser=False, ) user.user_permissions.add(develop_visualisations_permission) visualisation = factories.VisualisationCatalogueItemFactory.create( published=False, visualisation_template__gitlab_project_id=1 ) # Login to admin site client = Client(**get_http_sso_data(user)) client.post(reverse('admin:index'), follow=True) with _visualisation_ui_gitlab_mocks(): response = client.post( reverse( 'visualisations:approvals', args=(visualisation.visualisation_template.gitlab_project_id,), ), { "approved": "on", "approver": user.id, "visualisation": str(visualisation.visualisation_template.id), }, follow=True, ) assert response.status_code == 200 assert len(VisualisationApproval.objects.all()) == 1
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_commit_does_not_exist(self, mocker): application_template = factories.ApplicationTemplateFactory() factories.ApplicationInstanceFactory( application_template=application_template, commit_id='', spawner_application_template_options=json.dumps( {'CONTAINER_NAME': 'user-defined-container'}), spawner_application_instance_id=json.dumps( {'task_arn': 'arn:test:vis/task-id/999'}), ) mock_get_application_template = mocker.patch( 'dataworkspace.apps.applications.views._application_template') mock_get_application_template.return_value = application_template develop_visualisations_permission = Permission.objects.get( codename='develop_visualisations', content_type=ContentType.objects.get_for_model( ApplicationInstance), ) user = factories.UserFactory.create( username='******', is_staff=False, is_superuser=False, ) user.user_permissions.add(develop_visualisations_permission) client = Client(**get_http_sso_data(user)) client.post(reverse('admin:index'), follow=True) with _visualisation_ui_gitlab_mocks(): response = client.get( reverse('visualisations:logs', args=(1, 'xxx'))) assert response.status_code == 200 assert response.content == b'No logs were found for this visualisation.'
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_other_users_query_results(user): client = Client(raise_request_exception=False, **get_http_sso_data(user)) ql = QueryLogFactory.create(run_by_user=factories.UserFactory.create()) response = client.get(reverse("explorer:running_query", args=(ql.id, ))) assert response.status_code == 403 assert "You can collaborate on Data Explorer queries" in response.content.decode( response.charset)
def test_no_events(self, mocker): application_template = factories.ApplicationTemplateFactory() factories.ApplicationInstanceFactory( application_template=application_template, commit_id="xxx", spawner_application_template_options=json.dumps( {"CONTAINER_NAME": "user-defined-container"} ), spawner_application_instance_id=json.dumps({"task_arn": "arn:test:vis/task-id/999"}), ) mock_get_application_template = mocker.patch( "dataworkspace.apps.applications.views._application_template" ) mock_get_application_template.return_value = application_template mock_boto = mocker.patch("dataworkspace.apps.core.boto3_client.boto3.client") mock_boto.return_value.get_log_events.side_effect = botocore.exceptions.ClientError( error_response={"Error": {"Code": "ResourceNotFoundException"}}, operation_name="get_log_events", ) develop_visualisations_permission = Permission.objects.get( codename="develop_visualisations", content_type=ContentType.objects.get_for_model(ApplicationInstance), ) user = factories.UserFactory.create( username="******", is_staff=False, is_superuser=False, ) user.user_permissions.add(develop_visualisations_permission) client = Client(**get_http_sso_data(user)) client.post(reverse("admin:index"), follow=True) with _visualisation_ui_gitlab_mocks(): response = client.get(reverse("visualisations:logs", args=(1, "xxx"))) assert response.status_code == 200 assert response.content == b"No logs were found for this visualisation."
def test_find_datasets_includes_unpublished_results_based_on_permissions( permissions, result_dataset_names): user = User.objects.create(is_staff=True) perms = Permission.objects.filter(codename__in=permissions).all() user.user_permissions.add(*perms) user.save() client = Client(**get_http_sso_data(user)) factories.DataSetFactory.create(published=False, type=DataSetType.MASTER.value, name='Master dataset') factories.DataSetFactory.create(published=False, type=DataSetType.DATACUT.value, name='Datacut dataset') factories.ReferenceDatasetFactory.create(published=False, name='Reference dataset') factories.VisualisationCatalogueItemFactory.create(published=False, name='Visualisation') response = client.get(reverse('datasets:find_datasets')) assert response.status_code == 200 assert {dataset['name'] for dataset in response.context["datasets"] } == result_dataset_names
def test_user_with_size_config_shows_correct_config(self): group_name = "Visualisation Tools" template = factories.ApplicationTemplateFactory() template.group_name = group_name template.save() user = get_user_model().objects.create() UserToolConfiguration.objects.create( user=user, tool_template=template, size=UserToolConfiguration.SIZE_EXTRA_LARGE, ) client = Client(**get_http_sso_data(user)) response = client.get(reverse("applications:tools"), follow=True) assert len(response.context["tools"][group_name]["tools"]) == 3 tool = None for item in response.context["tools"][group_name]["tools"]: if item.name == template.nice_name: tool = item break assert tool is not None assert tool.tool_configuration.size_config.name == "Extra Large" assert tool.tool_configuration.size_config.cpu == 4096 assert tool.tool_configuration.size_config.memory == 30720
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_view_redirects_to_quicksight_sso_url(self, mock_boto_client): user = get_user_model().objects.create(is_staff=True, is_superuser=True) # Login to admin site client = Client(**get_http_sso_data(user)) client.post(reverse("admin:index"), follow=True) with mock.patch("dataworkspace.apps.applications.views.sync_quicksight_permissions"): resp = client.get(reverse("applications:quicksight_redirect"), follow=False) assert resp["Location"] == "https://sso.quicksight"
def test_with_events(self, mocker): application_template = factories.ApplicationTemplateFactory() factories.ApplicationInstanceFactory( application_template=application_template, commit_id='xxx', spawner_application_template_options=json.dumps( {'CONTAINER_NAME': 'user-defined-container'}), spawner_application_instance_id=json.dumps( {'task_arn': 'arn:test:vis/task-id/999'}), ) mock_get_application_template = mocker.patch( 'dataworkspace.apps.applications.views._application_template') mock_get_application_template.return_value = application_template mock_boto = mocker.patch( 'dataworkspace.apps.applications.utils.boto3.client') mock_boto.return_value.get_log_events.side_effect = [ { 'nextForwardToken': '12345', 'events': [{ 'timestamp': 1605891793796, 'message': 'log message 1' }], }, { 'events': [{ 'timestamp': 1605891793797, 'message': 'log message 2' }] }, ] develop_visualisations_permission = Permission.objects.get( codename='develop_visualisations', content_type=ContentType.objects.get_for_model( ApplicationInstance), ) user = factories.UserFactory.create( username='******', is_staff=False, is_superuser=False, ) user.user_permissions.add(develop_visualisations_permission) client = Client(**get_http_sso_data(user)) client.post(reverse('admin:index'), follow=True) with _visualisation_ui_gitlab_mocks(): response = client.get( reverse('visualisations:logs', args=(1, 'xxx'))) assert response.status_code == 200 assert response.content == ( b'2020-11-20 17:03:13.796000 - log message 1\n' b'2020-11-20 17:03:13.797000 - log message 2\n')
def test_dataset_preview_permission_denied(url_name, source_factory): user = factories.UserFactory.create(is_superuser=False) client = Client(raise_request_exception=False, **get_http_sso_data(user)) source = source_factory.create( dataset=factories.DataSetFactory.create( user_access_type=UserAccessType.REQUIRES_AUTHORIZATION, published=True), data_grid_enabled=True, ) response = client.get( reverse(url_name, args=(source.dataset.id, source.id))) assert response.status_code == 403 assert "You do not have permission to access this dataset" in response.content.decode( response.charset)
def test_user_with_no_size_config_shows_default_config(self): factories.ApplicationTemplateFactory() user = get_user_model().objects.create() client = Client(**get_http_sso_data(user)) response = client.get(reverse('applications:tools'), follow=True) assert len(response.context['applications']) == 1 assert (response.context['applications'][0] ['tool_configuration'].size_config.name == 'Medium') assert (response.context['applications'][0] ['tool_configuration'].size_config.cpu == 1024) assert (response.context['applications'][0] ['tool_configuration'].size_config.memory == 8192)
def test_dataset_shows_external_link_warning(source_urls, show_warning): ds = factories.DataSetFactory.create(published=True) user = User.objects.create() factories.DataSetUserPermissionFactory.create(user=user, dataset=ds) for source_url in source_urls: factories.SourceLinkFactory.create(dataset=ds, url=source_url) client = Client(**get_http_sso_data(user)) response = client.get(ds.get_absolute_url()) assert response.status_code == 200 assert ("This data set is hosted by an external source." in response.content.decode(response.charset)) is show_warning
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_dataset_unpublished(url_name, dataset_factories): user = factories.UserFactory.create(is_superuser=False) client = Client(raise_request_exception=False, **get_http_sso_data(user)) for factory in dataset_factories: ds = factory.create(published=True) dataset_id = getattr(ds, "uuid", ds.id) response = client.get(reverse(url_name, args=(dataset_id, ))) assert response.status_code in [200, 302] ds.published = False ds.save() response = client.get(reverse(url_name, args=(dataset_id, ))) assert response.status_code == 403 assert (f"This {ds.get_type_display().lower()} has not been published" in response.content.decode(response.charset))
def test_get_shows_all_size_choices(self): tool = factories.ApplicationTemplateFactory() user = get_user_model().objects.create() client = Client(**get_http_sso_data(user)) response = client.get( reverse( 'applications:configure_tool_size', kwargs={'tool_host_basename': tool.host_basename}, ), ) assert response.status_code == 200 assert b'Small' in response.content assert b'Medium (default)' in response.content assert b'Large' in response.content assert b'Extra Large' in response.content
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_dataset_preview_disabled(url_name, source_factory): user = factories.UserFactory.create(is_superuser=False) client = Client(raise_request_exception=False, **get_http_sso_data(user)) source = source_factory.create( dataset=factories.DataSetFactory.create( user_access_type=UserAccessType.REQUIRES_AUTHORIZATION, published=True), data_grid_enabled=False, ) response = client.get( reverse(url_name, args=(source.dataset.id, source.id))) assert response.status_code == 403 assert ( f"Data preview is not enabled for this {source.dataset.get_type_display().lower()}." in response.content.decode(response.charset))
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_view_starts_celery_polling_job(self, mock_boto_client): user = get_user_model().objects.create(is_staff=True, is_superuser=True) # Login to admin site client = Client(**get_http_sso_data(user)) client.post(reverse("admin:index"), follow=True) with mock.patch( "dataworkspace.apps.applications.views.sync_quicksight_permissions" ) as sync_mock: client.get(reverse("applications:quicksight_redirect"), follow=False) assert sync_mock.delay.call_args_list == [ mock.call( user_sso_ids_to_update=(user.profile.sso_id,), ) ]
def test_find_datasets_filters_by_access_and_use_only_returns_the_dataset_once( ): """Meant to prevent a regression where the combination of these two filters would return datasets multiple times based on the number of users with permissions to see that dataset, but the dataset didn't actually require any permission to use.""" user = factories.UserFactory.create(is_superuser=False) user2 = factories.UserFactory.create(is_superuser=False) client = Client(**get_http_sso_data(user)) access_granted_master = factories.DataSetFactory.create( published=True, type=DataSetType.MASTER.value, name='Master - access redundantly granted', user_access_type='REQUIRES_AUTHENTICATION', ) factories.DataSetUserPermissionFactory.create( user=user, dataset=access_granted_master) factories.DataSetUserPermissionFactory.create( user=user2, dataset=access_granted_master) response = client.get( reverse('datasets:find_datasets'), { "access": "yes", "use": str(DataSetType.MASTER.value) }, ) assert response.status_code == 200 assert list(response.context["datasets"]) == [{ 'id': access_granted_master.id, 'name': access_granted_master.name, 'slug': access_granted_master.slug, 'search_rank': mock.ANY, 'short_description': access_granted_master.short_description, 'source_tag_ids': mock.ANY, 'purpose': access_granted_master.type, }]
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_user_with_size_config_shows_correct_config(self): tool = factories.ApplicationTemplateFactory() user = get_user_model().objects.create() UserToolConfiguration.objects.create( user=user, tool_template=tool, size=UserToolConfiguration.SIZE_EXTRA_LARGE) client = Client(**get_http_sso_data(user)) response = client.get(reverse('applications:tools'), follow=True) assert len(response.context['applications']) == 1 assert (response.context['applications'][0] ['tool_configuration'].size_config.name == 'Extra Large') assert (response.context['applications'][0] ['tool_configuration'].size_config.cpu == 4096) assert (response.context['applications'][0] ['tool_configuration'].size_config.memory == 30720)
def test_with_events(self, mocker): application_template = factories.ApplicationTemplateFactory() factories.ApplicationInstanceFactory( application_template=application_template, commit_id="xxx", spawner_application_template_options=json.dumps( {"CONTAINER_NAME": "user-defined-container"} ), spawner_application_instance_id=json.dumps({"task_arn": "arn:test:vis/task-id/999"}), ) mock_get_application_template = mocker.patch( "dataworkspace.apps.applications.views._application_template" ) mock_get_application_template.return_value = application_template mock_boto = mocker.patch("dataworkspace.apps.core.boto3_client.boto3.client") mock_boto.return_value.get_log_events.side_effect = [ { "nextForwardToken": "12345", "events": [{"timestamp": 1605891793796, "message": "log message 1"}], }, {"events": [{"timestamp": 1605891793797, "message": "log message 2"}]}, ] develop_visualisations_permission = Permission.objects.get( codename="develop_visualisations", content_type=ContentType.objects.get_for_model(ApplicationInstance), ) user = factories.UserFactory.create( username="******", is_staff=False, is_superuser=False, ) user.user_permissions.add(develop_visualisations_permission) client = Client(**get_http_sso_data(user)) client.post(reverse("admin:index"), follow=True) with _visualisation_ui_gitlab_mocks(): response = client.get(reverse("visualisations:logs", args=(1, "xxx"))) assert response.status_code == 200 assert response.content == ( b"2020-11-20 17:03:13.796000 - log message 1\n" b"2020-11-20 17:03:13.797000 - log message 2\n" )
def test_post_creates_new_tool_configuration(self): tool = factories.ApplicationTemplateFactory(nice_name='RStudio') user = get_user_model().objects.create() assert not tool.user_tool_configuration.filter(user=user).first() client = Client(**get_http_sso_data(user)) response = client.post( reverse( 'applications:configure_tool_size', kwargs={'tool_host_basename': tool.host_basename}, ), {'size': UserToolConfiguration.SIZE_EXTRA_LARGE}, follow=True, ) assert response.status_code == 200 assert str(list( response.context['messages'])[0]) == 'Saved RStudio size' assert (tool.user_tool_configuration.filter( user=user).first().size == UserToolConfiguration.SIZE_EXTRA_LARGE)
def test_bad_post_data_approved_box_not_checked(self): develop_visualisations_permission = Permission.objects.get( codename='develop_visualisations', content_type=ContentType.objects.get_for_model( ApplicationInstance), ) user = factories.UserFactory.create( username='******', is_staff=False, is_superuser=False, ) user.user_permissions.add(develop_visualisations_permission) visualisation = factories.VisualisationCatalogueItemFactory.create( published=False, visualisation_template__gitlab_project_id=1) # Login to admin site client = Client(**get_http_sso_data(user)) client.post(reverse('admin:index'), follow=True) with _visualisation_ui_gitlab_mocks(): response = client.post( reverse( 'visualisations:approvals', args=(visualisation.visualisation_template. gitlab_project_id, ), ), { "action": "approve", "approver": user.id, "visualisation": str( visualisation.visualisation_template.id), }, follow=True, ) visualisation.refresh_from_db() assert response.status_code == 400 assert ("You must confirm that you have reviewed this visualisation" in response.content.decode(response.charset)) assert len(VisualisationApproval.objects.all()) == 0