diff --git a/apps/addons/models.py b/apps/addons/models.py index a6cd0d1ac3e..b571419ca52 100644 --- a/apps/addons/models.py +++ b/apps/addons/models.py @@ -5,7 +5,7 @@ import time from django.conf import settings -from django.db import models +from django.db import models, transaction from django.db.models import Q, Sum, Max from django.utils.translation import trans_real as translation @@ -16,7 +16,7 @@ import amo.models from amo.fields import DecimalCharField -from amo.utils import urlparams, sorted_groupby, JSONEncoder +from amo.utils import send_mail, urlparams, sorted_groupby, JSONEncoder from amo.urlresolvers import reverse from reviews.models import Review from stats.models import (Contribution as ContributionStats, @@ -209,6 +209,32 @@ class Meta: def __unicode__(self): return '%s: %s' % (self.id, self.name) + @transaction.commit_on_success + def delete(self, user, msg): + log.debug('Adding guid to blacklist: %s' % self.guid) + BlacklistedGuid(guid=self.guid, comments=msg).save() + log.debug('Deleting add-on: %s' % self.id) + + authors = [u.email for u in self.authors.all()] + to = [settings.FLIGTAR] + authors + email_msg = """ + The following add-on was deleted. + ADD-ON: %s + ID: %s + GUID: %s + AUTHORS: %s + TOTAL DOWNLOADS: %s + AVERAGE DAILY USERS: %s + NOTES: %s + """ % (self.name, self.id, self.guid, authors, self.total_downloads, + self.average_daily_users, msg) + log.debug('Sending delete email for add-on %s' % self.id) + subject = 'Deleting add-on %s' % self.id + + rv = super(Addon, self).delete() + send_mail(subject, email_msg, recipient_list=to) + return rv + def flush_urls(self): urls = ['*/addon/%d/' % self.id, # Doesn't take care of api '*/addon/%d/developers/' % self.id, diff --git a/apps/api/handlers.py b/apps/api/handlers.py index e37042d896d..a0e56258cef 100644 --- a/apps/api/handlers.py +++ b/apps/api/handlers.py @@ -54,7 +54,6 @@ def wrapper(*args, **kw): return wrapper - def _form_error(f): resp = rc.BAD_REQUEST error = ','.join(['%s (%s)' % (v[0], k) for k, v in f.errors.iteritems()]) @@ -84,9 +83,9 @@ def read(self, request): class AddonsHandler(BaseHandler): - allowed_methods = ('POST', 'PUT',) + allowed_methods = ('POST', 'PUT', 'DELETE',) model = Addon - fields = ('id', 'name', 'eula') + fields = ('id', 'name', 'eula', 'guid',) exclude = ('highest_status', 'icon_type') # Custom handler so translated text doesn't look weird @@ -123,6 +122,12 @@ def update(self, request, addon): a = form.save() return a + @check_addon_and_version + @throttle(5, 60 * 60) # Allow 5 delete per hour + def delete(self, request, addon): + addon.delete(user=request.amo_user, msg='Deleted via API') + return rc.DELETED + class VersionsHandler(BaseHandler): allowed_methods = ('POST', 'PUT', 'DELETE', 'GET',) diff --git a/apps/api/tests/test_oauth.py b/apps/api/tests/test_oauth.py index 66b4180203c..0b75f352e89 100644 --- a/apps/api/tests/test_oauth.py +++ b/apps/api/tests/test_oauth.py @@ -27,6 +27,7 @@ from django import forms from django.conf import settings +from django.core import mail from django.test.client import (encode_multipart, Client, FakePayload, BOUNDARY, MULTIPART_CONTENT) @@ -37,7 +38,7 @@ from piston.models import Consumer, Token from amo.urlresolvers import reverse -from addons.models import Addon +from addons.models import Addon, BlacklistedGuid from translations.models import Translation from versions.models import AppVersion, Version @@ -312,6 +313,18 @@ def test_create_nolicense(self): eq_(r.content, 'Bad Request: ' 'Invalid data provided: This field is required. (builtin)') + def test_delete(self): + data = self.create_addon() + id = data['id'] + guid = data['guid'] + + r = client.delete(('api.addon', id), self.accepted_consumer, + self.token) + eq_(r.status_code, 204, r.content) + eq_(Addon.objects.filter(pk=id).count(), 0, "Didn't delete.") + + assert BlacklistedGuid.objects.filter(guid=guid) + eq_(len(mail.outbox), 1) def test_update(self): # create an addon diff --git a/settings.py b/settings.py index 06162eaec31..eeee486b1fe 100644 --- a/settings.py +++ b/settings.py @@ -33,6 +33,8 @@ ) MANAGERS = ADMINS +FLIGTAR = 'amo-admins@mozilla.org' + DATABASES = { 'default': { 'NAME': 'zamboni', @@ -580,19 +582,19 @@ def JINJA_CONFIG(): CSP_ALLOW = ("'self'",) CSP_IMG_SRC = ("'self'", STATIC_URL, - "https://www.google.com", # Recaptcha actually comes from google + "https://www.google.com", # Recaptcha comes from google "https://statse.webtrendslive.com", "https://www.getpersonas.com",) CSP_SCRIPT_SRC = ("'self'", STATIC_URL, "https://api-secure.recaptcha.net", - "https://www.google.com", # Recaptcha + "https://www.google.com", # Recaptcha ) CSP_STYLE_SRC = ("'self'", STATIC_URL,) CSP_OBJECT_SRC = ("'none'",) CSP_MEDIA_SRC = ("'none'",) CSP_FRAME_SRC = ("'none'",) CSP_FONT_SRC = ("'self'", "fonts.mozilla.com",) -CSP_FRAME_ANCESTORS = ("'none'",) # We also send x-frame-options:DENY +CSP_FRAME_ANCESTORS = ("'none'",) # We also send x-frame-options:DENY # If you don't want experimental add-ons to show up in any search results or # have detail pages, flip this switch