Hold actions on the back of decisions, rather than process them, if content is high profile (#22766)
* seperate decision from action enforcement * implement hold actions for BAN_USER * implement hold actions for DELETE_RATING * implement hold action for DELETE_COLLECTION * migration number clash * implement old actions for DISABLE_ADDON
This commit is contained in:
Родитель
3a7d24badb
Коммит
8b100c94ae
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 4.2.16 on 2024-10-14 13:48
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('abuse', '0040_alter_cinderpolicy_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cinderdecision',
|
||||||
|
name='date',
|
||||||
|
field=models.DateTimeField(db_column='date', null=True),
|
||||||
|
),
|
||||||
|
migrations.RenameField(model_name='cinderdecision', old_name='date', new_name='action_date'),
|
||||||
|
]
|
|
@ -5,7 +5,6 @@ from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Exists, OuterRef, Q
|
from django.db.models import Exists, OuterRef, Q
|
||||||
from django.db.transaction import atomic
|
from django.db.transaction import atomic
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
from olympia import amo
|
from olympia import amo
|
||||||
|
@ -278,14 +277,13 @@ class CinderJob(ModelBase):
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
decision_cinder_id,
|
decision_cinder_id,
|
||||||
decision_date,
|
|
||||||
decision_action,
|
decision_action,
|
||||||
decision_notes,
|
decision_notes,
|
||||||
policy_ids,
|
policy_ids,
|
||||||
):
|
):
|
||||||
"""This is called for cinder originated decisions.
|
"""This is called for cinder originated decisions.
|
||||||
See resolve_job for reviewer tools originated decisions."""
|
See resolve_job for reviewer tools originated decisions."""
|
||||||
overriden_action = getattr(self.decision, 'action', None)
|
overridden_action = getattr(self.decision, 'action', None)
|
||||||
# We need either an AbuseReport or CinderDecision for the target props
|
# We need either an AbuseReport or CinderDecision for the target props
|
||||||
abuse_report_or_decision = (
|
abuse_report_or_decision = (
|
||||||
self.appealed_decisions.first() or self.abusereport_set.first()
|
self.appealed_decisions.first() or self.abusereport_set.first()
|
||||||
|
@ -301,7 +299,6 @@ class CinderJob(ModelBase):
|
||||||
'rating': abuse_report_or_decision.rating,
|
'rating': abuse_report_or_decision.rating,
|
||||||
'collection': abuse_report_or_decision.collection,
|
'collection': abuse_report_or_decision.collection,
|
||||||
'user': abuse_report_or_decision.user,
|
'user': abuse_report_or_decision.user,
|
||||||
'date': decision_date,
|
|
||||||
'cinder_id': decision_cinder_id,
|
'cinder_id': decision_cinder_id,
|
||||||
'action': decision_action,
|
'action': decision_action,
|
||||||
'notes': decision_notes[
|
'notes': decision_notes[
|
||||||
|
@ -313,14 +310,8 @@ class CinderJob(ModelBase):
|
||||||
policies = CinderPolicy.objects.filter(
|
policies = CinderPolicy.objects.filter(
|
||||||
uuid__in=policy_ids
|
uuid__in=policy_ids
|
||||||
).without_parents_if_their_children_are_present()
|
).without_parents_if_their_children_are_present()
|
||||||
self.decision.policies.add(*policies)
|
cinder_decision.policies.add(*policies)
|
||||||
action_helper = self.decision.get_action_helper(
|
cinder_decision.process_action(overridden_action)
|
||||||
overriden_action=overriden_action,
|
|
||||||
appealed_action=getattr(self.appealed_decisions.first(), 'action', None),
|
|
||||||
)
|
|
||||||
log_entry = action_helper.process_action()
|
|
||||||
self.notify_reporters(action_helper)
|
|
||||||
action_helper.notify_owners(log_entry_id=getattr(log_entry, 'id', None))
|
|
||||||
|
|
||||||
def resolve_job(self, *, log_entry):
|
def resolve_job(self, *, log_entry):
|
||||||
"""This is called for reviewer tools originated decisions.
|
"""This is called for reviewer tools originated decisions.
|
||||||
|
@ -349,7 +340,7 @@ class CinderJob(ModelBase):
|
||||||
appealed_action=getattr(self.appealed_decisions.first(), 'action', None),
|
appealed_action=getattr(self.appealed_decisions.first(), 'action', None),
|
||||||
)
|
)
|
||||||
self.update(decision=cinder_decision)
|
self.update(decision=cinder_decision)
|
||||||
if self.decision.is_delayed:
|
if cinder_decision.is_delayed:
|
||||||
version_list = log_entry.versionlog_set.values_list('version', flat=True)
|
version_list = log_entry.versionlog_set.values_list('version', flat=True)
|
||||||
self.pending_rejections.add(
|
self.pending_rejections.add(
|
||||||
*VersionReviewerFlags.objects.filter(version__in=version_list)
|
*VersionReviewerFlags.objects.filter(version__in=version_list)
|
||||||
|
@ -920,7 +911,7 @@ class CinderPolicy(ModelBase):
|
||||||
class CinderDecision(ModelBase):
|
class CinderDecision(ModelBase):
|
||||||
action = models.PositiveSmallIntegerField(choices=DECISION_ACTIONS.choices)
|
action = models.PositiveSmallIntegerField(choices=DECISION_ACTIONS.choices)
|
||||||
cinder_id = models.CharField(max_length=36, default=None, null=True, unique=True)
|
cinder_id = models.CharField(max_length=36, default=None, null=True, unique=True)
|
||||||
date = models.DateTimeField(default=timezone.now)
|
action_date = models.DateTimeField(null=True, db_column='date')
|
||||||
notes = models.TextField(max_length=1000, blank=True)
|
notes = models.TextField(max_length=1000, blank=True)
|
||||||
policies = models.ManyToManyField(to='abuse.CinderPolicy')
|
policies = models.ManyToManyField(to='abuse.CinderPolicy')
|
||||||
appeal_job = models.ForeignKey(
|
appeal_job = models.ForeignKey(
|
||||||
|
@ -1022,7 +1013,7 @@ class CinderDecision(ModelBase):
|
||||||
DECISION_ACTIONS.AMO_CLOSED_NO_ACTION: CinderActionAlreadyRemoved,
|
DECISION_ACTIONS.AMO_CLOSED_NO_ACTION: CinderActionAlreadyRemoved,
|
||||||
}.get(decision_action, CinderActionNotImplemented)
|
}.get(decision_action, CinderActionNotImplemented)
|
||||||
|
|
||||||
def get_action_helper(self, *, overriden_action=None, appealed_action=None):
|
def get_action_helper(self, *, overridden_action=None, appealed_action=None):
|
||||||
# Base case when it's a new decision, that wasn't an appeal
|
# Base case when it's a new decision, that wasn't an appeal
|
||||||
CinderActionClass = self.get_action_helper_class(self.action)
|
CinderActionClass = self.get_action_helper_class(self.action)
|
||||||
skip_reporter_notify = False
|
skip_reporter_notify = False
|
||||||
|
@ -1038,11 +1029,11 @@ class CinderDecision(ModelBase):
|
||||||
CinderActionClass = CinderActionTargetAppealRemovalAffirmation
|
CinderActionClass = CinderActionTargetAppealRemovalAffirmation
|
||||||
# (a reporter appeal doesn't need any alternate CinderAction class)
|
# (a reporter appeal doesn't need any alternate CinderAction class)
|
||||||
|
|
||||||
elif overriden_action in DECISION_ACTIONS.REMOVING:
|
elif overridden_action in DECISION_ACTIONS.REMOVING:
|
||||||
# override on a decision that was a takedown before, and wasn't an appeal
|
# override on a decision that was a takedown before, and wasn't an appeal
|
||||||
if self.action in DECISION_ACTIONS.APPROVING:
|
if self.action in DECISION_ACTIONS.APPROVING:
|
||||||
CinderActionClass = CinderActionOverrideApprove
|
CinderActionClass = CinderActionOverrideApprove
|
||||||
if self.action == overriden_action:
|
if self.action == overridden_action:
|
||||||
# For an override that is still a takedown we can send the same emails
|
# For an override that is still a takedown we can send the same emails
|
||||||
# to the target; but we don't want to notify the reporter again.
|
# to the target; but we don't want to notify the reporter again.
|
||||||
skip_reporter_notify = True
|
skip_reporter_notify = True
|
||||||
|
@ -1059,8 +1050,8 @@ class CinderDecision(ModelBase):
|
||||||
"""
|
"""
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
base_criteria = (
|
base_criteria = (
|
||||||
self.date
|
self.action_date
|
||||||
and self.date >= now - timedelta(days=APPEAL_EXPIRATION_DAYS)
|
and self.action_date >= now - timedelta(days=APPEAL_EXPIRATION_DAYS)
|
||||||
# Can never appeal an original decision that has been appealed and
|
# Can never appeal an original decision that has been appealed and
|
||||||
# for which we already have a new decision. In some cases the
|
# for which we already have a new decision. In some cases the
|
||||||
# appealed decision (new decision id) can be appealed by the author
|
# appealed decision (new decision id) can be appealed by the author
|
||||||
|
@ -1187,7 +1178,7 @@ class CinderDecision(ModelBase):
|
||||||
'Missing or invalid cinder_action in activity log details passed to '
|
'Missing or invalid cinder_action in activity log details passed to '
|
||||||
'notify_reviewer_decision'
|
'notify_reviewer_decision'
|
||||||
)
|
)
|
||||||
overriden_action = self.action
|
overridden_action = self.action
|
||||||
self.action = DECISION_ACTIONS.for_constant(
|
self.action = DECISION_ACTIONS.for_constant(
|
||||||
log_entry.details['cinder_action']
|
log_entry.details['cinder_action']
|
||||||
).value
|
).value
|
||||||
|
@ -1201,7 +1192,7 @@ class CinderDecision(ModelBase):
|
||||||
'reasoning': self.notes,
|
'reasoning': self.notes,
|
||||||
'policy_uuids': [policy.uuid for policy in policies],
|
'policy_uuids': [policy.uuid for policy in policies],
|
||||||
}
|
}
|
||||||
if not overriden_action and (
|
if not overridden_action and (
|
||||||
cinder_job := getattr(self, 'cinder_job', None)
|
cinder_job := getattr(self, 'cinder_job', None)
|
||||||
):
|
):
|
||||||
decision_cinder_id = entity_helper.create_job_decision(
|
decision_cinder_id = entity_helper.create_job_decision(
|
||||||
|
@ -1211,11 +1202,12 @@ class CinderDecision(ModelBase):
|
||||||
decision_cinder_id = entity_helper.create_decision(**create_decision_kw)
|
decision_cinder_id = entity_helper.create_decision(**create_decision_kw)
|
||||||
with atomic():
|
with atomic():
|
||||||
self.cinder_id = decision_cinder_id
|
self.cinder_id = decision_cinder_id
|
||||||
|
self.action_date = datetime.now()
|
||||||
self.save()
|
self.save()
|
||||||
self.policies.set(policies)
|
self.policies.set(policies)
|
||||||
|
|
||||||
action_helper = self.get_action_helper(
|
action_helper = self.get_action_helper(
|
||||||
overriden_action=overriden_action, appealed_action=appealed_action
|
overridden_action=overridden_action, appealed_action=appealed_action
|
||||||
)
|
)
|
||||||
if cinder_job := getattr(self, 'cinder_job', None):
|
if cinder_job := getattr(self, 'cinder_job', None):
|
||||||
cinder_job.notify_reporters(action_helper)
|
cinder_job.notify_reporters(action_helper)
|
||||||
|
@ -1249,6 +1241,29 @@ class CinderDecision(ModelBase):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def process_action(self, overridden_action=None):
|
||||||
|
"""currently only called by decisions from cinder.
|
||||||
|
see https://mozilla-hub.atlassian.net/browse/AMOENG-1125
|
||||||
|
"""
|
||||||
|
appealed_action = (
|
||||||
|
getattr(self.cinder_job.appealed_decisions.first(), 'action', None)
|
||||||
|
if hasattr(self, 'cinder_job')
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
action_helper = self.get_action_helper(
|
||||||
|
overridden_action=overridden_action,
|
||||||
|
appealed_action=appealed_action,
|
||||||
|
)
|
||||||
|
if not action_helper.should_hold_action():
|
||||||
|
log_entry = action_helper.process_action()
|
||||||
|
if cinder_job := getattr(self, 'cinder_job', None):
|
||||||
|
cinder_job.notify_reporters(action_helper)
|
||||||
|
action_helper.notify_owners(log_entry_id=getattr(log_entry, 'id', None))
|
||||||
|
self.update(action_date=datetime.now())
|
||||||
|
else:
|
||||||
|
action_helper.hold_action()
|
||||||
|
|
||||||
|
|
||||||
class CinderAppeal(ModelBase):
|
class CinderAppeal(ModelBase):
|
||||||
text = models.TextField(blank=False, help_text='The content of the appeal.')
|
text = models.TextField(blank=False, help_text='The content of the appeal.')
|
||||||
|
|
|
@ -31,6 +31,8 @@ from olympia.constants.abuse import (
|
||||||
ILLEGAL_CATEGORIES,
|
ILLEGAL_CATEGORIES,
|
||||||
ILLEGAL_SUBCATEGORIES,
|
ILLEGAL_SUBCATEGORIES,
|
||||||
)
|
)
|
||||||
|
from olympia.constants.promoted import RECOMMENDED
|
||||||
|
from olympia.core import set_user
|
||||||
from olympia.ratings.models import Rating
|
from olympia.ratings.models import Rating
|
||||||
from olympia.reviewers.models import NeedsHumanReview
|
from olympia.reviewers.models import NeedsHumanReview
|
||||||
from olympia.versions.models import Version, VersionReviewerFlags
|
from olympia.versions.models import Version, VersionReviewerFlags
|
||||||
|
@ -1172,7 +1174,6 @@ class TestCinderJob(TestCase):
|
||||||
cinder_job = CinderJob.objects.create(job_id='1234')
|
cinder_job = CinderJob.objects.create(job_id='1234')
|
||||||
target = user_factory()
|
target = user_factory()
|
||||||
AbuseReport.objects.create(user=target, cinder_job=cinder_job)
|
AbuseReport.objects.create(user=target, cinder_job=cinder_job)
|
||||||
new_date = datetime(2023, 1, 1)
|
|
||||||
policy_a = CinderPolicy.objects.create(uuid='123-45', name='aaa', text='AAA')
|
policy_a = CinderPolicy.objects.create(uuid='123-45', name='aaa', text='AAA')
|
||||||
policy_b = CinderPolicy.objects.create(uuid='678-90', name='bbb', text='BBB')
|
policy_b = CinderPolicy.objects.create(uuid='678-90', name='bbb', text='BBB')
|
||||||
|
|
||||||
|
@ -1184,13 +1185,11 @@ class TestCinderJob(TestCase):
|
||||||
action_mock.return_value = (True, mock.Mock(id=999))
|
action_mock.return_value = (True, mock.Mock(id=999))
|
||||||
cinder_job.process_decision(
|
cinder_job.process_decision(
|
||||||
decision_cinder_id='12345',
|
decision_cinder_id='12345',
|
||||||
decision_date=new_date,
|
|
||||||
decision_action=DECISION_ACTIONS.AMO_BAN_USER.value,
|
decision_action=DECISION_ACTIONS.AMO_BAN_USER.value,
|
||||||
decision_notes='teh notes',
|
decision_notes='teh notes',
|
||||||
policy_ids=['123-45', '678-90'],
|
policy_ids=['123-45', '678-90'],
|
||||||
)
|
)
|
||||||
assert cinder_job.decision.cinder_id == '12345'
|
assert cinder_job.decision.cinder_id == '12345'
|
||||||
assert cinder_job.decision.date == new_date
|
|
||||||
assert cinder_job.decision.action == DECISION_ACTIONS.AMO_BAN_USER
|
assert cinder_job.decision.action == DECISION_ACTIONS.AMO_BAN_USER
|
||||||
assert cinder_job.decision.notes == 'teh notes'
|
assert cinder_job.decision.notes == 'teh notes'
|
||||||
assert cinder_job.decision.user == target
|
assert cinder_job.decision.user == target
|
||||||
|
@ -1202,7 +1201,6 @@ class TestCinderJob(TestCase):
|
||||||
cinder_job = CinderJob.objects.create(job_id='1234')
|
cinder_job = CinderJob.objects.create(job_id='1234')
|
||||||
target = user_factory()
|
target = user_factory()
|
||||||
AbuseReport.objects.create(user=target, cinder_job=cinder_job)
|
AbuseReport.objects.create(user=target, cinder_job=cinder_job)
|
||||||
new_date = datetime(2023, 1, 1)
|
|
||||||
parent_policy = CinderPolicy.objects.create(
|
parent_policy = CinderPolicy.objects.create(
|
||||||
uuid='678-90', name='bbb', text='BBB'
|
uuid='678-90', name='bbb', text='BBB'
|
||||||
)
|
)
|
||||||
|
@ -1218,13 +1216,11 @@ class TestCinderJob(TestCase):
|
||||||
action_mock.return_value = (True, None)
|
action_mock.return_value = (True, None)
|
||||||
cinder_job.process_decision(
|
cinder_job.process_decision(
|
||||||
decision_cinder_id='12345',
|
decision_cinder_id='12345',
|
||||||
decision_date=new_date,
|
|
||||||
decision_action=DECISION_ACTIONS.AMO_BAN_USER.value,
|
decision_action=DECISION_ACTIONS.AMO_BAN_USER.value,
|
||||||
decision_notes='teh notes',
|
decision_notes='teh notes',
|
||||||
policy_ids=['123-45', '678-90'],
|
policy_ids=['123-45', '678-90'],
|
||||||
)
|
)
|
||||||
assert cinder_job.decision.cinder_id == '12345'
|
assert cinder_job.decision.cinder_id == '12345'
|
||||||
assert cinder_job.decision.date == new_date
|
|
||||||
assert cinder_job.decision.action == DECISION_ACTIONS.AMO_BAN_USER
|
assert cinder_job.decision.action == DECISION_ACTIONS.AMO_BAN_USER
|
||||||
assert cinder_job.decision.notes == 'teh notes'
|
assert cinder_job.decision.notes == 'teh notes'
|
||||||
assert cinder_job.decision.user == target
|
assert cinder_job.decision.user == target
|
||||||
|
@ -1237,7 +1233,6 @@ class TestCinderJob(TestCase):
|
||||||
cinder_job = CinderJob.objects.create(job_id='1234', target_addon=addon)
|
cinder_job = CinderJob.objects.create(job_id='1234', target_addon=addon)
|
||||||
report = AbuseReport.objects.create(guid=addon.guid, cinder_job=cinder_job)
|
report = AbuseReport.objects.create(guid=addon.guid, cinder_job=cinder_job)
|
||||||
assert not cinder_job.resolvable_in_reviewer_tools
|
assert not cinder_job.resolvable_in_reviewer_tools
|
||||||
new_date = datetime(2024, 1, 1)
|
|
||||||
responses.add(
|
responses.add(
|
||||||
responses.POST,
|
responses.POST,
|
||||||
f'{settings.CINDER_SERVER_URL}create_report',
|
f'{settings.CINDER_SERVER_URL}create_report',
|
||||||
|
@ -1247,7 +1242,6 @@ class TestCinderJob(TestCase):
|
||||||
|
|
||||||
cinder_job.process_decision(
|
cinder_job.process_decision(
|
||||||
decision_cinder_id='12345',
|
decision_cinder_id='12345',
|
||||||
decision_date=new_date,
|
|
||||||
decision_action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
|
decision_action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
|
||||||
decision_notes='blah',
|
decision_notes='blah',
|
||||||
policy_ids=[],
|
policy_ids=[],
|
||||||
|
@ -1325,7 +1319,7 @@ class TestCinderJob(TestCase):
|
||||||
assert 'entity' not in request_body
|
assert 'entity' not in request_body
|
||||||
cinder_job.reload()
|
cinder_job.reload()
|
||||||
assert cinder_job.decision.action == cinder_action
|
assert cinder_job.decision.action == cinder_action
|
||||||
self.assertCloseToNow(cinder_job.decision.date)
|
self.assertCloseToNow(cinder_job.decision.action_date)
|
||||||
assert list(cinder_job.decision.policies.all()) == policies
|
assert list(cinder_job.decision.policies.all()) == policies
|
||||||
assert len(mail.outbox) == (2 if expect_target_email else 1)
|
assert len(mail.outbox) == (2 if expect_target_email else 1)
|
||||||
assert mail.outbox[0].to == [abuse_report.reporter.email]
|
assert mail.outbox[0].to == [abuse_report.reporter.email]
|
||||||
|
@ -1399,7 +1393,7 @@ class TestCinderJob(TestCase):
|
||||||
assert cinder_job.decision.action == (
|
assert cinder_job.decision.action == (
|
||||||
DECISION_ACTIONS.AMO_REJECT_VERSION_WARNING_ADDON
|
DECISION_ACTIONS.AMO_REJECT_VERSION_WARNING_ADDON
|
||||||
)
|
)
|
||||||
self.assertCloseToNow(cinder_job.decision.date)
|
self.assertCloseToNow(cinder_job.decision.action_date)
|
||||||
assert list(cinder_job.decision.policies.all()) == policies
|
assert list(cinder_job.decision.policies.all()) == policies
|
||||||
assert set(cinder_job.pending_rejections.all()) == set(
|
assert set(cinder_job.pending_rejections.all()) == set(
|
||||||
VersionReviewerFlags.objects.filter(
|
VersionReviewerFlags.objects.filter(
|
||||||
|
@ -1470,7 +1464,7 @@ class TestCinderJob(TestCase):
|
||||||
assert 'entity' not in request_body
|
assert 'entity' not in request_body
|
||||||
appeal_job.reload()
|
appeal_job.reload()
|
||||||
assert appeal_job.decision.action == DECISION_ACTIONS.AMO_DISABLE_ADDON
|
assert appeal_job.decision.action == DECISION_ACTIONS.AMO_DISABLE_ADDON
|
||||||
self.assertCloseToNow(appeal_job.decision.date)
|
self.assertCloseToNow(appeal_job.decision.action_date)
|
||||||
assert list(appeal_job.decision.policies.all()) == policies
|
assert list(appeal_job.decision.policies.all()) == policies
|
||||||
assert len(mail.outbox) == 1
|
assert len(mail.outbox) == 1
|
||||||
|
|
||||||
|
@ -1602,7 +1596,7 @@ class TestCinderJob(TestCase):
|
||||||
assert request_body['reasoning'] == 'some review text'
|
assert request_body['reasoning'] == 'some review text'
|
||||||
cinder_job.reload()
|
cinder_job.reload()
|
||||||
assert cinder_job.decision.action == DECISION_ACTIONS.AMO_DISABLE_ADDON
|
assert cinder_job.decision.action == DECISION_ACTIONS.AMO_DISABLE_ADDON
|
||||||
self.assertCloseToNow(cinder_job.decision.date)
|
self.assertCloseToNow(cinder_job.decision.action_date)
|
||||||
assert list(cinder_job.decision.policies.all()) == policies
|
assert list(cinder_job.decision.policies.all()) == policies
|
||||||
assert len(mail.outbox) == 2
|
assert len(mail.outbox) == 2
|
||||||
assert mail.outbox[0].to == [abuse_report.reporter.email]
|
assert mail.outbox[0].to == [abuse_report.reporter.email]
|
||||||
|
@ -1797,6 +1791,7 @@ class TestCinderDecisionCanBeAppealed(TestCase):
|
||||||
cinder_id='fake_decision_id',
|
cinder_id='fake_decision_id',
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
addon=self.addon,
|
addon=self.addon,
|
||||||
|
action_date=datetime.now(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_appealed_decision_already_made(self):
|
def test_appealed_decision_already_made(self):
|
||||||
|
@ -1937,6 +1932,7 @@ class TestCinderDecisionCanBeAppealed(TestCase):
|
||||||
cinder_id='fake_appeal_decision_id',
|
cinder_id='fake_appeal_decision_id',
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
addon=self.addon,
|
addon=self.addon,
|
||||||
|
action_date=datetime.now(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
report = AbuseReport.objects.create(
|
report = AbuseReport.objects.create(
|
||||||
|
@ -1970,7 +1966,22 @@ class TestCinderDecisionCanBeAppealed(TestCase):
|
||||||
assert self.decision.can_be_appealed(
|
assert self.decision.can_be_appealed(
|
||||||
is_reporter=True, abuse_report=initial_report
|
is_reporter=True, abuse_report=initial_report
|
||||||
)
|
)
|
||||||
self.decision.update(date=self.days_ago(APPEAL_EXPIRATION_DAYS + 1))
|
self.decision.update(action_date=self.days_ago(APPEAL_EXPIRATION_DAYS + 1))
|
||||||
|
assert not self.decision.can_be_appealed(
|
||||||
|
is_reporter=True, abuse_report=initial_report
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_reporter_cant_appeal_when_no_action_date(self):
|
||||||
|
initial_report = AbuseReport.objects.create(
|
||||||
|
guid=self.addon.guid,
|
||||||
|
cinder_job=CinderJob.objects.create(decision=self.decision),
|
||||||
|
reporter=self.reporter,
|
||||||
|
reason=AbuseReport.REASONS.ILLEGAL,
|
||||||
|
)
|
||||||
|
assert self.decision.can_be_appealed(
|
||||||
|
is_reporter=True, abuse_report=initial_report
|
||||||
|
)
|
||||||
|
self.decision.update(action_date=None)
|
||||||
assert not self.decision.can_be_appealed(
|
assert not self.decision.can_be_appealed(
|
||||||
is_reporter=True, abuse_report=initial_report
|
is_reporter=True, abuse_report=initial_report
|
||||||
)
|
)
|
||||||
|
@ -2021,6 +2032,7 @@ class TestCinderDecisionCanBeAppealed(TestCase):
|
||||||
cinder_id='fake_appeal_decision_id',
|
cinder_id='fake_appeal_decision_id',
|
||||||
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
addon=self.addon,
|
addon=self.addon,
|
||||||
|
action_date=datetime.now(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.decision.update(appeal_job=appeal_job)
|
self.decision.update(appeal_job=appeal_job)
|
||||||
|
@ -2148,6 +2160,12 @@ class TestCinderPolicy(TestCase):
|
||||||
@override_switch('dsa-abuse-reports-review', active=True)
|
@override_switch('dsa-abuse-reports-review', active=True)
|
||||||
@override_switch('dsa-appeals-review', active=True)
|
@override_switch('dsa-appeals-review', active=True)
|
||||||
class TestCinderDecision(TestCase):
|
class TestCinderDecision(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# It's the webhook's responsibility to do this before calling the
|
||||||
|
# action. We need it for the ActivityLog creation to work.
|
||||||
|
self.task_user = user_factory(pk=settings.TASK_USER_ID)
|
||||||
|
set_user(self.task_user)
|
||||||
|
|
||||||
def test_get_reference_id(self):
|
def test_get_reference_id(self):
|
||||||
decision = CinderDecision()
|
decision = CinderDecision()
|
||||||
assert decision.get_reference_id() == 'NoClass#None'
|
assert decision.get_reference_id() == 'NoClass#None'
|
||||||
|
@ -2292,7 +2310,7 @@ class TestCinderDecision(TestCase):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
helper = decision.get_action_helper(
|
helper = decision.get_action_helper(
|
||||||
appealed_action=appealed_action, overriden_action=overridden_action
|
appealed_action=appealed_action, overridden_action=overridden_action
|
||||||
)
|
)
|
||||||
assert helper.__class__ == ActionClass
|
assert helper.__class__ == ActionClass
|
||||||
assert helper.decision == decision
|
assert helper.decision == decision
|
||||||
|
@ -2321,7 +2339,7 @@ class TestCinderDecision(TestCase):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
helper = decision.get_action_helper(
|
helper = decision.get_action_helper(
|
||||||
appealed_action=None, overriden_action=overridden_action
|
appealed_action=None, overridden_action=overridden_action
|
||||||
)
|
)
|
||||||
assert helper.reporter_template_path is None
|
assert helper.reporter_template_path is None
|
||||||
assert helper.reporter_appeal_template_path is None
|
assert helper.reporter_appeal_template_path is None
|
||||||
|
@ -2329,7 +2347,6 @@ class TestCinderDecision(TestCase):
|
||||||
assert ActionClass.reporter_appeal_template_path is not None
|
assert ActionClass.reporter_appeal_template_path is not None
|
||||||
|
|
||||||
def _test_appeal_as_target(self, *, resolvable_in_reviewer_tools):
|
def _test_appeal_as_target(self, *, resolvable_in_reviewer_tools):
|
||||||
user_factory(id=settings.TASK_USER_ID)
|
|
||||||
addon = addon_factory(
|
addon = addon_factory(
|
||||||
status=amo.STATUS_DISABLED,
|
status=amo.STATUS_DISABLED,
|
||||||
file_kw={'is_signed': True, 'status': amo.STATUS_DISABLED},
|
file_kw={'is_signed': True, 'status': amo.STATUS_DISABLED},
|
||||||
|
@ -2343,7 +2360,7 @@ class TestCinderDecision(TestCase):
|
||||||
resolvable_in_reviewer_tools=resolvable_in_reviewer_tools,
|
resolvable_in_reviewer_tools=resolvable_in_reviewer_tools,
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
cinder_id='4815162342-lost',
|
cinder_id='4815162342-lost',
|
||||||
date=self.days_ago(179),
|
action_date=self.days_ago(179),
|
||||||
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
addon=addon,
|
addon=addon,
|
||||||
),
|
),
|
||||||
|
@ -2405,7 +2422,7 @@ class TestCinderDecision(TestCase):
|
||||||
cinder_job=CinderJob.objects.create(
|
cinder_job=CinderJob.objects.create(
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
cinder_id='4815162342-lost',
|
cinder_id='4815162342-lost',
|
||||||
date=self.days_ago(179),
|
action_date=self.days_ago(179),
|
||||||
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
addon=addon,
|
addon=addon,
|
||||||
),
|
),
|
||||||
|
@ -2443,7 +2460,7 @@ class TestCinderDecision(TestCase):
|
||||||
cinder_job=CinderJob.objects.create(
|
cinder_job=CinderJob.objects.create(
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
cinder_id='4815162342-lost',
|
cinder_id='4815162342-lost',
|
||||||
date=self.days_ago(179),
|
action_date=self.days_ago(179),
|
||||||
# This (target is an add-on, decision is a user ban) shouldn't
|
# This (target is an add-on, decision is a user ban) shouldn't
|
||||||
# be possible but we want to make sure this is handled
|
# be possible but we want to make sure this is handled
|
||||||
# explicitly.
|
# explicitly.
|
||||||
|
@ -2487,7 +2504,7 @@ class TestCinderDecision(TestCase):
|
||||||
cinder_job=CinderJob.objects.create(
|
cinder_job=CinderJob.objects.create(
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
cinder_id='4815162342-lost',
|
cinder_id='4815162342-lost',
|
||||||
date=self.days_ago(179),
|
action_date=self.days_ago(179),
|
||||||
action=DECISION_ACTIONS.AMO_BAN_USER,
|
action=DECISION_ACTIONS.AMO_BAN_USER,
|
||||||
user=target,
|
user=target,
|
||||||
)
|
)
|
||||||
|
@ -2528,7 +2545,7 @@ class TestCinderDecision(TestCase):
|
||||||
target_addon=addon,
|
target_addon=addon,
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
cinder_id='4815162342-lost',
|
cinder_id='4815162342-lost',
|
||||||
date=self.days_ago(179),
|
action_date=self.days_ago(179),
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
addon=addon,
|
addon=addon,
|
||||||
),
|
),
|
||||||
|
@ -2572,7 +2589,7 @@ class TestCinderDecision(TestCase):
|
||||||
target_addon=addon,
|
target_addon=addon,
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
cinder_id='4815162342-lost',
|
cinder_id='4815162342-lost',
|
||||||
date=self.days_ago(179),
|
action_date=self.days_ago(179),
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
addon=addon,
|
addon=addon,
|
||||||
),
|
),
|
||||||
|
@ -2615,7 +2632,7 @@ class TestCinderDecision(TestCase):
|
||||||
cinder_job = CinderJob.objects.create(
|
cinder_job = CinderJob.objects.create(
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
cinder_id='4815162342-lost',
|
cinder_id='4815162342-lost',
|
||||||
date=self.days_ago(179),
|
action_date=self.days_ago(179),
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
addon=addon_factory(),
|
addon=addon_factory(),
|
||||||
)
|
)
|
||||||
|
@ -2638,7 +2655,7 @@ class TestCinderDecision(TestCase):
|
||||||
cinder_job = CinderJob.objects.create(
|
cinder_job = CinderJob.objects.create(
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
cinder_id='4815162342-lost',
|
cinder_id='4815162342-lost',
|
||||||
date=self.days_ago(179),
|
action_date=self.days_ago(179),
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
addon=addon,
|
addon=addon,
|
||||||
)
|
)
|
||||||
|
@ -2715,7 +2732,7 @@ class TestCinderDecision(TestCase):
|
||||||
assert request_body['enforcement_actions_slugs'] == [
|
assert request_body['enforcement_actions_slugs'] == [
|
||||||
cinder_action.api_value
|
cinder_action.api_value
|
||||||
]
|
]
|
||||||
self.assertCloseToNow(decision.date)
|
self.assertCloseToNow(decision.action_date)
|
||||||
assert list(decision.policies.all()) == policies
|
assert list(decision.policies.all()) == policies
|
||||||
assert CinderDecision.objects.count() == 1
|
assert CinderDecision.objects.count() == 1
|
||||||
assert decision.id
|
assert decision.id
|
||||||
|
@ -2730,7 +2747,7 @@ class TestCinderDecision(TestCase):
|
||||||
assert request_body['enforcement_actions_slugs'] == [
|
assert request_body['enforcement_actions_slugs'] == [
|
||||||
cinder_action.api_value
|
cinder_action.api_value
|
||||||
]
|
]
|
||||||
self.assertCloseToNow(decision.date)
|
self.assertCloseToNow(decision.action_date)
|
||||||
assert list(decision.policies.all()) == policies
|
assert list(decision.policies.all()) == policies
|
||||||
assert CinderDecision.objects.count() == 1
|
assert CinderDecision.objects.count() == 1
|
||||||
assert decision.id
|
assert decision.id
|
||||||
|
@ -2995,6 +3012,130 @@ class TestCinderDecision(TestCase):
|
||||||
not in mail.outbox[0].body
|
not in mail.outbox[0].body
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_process_action_ban_user_held(self):
|
||||||
|
user = user_factory(email='superstarops@mozilla.com')
|
||||||
|
decision = CinderDecision.objects.create(
|
||||||
|
user=user, action=DECISION_ACTIONS.AMO_BAN_USER
|
||||||
|
)
|
||||||
|
assert decision.action_date is None
|
||||||
|
decision.process_action()
|
||||||
|
assert decision.action_date is None
|
||||||
|
assert not user.reload().banned
|
||||||
|
assert (
|
||||||
|
ActivityLog.objects.filter(
|
||||||
|
action=amo.LOG.HELD_ACTION_ADMIN_USER_BANNED.id
|
||||||
|
).count()
|
||||||
|
== 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_process_action_ban_user(self):
|
||||||
|
user = user_factory()
|
||||||
|
decision = CinderDecision.objects.create(
|
||||||
|
user=user, action=DECISION_ACTIONS.AMO_BAN_USER
|
||||||
|
)
|
||||||
|
assert decision.action_date is None
|
||||||
|
decision.process_action()
|
||||||
|
self.assertCloseToNow(decision.action_date)
|
||||||
|
self.assertCloseToNow(user.reload().banned)
|
||||||
|
assert (
|
||||||
|
ActivityLog.objects.filter(action=amo.LOG.ADMIN_USER_BANNED.id).count() == 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_process_action_disable_addon_held(self):
|
||||||
|
addon = addon_factory()
|
||||||
|
self.make_addon_promoted(addon, RECOMMENDED, approve_version=True)
|
||||||
|
decision = CinderDecision.objects.create(
|
||||||
|
addon=addon, action=DECISION_ACTIONS.AMO_DISABLE_ADDON
|
||||||
|
)
|
||||||
|
assert decision.action_date is None
|
||||||
|
decision.process_action()
|
||||||
|
assert decision.action_date is None
|
||||||
|
assert addon.reload().status == amo.STATUS_APPROVED
|
||||||
|
assert (
|
||||||
|
ActivityLog.objects.filter(
|
||||||
|
action=amo.LOG.HELD_ACTION_FORCE_DISABLE.id
|
||||||
|
).count()
|
||||||
|
== 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_process_action_disable_addon(self):
|
||||||
|
addon = addon_factory()
|
||||||
|
decision = CinderDecision.objects.create(
|
||||||
|
addon=addon, action=DECISION_ACTIONS.AMO_DISABLE_ADDON
|
||||||
|
)
|
||||||
|
assert decision.action_date is None
|
||||||
|
decision.process_action()
|
||||||
|
self.assertCloseToNow(decision.action_date)
|
||||||
|
assert addon.reload().status == amo.STATUS_DISABLED
|
||||||
|
assert ActivityLog.objects.filter(action=amo.LOG.FORCE_DISABLE.id).count() == 1
|
||||||
|
|
||||||
|
def test_process_action_delete_collection_held(self):
|
||||||
|
collection = collection_factory(author=self.task_user)
|
||||||
|
decision = CinderDecision.objects.create(
|
||||||
|
collection=collection, action=DECISION_ACTIONS.AMO_DELETE_COLLECTION
|
||||||
|
)
|
||||||
|
assert decision.action_date is None
|
||||||
|
decision.process_action()
|
||||||
|
assert decision.action_date is None
|
||||||
|
assert not collection.reload().deleted
|
||||||
|
assert (
|
||||||
|
ActivityLog.objects.filter(
|
||||||
|
action=amo.LOG.HELD_ACTION_COLLECTION_DELETED.id
|
||||||
|
).count()
|
||||||
|
== 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_process_action_delete_collection(self):
|
||||||
|
collection = collection_factory(author=user_factory())
|
||||||
|
decision = CinderDecision.objects.create(
|
||||||
|
collection=collection, action=DECISION_ACTIONS.AMO_DELETE_COLLECTION
|
||||||
|
)
|
||||||
|
assert decision.action_date is None
|
||||||
|
decision.process_action()
|
||||||
|
self.assertCloseToNow(decision.action_date)
|
||||||
|
assert collection.reload().deleted
|
||||||
|
assert (
|
||||||
|
ActivityLog.objects.filter(action=amo.LOG.COLLECTION_DELETED.id).count()
|
||||||
|
== 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_process_action_delete_rating_held(self):
|
||||||
|
user = user_factory()
|
||||||
|
addon = addon_factory(users=[user])
|
||||||
|
rating = Rating.objects.create(
|
||||||
|
addon=addon,
|
||||||
|
user=user,
|
||||||
|
body='reply',
|
||||||
|
reply_to=Rating.objects.create(
|
||||||
|
addon=addon, user=user_factory(), body='sdsd'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
decision = CinderDecision.objects.create(
|
||||||
|
rating=rating, action=DECISION_ACTIONS.AMO_DELETE_RATING
|
||||||
|
)
|
||||||
|
self.make_addon_promoted(rating.addon, RECOMMENDED, approve_version=True)
|
||||||
|
assert decision.action_date is None
|
||||||
|
decision.process_action()
|
||||||
|
assert decision.action_date is None
|
||||||
|
assert not rating.reload().deleted
|
||||||
|
assert (
|
||||||
|
ActivityLog.objects.filter(
|
||||||
|
action=amo.LOG.HELD_ACTION_DELETE_RATING.id
|
||||||
|
).count()
|
||||||
|
== 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_process_action_delete_rating(self):
|
||||||
|
rating = Rating.objects.create(addon=addon_factory(), user=user_factory())
|
||||||
|
decision = CinderDecision.objects.create(
|
||||||
|
rating=rating, action=DECISION_ACTIONS.AMO_DELETE_RATING
|
||||||
|
)
|
||||||
|
assert decision.action_date is None
|
||||||
|
decision.process_action()
|
||||||
|
self.assertCloseToNow(decision.action_date)
|
||||||
|
assert rating.reload().deleted
|
||||||
|
assert ActivityLog.objects.filter(action=amo.LOG.DELETE_RATING.id).count() == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
|
@ -434,6 +434,7 @@ def test_addon_appeal_to_cinder_reporter(statsd_incr_mock):
|
||||||
cinder_id='4815162342-abc',
|
cinder_id='4815162342-abc',
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
addon=addon,
|
addon=addon,
|
||||||
|
action_date=datetime.now(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
abuse_report = AbuseReport.objects.create(
|
abuse_report = AbuseReport.objects.create(
|
||||||
|
@ -495,6 +496,7 @@ def test_addon_appeal_to_cinder_reporter_exception(statsd_incr_mock):
|
||||||
cinder_id='4815162342-abc',
|
cinder_id='4815162342-abc',
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
addon=addon,
|
addon=addon,
|
||||||
|
action_date=datetime.now(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
abuse_report = AbuseReport.objects.create(
|
abuse_report = AbuseReport.objects.create(
|
||||||
|
@ -536,6 +538,7 @@ def test_addon_appeal_to_cinder_authenticated_reporter():
|
||||||
cinder_id='4815162342-abc',
|
cinder_id='4815162342-abc',
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
addon=addon,
|
addon=addon,
|
||||||
|
action_date=datetime.now(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
abuse_report = AbuseReport.objects.create(
|
abuse_report = AbuseReport.objects.create(
|
||||||
|
@ -593,6 +596,7 @@ def test_addon_appeal_to_cinder_authenticated_author():
|
||||||
cinder_id='4815162342-abc',
|
cinder_id='4815162342-abc',
|
||||||
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
addon=addon,
|
addon=addon,
|
||||||
|
action_date=datetime.now(),
|
||||||
)
|
)
|
||||||
responses.add(
|
responses.add(
|
||||||
responses.POST,
|
responses.POST,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
@ -6,9 +8,10 @@ from waffle.testutils import override_switch
|
||||||
|
|
||||||
from olympia import amo
|
from olympia import amo
|
||||||
from olympia.activity.models import ActivityLog, ActivityLogToken
|
from olympia.activity.models import ActivityLog, ActivityLogToken
|
||||||
from olympia.addons.models import Addon
|
from olympia.addons.models import Addon, AddonUser
|
||||||
from olympia.amo.tests import TestCase, addon_factory, collection_factory, user_factory
|
from olympia.amo.tests import TestCase, addon_factory, collection_factory, user_factory
|
||||||
from olympia.constants.abuse import DECISION_ACTIONS
|
from olympia.constants.abuse import DECISION_ACTIONS
|
||||||
|
from olympia.constants.promoted import RECOMMENDED
|
||||||
from olympia.core import set_user
|
from olympia.core import set_user
|
||||||
from olympia.ratings.models import Rating
|
from olympia.ratings.models import Rating
|
||||||
|
|
||||||
|
@ -37,22 +40,22 @@ class BaseTestCinderAction:
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
notes="extra note's",
|
notes="extra note's",
|
||||||
addon=addon,
|
addon=addon,
|
||||||
|
action_date=datetime.now(),
|
||||||
)
|
)
|
||||||
self.cinder_job = CinderJob.objects.create(
|
self.cinder_job = CinderJob.objects.create(
|
||||||
job_id='1234', decision=self.decision
|
job_id='1234', decision=self.decision
|
||||||
)
|
)
|
||||||
self.decision.policies.add(
|
self.policy = CinderPolicy.objects.create(
|
||||||
CinderPolicy.objects.create(
|
uuid='1234',
|
||||||
uuid='1234',
|
name='Bad policy',
|
||||||
name='Bad policy',
|
text='This is bad thing',
|
||||||
text='This is bad thing',
|
parent=CinderPolicy.objects.create(
|
||||||
parent=CinderPolicy.objects.create(
|
uuid='p4r3nt',
|
||||||
uuid='p4r3nt',
|
name='Parent Policy',
|
||||||
name='Parent Policy',
|
text='Parent policy text',
|
||||||
text='Parent policy text',
|
),
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
self.decision.policies.add(self.policy)
|
||||||
self.abuse_report_no_auth = AbuseReport.objects.create(
|
self.abuse_report_no_auth = AbuseReport.objects.create(
|
||||||
reason=AbuseReport.REASONS.HATEFUL_VIOLENT_DECEPTIVE,
|
reason=AbuseReport.REASONS.HATEFUL_VIOLENT_DECEPTIVE,
|
||||||
guid=addon.guid,
|
guid=addon.guid,
|
||||||
|
@ -332,14 +335,18 @@ class TestCinderActionUser(BaseTestCinderAction, TestCase):
|
||||||
def _test_ban_user(self):
|
def _test_ban_user(self):
|
||||||
self.decision.update(action=DECISION_ACTIONS.AMO_BAN_USER)
|
self.decision.update(action=DECISION_ACTIONS.AMO_BAN_USER)
|
||||||
action = self.ActionClass(self.decision)
|
action = self.ActionClass(self.decision)
|
||||||
assert action.process_action() is None
|
activity = action.process_action()
|
||||||
|
assert activity.log == amo.LOG.ADMIN_USER_BANNED
|
||||||
|
assert ActivityLog.objects.count() == 1
|
||||||
|
assert activity.arguments == [self.user, self.policy]
|
||||||
|
assert activity.user == self.task_user
|
||||||
|
assert activity.details == {
|
||||||
|
'comments': self.decision.notes,
|
||||||
|
'cinder_action': DECISION_ACTIONS.AMO_BAN_USER,
|
||||||
|
}
|
||||||
|
|
||||||
self.user.reload()
|
self.user.reload()
|
||||||
self.assertCloseToNow(self.user.banned)
|
self.assertCloseToNow(self.user.banned)
|
||||||
assert ActivityLog.objects.count() == 1
|
|
||||||
activity = ActivityLog.objects.get(action=amo.LOG.ADMIN_USER_BANNED.id)
|
|
||||||
assert activity.arguments == [self.user]
|
|
||||||
assert activity.user == self.task_user
|
|
||||||
assert len(mail.outbox) == 0
|
assert len(mail.outbox) == 0
|
||||||
|
|
||||||
self.cinder_job.notify_reporters(action)
|
self.cinder_job.notify_reporters(action)
|
||||||
|
@ -420,6 +427,43 @@ class TestCinderActionUser(BaseTestCinderAction, TestCase):
|
||||||
action.notify_owners()
|
action.notify_owners()
|
||||||
self._test_owner_affirmation_email(f'Mozilla Add-ons: {self.user.name}')
|
self._test_owner_affirmation_email(f'Mozilla Add-ons: {self.user.name}')
|
||||||
|
|
||||||
|
def test_should_hold_action(self):
|
||||||
|
self.decision.update(action=DECISION_ACTIONS.AMO_BAN_USER)
|
||||||
|
action = self.ActionClass(self.decision)
|
||||||
|
assert action.should_hold_action() is False
|
||||||
|
|
||||||
|
self.user.update(email='superstarops@mozilla.com')
|
||||||
|
assert action.should_hold_action() is True
|
||||||
|
|
||||||
|
self.user.update(email='foo@baa')
|
||||||
|
assert action.should_hold_action() is False
|
||||||
|
del self.user.groups_list
|
||||||
|
self.grant_permission(self.user, 'this:thing')
|
||||||
|
assert action.should_hold_action() is True
|
||||||
|
|
||||||
|
self.user.groups_list = []
|
||||||
|
assert action.should_hold_action() is False
|
||||||
|
addon = addon_factory(users=[self.user])
|
||||||
|
assert action.should_hold_action() is False
|
||||||
|
self.make_addon_promoted(addon, RECOMMENDED, approve_version=True)
|
||||||
|
assert action.should_hold_action() is True
|
||||||
|
|
||||||
|
self.user.banned = datetime.now()
|
||||||
|
assert action.should_hold_action() is False
|
||||||
|
|
||||||
|
def test_hold_action(self):
|
||||||
|
self.decision.update(action=DECISION_ACTIONS.AMO_BAN_USER)
|
||||||
|
action = self.ActionClass(self.decision)
|
||||||
|
activity = action.hold_action()
|
||||||
|
assert activity.log == amo.LOG.HELD_ACTION_ADMIN_USER_BANNED
|
||||||
|
assert ActivityLog.objects.count() == 1
|
||||||
|
assert activity.arguments == [self.user, self.policy]
|
||||||
|
assert activity.user == self.task_user
|
||||||
|
assert activity.details == {
|
||||||
|
'comments': self.decision.notes,
|
||||||
|
'cinder_action': DECISION_ACTIONS.AMO_BAN_USER,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@override_switch('dsa-cinder-forwarded-review', active=True)
|
@override_switch('dsa-cinder-forwarded-review', active=True)
|
||||||
@override_switch('dsa-appeals-review', active=True)
|
@override_switch('dsa-appeals-review', active=True)
|
||||||
|
@ -442,7 +486,7 @@ class TestCinderActionAddon(BaseTestCinderAction, TestCase):
|
||||||
assert activity.log == amo.LOG.FORCE_DISABLE
|
assert activity.log == amo.LOG.FORCE_DISABLE
|
||||||
assert self.addon.reload().status == amo.STATUS_DISABLED
|
assert self.addon.reload().status == amo.STATUS_DISABLED
|
||||||
assert ActivityLog.objects.count() == 1
|
assert ActivityLog.objects.count() == 1
|
||||||
assert activity.arguments == [self.addon]
|
assert activity.arguments == [self.addon, self.policy]
|
||||||
assert activity.user == self.task_user
|
assert activity.user == self.task_user
|
||||||
assert len(mail.outbox) == 0
|
assert len(mail.outbox) == 0
|
||||||
|
|
||||||
|
@ -762,6 +806,30 @@ class TestCinderActionAddon(BaseTestCinderAction, TestCase):
|
||||||
)
|
)
|
||||||
assert 'right to appeal' not in mail_item.body
|
assert 'right to appeal' not in mail_item.body
|
||||||
|
|
||||||
|
def test_should_hold_action(self):
|
||||||
|
self.decision.update(action=DECISION_ACTIONS.AMO_DISABLE_ADDON)
|
||||||
|
action = self.ActionClass(self.decision)
|
||||||
|
assert action.should_hold_action() is False
|
||||||
|
|
||||||
|
self.make_addon_promoted(self.addon, RECOMMENDED, approve_version=True)
|
||||||
|
assert action.should_hold_action() is True
|
||||||
|
|
||||||
|
self.addon.status = amo.STATUS_DISABLED
|
||||||
|
assert action.should_hold_action() is False
|
||||||
|
|
||||||
|
def test_hold_action(self):
|
||||||
|
self.decision.update(action=DECISION_ACTIONS.AMO_DISABLE_ADDON)
|
||||||
|
action = self.ActionClass(self.decision)
|
||||||
|
activity = action.hold_action()
|
||||||
|
assert activity.log == amo.LOG.HELD_ACTION_FORCE_DISABLE
|
||||||
|
assert ActivityLog.objects.count() == 1
|
||||||
|
assert activity.arguments == [self.addon, self.policy]
|
||||||
|
assert activity.user == self.task_user
|
||||||
|
assert activity.details == {
|
||||||
|
'comments': self.decision.notes,
|
||||||
|
'cinder_action': DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestCinderActionCollection(BaseTestCinderAction, TestCase):
|
class TestCinderActionCollection(BaseTestCinderAction, TestCase):
|
||||||
ActionClass = CinderActionDeleteCollection
|
ActionClass = CinderActionDeleteCollection
|
||||||
|
@ -788,7 +856,7 @@ class TestCinderActionCollection(BaseTestCinderAction, TestCase):
|
||||||
assert ActivityLog.objects.count() == 1
|
assert ActivityLog.objects.count() == 1
|
||||||
activity = ActivityLog.objects.get(action=amo.LOG.COLLECTION_DELETED.id)
|
activity = ActivityLog.objects.get(action=amo.LOG.COLLECTION_DELETED.id)
|
||||||
assert activity == log_entry
|
assert activity == log_entry
|
||||||
assert activity.arguments == [self.collection]
|
assert activity.arguments == [self.collection, self.policy]
|
||||||
assert activity.user == self.task_user
|
assert activity.user == self.task_user
|
||||||
assert len(mail.outbox) == 0
|
assert len(mail.outbox) == 0
|
||||||
|
|
||||||
|
@ -870,6 +938,30 @@ class TestCinderActionCollection(BaseTestCinderAction, TestCase):
|
||||||
action.notify_owners()
|
action.notify_owners()
|
||||||
self._test_owner_affirmation_email(f'Mozilla Add-ons: {self.collection.name}')
|
self._test_owner_affirmation_email(f'Mozilla Add-ons: {self.collection.name}')
|
||||||
|
|
||||||
|
def test_should_hold_action(self):
|
||||||
|
self.decision.update(action=DECISION_ACTIONS.AMO_DELETE_COLLECTION)
|
||||||
|
action = self.ActionClass(self.decision)
|
||||||
|
assert action.should_hold_action() is False
|
||||||
|
|
||||||
|
self.collection.update(author=self.task_user)
|
||||||
|
assert action.should_hold_action() is True
|
||||||
|
|
||||||
|
self.collection.deleted = True
|
||||||
|
assert action.should_hold_action() is False
|
||||||
|
|
||||||
|
def test_hold_action(self):
|
||||||
|
self.decision.update(action=DECISION_ACTIONS.AMO_DELETE_COLLECTION)
|
||||||
|
action = self.ActionClass(self.decision)
|
||||||
|
activity = action.hold_action()
|
||||||
|
assert activity.log == amo.LOG.HELD_ACTION_COLLECTION_DELETED
|
||||||
|
assert ActivityLog.objects.count() == 1
|
||||||
|
assert activity.arguments == [self.collection, self.policy]
|
||||||
|
assert activity.user == self.task_user
|
||||||
|
assert activity.details == {
|
||||||
|
'comments': self.decision.notes,
|
||||||
|
'cinder_action': DECISION_ACTIONS.AMO_DELETE_COLLECTION,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestCinderActionRating(BaseTestCinderAction, TestCase):
|
class TestCinderActionRating(BaseTestCinderAction, TestCase):
|
||||||
ActionClass = CinderActionDeleteRating
|
ActionClass = CinderActionDeleteRating
|
||||||
|
@ -887,13 +979,21 @@ class TestCinderActionRating(BaseTestCinderAction, TestCase):
|
||||||
def _test_delete_rating(self):
|
def _test_delete_rating(self):
|
||||||
self.decision.update(action=DECISION_ACTIONS.AMO_DELETE_RATING)
|
self.decision.update(action=DECISION_ACTIONS.AMO_DELETE_RATING)
|
||||||
action = self.ActionClass(self.decision)
|
action = self.ActionClass(self.decision)
|
||||||
assert action.process_action() is None
|
activity = action.process_action()
|
||||||
|
assert activity.log == amo.LOG.DELETE_RATING
|
||||||
|
assert ActivityLog.objects.count() == 1
|
||||||
|
assert activity.arguments == [self.rating, self.policy, self.rating.addon]
|
||||||
|
assert activity.user == self.task_user
|
||||||
|
assert activity.details == {
|
||||||
|
'comments': self.decision.notes,
|
||||||
|
'cinder_action': DECISION_ACTIONS.AMO_DELETE_RATING,
|
||||||
|
'addon_id': self.rating.addon_id,
|
||||||
|
'addon_title': str(self.rating.addon.name),
|
||||||
|
'body': self.rating.body,
|
||||||
|
'is_flagged': False,
|
||||||
|
}
|
||||||
|
|
||||||
assert self.rating.reload().deleted
|
assert self.rating.reload().deleted
|
||||||
assert ActivityLog.objects.count() == 1
|
|
||||||
activity = ActivityLog.objects.get(action=amo.LOG.DELETE_RATING.id)
|
|
||||||
assert activity.arguments == [self.rating.addon, self.rating]
|
|
||||||
assert activity.user == self.task_user
|
|
||||||
assert len(mail.outbox) == 0
|
assert len(mail.outbox) == 0
|
||||||
|
|
||||||
self.cinder_job.notify_reporters(action)
|
self.cinder_job.notify_reporters(action)
|
||||||
|
@ -977,3 +1077,35 @@ class TestCinderActionRating(BaseTestCinderAction, TestCase):
|
||||||
self._test_owner_affirmation_email(
|
self._test_owner_affirmation_email(
|
||||||
f'Mozilla Add-ons: "Saying ..." for {self.rating.addon.name}'
|
f'Mozilla Add-ons: "Saying ..." for {self.rating.addon.name}'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_should_hold_action(self):
|
||||||
|
self.decision.update(action=DECISION_ACTIONS.AMO_DELETE_RATING)
|
||||||
|
action = self.ActionClass(self.decision)
|
||||||
|
assert action.should_hold_action() is False
|
||||||
|
|
||||||
|
AddonUser.objects.create(addon=self.rating.addon, user=self.rating.user)
|
||||||
|
assert action.should_hold_action() is False
|
||||||
|
self.make_addon_promoted(self.rating.addon, RECOMMENDED, approve_version=True)
|
||||||
|
assert action.should_hold_action() is False
|
||||||
|
self.rating.update(
|
||||||
|
reply_to=Rating.objects.create(
|
||||||
|
addon=self.rating.addon, user=user_factory(), body='original'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert action.should_hold_action() is True
|
||||||
|
|
||||||
|
self.rating.update(deleted=self.rating.id)
|
||||||
|
assert action.should_hold_action() is False
|
||||||
|
|
||||||
|
def test_hold_action(self):
|
||||||
|
self.decision.update(action=DECISION_ACTIONS.AMO_DELETE_RATING)
|
||||||
|
action = self.ActionClass(self.decision)
|
||||||
|
activity = action.hold_action()
|
||||||
|
assert activity.log == amo.LOG.HELD_ACTION_DELETE_RATING
|
||||||
|
assert ActivityLog.objects.count() == 1
|
||||||
|
assert activity.arguments == [self.rating, self.policy, self.rating.addon]
|
||||||
|
assert activity.user == self.task_user
|
||||||
|
assert activity.details == {
|
||||||
|
'comments': self.decision.notes,
|
||||||
|
'cinder_action': DECISION_ACTIONS.AMO_DELETE_RATING,
|
||||||
|
}
|
||||||
|
|
|
@ -1270,7 +1270,6 @@ class TestCinderWebhook(TestCase):
|
||||||
process_mock.assert_called()
|
process_mock.assert_called()
|
||||||
process_mock.assert_called_with(
|
process_mock.assert_called_with(
|
||||||
decision_cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
decision_cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
||||||
decision_date=datetime(2023, 10, 12, 9, 8, 37, 4789),
|
|
||||||
decision_action=DECISION_ACTIONS.AMO_DISABLE_ADDON.value,
|
decision_action=DECISION_ACTIONS.AMO_DISABLE_ADDON.value,
|
||||||
decision_notes='some notes',
|
decision_notes='some notes',
|
||||||
policy_ids=['f73ad527-54ed-430c-86ff-80e15e2a352b'],
|
policy_ids=['f73ad527-54ed-430c-86ff-80e15e2a352b'],
|
||||||
|
@ -1287,7 +1286,6 @@ class TestCinderWebhook(TestCase):
|
||||||
original_cinder_job = CinderJob.objects.get()
|
original_cinder_job = CinderJob.objects.get()
|
||||||
original_cinder_job.update(
|
original_cinder_job.update(
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
date=datetime(2023, 10, 12, 9, 8, 37, 4789),
|
|
||||||
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
appeal_job=CinderJob.objects.create(
|
appeal_job=CinderJob.objects.create(
|
||||||
|
@ -1302,7 +1300,6 @@ class TestCinderWebhook(TestCase):
|
||||||
assert process_mock.call_count == 1
|
assert process_mock.call_count == 1
|
||||||
process_mock.assert_called_with(
|
process_mock.assert_called_with(
|
||||||
decision_cinder_id='76e0006d-1a42-4ec7-9475-148bab1970f1',
|
decision_cinder_id='76e0006d-1a42-4ec7-9475-148bab1970f1',
|
||||||
decision_date=datetime(2024, 4, 24, 17, 45, 32, 8810),
|
|
||||||
decision_action=DECISION_ACTIONS.AMO_APPROVE.value,
|
decision_action=DECISION_ACTIONS.AMO_APPROVE.value,
|
||||||
decision_notes='still no!',
|
decision_notes='still no!',
|
||||||
policy_ids=['1c5d711a-78b7-4fc2-bdef-9a33024f5e8b'],
|
policy_ids=['1c5d711a-78b7-4fc2-bdef-9a33024f5e8b'],
|
||||||
|
@ -1325,7 +1322,7 @@ class TestCinderWebhook(TestCase):
|
||||||
original_cinder_job = CinderJob.objects.get()
|
original_cinder_job = CinderJob.objects.get()
|
||||||
original_cinder_job.update(
|
original_cinder_job.update(
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
date=datetime(2023, 10, 12, 9, 8, 37, 4789),
|
action_date=datetime(2023, 10, 12, 9, 8, 37, 4789),
|
||||||
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
appeal_job=CinderJob.objects.create(
|
appeal_job=CinderJob.objects.create(
|
||||||
|
@ -1340,7 +1337,6 @@ class TestCinderWebhook(TestCase):
|
||||||
assert process_mock.call_count == 1
|
assert process_mock.call_count == 1
|
||||||
process_mock.assert_called_with(
|
process_mock.assert_called_with(
|
||||||
decision_cinder_id='4f18b22c-6078-4934-b395-6a2e01cadf63',
|
decision_cinder_id='4f18b22c-6078-4934-b395-6a2e01cadf63',
|
||||||
decision_date=datetime(2024, 4, 24, 18, 19, 30, 274623),
|
|
||||||
decision_action=DECISION_ACTIONS.AMO_DISABLE_ADDON.value,
|
decision_action=DECISION_ACTIONS.AMO_DISABLE_ADDON.value,
|
||||||
decision_notes="fine I'll disable it",
|
decision_notes="fine I'll disable it",
|
||||||
policy_ids=[
|
policy_ids=[
|
||||||
|
@ -1359,7 +1355,7 @@ class TestCinderWebhook(TestCase):
|
||||||
original_cinder_job = CinderJob.objects.get()
|
original_cinder_job = CinderJob.objects.get()
|
||||||
original_cinder_job.update(
|
original_cinder_job.update(
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
date=datetime(2023, 10, 12, 9, 8, 37, 4789),
|
action_date=datetime(2023, 10, 12, 9, 8, 37, 4789),
|
||||||
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
||||||
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
appeal_job=CinderJob.objects.create(
|
appeal_job=CinderJob.objects.create(
|
||||||
|
@ -1386,7 +1382,7 @@ class TestCinderWebhook(TestCase):
|
||||||
original_cinder_job = CinderJob.objects.get()
|
original_cinder_job = CinderJob.objects.get()
|
||||||
original_cinder_job.update(
|
original_cinder_job.update(
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
date=datetime(2023, 10, 12, 9, 8, 37, 4789),
|
action_date=datetime(2023, 10, 12, 9, 8, 37, 4789),
|
||||||
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
||||||
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
appeal_job=CinderJob.objects.create(
|
appeal_job=CinderJob.objects.create(
|
||||||
|
@ -1413,7 +1409,7 @@ class TestCinderWebhook(TestCase):
|
||||||
original_cinder_job = CinderJob.objects.get()
|
original_cinder_job = CinderJob.objects.get()
|
||||||
original_cinder_job.update(
|
original_cinder_job.update(
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
date=datetime(2023, 10, 12, 9, 8, 37, 4789),
|
action_date=datetime(2023, 10, 12, 9, 8, 37, 4789),
|
||||||
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
appeal_job=CinderJob.objects.create(
|
appeal_job=CinderJob.objects.create(
|
||||||
|
@ -1448,7 +1444,7 @@ class TestCinderWebhook(TestCase):
|
||||||
original_cinder_job = CinderJob.objects.get()
|
original_cinder_job = CinderJob.objects.get()
|
||||||
original_cinder_job.update(
|
original_cinder_job.update(
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
date=datetime(2023, 10, 12, 9, 8, 37, 4789),
|
action_date=datetime(2023, 10, 12, 9, 8, 37, 4789),
|
||||||
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
appeal_job=CinderJob.objects.create(
|
appeal_job=CinderJob.objects.create(
|
||||||
|
@ -2472,7 +2468,7 @@ class TestAppeal(TestCase):
|
||||||
decision=CinderDecision.objects.create(
|
decision=CinderDecision.objects.create(
|
||||||
cinder_id='my-decision-id',
|
cinder_id='my-decision-id',
|
||||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
date=self.days_ago(1),
|
action_date=self.days_ago(1),
|
||||||
addon=self.addon,
|
addon=self.addon,
|
||||||
),
|
),
|
||||||
created=self.days_ago(2),
|
created=self.days_ago(2),
|
||||||
|
@ -2599,7 +2595,7 @@ class TestAppeal(TestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_appeal_approval_anonymous_report_with_email_post_cant_be_appealed(self):
|
def test_appeal_approval_anonymous_report_with_email_post_cant_be_appealed(self):
|
||||||
self.cinder_job.decision.update(date=self.days_ago(200))
|
self.cinder_job.decision.update(action_date=self.days_ago(200))
|
||||||
self.abuse_report.update(reporter_email='me@example.com')
|
self.abuse_report.update(reporter_email='me@example.com')
|
||||||
response = self.client.get(self.reporter_appeal_url)
|
response = self.client.get(self.reporter_appeal_url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
@ -2654,7 +2650,7 @@ class TestAppeal(TestCase):
|
||||||
assert self.appeal_mock.call_count == 0
|
assert self.appeal_mock.call_count == 0
|
||||||
|
|
||||||
def test_appeal_approval_logged_in_report_cant_be_appealed(self):
|
def test_appeal_approval_logged_in_report_cant_be_appealed(self):
|
||||||
self.cinder_job.decision.update(date=self.days_ago(200))
|
self.cinder_job.decision.update(action_date=self.days_ago(200))
|
||||||
self.user = user_factory()
|
self.user = user_factory()
|
||||||
self.abuse_report.update(reporter=self.user)
|
self.abuse_report.update(reporter=self.user)
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
|
@ -2720,6 +2716,7 @@ class TestAppeal(TestCase):
|
||||||
addon=self.addon,
|
addon=self.addon,
|
||||||
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
cinder_id='some-decision-id',
|
cinder_id='some-decision-id',
|
||||||
|
action_date=datetime.now(),
|
||||||
)
|
)
|
||||||
author_appeal_url = reverse(
|
author_appeal_url = reverse(
|
||||||
'abuse.appeal_author', kwargs={'decision_cinder_id': decision.cinder_id}
|
'abuse.appeal_author', kwargs={'decision_cinder_id': decision.cinder_id}
|
||||||
|
|
|
@ -51,11 +51,34 @@ class CinderAction:
|
||||||
f'{self.valid_targets}'
|
f'{self.valid_targets}'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def log_action(self, activity_log_action, *extra_args, extra_details=None):
|
||||||
|
return log_create(
|
||||||
|
activity_log_action,
|
||||||
|
self.target,
|
||||||
|
*(self.decision.policies.all()),
|
||||||
|
*extra_args,
|
||||||
|
details={
|
||||||
|
'comments': self.decision.notes,
|
||||||
|
'cinder_action': self.decision.action,
|
||||||
|
**(extra_details or {}),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def should_hold_action(self):
|
||||||
|
"""This should return false if the action should be processed immediately,
|
||||||
|
without further checks, and true if it should be held for further review."""
|
||||||
|
return False
|
||||||
|
|
||||||
def process_action(self):
|
def process_action(self):
|
||||||
"""This method should return an activity log instance for the action,
|
"""This method should return an activity log instance for the action,
|
||||||
if available."""
|
if available."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def hold_action(self):
|
||||||
|
"""This method should take no action, but create an activity log instance with
|
||||||
|
appropriate details."""
|
||||||
|
pass
|
||||||
|
|
||||||
def get_owners(self):
|
def get_owners(self):
|
||||||
"""No owner emails will be sent. Override to send owner emails"""
|
"""No owner emails will be sent. Override to send owner emails"""
|
||||||
return ()
|
return ()
|
||||||
|
@ -239,12 +262,30 @@ class CinderActionBanUser(CinderAction):
|
||||||
reporter_template_path = 'abuse/emails/reporter_takedown_user.txt'
|
reporter_template_path = 'abuse/emails/reporter_takedown_user.txt'
|
||||||
reporter_appeal_template_path = 'abuse/emails/reporter_appeal_takedown.txt'
|
reporter_appeal_template_path = 'abuse/emails/reporter_appeal_takedown.txt'
|
||||||
|
|
||||||
|
def should_hold_action(self):
|
||||||
|
return bool(
|
||||||
|
not self.target.banned
|
||||||
|
and (
|
||||||
|
self.target.is_staff # mozilla.com
|
||||||
|
or self.target.groups_list # has any permissions
|
||||||
|
# owns a high profile add-on
|
||||||
|
or any(
|
||||||
|
addon.promoted_group().high_profile
|
||||||
|
for addon in self.target.addons.all()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def process_action(self):
|
def process_action(self):
|
||||||
if not self.target.banned:
|
if not self.target.banned:
|
||||||
UserProfile.objects.filter(
|
UserProfile.objects.filter(
|
||||||
pk=self.target.pk
|
pk=self.target.pk
|
||||||
).ban_and_disable_related_content()
|
).ban_and_disable_related_content(skip_activity_log=True)
|
||||||
return None
|
return self.log_action(amo.LOG.ADMIN_USER_BANNED)
|
||||||
|
|
||||||
|
def hold_action(self):
|
||||||
|
if not self.target.banned:
|
||||||
|
return self.log_action(amo.LOG.HELD_ACTION_ADMIN_USER_BANNED)
|
||||||
|
|
||||||
def get_owners(self):
|
def get_owners(self):
|
||||||
return [self.target]
|
return [self.target]
|
||||||
|
@ -256,10 +297,22 @@ class CinderActionDisableAddon(CinderAction):
|
||||||
reporter_template_path = 'abuse/emails/reporter_takedown_addon.txt'
|
reporter_template_path = 'abuse/emails/reporter_takedown_addon.txt'
|
||||||
reporter_appeal_template_path = 'abuse/emails/reporter_appeal_takedown.txt'
|
reporter_appeal_template_path = 'abuse/emails/reporter_appeal_takedown.txt'
|
||||||
|
|
||||||
|
def should_hold_action(self):
|
||||||
|
return bool(
|
||||||
|
self.target.status != amo.STATUS_DISABLED
|
||||||
|
# is a high profile add-on
|
||||||
|
and self.target.promoted_group().high_profile
|
||||||
|
)
|
||||||
|
|
||||||
def process_action(self):
|
def process_action(self):
|
||||||
if self.target.status != amo.STATUS_DISABLED:
|
if self.target.status != amo.STATUS_DISABLED:
|
||||||
self.target.force_disable(skip_activity_log=True)
|
self.target.force_disable(skip_activity_log=True)
|
||||||
return log_create(amo.LOG.FORCE_DISABLE, self.target)
|
return self.log_action(amo.LOG.FORCE_DISABLE)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def hold_action(self):
|
||||||
|
if self.target.status != amo.STATUS_DISABLED:
|
||||||
|
return self.log_action(amo.LOG.HELD_ACTION_FORCE_DISABLE)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_owners(self):
|
def get_owners(self):
|
||||||
|
@ -269,10 +322,19 @@ class CinderActionDisableAddon(CinderAction):
|
||||||
class CinderActionRejectVersion(CinderActionDisableAddon):
|
class CinderActionRejectVersion(CinderActionDisableAddon):
|
||||||
description = 'Add-on version(s) have been rejected'
|
description = 'Add-on version(s) have been rejected'
|
||||||
|
|
||||||
|
def should_hold_action(self):
|
||||||
|
# This action should only be used by reviewer tools, not cinder webhook
|
||||||
|
# eventually, if add-on becomes non-public do as disable
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def process_action(self):
|
def process_action(self):
|
||||||
# This action should only be used by reviewer tools, not cinder webhook
|
# This action should only be used by reviewer tools, not cinder webhook
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def hold_action(self):
|
||||||
|
# This action should only be used by reviewer tools, not cinder webhook
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class CinderActionRejectVersionDelayed(CinderActionRejectVersion):
|
class CinderActionRejectVersionDelayed(CinderActionRejectVersion):
|
||||||
description = 'Add-on version(s) will be rejected'
|
description = 'Add-on version(s) will be rejected'
|
||||||
|
@ -295,10 +357,21 @@ class CinderActionDeleteCollection(CinderAction):
|
||||||
reporter_template_path = 'abuse/emails/reporter_takedown_collection.txt'
|
reporter_template_path = 'abuse/emails/reporter_takedown_collection.txt'
|
||||||
reporter_appeal_template_path = 'abuse/emails/reporter_appeal_takedown.txt'
|
reporter_appeal_template_path = 'abuse/emails/reporter_appeal_takedown.txt'
|
||||||
|
|
||||||
|
def should_hold_action(self):
|
||||||
|
return (
|
||||||
|
# Mozilla-owned collection
|
||||||
|
not self.target.deleted and self.target.author_id == settings.TASK_USER_ID
|
||||||
|
)
|
||||||
|
|
||||||
def process_action(self):
|
def process_action(self):
|
||||||
if not self.target.deleted:
|
if not self.target.deleted:
|
||||||
self.target.delete(clear_slug=False)
|
self.target.delete(clear_slug=False)
|
||||||
return log_create(amo.LOG.COLLECTION_DELETED, self.target)
|
return self.log_action(amo.LOG.COLLECTION_DELETED)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def hold_action(self):
|
||||||
|
if not self.target.deleted:
|
||||||
|
return self.log_action(amo.LOG.HELD_ACTION_COLLECTION_DELETED)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_owners(self):
|
def get_owners(self):
|
||||||
|
@ -311,9 +384,32 @@ class CinderActionDeleteRating(CinderAction):
|
||||||
reporter_template_path = 'abuse/emails/reporter_takedown_rating.txt'
|
reporter_template_path = 'abuse/emails/reporter_takedown_rating.txt'
|
||||||
reporter_appeal_template_path = 'abuse/emails/reporter_appeal_takedown.txt'
|
reporter_appeal_template_path = 'abuse/emails/reporter_appeal_takedown.txt'
|
||||||
|
|
||||||
|
def should_hold_action(self):
|
||||||
|
# Developer reply in recommended or partner extensions
|
||||||
|
return bool(
|
||||||
|
not self.target.deleted
|
||||||
|
and self.target.reply_to
|
||||||
|
and self.target.addon.promoted_group().high_profile_rating
|
||||||
|
)
|
||||||
|
|
||||||
def process_action(self):
|
def process_action(self):
|
||||||
if not self.target.deleted:
|
if not self.target.deleted:
|
||||||
self.target.delete(clear_flags=False)
|
self.target.delete(skip_activity_log=True, clear_flags=False)
|
||||||
|
return self.log_action(
|
||||||
|
amo.LOG.DELETE_RATING,
|
||||||
|
self.target.addon,
|
||||||
|
extra_details={
|
||||||
|
'body': str(self.target.body),
|
||||||
|
'addon_id': self.target.addon.pk,
|
||||||
|
'addon_title': str(self.target.addon.name),
|
||||||
|
'is_flagged': self.target.ratingflag_set.exists(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def hold_action(self):
|
||||||
|
if not self.target.deleted:
|
||||||
|
return self.log_action(amo.LOG.HELD_ACTION_DELETE_RATING, self.target.addon)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_owners(self):
|
def get_owners(self):
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
from datetime import datetime, timezone
|
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -169,18 +168,6 @@ class CinderInboundPermission:
|
||||||
return hmac.compare_digest(header, digest)
|
return hmac.compare_digest(header, digest)
|
||||||
|
|
||||||
|
|
||||||
def process_datestamp(date_string):
|
|
||||||
try:
|
|
||||||
return (
|
|
||||||
datetime.fromisoformat(date_string.replace(' ', ''))
|
|
||||||
.astimezone(timezone.utc)
|
|
||||||
.replace(tzinfo=None)
|
|
||||||
)
|
|
||||||
except ValueError:
|
|
||||||
log.warn('Invalid timestamp from cinder webhook %s', date_string)
|
|
||||||
return datetime.now()
|
|
||||||
|
|
||||||
|
|
||||||
def filter_enforcement_actions(enforcement_actions, cinder_job):
|
def filter_enforcement_actions(enforcement_actions, cinder_job):
|
||||||
target = cinder_job.target
|
target = cinder_job.target
|
||||||
if not target:
|
if not target:
|
||||||
|
@ -253,7 +240,6 @@ def cinder_webhook(request):
|
||||||
|
|
||||||
cinder_job.process_decision(
|
cinder_job.process_decision(
|
||||||
decision_cinder_id=source.get('decision', {}).get('id'),
|
decision_cinder_id=source.get('decision', {}).get('id'),
|
||||||
decision_date=process_datestamp(payload.get('timestamp')),
|
|
||||||
decision_action=enforcement_actions[0],
|
decision_action=enforcement_actions[0],
|
||||||
decision_notes=payload.get('notes') or '',
|
decision_notes=payload.get('notes') or '',
|
||||||
policy_ids=policy_ids,
|
policy_ids=policy_ids,
|
||||||
|
|
|
@ -1059,6 +1059,39 @@ class DENY_APPEAL_JOB(_LOG):
|
||||||
reviewer_review_action = True
|
reviewer_review_action = True
|
||||||
|
|
||||||
|
|
||||||
|
class HELD_ACTION_ADMIN_USER_BANNED(_LOG):
|
||||||
|
id = 193
|
||||||
|
format = _('User {user} ban action held for further review.')
|
||||||
|
short = 'Held user ban'
|
||||||
|
admin_event = True
|
||||||
|
|
||||||
|
|
||||||
|
class HELD_ACTION_DELETE_RATING(_LOG):
|
||||||
|
"""Requires rating.id and add-on objects."""
|
||||||
|
|
||||||
|
id = 194
|
||||||
|
action_class = 'review'
|
||||||
|
format = _('Review {rating} for {addon} delete held for further review.')
|
||||||
|
reviewer_format = 'Held {user_responsible}s delete {rating} for {addon}'
|
||||||
|
admin_event = True
|
||||||
|
|
||||||
|
|
||||||
|
class HELD_ACTION_COLLECTION_DELETED(_LOG):
|
||||||
|
id = 195
|
||||||
|
format = _('Collection {collection} deletion held for further review')
|
||||||
|
admin_event = True
|
||||||
|
|
||||||
|
|
||||||
|
class HELD_ACTION_FORCE_DISABLE(_LOG):
|
||||||
|
id = 196
|
||||||
|
reviewer_review_action = True
|
||||||
|
format = _('{addon} force-disable held for further review')
|
||||||
|
reviewer_format = 'Held {addon} force-disable by {user_responsible}.'
|
||||||
|
admin_format = reviewer_format
|
||||||
|
short = 'Held force disable'
|
||||||
|
admin_event = True
|
||||||
|
|
||||||
|
|
||||||
LOGS = [x for x in vars().values() if isclass(x) and issubclass(x, _LOG) and x != _LOG]
|
LOGS = [x for x in vars().values() if isclass(x) and issubclass(x, _LOG) and x != _LOG]
|
||||||
# Make sure there's no duplicate IDs.
|
# Make sure there's no duplicate IDs.
|
||||||
assert len(LOGS) == len({log.id for log in LOGS})
|
assert len(LOGS) == len({log.id for log in LOGS})
|
||||||
|
|
|
@ -18,10 +18,12 @@ _PromotedSuperClass = namedtuple(
|
||||||
'admin_review',
|
'admin_review',
|
||||||
'badged', # See BADGE_CATEGORIES in frontend too: both need changing
|
'badged', # See BADGE_CATEGORIES in frontend too: both need changing
|
||||||
'autograph_signing_states',
|
'autograph_signing_states',
|
||||||
'can_primary_hero',
|
'can_primary_hero', # can be added to a primary hero shelf
|
||||||
'immediate_approval',
|
'immediate_approval', # will addon be auto-approved once added
|
||||||
'flag_for_human_review',
|
'flag_for_human_review', # will be add-on be flagged for another review
|
||||||
'can_be_compatible_with_all_fenix_versions', # If addon is promoted for Android
|
'can_be_compatible_with_all_fenix_versions', # If addon is promoted for Android
|
||||||
|
'high_profile', # the add-on is considered high-profile for review purposes
|
||||||
|
'high_profile_rating', # developer replies are considered high-profile
|
||||||
],
|
],
|
||||||
defaults=(
|
defaults=(
|
||||||
# "Since fields with a default value must come after any fields without
|
# "Since fields with a default value must come after any fields without
|
||||||
|
@ -33,10 +35,12 @@ _PromotedSuperClass = namedtuple(
|
||||||
False, # admin_review
|
False, # admin_review
|
||||||
False, # badged
|
False, # badged
|
||||||
{}, # autograph_signing_states - should be a dict of App.short: state
|
{}, # autograph_signing_states - should be a dict of App.short: state
|
||||||
False, # can_primary_hero - can be added to a primary hero shelf
|
False, # can_primary_hero
|
||||||
False, # immediate_approval - will addon be auto-approved once added
|
False, # immediate_approval
|
||||||
False, # flag_for_human_review - will be add-on be flagged for another review
|
False, # flag_for_human_review
|
||||||
False, # can_be_compatible_with_all_fenix_versions
|
False, # can_be_compatible_with_all_fenix_versions
|
||||||
|
False, # high_profile
|
||||||
|
False, # high_profile_rating
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -67,6 +71,8 @@ RECOMMENDED = PromotedClass(
|
||||||
},
|
},
|
||||||
can_primary_hero=True,
|
can_primary_hero=True,
|
||||||
can_be_compatible_with_all_fenix_versions=True,
|
can_be_compatible_with_all_fenix_versions=True,
|
||||||
|
high_profile=True,
|
||||||
|
high_profile_rating=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Obsolete, never used in production, only there to prevent us from re-using
|
# Obsolete, never used in production, only there to prevent us from re-using
|
||||||
|
@ -89,6 +95,8 @@ LINE = PromotedClass(
|
||||||
},
|
},
|
||||||
can_primary_hero=True,
|
can_primary_hero=True,
|
||||||
can_be_compatible_with_all_fenix_versions=True,
|
can_be_compatible_with_all_fenix_versions=True,
|
||||||
|
high_profile=True,
|
||||||
|
high_profile_rating=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
SPOTLIGHT = PromotedClass(
|
SPOTLIGHT = PromotedClass(
|
||||||
|
@ -99,6 +107,7 @@ SPOTLIGHT = PromotedClass(
|
||||||
admin_review=True,
|
admin_review=True,
|
||||||
can_primary_hero=True,
|
can_primary_hero=True,
|
||||||
immediate_approval=True,
|
immediate_approval=True,
|
||||||
|
high_profile=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
STRATEGIC = PromotedClass(
|
STRATEGIC = PromotedClass(
|
||||||
|
@ -115,6 +124,7 @@ NOTABLE = PromotedClass(
|
||||||
listed_pre_review=True,
|
listed_pre_review=True,
|
||||||
unlisted_pre_review=True,
|
unlisted_pre_review=True,
|
||||||
flag_for_human_review=True,
|
flag_for_human_review=True,
|
||||||
|
high_profile=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -111,7 +111,7 @@ class UserEmailBoundField(forms.boundfield.BoundField):
|
||||||
|
|
||||||
|
|
||||||
class UserQuerySet(BaseQuerySet):
|
class UserQuerySet(BaseQuerySet):
|
||||||
def ban_and_disable_related_content(self):
|
def ban_and_disable_related_content(self, *, skip_activity_log=False):
|
||||||
"""Admin method to ban multiple users and disable the content they
|
"""Admin method to ban multiple users and disable the content they
|
||||||
produced.
|
produced.
|
||||||
|
|
||||||
|
@ -222,7 +222,8 @@ class UserQuerySet(BaseQuerySet):
|
||||||
ratings_qs.delete()
|
ratings_qs.delete()
|
||||||
# And then ban the users.
|
# And then ban the users.
|
||||||
for user in users:
|
for user in users:
|
||||||
activity.log_create(amo.LOG.ADMIN_USER_BANNED, user)
|
if not skip_activity_log:
|
||||||
|
activity.log_create(amo.LOG.ADMIN_USER_BANNED, user)
|
||||||
log.info(
|
log.info(
|
||||||
'User (%s: <%s>) is being banned.',
|
'User (%s: <%s>) is being banned.',
|
||||||
user,
|
user,
|
||||||
|
|
Загрузка…
Ссылка в новой задаче