class TestScannerResultAdmin(TestCase): def setUp(self): super().setUp() self.user = user_factory() self.grant_permission(self.user, 'Admin:Advanced') self.client.login(email=self.user.email) self.list_url = reverse('admin:scanners_scannerresult_changelist') self.admin = ScannerResultAdmin(model=ScannerResult, admin_site=AdminSite()) def test_list_view(self): response = self.client.get(self.list_url) assert response.status_code == 200 def test_list_view_is_restricted(self): user = user_factory() self.grant_permission(user, 'Admin:Curation') self.client.login(email=user.email) response = self.client.get(self.list_url) assert response.status_code == 403 def test_has_add_permission(self): assert self.admin.has_add_permission(request=None) is False def test_has_delete_permission(self): assert self.admin.has_delete_permission(request=None) is False def test_has_change_permission(self): assert self.admin.has_change_permission(request=None) is False def test_formatted_addon(self): addon = addon_factory() version = version_factory(addon=addon, channel=amo.RELEASE_CHANNEL_LISTED) result = ScannerResult(version=version) assert self.admin.formatted_addon(result) == ( '<a href="{}">{} (version: {})</a>'.format( reverse('reviewers.review', args=[addon.slug]), addon.name, version.id, )) def test_formatted_addon_without_version(self): result = ScannerResult(version=None) assert self.admin.formatted_addon(result) == '-' def test_listed_channel(self): version = version_factory(addon=addon_factory(), channel=amo.RELEASE_CHANNEL_LISTED) result = ScannerResult(version=version) assert self.admin.channel(result) == 'Listed' def test_unlisted_channel(self): version = version_factory(addon=addon_factory(), channel=amo.RELEASE_CHANNEL_UNLISTED) result = ScannerResult(version=version) assert self.admin.channel(result) == 'Unlisted' def test_channel_without_version(self): result = ScannerResult(version=None) assert self.admin.channel(result) == '-' def test_formatted_results(self): results = {'some': 'results'} result = ScannerResult(results=results) assert self.admin.formatted_results(result) == format_html( '<pre>{}</pre>', json.dumps(results, indent=2)) def test_formatted_results_without_results(self): result = ScannerResult() assert self.admin.formatted_results(result) == '<pre>{}</pre>' def test_list_queries(self): ScannerResult.objects.create(scanner=CUSTOMS, version=addon_factory().current_version) ScannerResult.objects.create(scanner=WAT, version=addon_factory().current_version) ScannerResult.objects.create(scanner=CUSTOMS, version=addon_factory().current_version) with self.assertNumQueries(9): # 9 queries: # - 2 transaction savepoints because of tests # - 2 user and groups # - 2 COUNT(*) on scanners results for pagination and total display # - 1 scanners results and versions in one query # - 1 all add-ons in one query # - 1 all add-ons translations in one query response = self.client.get(self.list_url, { MatchesFilter.parameter_name: 'all', }) assert response.status_code == 200 html = pq(response.content) expected_length = ScannerResult.objects.count() assert html('#result_list tbody tr').length == expected_length def test_formatted_matches(self): result = ScannerResult() result.add_match(rule='some-rule') assert self.admin.formatted_matches(result) == format_html( '<pre>{}</pre>', json.dumps(result.matches, indent=4)) def test_formatted_matches_without_matches(self): result = ScannerResult() assert self.admin.formatted_matches(result) == '<pre>[]</pre>' def test_list_shows_matches_only_by_default(self): # Create one entry without matches ScannerResult.objects.create(scanner=YARA) # Create one entry with matches with_matches = ScannerResult(scanner=YARA) with_matches.add_match(rule='some-rule') with_matches.save() response = self.client.get(self.list_url) assert response.status_code == 200 html = pq(response.content) assert html('#result_list tbody tr').length == 1 def test_list_can_show_all_entries(self): # Create one entry without matches ScannerResult.objects.create(scanner=YARA) # Create one entry with matches with_matches = ScannerResult(scanner=YARA) with_matches.add_match(rule='some-rule') with_matches.save() response = self.client.get(self.list_url, {MatchesFilter.parameter_name: 'all'}) assert response.status_code == 200 html = pq(response.content) expected_length = ScannerResult.objects.count() assert html('#result_list tbody tr').length == expected_length
class TestScannerResultAdmin(TestCase): def setUp(self): super().setUp() self.user = user_factory() self.grant_permission(self.user, 'Admin:*') self.client.login(email=self.user.email) self.list_url = reverse('admin:scanners_scannerresult_changelist') self.admin = ScannerResultAdmin(model=ScannerResult, admin_site=AdminSite()) def test_list_view(self): rule = ScannerRule.objects.create(name='rule', scanner=CUSTOMS) ScannerResult.objects.create(scanner=CUSTOMS, version=addon_factory().current_version, results={'matchedRules': [rule.name]}) response = self.client.get(self.list_url) assert response.status_code == 200 html = pq(response.content) assert html('.column-result_actions').length == 1 def test_list_view_for_non_admins(self): rule = ScannerRule.objects.create(name='rule', scanner=CUSTOMS) ScannerResult.objects.create(scanner=CUSTOMS, version=addon_factory().current_version, results={'matchedRules': [rule.name]}) user = user_factory() self.grant_permission(user, 'Admin:ScannersResultsView') self.client.login(email=user.email) response = self.client.get(self.list_url) assert response.status_code == 200 html = pq(response.content) assert html('.column-result_actions').length == 0 def test_list_view_is_restricted(self): user = user_factory() self.grant_permission(user, 'Admin:Curation') self.client.login(email=user.email) response = self.client.get(self.list_url) assert response.status_code == 403 def test_has_add_permission(self): assert self.admin.has_add_permission(request=None) is False def test_has_delete_permission(self): assert self.admin.has_delete_permission(request=None) is False def test_has_change_permission(self): assert self.admin.has_change_permission(request=None) is False def test_formatted_listed_addon(self): addon = addon_factory() version = version_factory(addon=addon, channel=amo.RELEASE_CHANNEL_LISTED) result = ScannerResult(version=version) assert self.admin.formatted_addon(result) == ( '<a href="{}">{} (version: {})</a>'.format( urljoin( settings.EXTERNAL_SITE_URL, reverse('reviewers.review', args=['listed', addon.id]), ), addon.name, version.version, )) def test_formatted_unlisted_addon(self): addon = addon_factory() version = version_factory(addon=addon, channel=amo.RELEASE_CHANNEL_UNLISTED) result = ScannerResult(version=version) assert self.admin.formatted_addon(result) == ( '<a href="{}">{} (version: {})</a>'.format( urljoin( settings.EXTERNAL_SITE_URL, reverse('reviewers.review', args=['unlisted', addon.id]), ), addon.name, version.version, )) def test_formatted_addon_without_version(self): result = ScannerResult(version=None) assert self.admin.formatted_addon(result) == '-' def test_guid(self): version = version_factory(addon=addon_factory()) result = ScannerResult(version=version) assert self.admin.guid(result) == version.addon.guid def test_guid_without_version(self): result = ScannerResult(version=None) assert self.admin.guid(result) == '-' def test_listed_channel(self): version = version_factory(addon=addon_factory(), channel=amo.RELEASE_CHANNEL_LISTED) result = ScannerResult(version=version) assert self.admin.channel(result) == 'Listed' def test_unlisted_channel(self): version = version_factory(addon=addon_factory(), channel=amo.RELEASE_CHANNEL_UNLISTED) result = ScannerResult(version=version) assert self.admin.channel(result) == 'Unlisted' def test_channel_without_version(self): result = ScannerResult(version=None) assert self.admin.channel(result) == '-' def test_formatted_results(self): results = {'some': 'results'} result = ScannerResult(results=results) assert self.admin.formatted_results(result) == format_html( '<pre>{}</pre>', json.dumps(results, indent=2)) def test_formatted_results_without_results(self): result = ScannerResult() assert self.admin.formatted_results(result) == '<pre>[]</pre>' def test_formatted_matched_rules_with_files(self): version = addon_factory().current_version result = ScannerResult.objects.create(scanner=YARA, version=version) rule = ScannerRule.objects.create(name='bar', scanner=YARA) filename = 'some/file.js' result.add_yara_result(rule=rule.name, meta={'filename': filename}) result.save() external_site_url = 'http://example.org' file_id = version.all_files[0].id assert file_id is not None expect_file_item = '<a href="{}{}">{}</a>'.format( external_site_url, reverse('files.list', args=[file_id, 'file', filename]), filename) with override_settings(EXTERNAL_SITE_URL=external_site_url): assert (expect_file_item in self.admin.formatted_matched_rules_with_files(result)) def test_formatted_matched_rules_with_files_without_version(self): result = ScannerResult.objects.create(scanner=YARA) rule = ScannerRule.objects.create(name='bar', scanner=YARA) filename = 'some/file.js' result.add_yara_result(rule=rule.name, meta={'filename': filename}) result.save() # We list the file related to the matched rule... assert (filename in self.admin.formatted_matched_rules_with_files(result)) # ...but we do not add a link to it because there is no associated # version. assert ('/file/' not in self.admin.formatted_matched_rules_with_files(result)) def test_list_queries(self): ScannerResult.objects.create(scanner=CUSTOMS, version=addon_factory().current_version) ScannerResult.objects.create(scanner=WAT, version=addon_factory().current_version) deleted_addon = addon_factory(name='a deleted add-on') ScannerResult.objects.create(scanner=CUSTOMS, version=deleted_addon.current_version) deleted_addon.delete() with self.assertNumQueries(11): # 10 queries: # - 2 transaction savepoints because of tests # - 2 user and groups # - 2 COUNT(*) on scanners results for pagination and total display # - 1 get all available rules for filtering # - 1 scanners results and versions in one query # - 1 all add-ons in one query # - 1 all add-ons translations in one query # - 1 all scanner rules in one query response = self.client.get(self.list_url, {MatchesFilter.parameter_name: 'all'}) assert response.status_code == 200 html = pq(response.content) expected_length = ScannerResult.objects.count() assert html('#result_list tbody tr').length == expected_length # The name of the deleted add-on should be displayed. assert str(deleted_addon.name) in html.text() def test_list_filters(self): rule_bar = ScannerRule.objects.create(name='bar', scanner=YARA) rule_hello = ScannerRule.objects.create(name='hello', scanner=YARA) rule_foo = ScannerRule.objects.create(name='foo', scanner=CUSTOMS) response = self.client.get(self.list_url) assert response.status_code == 200 doc = pq(response.content) expected = [ ('All', '?'), ('customs', '?scanner__exact=1'), ('wat', '?scanner__exact=2'), ('yara', '?scanner__exact=3'), ('All', '?has_matched_rules=all'), (' With matched rules only', '?'), ('All', '?state=all'), ('Unknown', '?'), ('True positive', '?state=1'), ('False positive', '?state=2'), ('All', '?'), ('foo (customs)', f'?matched_rules__id__exact={rule_foo.pk}'), ('bar (yara)', f'?matched_rules__id__exact={rule_bar.pk}'), ('hello (yara)', f'?matched_rules__id__exact={rule_hello.pk}'), ('All', '?has_version=all'), (' With version only', '?'), ] filters = [(x.text, x.attrib['href']) for x in doc('#changelist-filter a')] assert filters == expected def test_list_filter_matched_rules(self): rule_bar = ScannerRule.objects.create(name='bar', scanner=YARA) rule_hello = ScannerRule.objects.create(name='hello', scanner=YARA) rule_foo = ScannerRule.objects.create(name='foo', scanner=CUSTOMS) with_bar_matches = ScannerResult(scanner=YARA) with_bar_matches.add_yara_result(rule=rule_bar.name) with_bar_matches.add_yara_result(rule=rule_hello.name) with_bar_matches.save() ScannerResult.objects.create(scanner=CUSTOMS, results={'matchedRules': [rule_foo.name]}) with_hello_match = ScannerResult(scanner=YARA) with_hello_match.add_yara_result(rule=rule_hello.name) response = self.client.get( self.list_url, { 'matched_rules__id__exact': rule_bar.pk, WithVersionFilter.parameter_name: 'all', }) assert response.status_code == 200 doc = pq(response.content) assert doc('#result_list tbody tr').length == 1 assert doc('.field-formatted_matched_rules').text() == 'bar, hello' def test_list_default(self): # Create one entry without matches, it will not be shown by default ScannerResult.objects.create( scanner=YARA, version=version_factory(addon=addon_factory()), ) # Create one entry with matches, it will be shown by default rule = ScannerRule.objects.create(name='some-rule', scanner=YARA) with_matches = ScannerResult( scanner=YARA, version=version_factory(addon=addon_factory()), ) with_matches.add_yara_result(rule=rule.name) with_matches.save() # Create a false positive, it will not be shown by default false_positive = ScannerResult( scanner=YARA, state=FALSE_POSITIVE, version=version_factory(addon=addon_factory()), ) false_positive.add_yara_result(rule=rule.name) false_positive.save() # Create an entry without a version, it will not be shown by default without_version = ScannerResult(scanner=YARA) without_version.add_yara_result(rule=rule.name) without_version.save() response = self.client.get(self.list_url) assert response.status_code == 200 html = pq(response.content) assert html('#result_list tbody tr').length == 1 def test_list_can_show_all_entries(self): # Create one entry without matches ScannerResult.objects.create(scanner=YARA) # Create one entry with matches rule = ScannerRule.objects.create(name='some-rule', scanner=YARA) with_matches = ScannerResult(scanner=YARA) with_matches.add_yara_result(rule=rule.name) with_matches.save() # Create a false positive false_positive = ScannerResult(scanner=YARA, state=FALSE_POSITIVE) false_positive.add_yara_result(rule=rule.name) false_positive.save() # Create an entry without a version without_version = ScannerResult(scanner=YARA) without_version.add_yara_result(rule=rule.name) without_version.save() response = self.client.get( self.list_url, { MatchesFilter.parameter_name: 'all', StateFilter.parameter_name: 'all', WithVersionFilter.parameter_name: 'all', }, ) assert response.status_code == 200 html = pq(response.content) expected_length = ScannerResult.objects.count() assert html('#result_list tbody tr').length == expected_length def test_handle_true_positive(self): # Create one entry with matches rule = ScannerRule.objects.create(name='some-rule', scanner=YARA) result = ScannerResult(scanner=YARA) result.add_yara_result(rule=rule.name) result.save() assert result.state == UNKNOWN response = self.client.post( reverse( 'admin:scanners_scannerresult_handletruepositive', args=[result.pk], ), follow=True, ) result.refresh_from_db() assert result.state == TRUE_POSITIVE # The action should send a redirect. last_url, status_code = response.redirect_chain[-1] assert status_code == 302 # The action should redirect to the list view and the default list # filters should hide the result (because its state is not UNKNOWN # anymore). html = pq(response.content) assert html('#result_list tbody tr').length == 0 # A confirmation message should also appear. assert html('.messagelist .info').length == 1 @override_settings(YARA_GIT_REPOSITORY='git/repo') def test_handle_yara_false_positive(self): # Create one entry with matches rule = ScannerRule.objects.create(name='some-rule', scanner=YARA) result = ScannerResult(scanner=YARA) result.add_yara_result(rule=rule.name) result.save() assert result.state == UNKNOWN response = self.client.post( reverse( 'admin:scanners_scannerresult_handlefalsepositive', args=[result.pk], )) result.refresh_from_db() assert result.state == FALSE_POSITIVE # This action should send a redirect to GitHub. assert response.status_code == 302 # We create a GitHub issue draft by passing some query parameters to # GitHub. assert response['Location'].startswith( 'https://github.com/git/repo/issues/new?') assert (urlencode({ 'title': 'False positive report for ' 'ScannerResult {}'.format(result.pk) }) in response['Location']) assert urlencode({'body': '### Report'}) in response['Location'] assert (urlencode({'labels': 'false positive report'}) in response['Location']) assert 'Raw+scanner+results' in response['Location'] @override_settings(CUSTOMS_GIT_REPOSITORY='git/repo') def test_handle_customs_false_positive(self): # Create one entry with matches rule = ScannerRule.objects.create(name='some-rule', scanner=CUSTOMS) result = ScannerResult(scanner=CUSTOMS, results={'matchedRules': [rule.name]}) result.save() assert result.state == UNKNOWN response = self.client.post( reverse( 'admin:scanners_scannerresult_handlefalsepositive', args=[result.pk], )) result.refresh_from_db() assert result.state == FALSE_POSITIVE # This action should send a redirect to GitHub. assert response.status_code == 302 assert 'Raw+scanner+results' not in response['Location'] def test_handle_revert_report(self): # Create one entry with matches rule = ScannerRule.objects.create(name='some-rule', scanner=YARA) result = ScannerResult(scanner=YARA, version=version_factory(addon=addon_factory())) result.add_yara_result(rule=rule.name) result.state = TRUE_POSITIVE result.save() assert result.state == TRUE_POSITIVE response = self.client.post( reverse('admin:scanners_scannerresult_handlerevert', args=[result.pk]), follow=True, ) result.refresh_from_db() assert result.state == UNKNOWN # The action should send a redirect. last_url, status_code = response.redirect_chain[-1] assert status_code == 302 # The action should redirect to the list view and the default list # filters should show the result (because its state is UNKNOWN again). html = pq(response.content) assert html('#result_list tbody tr').length == 1 # A confirmation message should also appear. assert html('.messagelist .info').length == 1 def test_handle_true_positive_and_non_admin_user(self): result = ScannerResult(scanner=CUSTOMS) user = user_factory() self.grant_permission(user, 'Admin:ScannersResultsView') self.client.login(email=user.email) response = self.client.post( reverse( 'admin:scanners_scannerresult_handletruepositive', args=[result.pk], )) assert response.status_code == 404 def test_handle_false_positive_and_non_admin_user(self): result = ScannerResult(scanner=CUSTOMS) user = user_factory() self.grant_permission(user, 'Admin:ScannersResultsView') self.client.login(email=user.email) response = self.client.post( reverse( 'admin:scanners_scannerresult_handlefalsepositive', args=[result.pk], )) assert response.status_code == 404 def test_handle_revert_report_and_non_admin_user(self): result = ScannerResult(scanner=CUSTOMS) user = user_factory() self.grant_permission(user, 'Admin:ScannersResultsView') self.client.login(email=user.email) response = self.client.post( reverse( 'admin:scanners_scannerresult_handlerevert', args=[result.pk], )) assert response.status_code == 404
class TestScannerResultAdmin(TestCase): def setUp(self): super().setUp() self.user = user_factory() self.grant_permission(self.user, 'Admin:Advanced') self.client.login(email=self.user.email) self.list_url = reverse('admin:scanners_scannerresult_changelist') self.admin = ScannerResultAdmin( model=ScannerResult, admin_site=AdminSite() ) def test_list_view(self): response = self.client.get(self.list_url) assert response.status_code == 200 def test_list_view_is_restricted(self): user = user_factory() self.grant_permission(user, 'Admin:Curation') self.client.login(email=user.email) response = self.client.get(self.list_url) assert response.status_code == 403 def test_has_add_permission(self): assert self.admin.has_add_permission(request=None) is False def test_has_delete_permission(self): assert self.admin.has_delete_permission(request=None) is False def test_has_change_permission(self): assert self.admin.has_change_permission(request=None) is False def test_formatted_addon(self): addon = addon_factory() version = version_factory( addon=addon, channel=amo.RELEASE_CHANNEL_LISTED ) result = ScannerResult(version=version) assert self.admin.formatted_addon(result) == ( '<a href="{}">{} (version: {})</a>'.format( urljoin( settings.EXTERNAL_SITE_URL, reverse('reviewers.review', args=[addon.id]), ), addon.name, version.id, ) ) def test_formatted_addon_without_version(self): result = ScannerResult(version=None) assert self.admin.formatted_addon(result) == '-' def test_guid(self): version = version_factory(addon=addon_factory()) result = ScannerResult(version=version) assert self.admin.guid(result) == version.addon.guid def test_guid_without_version(self): result = ScannerResult(version=None) assert self.admin.guid(result) == '-' def test_listed_channel(self): version = version_factory( addon=addon_factory(), channel=amo.RELEASE_CHANNEL_LISTED ) result = ScannerResult(version=version) assert self.admin.channel(result) == 'Listed' def test_unlisted_channel(self): version = version_factory( addon=addon_factory(), channel=amo.RELEASE_CHANNEL_UNLISTED ) result = ScannerResult(version=version) assert self.admin.channel(result) == 'Unlisted' def test_channel_without_version(self): result = ScannerResult(version=None) assert self.admin.channel(result) == '-' def test_formatted_results(self): results = {'some': 'results'} result = ScannerResult(results=results) assert self.admin.formatted_results(result) == format_html( '<pre>{}</pre>', json.dumps(results, indent=2) ) def test_formatted_results_without_results(self): result = ScannerResult() assert self.admin.formatted_results(result) == '<pre>[]</pre>' def test_list_queries(self): ScannerResult.objects.create( scanner=CUSTOMS, version=addon_factory().current_version ) ScannerResult.objects.create( scanner=WAT, version=addon_factory().current_version ) deleted_addon = addon_factory(name='a deleted add-on') ScannerResult.objects.create( scanner=CUSTOMS, version=deleted_addon.current_version ) deleted_addon.delete() with self.assertNumQueries(10): # 10 queries: # - 2 transaction savepoints because of tests # - 2 user and groups # - 2 COUNT(*) on scanners results for pagination and total display # - 1 scanners results and versions in one query # - 1 all add-ons in one query # - 1 all add-ons translations in one query # - 1 all scanner rules in one query response = self.client.get( self.list_url, {MatchesFilter.parameter_name: 'all'} ) assert response.status_code == 200 html = pq(response.content) expected_length = ScannerResult.objects.count() assert html('#result_list tbody tr').length == expected_length # The name of the deleted add-on should be displayed. assert str(deleted_addon.name) in html.text() def test_list_shows_matches_and_unknown_state_only_by_default(self): # Create one entry without matches ScannerResult.objects.create(scanner=YARA) # Create one entry with matches rule = ScannerRule.objects.create(name='some-rule', scanner=YARA) with_matches = ScannerResult(scanner=YARA) with_matches.add_yara_result(rule=rule.name) with_matches.save() # Create a false positive false_positive = ScannerResult(scanner=YARA, state=FALSE_POSITIVE) false_positive.add_yara_result(rule=rule.name) false_positive.save() response = self.client.get(self.list_url) assert response.status_code == 200 html = pq(response.content) assert html('#result_list tbody tr').length == 1 def test_list_can_show_all_entries(self): # Create one entry without matches ScannerResult.objects.create(scanner=YARA) # Create one entry with matches rule = ScannerRule.objects.create(name='some-rule', scanner=YARA) with_matches = ScannerResult(scanner=YARA) with_matches.add_yara_result(rule=rule.name) with_matches.save() # Create a false positive false_positive = ScannerResult(scanner=YARA, state=FALSE_POSITIVE) false_positive.add_yara_result(rule=rule.name) false_positive.save() response = self.client.get( self.list_url, { MatchesFilter.parameter_name: 'all', StateFilter.parameter_name: 'all', }, ) assert response.status_code == 200 html = pq(response.content) expected_length = ScannerResult.objects.count() assert html('#result_list tbody tr').length == expected_length def test_handle_true_positive(self): # Create one entry with matches rule = ScannerRule.objects.create(name='some-rule', scanner=YARA) result = ScannerResult(scanner=YARA) result.add_yara_result(rule=rule.name) result.save() assert result.state == UNKNOWN response = self.client.get( reverse( 'admin:scanners_scannerresult_handletruepositive', args=[result.pk], ), follow=True, ) result.refresh_from_db() assert result.state == TRUE_POSITIVE # The action should send a redirect. last_url, status_code = response.redirect_chain[-1] assert status_code == 302 # The action should redirect to the list view and the default list # filters should hide the result (because its state is not UNKNOWN # anymore). html = pq(response.content) assert html('#result_list tbody tr').length == 0 # A confirmation message should also appear. assert html('.messagelist .info').length == 1 @override_settings(YARA_GIT_REPOSITORY='git/repo') def test_handle_false_positive(self): # Create one entry with matches rule = ScannerRule.objects.create(name='some-rule', scanner=YARA) result = ScannerResult(scanner=YARA) result.add_yara_result(rule=rule.name) result.save() assert result.state == UNKNOWN response = self.client.get( reverse( 'admin:scanners_scannerresult_handlefalsepositive', args=[result.pk], ) ) result.refresh_from_db() assert result.state == FALSE_POSITIVE # This action should send a redirect to GitHub. assert response.status_code == 302 # We create a GitHub issue draft by passing some query parameters to # GitHub. assert response['Location'].startswith( 'https://github.com/git/repo/issues/new?' ) assert ( urlencode( { 'title': 'False positive report for ' 'ScannerResult {}'.format(result.pk) } ) in response['Location'] ) assert urlencode({'body': '### Report'}) in response['Location'] assert ( urlencode({'labels': 'false positive report'}) in response['Location'] )