Add form to generate site permission add-ons (#18597)
* Add form to generate site permission add-ons * Make non-submit developer agreement page generic, use it for site permission generator
This commit is contained in:
Родитель
0e642cf4ef
Коммит
43feed46e5
|
@ -2,7 +2,7 @@ import os
|
|||
import tarfile
|
||||
import zipfile
|
||||
|
||||
from urllib.parse import urlsplit
|
||||
from urllib.parse import urlparse, urlsplit
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
|
@ -1396,3 +1396,41 @@ class SingleCategoryForm(forms.Form):
|
|||
AddonCategory(addon=self.addon, category_id=category.id).save()
|
||||
# Remove old, outdated categories cache on the model.
|
||||
del self.addon.all_categories
|
||||
|
||||
|
||||
class SitePermissionGeneratorForm(forms.Form):
|
||||
origin = forms.URLField(
|
||||
label=_('Origin'),
|
||||
widget=forms.TextInput(attrs={'placeholder': 'https://example.com'}),
|
||||
)
|
||||
site_permissions = forms.MultipleChoiceField(
|
||||
label=_('Permissions'), choices=(('midi-sysex', 'WebMIDI'),)
|
||||
)
|
||||
|
||||
def clean_origin(self):
|
||||
actual_value = str(self.data.get('origin'))
|
||||
value = self.cleaned_data.get('origin')
|
||||
# Note that URLField should already ensure it's an URL.
|
||||
error_message = _(
|
||||
'Origin should include only a scheme (protocol), a hostname (domain) and '
|
||||
'an optional port'
|
||||
)
|
||||
try:
|
||||
parsed = urlparse(value)
|
||||
except ValueError:
|
||||
raise forms.ValidationError(error_message)
|
||||
if (
|
||||
not parsed.scheme
|
||||
or parsed.scheme not in ('https', 'http')
|
||||
or not parsed.netloc
|
||||
# Django's URLField adds a scheme if there wasn't one, translating
|
||||
# "foo" into "http://foo". We want to make sure the scheme was
|
||||
# explicitly present in the submitted value.
|
||||
or not actual_value.startswith(parsed.scheme)
|
||||
or parsed.path
|
||||
or parsed.params
|
||||
or parsed.query
|
||||
or parsed.fragment
|
||||
):
|
||||
raise forms.ValidationError(error_message)
|
||||
return value
|
||||
|
|
|
@ -29,6 +29,7 @@ import olympia.core.logger
|
|||
|
||||
from olympia import amo
|
||||
from olympia.addons.models import Addon, Preview
|
||||
from olympia.addons.utils import SitePermissionVersionCreator
|
||||
from olympia.amo.celery import task
|
||||
from olympia.amo.decorators import set_modified_on, use_primary_db
|
||||
from olympia.amo.utils import (
|
||||
|
@ -50,6 +51,7 @@ from olympia.files.utils import (
|
|||
UnsupportedFileType,
|
||||
InvalidZipFile,
|
||||
)
|
||||
from olympia.users.models import UserProfile
|
||||
|
||||
|
||||
log = olympia.core.logger.getLogger('z.devhub.task')
|
||||
|
@ -742,3 +744,20 @@ def send_api_key_revocation_email(emails):
|
|||
use_deny_list=False,
|
||||
perm_setting='individual_contact',
|
||||
)
|
||||
|
||||
|
||||
@task
|
||||
@use_primary_db
|
||||
def create_site_permission_version(
|
||||
*, user_pk, remote_addr, install_origins, site_permissions, **kwargs
|
||||
):
|
||||
# Can raise if the user does not exist, but that's ok, we don't want to go
|
||||
# any further if that's the case.
|
||||
user = UserProfile.objects.filter(deleted=False).get(pk=user_pk)
|
||||
generator = SitePermissionVersionCreator(
|
||||
user=user,
|
||||
remote_addr=remote_addr,
|
||||
install_origins=install_origins,
|
||||
site_permissions=site_permissions,
|
||||
)
|
||||
generator.create_version()
|
||||
|
|
|
@ -53,12 +53,9 @@
|
|||
</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<h3>{{ _("Version Signature Pending") }}</h3>
|
||||
<p>
|
||||
{{ _("You’re done! ✨ You will be notified by email when the signed file is ready to be downloaded from the Developer Hub. If you do not see an email after 24 hours, please check your spam folder.") }}
|
||||
</p>
|
||||
{% include "devhub/includes/done_unlisted.html" %}
|
||||
{% endif %}
|
||||
{% if addon.type != amo.ADDON_STATICTHEME %}
|
||||
{% if addon.type not in (amo.ADDON_STATICTHEME, amo.ADDON_SITE_PERMISSION) %}
|
||||
<p> {{ _('As a reminder, your add-on is subject to manual review at any time.') }} </p>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
{% block primary %}
|
||||
<h3>{{ _('Add-on Distribution Agreement') }}</h3>
|
||||
|
||||
{% include "devhub/agreement.html" %}
|
||||
{% include "devhub/includes/agreement.html" %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,42 +1,20 @@
|
|||
<form method="post">
|
||||
{% if not agreement_form.has_error('__all__') %}
|
||||
<p>
|
||||
{{ agreement_message }}
|
||||
</p>
|
||||
<ul class="agreement-links">
|
||||
<li>{{ agreement_form.distribution_agreement }}<a href="{{ url('devhub.docs', 'policies/agreement') }}" target="_blank" rel="noopener noreferrer">{{ _('Firefox Add-on Distribution Agreement') }}</a> {{ agreement_form.distribution_agreement.errors }}</li>
|
||||
<li>{{ agreement_form.review_policy }}<a href="{{ url('devhub.docs', 'policies/reviews') }}" target="_blank" rel="noopener noreferrer">{{ _('Review Policies and Rules') }}</a> {{ agreement_form.review_policy.errors }}</li>
|
||||
</ul>
|
||||
<p>
|
||||
{{ _('I have read and accept this Agreement and the Rules and Policies') }}.
|
||||
</p>
|
||||
{% extends "devhub/base.html" %}
|
||||
|
||||
{% if 'display_name' in agreement_form.fields %}
|
||||
<h3> {{ _('Display Name') }} </h3>
|
||||
<p> {{ _('Your account needs a display name set so users know who your add-on is coming from. Please enter one below.' )}} </p>
|
||||
<p>
|
||||
{{ agreement_form.display_name.label_tag() }}
|
||||
{{ agreement_form.display_name }}
|
||||
{{ agreement_form.display_name.errors }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% set title = _('Firefox Add-on Distribution Agreement') %}
|
||||
|
||||
{% if 'recaptcha' in agreement_form.fields %}
|
||||
<p>
|
||||
{{ agreement_form.recaptcha }}
|
||||
{{ agreement_form.recaptcha.errors }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% block title %}
|
||||
{{ dev_page_title(title) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% set page_title = _('Firefox Add-on Distribution Agreement') %}
|
||||
<header>
|
||||
<h2 class="is_addon">{{ title }}</h2>
|
||||
</header>
|
||||
|
||||
<section class="addon-submission-process" role="main">
|
||||
{% include "devhub/includes/agreement.html" %}
|
||||
</section>
|
||||
{% endblock content %}
|
||||
|
||||
<div class="submit-buttons">
|
||||
{% csrf_token %}
|
||||
<button id="accept-agreement" type="submit">
|
||||
{{ _('Accept') }}
|
||||
</button>
|
||||
{{ _('or <a href="{0}">Cancel</a>')|format_html(url('devhub.index')) }}
|
||||
</div>
|
||||
{% else %}
|
||||
{{ agreement_form.non_field_errors() }}
|
||||
{% endif %}
|
||||
</form>
|
||||
<p><a href="https://extensionworkshop.com/documentation/publish/developer-accounts/">{{ _('More information on Developer Accounts') }}</a></p>
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
{% extends "devhub/base.html" %}
|
||||
|
||||
{% set title = _('Firefox Add-on Distribution Agreement') %}
|
||||
|
||||
{% block title %}
|
||||
{{ dev_page_title(title) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% set page_title = _('Firefox Add-on Distribution Agreement') %}
|
||||
<header>
|
||||
<h2 class="is_addon">{{ title }}</h2>
|
||||
</header>
|
||||
|
||||
<section class="addon-submission-process" role="main">
|
||||
{% include "devhub/agreement.html" %}
|
||||
</section>
|
||||
{% endblock content %}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<form method="post">
|
||||
{% if not agreement_form.has_error('__all__') %}
|
||||
<p>
|
||||
{{ agreement_message }}
|
||||
</p>
|
||||
<ul class="agreement-links">
|
||||
<li>{{ agreement_form.distribution_agreement }}<a href="{{ url('devhub.docs', 'policies/agreement') }}" target="_blank" rel="noopener noreferrer">{{ _('Firefox Add-on Distribution Agreement') }}</a> {{ agreement_form.distribution_agreement.errors }}</li>
|
||||
<li>{{ agreement_form.review_policy }}<a href="{{ url('devhub.docs', 'policies/reviews') }}" target="_blank" rel="noopener noreferrer">{{ _('Review Policies and Rules') }}</a> {{ agreement_form.review_policy.errors }}</li>
|
||||
</ul>
|
||||
<p>
|
||||
{{ _('I have read and accept this Agreement and the Rules and Policies') }}.
|
||||
</p>
|
||||
|
||||
{% if 'display_name' in agreement_form.fields %}
|
||||
<h3> {{ _('Display Name') }} </h3>
|
||||
<p> {{ _('Your account needs a display name set so users know who your add-on is coming from. Please enter one below.' )}} </p>
|
||||
<p>
|
||||
{{ agreement_form.display_name.label_tag() }}
|
||||
{{ agreement_form.display_name }}
|
||||
{{ agreement_form.display_name.errors }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if 'recaptcha' in agreement_form.fields %}
|
||||
<p>
|
||||
{{ agreement_form.recaptcha }}
|
||||
{{ agreement_form.recaptcha.errors }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="submit-buttons">
|
||||
{% csrf_token %}
|
||||
<button id="accept-agreement" type="submit">
|
||||
{{ _('Accept') }}
|
||||
</button>
|
||||
{{ _('or <a href="{0}">Cancel</a>')|format_html(url('devhub.index')) }}
|
||||
</div>
|
||||
{% else %}
|
||||
{{ agreement_form.non_field_errors() }}
|
||||
{% endif %}
|
||||
</form>
|
||||
<p><a href="https://extensionworkshop.com/documentation/publish/developer-accounts/">{{ _('More information on Developer Accounts') }}</a></p>
|
|
@ -0,0 +1,4 @@
|
|||
<h3>{{ _("Version Signature Pending") }}</h3>
|
||||
<p>
|
||||
{{ _("You’re done! ✨ You will be notified by email when the signed file is ready to be downloaded from the Developer Hub. If you do not see an email after 24 hours, please check your spam folder.") }}
|
||||
</p>
|
|
@ -0,0 +1,48 @@
|
|||
{% extends "devhub/base.html" %}
|
||||
|
||||
{% from "devhub/includes/macros.html" import tip %}
|
||||
|
||||
|
||||
{% set title = _('Generate Site Permission Add-on') %}
|
||||
|
||||
{% block title %}
|
||||
{{ dev_page_title(title) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<header>
|
||||
<h2 class="is_addon">{{ title }}</h2>
|
||||
</header>
|
||||
|
||||
<section class="site_permission_generator" role="main">
|
||||
{% if success %}
|
||||
{% include "devhub/includes/done_unlisted.html" %}
|
||||
{% else %}
|
||||
<div class="devhub-form">
|
||||
<form method="post">
|
||||
<div class="item">
|
||||
<div class="item_wrapper">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
<tr>
|
||||
<th><label for="{{ form.origin.auto_id }}">{{ form.origin.label }}
|
||||
{{ tip(None, _("The origin (scheme + hostname + optional port) you need the permission to be enabled on. This will also be where you need to host the add-on.")) }}</label></th>
|
||||
<td>{{ form.origin }}{{ form.origin.errors }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="{{ form.site_permissions.auto_id }}">{{ form.site_permissions.label }}
|
||||
{{ tip(None, _("The permission(s) the generated add-on will grant on the origin.")) }}</label></th>
|
||||
<td>{{ form.site_permissions }}{{ form.site_permissions.errors }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="listing-footer">
|
||||
<button type="submit">{{ _('Submit') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endblock content %}
|
||||
|
|
@ -887,3 +887,51 @@ class TestCategoryForm(TestCase):
|
|||
form = forms.CategoryFormSet(addon=addon, request=request)
|
||||
apps = [f.app for f in form.forms]
|
||||
assert apps == [amo.FIREFOX]
|
||||
|
||||
|
||||
_DEFAULT_SITE_PERMISSIONS = [
|
||||
forms.SitePermissionGeneratorForm.declared_fields['site_permissions'].choices[0][0]
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'origin',
|
||||
[
|
||||
'https://foo.com/testing', # path
|
||||
'file:/foo/bar', # invalid scheme
|
||||
'file:///foo/bar', # invalid scheme
|
||||
'ftp://somewhere.com', # invalid scheme
|
||||
'https://foo.bar.栃木.jp/', # trailing slash
|
||||
'', # empty string
|
||||
[], # array (doh!)
|
||||
{}, # dict (doh!)
|
||||
'https://*.wildcard.com', # wildcard
|
||||
'example.com', # no scheme
|
||||
'https://', # no hostname
|
||||
None, # null
|
||||
42, # int
|
||||
],
|
||||
)
|
||||
def test_site_permission_generator_origin_invalid(origin):
|
||||
form = forms.SitePermissionGeneratorForm(
|
||||
{'site_permissions': _DEFAULT_SITE_PERMISSIONS, 'origin': origin}
|
||||
)
|
||||
assert not form.is_valid()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'origin',
|
||||
[
|
||||
'https://example.com',
|
||||
'https://foo.example.com',
|
||||
'https://xn--fo-9ja.com',
|
||||
'https://foo.bar.栃木.jp',
|
||||
'https://example.com:8888',
|
||||
],
|
||||
)
|
||||
def test_site_permission_generator_origin_valid(origin):
|
||||
form = forms.SitePermissionGeneratorForm(
|
||||
{'site_permissions': _DEFAULT_SITE_PERMISSIONS, 'origin': origin}
|
||||
)
|
||||
assert form.is_valid()
|
||||
assert form.cleaned_data['origin'] == origin
|
||||
|
|
|
@ -891,3 +891,27 @@ class TestForwardLinterResults(TestCase):
|
|||
results = {'errors': 1}
|
||||
returned_results = tasks.forward_linter_results(results, 123)
|
||||
assert results == returned_results
|
||||
|
||||
|
||||
class TestCreateSitePermissionVersion(TestCase):
|
||||
@mock.patch('olympia.devhub.tasks.SitePermissionVersionCreator')
|
||||
def test_pass_down_to_creator_util(self, SitePermissionVersionCreator_mock):
|
||||
user = user_factory()
|
||||
tasks.create_site_permission_version(
|
||||
user_pk=user.pk,
|
||||
remote_addr='127.0.0.42',
|
||||
install_origins=['https://example.com'],
|
||||
site_permissions=['foo'],
|
||||
)
|
||||
assert SitePermissionVersionCreator_mock.call_count == 1
|
||||
assert SitePermissionVersionCreator_mock.call_args[0] == ()
|
||||
assert SitePermissionVersionCreator_mock.call_args[1] == {
|
||||
'user': user,
|
||||
'remote_addr': '127.0.0.42',
|
||||
'install_origins': ['https://example.com'],
|
||||
'site_permissions': ['foo'],
|
||||
}
|
||||
assert (
|
||||
SitePermissionVersionCreator_mock.return_value.create_version.call_count
|
||||
== 1
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@ import json
|
|||
import os
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from urllib.parse import urlencode
|
||||
from urllib.parse import quote, urlencode
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import mail
|
||||
|
@ -28,7 +28,13 @@ from olympia.amo.templatetags.jinja_helpers import (
|
|||
url as url_reverse,
|
||||
urlparams,
|
||||
)
|
||||
from olympia.amo.tests import TestCase, addon_factory, user_factory, version_factory
|
||||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
fxa_login_link,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
from olympia.amo.tests.test_helpers import get_image_path
|
||||
from olympia.api.models import SYMMETRIC_JWT_TYPE, APIKey, APIKeyConfirmation
|
||||
from olympia.applications.models import AppVersion
|
||||
|
@ -779,7 +785,7 @@ class TestActivityFeed(TestCase):
|
|||
assert '<a href=' not in timestamp.html()
|
||||
|
||||
|
||||
class TestAPIAgreement(TestCase):
|
||||
class TestDeveloperAgreement(TestCase):
|
||||
fixtures = ['base/addon_3615', 'base/addon_5579', 'base/users']
|
||||
|
||||
def setUp(self):
|
||||
|
@ -790,12 +796,24 @@ class TestAPIAgreement(TestCase):
|
|||
|
||||
def test_agreement_read(self):
|
||||
self.user.update(read_dev_agreement=self.days_ago(0))
|
||||
response = self.client.get(reverse('devhub.api_key_agreement'))
|
||||
response = self.client.get(reverse('devhub.developer_agreement'))
|
||||
self.assert3xx(response, reverse('devhub.index'))
|
||||
|
||||
def test_custom_redirect(self):
|
||||
self.user.update(read_dev_agreement=self.days_ago(0))
|
||||
response = self.client.get(
|
||||
'%s%s%s'
|
||||
% (
|
||||
reverse('devhub.developer_agreement'),
|
||||
'?to=',
|
||||
quote(reverse('devhub.api_key')),
|
||||
)
|
||||
)
|
||||
self.assert3xx(response, reverse('devhub.api_key'))
|
||||
|
||||
def test_agreement_unread_captcha_inactive(self):
|
||||
self.user.update(read_dev_agreement=None)
|
||||
response = self.client.get(reverse('devhub.api_key_agreement'))
|
||||
response = self.client.get(reverse('devhub.developer_agreement'))
|
||||
assert response.status_code == 200
|
||||
assert 'agreement_form' in response.context
|
||||
form = response.context['agreement_form']
|
||||
|
@ -806,7 +824,7 @@ class TestAPIAgreement(TestCase):
|
|||
@override_switch('developer-agreement-captcha', active=True)
|
||||
def test_agreement_unread_captcha_active(self):
|
||||
self.user.update(read_dev_agreement=None)
|
||||
response = self.client.get(reverse('devhub.api_key_agreement'))
|
||||
response = self.client.get(reverse('devhub.developer_agreement'))
|
||||
assert response.status_code == 200
|
||||
assert 'agreement_form' in response.context
|
||||
form = response.context['agreement_form']
|
||||
|
@ -817,21 +835,21 @@ class TestAPIAgreement(TestCase):
|
|||
def test_agreement_submit_success(self):
|
||||
self.user.update(read_dev_agreement=None)
|
||||
response = self.client.post(
|
||||
reverse('devhub.api_key_agreement'),
|
||||
reverse('devhub.developer_agreement'),
|
||||
data={
|
||||
'distribution_agreement': 'on',
|
||||
'review_policy': 'on',
|
||||
},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert response['Location'] == reverse('devhub.api_key')
|
||||
assert response['Location'] == reverse('devhub.index')
|
||||
self.user.reload()
|
||||
self.assertCloseToNow(self.user.read_dev_agreement)
|
||||
|
||||
@override_switch('developer-agreement-captcha', active=True)
|
||||
def test_agreement_submit_captcha_active_error(self):
|
||||
self.user.update(read_dev_agreement=None)
|
||||
response = self.client.post(reverse('devhub.api_key_agreement'))
|
||||
response = self.client.post(reverse('devhub.developer_agreement'))
|
||||
|
||||
# Captcha is properly rendered
|
||||
doc = pq(response.content)
|
||||
|
@ -857,7 +875,7 @@ class TestAPIAgreement(TestCase):
|
|||
)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('devhub.api_key_agreement'),
|
||||
reverse('devhub.developer_agreement'),
|
||||
data={
|
||||
'g-recaptcha-response': 'test',
|
||||
'distribution_agreement': 'on',
|
||||
|
@ -866,7 +884,7 @@ class TestAPIAgreement(TestCase):
|
|||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response['Location'] == reverse('devhub.api_key')
|
||||
assert response['Location'] == reverse('devhub.index')
|
||||
self.user.reload()
|
||||
self.assertCloseToNow(self.user.read_dev_agreement)
|
||||
|
||||
|
@ -874,7 +892,7 @@ class TestAPIAgreement(TestCase):
|
|||
set_config('last_dev_agreement_change_date', '2018-01-01 12:00')
|
||||
before_agreement_last_changed = datetime(2018, 1, 1, 12, 0) - timedelta(days=1)
|
||||
self.user.update(read_dev_agreement=before_agreement_last_changed)
|
||||
response = self.client.get(reverse('devhub.api_key_agreement'))
|
||||
response = self.client.get(reverse('devhub.developer_agreement'))
|
||||
assert response.status_code == 200
|
||||
assert 'agreement_form' in response.context
|
||||
|
||||
|
@ -883,7 +901,7 @@ class TestAPIAgreement(TestCase):
|
|||
is_submission_allowed_mock.return_value = False
|
||||
self.user.update(read_dev_agreement=None)
|
||||
response = self.client.post(
|
||||
reverse('devhub.api_key_agreement'),
|
||||
reverse('devhub.developer_agreement'),
|
||||
data={
|
||||
'distribution_agreement': 'on',
|
||||
'review_policy': 'on',
|
||||
|
@ -909,7 +927,7 @@ class TestAPIAgreement(TestCase):
|
|||
IPNetworkUserRestriction.objects.create(network='127.0.0.1/32')
|
||||
self.user.update(read_dev_agreement=None)
|
||||
response = self.client.post(
|
||||
reverse('devhub.api_key_agreement'),
|
||||
reverse('devhub.developer_agreement'),
|
||||
data={
|
||||
'distribution_agreement': 'on',
|
||||
'review_policy': 'on',
|
||||
|
@ -931,7 +949,7 @@ class TestAPIAgreement(TestCase):
|
|||
# api keys page.
|
||||
is_submission_allowed_mock.return_value = False
|
||||
self.user.update(read_dev_agreement=self.days_ago(0))
|
||||
response = self.client.get(reverse('devhub.api_key_agreement'))
|
||||
response = self.client.get(reverse('devhub.developer_agreement'))
|
||||
assert response.status_code == 200
|
||||
assert 'agreement_form' in response.context
|
||||
|
||||
|
@ -949,12 +967,26 @@ class TestAPIKeyPage(TestCase):
|
|||
def test_key_redirect(self):
|
||||
self.user.update(read_dev_agreement=None)
|
||||
response = self.client.get(reverse('devhub.api_key'))
|
||||
self.assert3xx(response, reverse('devhub.api_key_agreement'))
|
||||
self.assert3xx(
|
||||
response,
|
||||
'%s%s'
|
||||
% (
|
||||
reverse('devhub.developer_agreement'),
|
||||
'?to=%2Fen-US%2Fdevelopers%2Faddon%2Fapi%2Fkey%2F',
|
||||
),
|
||||
)
|
||||
|
||||
def test_redirect_if_restricted(self):
|
||||
IPNetworkUserRestriction.objects.create(network='127.0.0.1/32')
|
||||
response = self.client.get(reverse('devhub.api_key'))
|
||||
self.assert3xx(response, reverse('devhub.api_key_agreement'))
|
||||
self.assert3xx(
|
||||
response,
|
||||
'%s%s'
|
||||
% (
|
||||
reverse('devhub.developer_agreement'),
|
||||
'?to=%2Fen-US%2Fdevelopers%2Faddon%2Fapi%2Fkey%2F',
|
||||
),
|
||||
)
|
||||
|
||||
def test_view_without_credentials_not_confirmed_yet(self):
|
||||
response = self.client.get(self.url)
|
||||
|
@ -2056,3 +2088,91 @@ class TestStatsLinksInManageMySubmissionsPage(TestCase):
|
|||
assert reverse('stats.overview', args=[self.addon.slug]) in str(
|
||||
response.content
|
||||
)
|
||||
|
||||
|
||||
class TestSitePermissionGenerator(TestCase):
|
||||
def setUp(self):
|
||||
self.url = reverse('devhub.site_permission_generator')
|
||||
self.user = user_factory()
|
||||
self.client.login(email=self.user.email)
|
||||
self.user.update(last_login_ip='192.168.1.1')
|
||||
set_config('last_dev_agreement_change_date', '2018-01-01 12:00')
|
||||
|
||||
def test_not_logged_in(self):
|
||||
self.client.logout()
|
||||
response = self.client.get(self.url)
|
||||
self.assert3xx(response, fxa_login_link(response=response, to=self.url))
|
||||
|
||||
def test_redirect_to_agreement_if_restricted(self):
|
||||
assert self.user.read_dev_agreement is None
|
||||
response = self.client.get(self.url)
|
||||
self.assert3xx(
|
||||
response,
|
||||
'%s%s%s' % (reverse('devhub.developer_agreement'), '?to=', self.url),
|
||||
)
|
||||
|
||||
def test_errors(self):
|
||||
self.user.update(read_dev_agreement=self.days_ago(1))
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
data = {
|
||||
'origin': 'foo', # wrong
|
||||
'site_permissions': ['bar'], # also wrong
|
||||
}
|
||||
response = self.client.post(self.url, data)
|
||||
assert response.status_code == 200
|
||||
assert not response.context['form'].is_valid()
|
||||
doc = pq(response.content)
|
||||
errors = doc('.errorlist')
|
||||
assert len(errors) == 2
|
||||
assert errors[0].text_content() == 'Enter a valid URL.'
|
||||
assert (
|
||||
errors[1].text_content()
|
||||
== 'Select a valid choice. bar is not one of the available choices.'
|
||||
)
|
||||
|
||||
@override_switch('record-install-origins', active=True)
|
||||
def test_success(self):
|
||||
user_factory(pk=settings.TASK_USER_ID)
|
||||
AppVersion.objects.get_or_create(application=amo.FIREFOX.id, version='97.0')
|
||||
AppVersion.objects.get_or_create(application=amo.FIREFOX.id, version='*')
|
||||
AppVersion.objects.get_or_create(application=amo.ANDROID.id, version='97.0')
|
||||
AppVersion.objects.get_or_create(application=amo.ANDROID.id, version='*')
|
||||
|
||||
self.user.update(read_dev_agreement=self.days_ago(1))
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
doc = pq(response.content)
|
||||
assert doc('.devhub-form form')
|
||||
data = {
|
||||
'origin': 'https://foo.com',
|
||||
'site_permissions': ['midi-sysex'],
|
||||
}
|
||||
response = self.client.post(self.url, data, REMOTE_ADDR='15.16.23.42')
|
||||
assert response.status_code == 200
|
||||
doc = pq(response.content)
|
||||
assert not doc('.devhub-form form')
|
||||
assert 'Version Signature Pending' in response.content.decode('utf-8')
|
||||
# Since we're in tests, tasks are executed synchronously so we can
|
||||
# directly check the add-on has been created.
|
||||
assert Addon.objects.count() == 1
|
||||
addon = Addon.objects.get()
|
||||
version = addon.versions.all()[0]
|
||||
file_ = version.file
|
||||
assert version.pk
|
||||
assert version.channel == amo.RELEASE_CHANNEL_UNLISTED
|
||||
assert version.version == '1.0'
|
||||
assert sorted(
|
||||
version.installorigin_set.all().values_list('origin', flat=True)
|
||||
) == [
|
||||
'https://foo.com',
|
||||
]
|
||||
assert addon.status == amo.STATUS_NULL
|
||||
assert addon.type == amo.ADDON_SITE_PERMISSION
|
||||
assert list(addon.authors.all()) == [self.user]
|
||||
assert file_.status == amo.STATUS_AWAITING_REVIEW
|
||||
assert file_._site_permissions
|
||||
assert file_._site_permissions.permissions == ['midi-sysex']
|
||||
activity = ActivityLog.objects.for_addons(addon).latest('pk')
|
||||
assert activity.user == self.user
|
||||
assert activity.iplog_set.all()[0].ip_address == '15.16.23.42'
|
||||
|
|
|
@ -206,8 +206,8 @@ urlpatterns = decorate(
|
|||
# Submission API
|
||||
re_path(
|
||||
r'^addon/agreement/$',
|
||||
views.api_key_agreement,
|
||||
name='devhub.api_key_agreement',
|
||||
views.developer_agreement,
|
||||
name='devhub.developer_agreement',
|
||||
),
|
||||
re_path(r'^addon/api/key/$', views.api_key, name='devhub.api_key'),
|
||||
# Standalone validator:
|
||||
|
@ -250,6 +250,11 @@ urlpatterns = decorate(
|
|||
views.standalone_upload_detail,
|
||||
name='devhub.standalone_upload_detail',
|
||||
),
|
||||
re_path(
|
||||
r'^site_permission_generator/$',
|
||||
views.site_permission_generator,
|
||||
name='devhub.site_permission_generator',
|
||||
),
|
||||
# URLs for a single add-on.
|
||||
re_path(r'^addon/%s/' % ADDON_ID, include(detail_patterns)),
|
||||
re_path(r'^ajax/addon/%s/' % ADDON_ID, include(ajax_patterns)),
|
||||
|
|
|
@ -2,6 +2,7 @@ import datetime
|
|||
import os
|
||||
import time
|
||||
|
||||
from urllib.parse import quote
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from django import forms as django_forms, http
|
||||
|
@ -162,6 +163,32 @@ def dashboard(request, theme=False):
|
|||
return TemplateResponse(request, 'devhub/addons/dashboard.html', context=data)
|
||||
|
||||
|
||||
@login_required
|
||||
def site_permission_generator(request):
|
||||
if not RestrictionChecker(request=request).is_submission_allowed():
|
||||
return redirect(
|
||||
'%s%s%s'
|
||||
% (reverse('devhub.developer_agreement'), '?to=', quote(request.path))
|
||||
)
|
||||
form = forms.SitePermissionGeneratorForm(
|
||||
request.POST if request.method == 'POST' else None
|
||||
)
|
||||
success = None
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
tasks.create_site_permission_version.delay(
|
||||
user_pk=request.user.pk,
|
||||
remote_addr=request.META.get('REMOTE_ADDR', ''),
|
||||
install_origins=[form.cleaned_data['origin']],
|
||||
site_permissions=form.cleaned_data['site_permissions'],
|
||||
)
|
||||
success = True
|
||||
return TemplateResponse(
|
||||
request,
|
||||
'devhub/site_permission_generator.html',
|
||||
context={'form': form, 'success': success},
|
||||
)
|
||||
|
||||
|
||||
@dev_required
|
||||
def ajax_compat_status(request, addon_id, addon):
|
||||
if not (addon.accepts_compatible_apps() and addon.current_version):
|
||||
|
@ -1933,11 +1960,11 @@ def docs(request, doc_name=None):
|
|||
|
||||
|
||||
@login_required
|
||||
def api_key_agreement(request):
|
||||
def developer_agreement(request):
|
||||
return render_agreement(
|
||||
request=request,
|
||||
template='devhub/api/agreement.html',
|
||||
next_step='devhub.api_key',
|
||||
template='devhub/agreement.html',
|
||||
next_step=request.GET.get('to'),
|
||||
)
|
||||
|
||||
|
||||
|
@ -1945,6 +1972,8 @@ def render_agreement(request, template, next_step, **extra_context):
|
|||
form = forms.AgreementForm(
|
||||
request.POST if request.method == 'POST' else None, request=request
|
||||
)
|
||||
if not is_safe_url(next_step, request):
|
||||
next_step = reverse('devhub.index')
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
# Developer has validated the form: let's update its profile and
|
||||
# redirect to next step. Note that the form is supposed to always be
|
||||
|
@ -1978,7 +2007,10 @@ def render_agreement(request, template, next_step, **extra_context):
|
|||
@transaction.atomic
|
||||
def api_key(request):
|
||||
if not RestrictionChecker(request=request).is_submission_allowed():
|
||||
return redirect(reverse('devhub.api_key_agreement'))
|
||||
return redirect(
|
||||
'%s%s%s'
|
||||
% (reverse('devhub.developer_agreement'), '?to=', quote(request.path))
|
||||
)
|
||||
|
||||
try:
|
||||
credentials = APIKey.get_jwt_key(user=request.user)
|
||||
|
|
|
@ -1079,6 +1079,7 @@ CELERY_TASK_ROUTES = {
|
|||
# AMO Devhub.
|
||||
'olympia.devhub.tasks.check_for_api_keys_in_file': {'queue': 'devhub'},
|
||||
'olympia.devhub.tasks.create_initial_validation_results': {'queue': 'devhub'},
|
||||
'olympia.devhub.tasks.create_site_permission_version': {'queue': 'devhub'},
|
||||
'olympia.devhub.tasks.forward_linter_results': {'queue': 'devhub'},
|
||||
'olympia.devhub.tasks.get_preview_sizes': {'queue': 'devhub'},
|
||||
'olympia.devhub.tasks.handle_file_validation_result': {'queue': 'devhub'},
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
th {
|
||||
color: #888;
|
||||
font-size: 11px;
|
||||
padding: 0 0 5px;
|
||||
padding: 8px 20px 8px 0;
|
||||
vertical-align: top;
|
||||
width: 130px;
|
||||
&.version-delete {
|
||||
|
|
|
@ -1599,3 +1599,8 @@ span.distribution-tag-unlisted {
|
|||
.source-submission-note p:last-child {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.site_permission_generator select, .site_permission_generator input[type="text"] {
|
||||
box-sizing: border-box;
|
||||
width: 80%;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче