Skip to content
This repository has been archived by the owner on Mar 15, 2018. It is now read-only.

Commit

Permalink
Add abuse reporting endpoint for extensions aka add-ons.
Browse files Browse the repository at this point in the history
Bonus: remove high abuse reporting trigger code that doesn't work.
  • Loading branch information
eviljeff committed Nov 23, 2015
1 parent c9c7701 commit c9d2a9e
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 196 deletions.
21 changes: 21 additions & 0 deletions mkt/abuse/migrations/0004_abusereport_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


class Migration(migrations.Migration):

dependencies = [
('extensions', '0020_change_filename_scheme'),
('abuse', '0003_abusereport_website'),
]

operations = [
migrations.AddField(
model_name='abusereport',
name='extension',
field=models.ForeignKey(related_name='abuse_reports', to='extensions.Extension', null=True),
preserve_default=True,
),
]
54 changes: 8 additions & 46 deletions mkt/abuse/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.conf import settings
from django.db import models

from mkt.extensions.models import Extension
from mkt.site.mail import send_mail
from mkt.site.models import ModelBase
from mkt.users.models import UserProfile
Expand All @@ -18,13 +19,15 @@ class AbuseReport(ModelBase):
reporter = models.ForeignKey(UserProfile, null=True,
blank=True, related_name='abuse_reported')
ip_address = models.CharField(max_length=255, default='0.0.0.0')
# An abuse report can be for an addon, a user, or a website. Only one of
# these should be set.
# An abuse report can be for an app, a user, a website, or an extension.
# Only one of these should be set.
addon = models.ForeignKey(Webapp, null=True, related_name='abuse_reports')
user = models.ForeignKey(UserProfile, null=True,
related_name='abuse_reports')
website = models.ForeignKey(Website, null=True,
related_name='abuse_reports')
extension = models.ForeignKey(Extension, null=True,
related_name='abuse_reports')
message = models.TextField()
read = models.BooleanField(default=False)

Expand All @@ -33,7 +36,7 @@ class Meta:

@property
def object(self):
return self.addon or self.user or self.website
return self.addon or self.user or self.website or self.extension

def send(self):
obj = self.object
Expand All @@ -54,6 +57,8 @@ def send(self):
type_ = 'App'
elif self.user:
type_ = 'User'
elif self.extension:
type_ = 'FxOS Add-on'
subject = u'[%s] Abuse Report for %s' % (type_, obj.name)
recipient_list = (settings.ABUSE_EMAIL,)

Expand All @@ -62,49 +67,6 @@ def send(self):
self.message)
send_mail(subject, msg, recipient_list=recipient_list)

@classmethod
def recent_high_abuse_reports(cls, threshold, period, addon_id=None):
"""
Returns AbuseReport objects for the given threshold over the given time
period (in days). Filters by addon_id if provided.
E.g. Greater than 5 abuse reports for all webapps in the past 7 days.
"""
abuse_sql = ['''
SELECT `abuse_reports`.*,
COUNT(`abuse_reports`.`addon_id`) AS `num_reports`
FROM `abuse_reports`
INNER JOIN `addons` ON (`abuse_reports`.`addon_id` = `addons`.`id`)
WHERE `abuse_reports`.`created` >= %s ''']
params = [period]
if addon_id:
abuse_sql.append('AND `addons`.`id` = %s ')
params.append(addon_id)
abuse_sql.append('GROUP BY addon_id HAVING num_reports > %s')
params.append(threshold)

return list(cls.objects.raw(''.join(abuse_sql), params))


# Add index on `created`.
AbuseReport._meta.get_field('created').db_index = True


def send_abuse_report(request, obj, message):
report = AbuseReport(ip_address=request.META.get('REMOTE_ADDR'),
message=message)
if request.user.is_authenticated():
report.reporter = request.user
if isinstance(obj, Webapp):
report.addon = obj
elif isinstance(obj, UserProfile):
report.user = obj
elif isinstance(obj, Website):
report.website = obj
report.save()
report.send()

