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:
Mathieu Pillard 2022-01-14 15:30:51 +01:00 коммит произвёл GitHub
Родитель 0e642cf4ef
Коммит 43feed46e5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
17 изменённых файлов: 431 добавлений и 90 удалений

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

@ -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>
{{ _("Youre 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>
{{ _("Youre 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%;
}