diff --git a/src/olympia/addons/management/commands/process_addons.py b/src/olympia/addons/management/commands/process_addons.py index 2b135b0d89eb..b9a90d36dfa5 100644 --- a/src/olympia/addons/management/commands/process_addons.py +++ b/src/olympia/addons/management/commands/process_addons.py @@ -1,5 +1,3 @@ -from datetime import datetime - from django.db.models import Exists, F, OuterRef, Q from olympia import amo @@ -20,6 +18,7 @@ _ADDON_PLUGIN, _ADDON_THEME, _ADDON_WEBAPP, + COSE_DATE_CUTOFF, ) from olympia.devhub.tasks import get_preview_sizes, recreate_previews from olympia.lib.crypto.tasks import bump_and_resign_addons @@ -82,7 +81,7 @@ def get_recalc_needed_filters(): # created before the 5th of April Q( status=amo.STATUS_APPROVED, - _current_version__created__lt=datetime(2019, 4, 5), + _current_version__created__lt=COSE_DATE_CUTOFF, disabled_by_user=False, type__in=( amo.ADDON_EXTENSION, diff --git a/src/olympia/constants/base.py b/src/olympia/constants/base.py index fa91c5bb1e1e..85218e7c59a1 100644 --- a/src/olympia/constants/base.py +++ b/src/olympia/constants/base.py @@ -1,5 +1,6 @@ import re from collections import namedtuple +from datetime import date from django.utils.translation import gettext_lazy as _ @@ -433,3 +434,5 @@ SURVEY_LINK = { DEV_EXP_SURVEY_ALCHEMER_ID: 'https://survey.alchemer.com/s3/7953020/MV3-Developer-Sentiment-2024' } + +COSE_DATE_CUTOFF = date(2019, 4, 5) diff --git a/src/olympia/scanners/admin.py b/src/olympia/scanners/admin.py index 2e5cacfd43a9..3cbc9cf43d45 100644 --- a/src/olympia/scanners/admin.py +++ b/src/olympia/scanners/admin.py @@ -804,6 +804,7 @@ class ScannerQueryRuleAdmin(AbstractScannerRuleAdminMixin, AMOModelAdmin): 'run_on_specific_channel', 'run_on_current_version_only', 'exclude_promoted_addons', + 'created_after', 'created', 'state_with_actions', 'completion_rate', @@ -819,6 +820,7 @@ class ScannerQueryRuleAdmin(AbstractScannerRuleAdminMixin, AMOModelAdmin): 'run_on_specific_channel', 'run_on_current_version_only', 'exclude_promoted_addons', + 'created_after', 'state_with_actions', 'name', 'pretty_name', diff --git a/src/olympia/scanners/migrations/0086_scannerqueryrule_created_after_and_more.py b/src/olympia/scanners/migrations/0086_scannerqueryrule_created_after_and_more.py new file mode 100644 index 000000000000..ca45acdcbaca --- /dev/null +++ b/src/olympia/scanners/migrations/0086_scannerqueryrule_created_after_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 5.2.16 on 2026-08-06 16:30 + +import datetime +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('abuse', '0070_alter_cinderpolicy_expose_in_reviewer_tools_and_more'), + ('scanners', '0085_delete_enable_scanner_webhooks_waffle_switch'), + ] + + operations = [ + migrations.AddField( + model_name='scannerqueryrule', + name='created_after', + field=models.DateField(blank=True, db_default=None, default=datetime.date(2019, 4, 5), null=True), + ), + migrations.AlterField( + model_name='scannerqueryrule', + name='run_on_specific_channel', + field=models.PositiveSmallIntegerField(blank=True, choices=[(None, ''), (1, 'Unlisted'), (2, 'Listed'), (3, 'Enterprise')], default=None, help_text='Run this rule on versions in the specific channel only.', null=True), + ), + migrations.AlterField( + model_name='scannerrule', + name='policy', + field=models.ForeignKey(blank=True, help_text='Policy used to automatically derive an action from when the rule hit.', limit_choices_to={'expose_in_reviewer_tools__in': [1, 2]}, null=True, on_delete=django.db.models.deletion.SET_NULL, to='abuse.cinderpolicy'), + ), + ] diff --git a/src/olympia/scanners/models.py b/src/olympia/scanners/models.py index 485a9d3a8309..20d2932f0809 100644 --- a/src/olympia/scanners/models.py +++ b/src/olympia/scanners/models.py @@ -23,7 +23,7 @@ from olympia.access.models import Group, GroupUser from olympia.amo.models import ModelBase from olympia.constants.abuse import POLICY_EXPOSURE -from olympia.constants.base import ADDON_EXTENSION +from olympia.constants.base import ADDON_EXTENSION, COSE_DATE_CUTOFF from olympia.constants.scanners import ( ABORTED, ABORTING, @@ -650,6 +650,9 @@ class ScannerQueryRule(AbstractScannerRule): help_text='Run this rule on versions in the specific channel only.', choices=[(None, '')] + list(amo.CHANNEL_CHOICES.items()), ) + created_after = models.DateField( + db_default=None, default=COSE_DATE_CUTOFF, null=True, blank=True + ) celery_group_result_id = models.UUIDField(default=None, null=True) task_count = models.PositiveIntegerField(default=0) completed = models.DateTimeField(default=None, null=True, blank=True) diff --git a/src/olympia/scanners/tasks.py b/src/olympia/scanners/tasks.py index 2c66b50a9642..18b846f57917 100644 --- a/src/olympia/scanners/tasks.py +++ b/src/olympia/scanners/tasks.py @@ -664,6 +664,8 @@ def run_scanner_query_rule(query_rule_pk): qs = qs.filter(channel=rule.run_on_specific_channel) if rule.run_on_current_version_only: qs = qs.filter(pk=F('addon___current_version')) + if rule.created_after: + qs = qs.filter(created__gte=rule.created_after) if rule.exclude_promoted_addons: qs = qs.exclude(addon__promotedaddon__isnull=False) qs = qs.values_list('id', flat=True).order_by('-pk') diff --git a/src/olympia/scanners/tests/test_tasks.py b/src/olympia/scanners/tests/test_tasks.py index 958cc793393e..11ddb1a8fac1 100644 --- a/src/olympia/scanners/tests/test_tasks.py +++ b/src/olympia/scanners/tests/test_tasks.py @@ -2126,6 +2126,39 @@ def test_run_on_specific_channel(self): # just make sure the id was set to something. assert self.rule.celery_group_result_id is not None + def test_run_created_after(self): + # Pretend we went through the admin, run after a specific date + some_time_ago = self.days_ago(42) + self.rule.update(state=SCHEDULED, created_after=some_time_ago) + + # Make existing version old: it shouldn't be found. + self.version.update(created=self.days_ago(1200)) + + # Add a couple versions that should be found. + expected_versions = [ + version_factory( + file_kw={'filename': 'webextension.xpi'}, + addon=self.version.addon, + ), + addon_factory( + version_kw={'created': some_time_ago}, + file_kw={'filename': 'webextension.xpi'}, + ).current_version, + ] + + # Run the task. + run_scanner_query_rule.delay(self.rule.pk) + + # Only the newer versions should have been found. + assert ScannerQueryResult.objects.count() == 2 + + assert sorted( + ScannerQueryResult.objects.values_list('version_id', flat=True) + ) == sorted(v.pk for v in expected_versions) + self.rule.reload() + assert self.rule.state == COMPLETED + assert self.rule.task_count == 1 + def test_run_not_new(self): self.rule.update(state=RUNNING) # Not SCHEDULED. run_scanner_query_rule.delay(self.rule.pk)