Only allows one app per domain (bug 697667)

This commit is contained in:
Kumar McMillan 2011-11-02 11:08:03 -05:00
Родитель 89eb68496a
Коммит ef5bc76fb2
10 изменённых файлов: 125 добавлений и 3 удалений

Просмотреть файл

@ -242,6 +242,8 @@ class Addon(amo.models.OnChangeMixin, amo.models.ModelBase):
default=amo.ADDON_FREE)
manifest_url = models.URLField(max_length=255, blank=True, null=True,
verify_exists=False)
app_domain = models.CharField(max_length=255, blank=True, null=True,
db_index=True)
_current_version = models.ForeignKey(Version, related_name='___ignore',
db_column='current_version', null=True, on_delete=models.SET_NULL)
@ -369,6 +371,7 @@ class Addon(amo.models.OnChangeMixin, amo.models.ModelBase):
addon.default_locale = to_language(translation.get_language())
if addon.is_webapp():
addon.manifest_url = upload.name
addon.app_domain = addon.domain_from_url(addon.manifest_url)
addon.save()
Version.from_upload(upload, addon, platforms)
amo.log(amo.LOG.CREATE_ADDON, addon)

Просмотреть файл

@ -1394,6 +1394,12 @@ class TestAddonFromUpload(UploadTest):
assert addon.is_webapp()
eq_(addon.manifest_url, upload.name)
def test_app_domain(self):
upload = self.get_upload(abspath=self.webapp())
upload.name = 'http://mozilla.com/my/rad/app.webapp' # manifest URL
addon = Addon.from_upload(upload, [self.platform])
eq_(addon.app_domain, 'mozilla.com')
def test_xpi_version(self):
addon = Addon.from_upload(self.get_upload('extension.xpi'),
[self.platform])

Просмотреть файл

