Only allows one app per domain (bug 697667)
This commit is contained in:
Родитель
89eb68496a
Коммит
ef5bc76fb2
|
@ -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.
|
||||
|
|
Загрузка…
Ссылка в новой задаче