Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/olympia/abuse/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,8 @@ def source(self):
return DECISION_SOURCES.REVIEWER
elif self.from_job_queue == CinderAddonHandledByLegal.queue:
return DECISION_SOURCES.LEGAL
elif self.from_job_queue is None:
return DECISION_SOURCES.MANUAL
else:
return DECISION_SOURCES.TASKUS

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"event": "decision.created",
"payload": {
"appeals_resolved": [],
"enforcement_actions": [
"amo-approve"
],
"enforcement_actions_removed": [],
"entity": {
"attributes": {
"average_daily_users": 0,
"created": "2025-09-09T08:12:58.308105",
"guid": "{58d58fa6-2667-4695-a2d9-b05365899ba6}",
"id": "10001",
"last_updated": "2025-09-09T08:20:24.728686",
"name": "Livemarks_rollback2",
"promoted": "",
"slug": "livemarks_rollback2",
"summary": "Restores RSS live bookmark support to Firefox 64 and above."
},
"entity_schema": "amo_addon"
},
"notes": "some notes",
"point_updates": [],
"policies": [
{
"enforcement_actions": [
"amo-approve"
],
"id": "bfc16995-6a3f-4fb7-abd5-58c4ecab44e6",
"is_illegal": false,
"is_non_violating": true,
"name": "Approve"
}
],
"policies_removed": [],
"source": {
"decision": {
"id": "a51b39a6-e628-4cf6-b275-2a78bae497ee",
"metadata": {},
"notes": "some notes",
"type": "manual",
"user": {
"email": "awilliamson@test.com",
"groups": [
{
"name": "Admin"
},
{
"name": "Everyone"
}
],
"name": "Andrew Williamson"
}
},
"user": {
"email": "awilliamson@test.com",
"groups": [
{
"name": "Admin"
},
{
"name": "Everyone"
}
],
"name": "Andrew Williamson"
}
},
"timestamp": "2026-05-28T12:01:13.403410+00:00"
}
}
2 changes: 1 addition & 1 deletion src/olympia/abuse/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2503,7 +2503,7 @@ def test_souce(self):
decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON, addon=addon
)
assert decision.source == DECISION_SOURCES.TASKUS
assert decision.source == DECISION_SOURCES.MANUAL

decision.update(reviewer_user=self.task_user)
assert decision.source == DECISION_SOURCES.AUTOMATION
Expand Down
29 changes: 27 additions & 2 deletions src/olympia/abuse/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1742,8 +1742,33 @@ def test_reviewer_tools_resolved_decision(self):
}

def test_proactive_decision_from_cinder(self):
data = self.get_data(filename='proactive_decision_from_cinder.json')
addon = addon_factory(
id=10001,
created=datetime.fromisoformat(
data['payload']['entity']['attributes']['created']
),
)
self._setup_reports()
req = self.get_request(data=data)
with mock.patch.object(CinderJob, 'create_and_execute_decision') as create_mock:
response = cinder_webhook(req)
create_mock.assert_called()
create_mock.assert_called_with(
None,
target=addon,
decision_cinder_id=data['payload']['source']['decision']['id'],
decision_actions=[DECISION_ACTIONS.AMO_APPROVE.value],
decision_notes='some notes',
policy_ids=[data['payload']['policies'][0]['id']],
job_queue=None,
)
assert response.status_code == 201
assert response.data == {'amo': {'received': True, 'handled': True}}

def test_unsupported_decision_from_cinder(self):
data = self.get_data(filename='proactive_decision_from_amo.json')
data['payload']['source']['decision']['type'] = 'queue_review'
data['payload']['source']['decision']['type'] = 'something'
self._setup_reports()
req = self.get_request(data=data)
with mock.patch.object(CinderJob, 'create_and_execute_decision') as create_mock:
Expand All @@ -1754,7 +1779,7 @@ def test_proactive_decision_from_cinder(self):
'amo': {
'received': True,
'handled': False,
'not_handled_reason': 'Unsupported Manual decision',
'not_handled_reason': 'Unsupported decision',
}
}

Expand Down
12 changes: 7 additions & 5 deletions src/olympia/abuse/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ def reportable(self):


def get_job_from_payload(payload):
if job_id := payload.get('source', {}).get('job', {}).get('id'):
source = payload.get('source', {})
if job_id := source.get('job', {}).get('id'):
try:
return CinderJob.objects.get(job_id=job_id)
except CinderJob.DoesNotExist:
Expand All @@ -208,11 +209,12 @@ def get_job_from_payload(payload):
raise CinderWebhookMissingIdError('No matching decision id found') from exc
return prev_decision.cinder_job

if source.get('decision', {}).get('type') == 'manual':
log.debug('Cinder webhook decision is a manual decision, no job to link to.')
return None
else:
# We may support this one day, but we currently don't support manual decisions
# that originated in Cinder
log.debug('Cinder webhook decision for cinder proactive decision skipped.')
raise CinderWebhookError('Unsupported Manual decision')
log.debug('Cinder webhook decision for unsupported scenario skipped.')
raise CinderWebhookError('Unsupported decision')


def get_target_from_payload_entity(entity):
Expand Down
1 change: 1 addition & 0 deletions src/olympia/constants/abuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,3 +357,4 @@ class DECISION_SOURCES(StrEnum):
REVIEWER = 'AMO Reviewer'
LEGAL = 'Legal'
TASKUS = 'TaskUs'
MANUAL = 'Manual'
18 changes: 16 additions & 2 deletions src/olympia/reviewers/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8121,10 +8121,13 @@ def setUp(self):
self.url = reverse('reviewers.queue_decisions')

self.addon_decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON, addon=addon_factory()
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
addon=addon_factory(),
from_job_queue='some-extension-queue',
)
self.user_decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_BAN_USER, user=user_factory()
action=DECISION_ACTIONS.AMO_BAN_USER,
user=user_factory(),
)
self.collection_decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DELETE_COLLECTION,
Expand Down Expand Up @@ -8211,6 +8214,7 @@ def setUp(self):
metadata={
ContentDecision.POLICY_DYNAMIC_VALUES: {policy.uuid: {'FOO': 'baa'}}
},
from_job_queue='some-cinder-queue',
)
self.version = self.decision.addon.current_version
self.decision.target_versions.set([self.version])
Expand Down Expand Up @@ -8427,3 +8431,13 @@ def test_cant_submit_a_resolved_decision(self):
field=None,
errors=['Not currently held for 2nd level approval'],
)

def test_manual_source(self):
self.decision.update(from_job_queue=None)
response = self.client.get(self.url)
assert response.status_code == 200
doc = pq(response.content)('.entity-type-Extension')

assert f'Extension Decision for {self.decision.addon.name}' in doc.html()
assert doc('h2').attr('class') == 'held-item MANUAL'
assert 'Manual' == doc('.decision-source td').text()
Loading