@ -34,6 +34,7 @@ from translations.fields import TransTextarea, TransField
from translations.models import delete_translation, Translation
from translations.forms import TranslationFormMixin
from versions.models import License, Version, ApplicationsVersions
from webapps.models import Webapp
from . import tasks
@ -432,12 +433,26 @@ CompatFormSet = modelformset_factory(
form=CompatForm, can_delete=True, extra=0)
def verify_app_domain(manifest_url):
if settings.WEBAPPS_UNIQUE_BY_DOMAIN:
domain = Webapp.domain_from_url(manifest_url)
if Addon.objects.filter(app_domain=domain).exists():
raise forms.ValidationError(
_('An app already exists on this domain, '
'only one app per domain is allowed.'))
class NewWebappForm(happyforms.Form):
upload = forms.ModelChoiceField(widget=forms.HiddenInput,
queryset=FileUpload.objects.filter(valid=True),
error_messages={'invalid_choice': _lazy('There was an error with your '
'upload. Please try again.')})
def clean_upload(self):
upload = self.cleaned_data['upload']
verify_app_domain(upload.name) # JS puts manifest URL here
return upload
class NewAddonForm(happyforms.Form):
upload = forms.ModelChoiceField(widget=forms.HiddenInput,
@ -926,6 +941,11 @@ class CheckCompatibilityForm(happyforms.Form):
class NewManifestForm(happyforms.Form):
manifest = forms.URLField(verify_exists=False)
def clean_manifest(self):
manifest = self.cleaned_data['manifest']
verify_app_domain(manifest)
return manifest
class PremiumForm(happyforms.Form):
"""

Просмотреть файл

@ -2915,6 +2915,49 @@ class TestCreateWebApp(BaseWebAppTest):
eq_(files[0].status, amo.STATUS_PUBLIC)
class TestCreateWebAppFromManifest(BaseWebAppTest):
def setUp(self):
super(TestCreateWebAppFromManifest, self).setUp()
Addon.objects.create(type=amo.ADDON_WEBAPP,
app_domain='existing-app.com')
def upload_webapp(self, manifest_url, **post_kw):
self.upload.update(name=manifest_url) # simulate JS upload
return self.post(**post_kw)
def post_manifest(self, manifest_url):
rs = self.client.post(reverse('devhub.upload_manifest'),
dict(manifest=manifest_url))
if 'json' in rs['content-type']:
rs = json.loads(rs.content)
return rs
def test_duplicate_domain(self):
rs = self.upload_webapp('http://existing-app.com/my.webapp',
expect_errors=True)
eq_(rs.context['new_addon_form'].errors.as_text(),
'* upload\n '
'* An app already exists on this domain, only one '
'app per domain is allowed.')
@mock.patch.object(settings, 'WEBAPPS_UNIQUE_BY_DOMAIN', False)
def test_allow_duplicate_domains(self):
self.upload_webapp('http://existing-app.com/my.webapp') # no errors
def test_duplicate_domain_from_js(self):
data = self.post_manifest('http://existing-app.com/my.webapp')
eq_(data['validation']['errors'], 1)
eq_(data['validation']['messages'][0]['message'],
'An app already exists on this domain, '
'only one app per domain is allowed.')
@mock.patch.object(settings, 'WEBAPPS_UNIQUE_BY_DOMAIN', False)
def test_allow_duplicate_domains_from_js(self):
rs = self.post_manifest('http://existing-app.com/my.webapp')
eq_(rs.status_code, 302)
class TestDeleteAddon(amo.tests.TestCase):
fixtures = ['base/apps', 'base/users', 'base/addon_3615']

Просмотреть файл

@ -191,7 +191,8 @@ urlpatterns = decorate(write, patterns('',
url('^standalone-upload/([^/]+)$', views.standalone_upload_detail,
name='devhub.standalone_upload_detail'),
url('^upload-manifest$', views.upload_manifest, name='devhub.upload_manifest'),
url('^upload-manifest$', views.upload_manifest,
name='devhub.upload_manifest'),
# URLs for a single add-on.
url('^addon/%s/' % ADDON_ID, include(detail_patterns)),

Просмотреть файл

@ -56,6 +56,17 @@ class Webapp(Addon):
view = 'apps.detail_more' if more else 'apps.detail'
return reverse(view, args=[self.app_slug])
@staticmethod
def domain_from_url(url):
if not url:
raise ValueError('URL was empty')
hostname = urlparse.urlparse(url).hostname
if hostname:
hostname = hostname.lower()
if hostname.startswith('www.'):
hostname = hostname[4:]
return hostname
@classmethod
def pending(cls):
# - Holding

Просмотреть файл

@ -1,7 +1,8 @@
import json
import unittest
import test_utils
from nose.tools import eq_
from nose.tools import eq_, raises
import amo
from addons.models import Addon, BlacklistedSlug
@ -110,3 +111,35 @@ class TestManifest(BaseWebAppTest):
with open(self.manifest, 'r') as mf:
manifest_json = json.load(mf)
eq_(webapp.get_manifest_json(), manifest_json)
class TestDomainFromURL(unittest.TestCase):
def test_simple(self):
eq_(Webapp.domain_from_url('http://mozilla.com/'), 'mozilla.com')
def test_long_path(self):
eq_(Webapp.domain_from_url('http://mozilla.com/super/rad.webapp'),
'mozilla.com')
def test_normalize_www(self):
eq_(Webapp.domain_from_url('http://www.mozilla.com/super/rad.webapp'),
'mozilla.com')
def test_with_port(self):
eq_(Webapp.domain_from_url('http://mozilla.com:9000/'), 'mozilla.com')
def test_subdomains(self):
eq_(Webapp.domain_from_url('http://apps.mozilla.com/'),
'apps.mozilla.com')
def test_https(self):
eq_(Webapp.domain_from_url('https://mozilla.com/'), 'mozilla.com')
@raises(ValueError)
def test_none(self):
Webapp.domain_from_url(None)
@raises(ValueError)
def test_empty(self):
Webapp.domain_from_url('')

Просмотреть файл

@ -151,7 +151,7 @@ $(document).ready(function() {
error_message = format(ngettext(
"Your app failed validation with {0} error.",
"Your app failed validation with {0} errors.",
v.errors), [v.errors.length]);
v.errors), [v.errors]);
$(this).trigger('upload_finished', [false, r, error_message]);
$('#validate_app').attr('disabled', false);

Просмотреть файл

@ -0,0 +1,3 @@
-- Sorry :) time to make tea.
ALTER TABLE addons ADD COLUMN `app_domain` varchar(255) NULL;
CREATE INDEX `addons_609c04a9` ON `addons` (`app_domain`);

Просмотреть файл

@ -1255,6 +1255,8 @@ LOGIN_RATELIMIT_ALL_USERS = '15/m'
WEBAPPS_RESTRICTED = True
# The key we'll use to sign webapp receipts.
WEBAPPS_RECEIPT_KEY = ''
# If True, only allow one webapp per domain.
WEBAPPS_UNIQUE_BY_DOMAIN = True
# How long a watermarked addon should be re-used for, after this
# time it will be regenerated.