def test_runs_flag_for_human_review(self, flag_for_human_review_mock): self.scanner_rule.update(action=FLAG_FOR_HUMAN_REVIEW) ScannerResult.run_action(self.version) assert flag_for_human_review_mock.called flag_for_human_review_mock.assert_called_with(self.version)
def test_returns_when_no_action_found(self, log_mock): self.scanner_rule.delete() ScannerResult.run_action(self.version) log_mock.assert_called_with('No action to execute for version %s.', self.version.id)
def test_runs_no_action(self, no_action_mock): self.scanner_rule.update(action=NO_ACTION) ScannerResult.run_action(self.version) assert no_action_mock.called no_action_mock.assert_called_with(self.version)
def test_runs_delay_auto_approval(self, _delay_auto_approval_mock): self.scanner_rule.update(action=DELAY_AUTO_APPROVAL) ScannerResult.run_action(self.version) assert _delay_auto_approval_mock.called _delay_auto_approval_mock.assert_called_with(self.version)
def test_flags_for_human_review_by_mad_when_score_is_too_high(self): version = version_factory(addon=addon_factory()) results = {'scanners': {'customs': {'score': 0.99}}} ScannerResult.objects.create(version=version, scanner=MAD, results=results) ScannerResult.run_action(version) assert version.reviewerflags.needs_human_review_by_mad
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_does_not_flag_for_human_review_by_mad_when_score_is_okay(self): version = version_factory(addon=addon_factory()) results = {'scanners': {'customs': {'score': 0.2}}} ScannerResult.objects.create(version=version, scanner=MAD, results=results) ScannerResult.run_action(version) with self.assertRaises(VersionReviewerFlags.DoesNotExist): version.reviewerflags
def test_selects_the_action_with_the_highest_severity( self, flag_for_human_review_mock, no_action_mock): # Create another rule and add it to the current scanner result. This # rule is more severe than `rule-1` created in `setUp()`. rule = ScannerRule.objects.create(name='rule-2', scanner=self.scanner, action=FLAG_FOR_HUMAN_REVIEW) self.scanner_result.matched_rules.add(rule) ScannerResult.run_action(self.version) assert not no_action_mock.called assert flag_for_human_review_mock.called
def test_flags_for_human_review_by_mad_when_models_disagree(self): version = version_factory(addon=addon_factory()) results = { 'scanners': { 'customs': {'result_details': {'models_agree': False}} } } ScannerResult.objects.create( version=version, scanner=MAD, results=results ) ScannerResult.run_action(version) assert version.versionreviewerflags.needs_human_review_by_mad
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']
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_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_selects_active_actions_only(self, flag_for_human_review_mock, no_action_mock): # Create another rule and add it to the current scanner result. This # rule is more severe than `rule-1` created in `setUp()`. In this test # case, we disable this rule, though. rule = ScannerRule.objects.create( name='rule-2', scanner=self.scanner, action=FLAG_FOR_HUMAN_REVIEW, is_active=False, ) self.scanner_result.matched_rules.add(rule) ScannerResult.run_action(self.version) assert no_action_mock.called assert not flag_for_human_review_mock.called
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
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_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_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
def test_change_view_contains_link_to_results(self): rule = ScannerRule.objects.create(name='bar', scanner=YARA) result = ScannerResult(scanner=YARA) result.add_yara_result(rule=rule.name) result.save() ScannerResult.objects.create(scanner=YARA) # Doesn't match url = reverse('admin:scanners_scannerrule_change', args=(rule.pk, )) response = self.client.get(url) assert response.status_code == 200 doc = pq(response.content) link = doc('.field-matched_results_link a') assert link results_list_url = reverse('admin:scanners_scannerresult_changelist') expected_href = ( f'{results_list_url}?matched_rules__id__exact={rule.pk}' f'&has_version=all&state=all&scanner={rule.scanner}') assert link.attr('href') == expected_href assert link.text() == '1'
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_channel_without_version(self): result = ScannerResult(version=None) assert self.admin.channel(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_raise_when_action_is_invalid(self): # `12345` is an invalid action ID self.scanner_rule.update(action=12345) with pytest.raises(Exception, match='invalid action 12345'): ScannerResult.run_action(self.version)
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_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_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_formatted_results_without_results(self): result = ScannerResult() assert self.admin.formatted_results(result) == '<pre>[]</pre>'
def process(self, version): """Process a single version, figuring out if it should be auto-approved and calling the approval code if necessary.""" already_locked = AutoApprovalSummary.check_is_locked(version) if not already_locked: # Lock the addon for ourselves if possible. Even though # AutoApprovalSummary.create_summary_for_version() will do # call check_is_locked() again later when calculating the verdict, # we have to do it now to prevent overwriting an existing lock with # our own. set_reviewing_cache(version.addon.pk, settings.TASK_USER_ID) try: with transaction.atomic(): log.info('Processing %s version %s...', str(version.addon.name), str(version.version)) if waffle.switch_is_active('run-action-in-auto-approve'): # We want to execute `run_action()` only once. summary_exists = AutoApprovalSummary.objects.filter( version=version).exists() if summary_exists: log.info('Not running run_action() because it has ' 'already been executed') else: ScannerResult.run_action(version) summary, info = AutoApprovalSummary.create_summary_for_version( version, dry_run=self.dry_run) self.stats.update({k: int(v) for k, v in info.items()}) if summary.verdict == self.successful_verdict: if summary.verdict == amo.AUTO_APPROVED: self.approve(version) self.stats['auto_approved'] += 1 verdict_string = summary.get_verdict_display() else: verdict_string = '%s (%s)' % ( summary.get_verdict_display(), ', '.join( summary.verdict_info_prettifier(info))) log.info('Auto Approval for %s version %s: %s', str(version.addon.name), str(version.version), verdict_string) # At this point, any exception should have rolled back the transaction, # so even if we did create/update an AutoApprovalSummary instance that # should have been rolled back. This ensures that, for instance, a # signing error doesn't leave the version and its autoapprovalsummary # in conflicting states. except (AutoApprovalNotEnoughFilesError, AutoApprovalNoValidationResultError): log.info( 'Version %s was skipped either because it had no ' 'files or because it had no validation attached.', version) self.stats['error'] += 1 except SigningError: statsd.incr('reviewers.auto_approve.approve.failure') log.info('Version %s was skipped because of a signing error', version) self.stats['error'] += 1 finally: # Always clear our own lock no matter what happens (but only ours). if not already_locked: clear_reviewing_cache(version.addon.pk)
def test_formatted_addon_without_version(self): result = ScannerResult(version=None) assert self.admin.formatted_addon(result) == '-'