# Trigger addon high abuse report detection task.
if isinstance(obj, Webapp):
from mkt.webapps.tasks import find_abuse_escalations
find_abuse_escalations.delay(obj.id)
13 changes: 13 additions & 0 deletions mkt/abuse/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from mkt.account.serializers import UserSerializer
from mkt.api.fields import SlugOrPrimaryKeyRelatedField, SplitField
from mkt.api.serializers import PotatoCaptchaSerializer
from mkt.extensions.models import Extension
from mkt.extensions.serializers import ExtensionSerializer
from mkt.webapps.models import Webapp
from mkt.webapps.serializers import SimpleAppSerializer
from mkt.websites.serializers import WebsiteSerializer
Expand Down Expand Up @@ -52,3 +54,14 @@ class WebsiteAbuseSerializer(BaseAbuseSerializer):

class Meta(BaseAbuseSerializer.Meta):
fields = BaseAbuseSerializer.Meta.fields + ('website',)


class ExtensionAbuseSerializer(BaseAbuseSerializer):
extension = SplitField(
SlugOrPrimaryKeyRelatedField(
source='extension', slug_field='slug',
queryset=Extension.objects.without_deleted().public()),
ExtensionSerializer())

class Meta(BaseAbuseSerializer.Meta):
fields = BaseAbuseSerializer.Meta.fields + ('extension',)
9 changes: 9 additions & 0 deletions mkt/abuse/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# -*- coding: utf-8 -*-
from django.conf import settings
from django.core import mail

from nose.tools import eq_

import mkt.site.tests
from mkt.abuse.models import AbuseReport
from mkt.extensions.models import Extension
from mkt.site.fixtures import fixture
from mkt.webapps.models import Webapp
from mkt.users.models import UserProfile
Expand Down Expand Up @@ -39,3 +41,10 @@ def test_website(self):
AbuseReport(website=website).send()
assert mail.outbox[0].subject.startswith('[Website]')
eq_(mail.outbox[0].to, [settings.MKT_FEEDBACK_EMAIL])

def test_extension(self):
extension = Extension.objects.create(
name=u'Test Êxtension', slug=u'test-ëxtension')
AbuseReport(extension=extension).send()
assert mail.outbox[0].subject.startswith('[FxOS Add-on]')
eq_(mail.outbox[0].to, [settings.ABUSE_EMAIL])
42 changes: 40 additions & 2 deletions mkt/abuse/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# -*- coding: utf-8 -*-
import json
import urllib

from django.core import mail
from django.core.urlresolvers import reverse
from django.utils.http import urlencode

from nose.tools import eq_

from mkt.abuse.models import AbuseReport
from mkt.api.tests.test_oauth import RestOAuth
from mkt.constants.base import STATUS_PUBLIC
from mkt.extensions.models import Extension
from mkt.site.fixtures import fixture
from mkt.webapps.models import Webapp
from mkt.users.models import UserProfile
Expand Down Expand Up @@ -44,7 +47,7 @@ def _call(self, anonymous=False, data=None):
post_data.update(data)

client = self.anon if anonymous else self.client
res = client.post(self.list_url, data=urllib.urlencode(post_data),
res = client.post(self.list_url, data=urlencode(post_data),
content_type='application/x-www-form-urlencoded',
**self.headers)
try:
Expand Down Expand Up @@ -74,6 +77,9 @@ def _test_success(self, res, data):
if 'website' in fields:
eq_(int(data.pop('website')['id']), self.website.pk)
del fields['website']
if 'extension' in fields:
eq_(int(data.pop('extension')['id']), self.extension.pk)
del fields['extension']

for name in fields.keys():
eq_(fields[name], data[name])
Expand Down Expand Up @@ -170,3 +176,35 @@ def test_invalid_website(self):
res, data = self._call(data={'website': self.website.pk + 42})
eq_(400, res.status_code)
assert 'does not exist' in data['website'][0]


class TestExtensionAbuseResource(AbuseResourceTests, BaseTestAbuseResource,
RestOAuth):
resource_name = 'extension'

def setUp(self):
super(TestExtensionAbuseResource, self).setUp()
self.extension = Extension.objects.create(
name=u'Test Êxtension')
self.extension.update(status=STATUS_PUBLIC)
self.default_data = {
'text': 'Lies! This extension is an add-on!',
'sprout': 'potato',
'extension': self.extension.pk
}

def test_invalid_extension(self):
res, data = self._call(data={'extension': -1})
eq_(400, res.status_code)
assert 'does not exist' in data['extension'][0]

def test_deleted_extension(self):
data = {'extension': self.extension.slug}
self.extension.delete()
res, data = self._call(data=data)
eq_(400, res.status_code)
assert 'does not exist' in data['extension'][0]

def test_slug_extension(self):
res, data = self._call(data={'extension': self.extension.slug})
eq_(201, res.status_code)
5 changes: 3 additions & 2 deletions mkt/abuse/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

from rest_framework.routers import SimpleRouter

from mkt.abuse.views import (AppAbuseViewSet, UserAbuseViewSet,
WebsiteAbuseViewSet)
from mkt.abuse.views import (AppAbuseViewSet, ExtensionAbuseViewSet,
UserAbuseViewSet, WebsiteAbuseViewSet)

abuse = SimpleRouter()
abuse.register('user', UserAbuseViewSet, base_name='user-abuse')
abuse.register('app', AppAbuseViewSet, base_name='app-abuse')
abuse.register('website', WebsiteAbuseViewSet, base_name='website-abuse')
abuse.register('extension', ExtensionAbuseViewSet, base_name='extension-abuse')


api_patterns = patterns(
Expand Down
8 changes: 7 additions & 1 deletion mkt/abuse/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from rest_framework.permissions import AllowAny
from rest_framework.throttling import UserRateThrottle

from mkt.abuse.serializers import (AppAbuseSerializer, UserAbuseSerializer,
from mkt.abuse.serializers import (AppAbuseSerializer,
ExtensionAbuseSerializer,
UserAbuseSerializer,
WebsiteAbuseSerializer)
from mkt.api.authentication import (RestOAuthAuthentication,
RestAnonymousAuthentication,
Expand Down Expand Up @@ -40,3 +42,7 @@ class UserAbuseViewSet(BaseAbuseViewSet):

class WebsiteAbuseViewSet(BaseAbuseViewSet):
serializer_class = WebsiteAbuseSerializer


class ExtensionAbuseViewSet(BaseAbuseViewSet):
serializer_class = ExtensionAbuseSerializer
6 changes: 6 additions & 0 deletions mkt/commonplace/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ def fireplace_route(path, name=None):
fireplace_route('', 'website.detail'),
)

fireplace_extension_patterns = patterns(
'',
fireplace_route('', 'extension.detail'),
)

urlpatterns = patterns(
'',
# Fireplace:
Expand All @@ -48,6 +53,7 @@ def fireplace_route(path, name=None):
name='commonplace.fxa_authorize'),
(r'^app/%s/' % mkt.APP_SLUG, include(fireplace_app_patterns)),
(r'^website/(?P<pk>\d+)', include(fireplace_website_patterns)),
(r'^addon/%s/' % mkt.APP_SLUG, include(fireplace_extension_patterns)),
url(r'^iframe-install.html/?$', views.iframe_install,
name='commonplace.iframe-install'),
url(r'^potatolytics.html$', views.potatolytics,
Expand Down
3 changes: 3 additions & 0 deletions mkt/extensions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ def get_icon_url(self, size):
def get_indexer(cls):
return ExtensionIndexer

def get_url_path(self):
return reverse('extension.detail', kwargs={'app_slug': self.slug})

@property
def icon_type(self):
return 'png' if self.icon_hash else ''
Expand Down
Loading

0 comments on commit c9d2a9e

Please sign in to comment.