addons api (pending) authors create and confirm endpoint (#19191)

* addons api (pending) authors create and confirm endpoint

* update docs; add to tests
This commit is contained in:
Andrew Williamson 2022-05-05 11:25:01 +01:00 коммит произвёл GitHub
Родитель 1e0ed88eb4
Коммит e4fe20f0ff
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 535 добавлений и 116 удалений

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

@ -732,100 +732,6 @@ This endpoint allows the metadata for an existing preview for a non-theme add-on
.. http:delete:: /api/v5/addons/addon/(int:addon_id|string:addon_slug|string:addon_guid)/previews/(int:id)/
-----------
Author List
-----------
.. _addon-author-list:
This endpoint returns a list of all the authors of an add-on. No pagination is supported.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be an author of the add-on.
.. http:get:: /api/v5/addons/addon/(int:id|string:slug|string:guid)/authors/
-------------
Author Detail
-------------
.. _addon-author-detail:
This endpoint allows the properties of an add-on author to be retrieved, given a user (account) id.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be an author of the add-on.
.. http:get:: /api/v5/addons/addon/(int:id|string:slug|string:guid)/authors/(int:user_id)/
.. _addon-author-detail-object:
:>json int user_id: The user id for an author.
:>json string name: The name for an author.
:>json string email: The email address for an author.
:>json string role: The :ref:`role <addon-author-detail-role>` the author holds on this add-on.
:>json boolean listed: If ``true`` the user will be listed as an add-on author publicly on the add-on detail page. (If ``false`` the user is not exposed as an author.)
:>json int position: The position the author should be returned in the list of authors of the add-on :ref:`detail <addon-detail-object>`. Order is ascending so lower positions are placed earlier.
.. _addon-author-detail-role:
Possible values for the ``role`` field:
============== ==============================================================
Value Description
============== ==============================================================
developer A developer of the add-on. Developers can change all add-on
metadata and create, delete, and edit versions of the add-on.
owner An owner of the add-on. Owners have all the abilities of a
developer, plus they can add, remove, and edit add-on authors,
and delete the add-on.
============== ==============================================================
-----------
Author Edit
-----------
.. _addon-author-edit:
This endpoint allows the properties of an add-on author to be edited.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be an owner of the add-on.
.. warning::
If you change your own author role to developer from owner you will lose permission to make any further author changes.
.. http:patch:: /api/v5/addons/addon/(int:addon_id|string:addon_slug|string:addon_guid)/authors/(int:user_id)/
.. _addon-author-edit-request:
:<json string role: The :ref:`role <addon-author-detail-role>` the author holds on this add-on. Add-ons must have at least one owner author.
:<json boolean listed: If ``true`` the user will be listed as an add-on author publicly on the add-on detail page. (If ``false`` the user is not exposed as an author.) Add-ons must have at least one listed author.
:<json int position: The position the author should be returned in the list of authors of the add-on :ref:`detail <addon-detail-object>`. Order is ascending so lower positions are placed earlier.
-------------
Author Delete
-------------
.. _addon-author-delete:
This endpoint allows an add-on author to be removed from an add-on.
Add-ons must have at least one owner, and at least one listed author.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be an owner of the add-on.
.. warning::
If you delete yourself as an add-on author you will lose all access to the add-on.
.. http:delete:: /api/v5/addons/addon/(int:addon_id|string:addon_slug|string:addon_guid)/authors/(int:user_id)/
-------------
Upload Create
-------------

225
docs/topics/api/authors.rst Normal file
Просмотреть файл

@ -0,0 +1,225 @@
==============
Add-on Authors
==============
.. note::
These APIs are not frozen and can change at any time without warning.
See :ref:`the API versions available<api-versions-list>` for alternatives
if you need stability - note the following APIs are only available on v5+ though.
-----------
Author List
-----------
.. _addon-author-list:
This endpoint returns a list of all the authors of an add-on. No pagination is supported.
New authors are created as pending authors, which become authors once the user has confirmed.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be an author of the add-on.
.. http:get:: /api/v5/addons/addon/(int:id|string:slug|string:guid)/authors/
-------------
Author Detail
-------------
.. _addon-author-detail:
This endpoint allows the properties of an add-on author to be retrieved, given a user (account) id.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be an author of the add-on.
.. http:get:: /api/v5/addons/addon/(int:id|string:slug|string:guid)/authors/(int:user_id)/
.. _addon-author-detail-object:
:>json int user_id: The user id for an author.
:>json string name: The name for an author.
:>json string email: The email address for an author.
:>json string role: The :ref:`role <addon-author-detail-role>` the author holds on this add-on.
:>json boolean listed: If ``true`` the user will be listed as an add-on author publicly on the add-on detail page. (If ``false`` the user is not exposed as an author.)
:>json int position: The position the author should be returned in the list of authors of the add-on :ref:`detail <addon-detail-object>`. Order is ascending so lower positions are placed earlier.
.. _addon-author-detail-role:
Possible values for the ``role`` field:
============== ==============================================================
Value Description
============== ==============================================================
developer A developer of the add-on. Developers can change all add-on
metadata and create, delete, and edit versions of the add-on.
owner An owner of the add-on. Owners have all the abilities of a
developer, plus they can add, remove, and edit add-on authors,
and delete the add-on.
============== ==============================================================
-----------
Author Edit
-----------
.. _addon-author-edit:
This endpoint allows the properties of an add-on author to be edited.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be an owner of the add-on.
.. warning::
If you change your own author role to developer from owner you will lose permission to make any further author changes.
.. http:patch:: /api/v5/addons/addon/(int:addon_id|string:addon_slug|string:addon_guid)/authors/(int:user_id)/
.. _addon-author-edit-request:
:<json string role: The :ref:`role <addon-author-detail-role>` the author holds on this add-on. Add-ons must have at least one owner author.
:<json boolean listed: If ``true`` the user will be listed as an add-on author publicly on the add-on detail page. (If ``false`` the user is not exposed as an author.) Add-ons must have at least one listed author.
:<json int position: The position the author should be returned in the list of authors of the add-on :ref:`detail <addon-detail-object>`. Order is ascending so lower positions are placed earlier.
-------------
Author Delete
-------------
.. _addon-author-delete:
This endpoint allows an add-on author to be removed from an add-on.
Add-ons must have at least one owner, and at least one listed author.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be an owner of the add-on.
.. warning::
If you delete yourself as an add-on author you will lose all access to the add-on.
.. http:delete:: /api/v5/addons/addon/(int:addon_id|string:addon_slug|string:addon_guid)/authors/(int:user_id)/
---------------------
Pending Author Create
---------------------
.. _addon-pending-author-create:
This endpoint allows an owner to invite a user to become an author of an add-on - the user will be sent an email notifying them of the invitation.
A pending author is created for the add-on, and once they confirm the invitation, they will be an author of that add-on.
Authors will be given the position at the end of the list of authors when confirmed.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be an owner of the add-on.
.. http:post:: /api/v5/addons/addon/(int:addon_id|string:addon_slug|string:addon_guid)/pending-authors/
.. _addon-pending-author-create-request:
:<json int user_id: The user to invited to become an author of this add-on.
:<json string role: The :ref:`role <addon-author-detail-role>` the author will hold on this add-on.
:<json boolean listed: If ``true`` the user will be listed as an add-on author publicly on the add-on detail page once confirmed. (If ``false`` the user will not be exposed as an author.)
----------------------
Pending Author Confirm
----------------------
.. _addon-pending-author-confirm:
This endpoint allows a user to confirm they want to be an author of an add-on.
Authors will be given the position at the end of the list of authors when confirmed.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be invited to be an author (to be a pending author).
.. http:post:: /api/v5/addons/addon/(int:addon_id|string:addon_slug|string:addon_guid)/pending-authors/confirm
----------------------
Pending Author Decline
----------------------
.. _addon-pending-author-decline:
This endpoint allows a user to decline the invitation to be an author of an add-on.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be invited to be an author (to be a pending author).
.. http:post:: /api/v5/addons/addon/(int:addon_id|string:addon_slug|string:addon_guid)/pending-authors/decline
-------------------
Pending Author List
-------------------
.. _addon-pending-author-list:
This endpoint returns a list of all the pending authors of an add-on. No pagination is supported.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be an author of the add-on.
.. http:get:: /api/v5/addons/addon/(int:id|string:slug|string:guid)/pending-authors/
---------------------
Pending Author Detail
---------------------
.. _addon-pending-author-detail:
This endpoint allows the properties of a pending add-on author to be retrieved, given a user (account) id.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be an author of the add-on.
.. http:get:: /api/v5/addons/addon/(int:id|string:slug|string:guid)/pending-authors/(int:user_id)/
.. _addon-pending-author-detail-object:
:>json int user_id: The user id for a pending author.
:>json string name: The name for a pending author.
:>json string email: The email address for a pending author.
:>json string role: The :ref:`role <addon-author-detail-role>` the author will hold on this add-on.
:>json boolean listed: If ``true`` the user will be listed as an add-on author publicly on the add-on detail page once confirmed. (If ``false`` the user will not be exposed as an author.)
-------------------
Pending Author Edit
-------------------
.. _addon-pending-author-edit:
This endpoint allows the properties of a pending add-on author to be edited.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be an owner of the add-on.
.. http:patch:: /api/v5/addons/addon/(int:addon_id|string:addon_slug|string:addon_guid)/pending-authors/(int:user_id)/
.. _addon-pending-author-edit-request:
:<json string role: The :ref:`role <addon-author-detail-role>` the author will hold on this add-on.
:<json boolean listed: If ``true`` the user will be listed as an add-on author publicly on the add-on detail page once confirmed. (If ``false`` the user will not be exposed as an author.)
---------------------
Pending Author Delete
---------------------
.. _addon-pending-author-delete:
This endpoint allows a pending add-on author to be deleted, so the user is no longer able to confirm to become an author of the add-on.
.. note::
This API requires :doc:`authentication <auth>`, and for the user to be an owner of the add-on.
.. http:delete:: /api/v5/addons/addon/(int:addon_id|string:addon_slug|string:addon_guid)/pending-authors/(int:user_id)/

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

@ -36,6 +36,7 @@ using the API.
accounts
activity
addons
authors
applications
blocklist
categories

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

@ -441,6 +441,7 @@ These are `v5` specific changes - `v4` changes apply also.
* 2022-04-28: added the ability to delete add-ons via addon detail api endpoint. https://github.com/mozilla/addons-server/issues/19072
* 2022-05-05: added the ability to list and edit add-on authors. https://github.com/mozilla/addons-server/issues/18231
* 2022-05-05: added the ability to delete add-on authors. https://github.com/mozilla/addons-server/issues/19163
* 2022-05-12: added the ability to add new add-on authors, as pending authors. https://github.com/mozilla/addons-server/issues/19164
.. _`#11380`: https://github.com/mozilla/addons-server/issues/11380/
.. _`#11379`: https://github.com/mozilla/addons-server/issues/11379/

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

@ -9,6 +9,7 @@ from olympia.tags.views import TagListView
from .views import (
AddonAuthorViewSet,
AddonPendingAuthorViewSet,
AddonAutoCompleteSearchView,
AddonFeaturedView,
AddonRecommendationView,
@ -31,6 +32,9 @@ sub_addons = NestedSimpleRouter(addons, r'addon', lookup='addon')
sub_addons.register('versions', AddonVersionViewSet, basename='addon-version')
sub_addons.register('previews', AddonPreviewViewSet, basename='addon-preview')
sub_addons.register('authors', AddonAuthorViewSet, basename='addon-author')
sub_addons.register(
'pending-authors', AddonPendingAuthorViewSet, basename='addon-pending-author'
)
sub_versions = NestedSimpleRouter(sub_addons, r'versions', lookup='version')
sub_versions.register(
r'reviewnotes', VersionReviewNotesViewSet, basename='version-reviewnotes'

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

@ -1,6 +1,7 @@
import os
import re
from django.core.exceptions import ValidationError as DjangoValidationError
from django.urls import reverse
from django.utils.translation import gettext
@ -43,7 +44,7 @@ from olympia.promoted.models import PromotedAddon
from olympia.search.filters import AddonAppVersionQueryParam
from olympia.ratings.utils import get_grouped_ratings
from olympia.tags.models import Tag
from olympia.users.models import UserProfile
from olympia.users.models import EmailUserRestriction, RESTRICTION_TYPES, UserProfile
from olympia.versions.models import (
ApplicationsVersions,
License,
@ -66,6 +67,7 @@ from .models import (
AddonApprovalsCounter,
AddonReviewerFlags,
AddonUser,
AddonUserPendingConfirmation,
DeniedSlug,
Preview,
ReplacementAddon,
@ -681,25 +683,23 @@ class AddonDeveloperSerializer(BaseUserSerializer):
class AddonAuthorSerializer(serializers.ModelSerializer):
user_id = serializers.IntegerField(source='user.id', read_only=True)
name = serializers.CharField(source='user.name', read_only=True)
email = serializers.CharField(source='user.email', read_only=True)
role = ReverseChoiceField(choices=list(amo.AUTHOR_CHOICES_API.items()))
role = ReverseChoiceField(
choices=list(amo.AUTHOR_CHOICES_API.items()), default=amo.AUTHOR_ROLE_OWNER
)
class Meta:
model = AddonUser
fields = ('user_id', 'name', 'email', 'listed', 'position', 'role')
writeable_fields = (
'listed',
'position',
'role',
)
writeable_fields = ('listed', 'position', 'role')
read_only_fields = tuple(set(fields) - set(writeable_fields))
def validate_role(self, value):
if (
value != amo.AUTHOR_ROLE_OWNER
self.instance
and value != amo.AUTHOR_ROLE_OWNER
and not AddonUser.objects.filter(
addon_id=self.instance.addon_id, role=amo.AUTHOR_ROLE_OWNER
)
@ -713,7 +713,8 @@ class AddonAuthorSerializer(serializers.ModelSerializer):
def validate_listed(self, value):
if (
value is not True
self.instance
and value is not True
and not AddonUser.objects.filter(
addon_id=self.instance.addon_id, listed=True
)
@ -726,6 +727,49 @@ class AddonAuthorSerializer(serializers.ModelSerializer):
return value
class AddonPendingAuthorSerializer(AddonAuthorSerializer):
user_id = serializers.IntegerField()
addon = serializers.HiddenField(default=ThisAddonDefault())
class Meta:
model = AddonUserPendingConfirmation
fields = ('addon', 'user_id', 'name', 'email', 'listed', 'role')
writeable_fields = ('addon', 'user_id', 'listed', 'role')
read_only_fields = tuple(set(fields) - set(writeable_fields))
def validate_user_id(self, value):
try:
user = UserProfile.objects.get(id=value)
except UserProfile.DoesNotExist:
raise exceptions.ValidationError(gettext('Account not found.'))
if not EmailUserRestriction.allow_email(
user.email, restriction_type=RESTRICTION_TYPES.SUBMISSION
):
raise exceptions.ValidationError(EmailUserRestriction.error_message)
if self.context['view'].get_addon_object().authors.filter(pk=user.pk).exists():
raise exceptions.ValidationError(
gettext('An author can only be present once.')
)
try:
if user.display_name is None:
raise DjangoValidationError('') # raise so we can catch below.
for validator in user._meta.get_field('display_name').validators:
validator(user.display_name)
except DjangoValidationError:
raise exceptions.ValidationError(
gettext(
'The account needs a display name before it can be added as an '
'author.'
)
)
return value
class PromotedAddonSerializer(serializers.ModelSerializer):
GROUP_CHOICES = [(group.id, group.api_name) for group in PROMOTED_GROUPS]
apps = serializers.SerializerMethodField()

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

@ -57,7 +57,7 @@ from olympia.files.utils import parse_addon, parse_xpi
from olympia.files.tests.test_models import UploadMixin
from olympia.ratings.models import Rating
from olympia.tags.models import Tag
from olympia.users.models import UserProfile
from olympia.users.models import EmailUserRestriction, UserProfile
from olympia.versions.models import (
ApplicationsVersions,
AppVersion,
@ -72,12 +72,14 @@ from ..models import (
AddonRegionalRestrictions,
AddonReviewerFlags,
AddonUser,
AddonUserPendingConfirmation,
DeniedSlug,
ReplacementAddon,
Preview,
)
from ..serializers import (
AddonAuthorSerializer,
AddonPendingAuthorSerializer,
AddonSerializer,
AddonSerializerWithUnlistedData,
DeveloperVersionSerializer,
@ -5897,3 +5899,190 @@ class TestAddonAuthorViewSet(TestCase):
new_author.update(listed=True)
response = self.client.delete(self.detail_url)
assert response.status_code == 204
class TestAddonPendingAuthorViewSet(TestCase):
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory(read_dev_agreement=self.days_ago(0))
self.addon = addon_factory(users=(self.user,))
self.pending_author = AddonUserPendingConfirmation.objects.create(
addon=self.addon, user=user_factory(), role=amo.AUTHOR_ROLE_OWNER
)
self.detail_url = reverse_ns(
'addon-pending-author-detail',
kwargs={'addon_pk': self.addon.pk, 'user_id': self.pending_author.user.id},
api_version='v5',
)
self.list_url = reverse_ns(
'addon-pending-author-list',
kwargs={'addon_pk': self.addon.pk},
api_version='v5',
)
def test_list(self):
dev_author = AddonUserPendingConfirmation.objects.create(
addon=self.addon, user=user_factory(), role=amo.AUTHOR_ROLE_DEV
)
hidden_author = AddonUserPendingConfirmation.objects.create(
addon=self.addon, user=user_factory(), listed=False
)
assert self.client.get(self.list_url).status_code == 401
self.client.login_api(user_factory())
assert self.client.get(self.list_url).status_code == 403
self.client.login_api(self.pending_author.user)
assert self.client.get(self.list_url).status_code == 403
self.client.login_api(self.user)
response = self.client.get(self.list_url)
assert response.status_code == 200, response.content
assert len(response.data) == 3, response.data
assert response.data[0] == AddonPendingAuthorSerializer().to_representation(
instance=self.pending_author
)
assert response.data[1] == AddonPendingAuthorSerializer().to_representation(
instance=dev_author
)
assert response.data[2] == AddonPendingAuthorSerializer().to_representation(
instance=hidden_author
)
def test_detail(self):
assert self.client.get(self.detail_url).status_code == 401
self.client.login_api(user_factory())
assert self.client.get(self.detail_url).status_code == 403
self.client.login_api(self.pending_author.user)
assert self.client.get(self.detail_url).status_code == 403
self.client.login_api(self.user)
response = self.client.get(self.detail_url)
assert response.status_code == 200, response.content
assert response.data == AddonPendingAuthorSerializer().to_representation(
instance=self.pending_author
)
def test_delete(self):
assert self.client.delete(self.detail_url).status_code == 401
self.client.login_api(user_factory())
assert self.client.delete(self.detail_url).status_code == 403
self.client.login_api(self.pending_author.user)
assert self.client.get(self.detail_url).status_code == 403
self.client.login_api(self.user)
response = self.client.delete(self.detail_url)
assert response.status_code == 204
assert not AddonUserPendingConfirmation.objects.exists()
def test_create(self):
# This will be user that will be created
user = user_factory(display_name='new_guy')
data = {'user_id': user.id, 'role': 'developer'}
assert self.client.post(self.list_url, data=data).status_code == 401
self.client.login_api(user_factory())
assert self.client.post(self.list_url, data=data).status_code == 403
self.client.login_api(self.user)
response = self.client.post(self.list_url, data=data)
assert response.status_code == 201, response.content
assert AddonUserPendingConfirmation.objects.filter(
user=user, addon=self.addon, role=amo.AUTHOR_ROLE_DEV
).exists()
@override_settings(API_THROTTLING=False)
def test_create_validation(self):
self.client.login_api(self.user)
# user doesn't exist
response = self.client.post(self.list_url, data={'user_id': 12345})
assert response.status_code == 400, response.content
assert response.data == {'user_id': ['Account not found.']}
# not allowed
user = user_factory(email='foo@baa.com')
EmailUserRestriction.objects.create(email_pattern='*@baa.com')
response = self.client.post(self.list_url, data={'user_id': user.id})
assert response.status_code == 400, response.content
assert response.data == {
'user_id': [
'The email address used for your account is not allowed for add-on '
'submission.'
]
}
# can't add a user that is already an author
user.update(email='foo@mozilla.com')
dupe_addonuser = AddonUser.objects.create(addon=self.addon, user=user)
response = self.client.post(self.list_url, data={'user_id': user.id})
assert response.status_code == 400, response.content
assert response.data == {'user_id': ['An author can only be present once.']}
# account needs a display name
dupe_addonuser.delete()
user.update(display_name=None)
response = self.client.post(self.list_url, data={'user_id': user.id})
assert response.status_code == 400, response.content
assert response.data == {
'user_id': [
'The account needs a display name before it can be added as an '
'author.'
]
}
def test_confirm(self):
AddonUser.objects.create(addon=self.addon, user=user_factory(), position=3)
confirm_url = reverse_ns(
'addon-pending-author-confirm',
kwargs={'addon_pk': self.addon.pk},
api_version='v5',
)
assert self.client.post(confirm_url).status_code == 401
self.client.login_api(user_factory()) # random user can't confirm, only invited
assert self.client.post(confirm_url).status_code == 403
pending_user = self.pending_author.user
assert pending_user not in self.addon.reload().authors.all()
self.client.login_api(pending_user)
response = self.client.post(confirm_url)
assert response.status_code == 200
assert pending_user in self.addon.reload().authors.all()
assert not AddonUserPendingConfirmation.objects.filter(
id=self.pending_author.id
).exists()
addonuser = AddonUser.objects.get(addon=self.addon, user=pending_user)
assert addonuser.position == 4 # should be + 1 after the existing authors
assert addonuser.role == self.pending_author.role
assert addonuser.listed == self.pending_author.listed
def test_decline(self):
decline_url = reverse_ns(
'addon-pending-author-decline',
kwargs={'addon_pk': self.addon.pk},
api_version='v5',
)
assert self.client.post(decline_url).status_code == 401
self.client.login_api(user_factory()) # random user can't decline, only invited
assert self.client.post(decline_url).status_code == 403
pending_user = self.pending_author.user
self.client.login_api(pending_user)
response = self.client.post(decline_url)
assert response.status_code == 200
assert not AddonUserPendingConfirmation.objects.filter(
id=self.pending_author.id
).exists()
assert pending_user not in self.addon.reload().authors.all()

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

@ -1,7 +1,7 @@
from collections import OrderedDict
from django import http
from django.db.models import Prefetch
from django.db.models import Max, Prefetch
from django.db.transaction import non_atomic_requests
from django.shortcuts import redirect
from django.utils.cache import patch_cache_control
@ -19,6 +19,7 @@ from rest_framework.mixins import (
RetrieveModelMixin,
UpdateModelMixin,
)
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
@ -71,7 +72,7 @@ from olympia.versions.models import Version
from .decorators import addon_view_factory
from .indexers import AddonIndexer
from .models import Addon, ReplacementAddon
from .models import Addon, AddonUser, AddonUserPendingConfirmation, ReplacementAddon
from .serializers import (
AddonEulaPolicySerializer,
AddonAuthorSerializer,
@ -83,6 +84,7 @@ from .serializers import (
ESAddonSerializer,
LanguageToolsSerializer,
ReplacementAddonSerializer,
AddonPendingAuthorSerializer,
PreviewSerializer,
StaticCategorySerializer,
VersionSerializer,
@ -376,7 +378,6 @@ class AddonChildMixin:
used."""
if hasattr(self, 'addon_object'):
return self.addon_object
if permission_classes is None:
permission_classes = (
AddonViewSet.write_permission_classes
@ -699,7 +700,7 @@ class AddonAuthorViewSet(
# for list and get - see check_permissions and check_object_permissions. Permissions
# are also always checked against the parent add-on in get_addon_object() using
# AddonViewSet's permissions.
permission_classes = []
permission_classes = [APIGatePermission('addon-submission-api')]
authentication_classes = [
JWTKeyAuthentication,
SessionIDAuthentication,
@ -718,7 +719,6 @@ class AddonAuthorViewSet(
# We're overriding the permission_classes to restrict access to add-on authors.
addon = self.get_addon_object(
permission_classes=[
APIGatePermission('addon-submission-api'),
AnyOf(
AllowAddonAuthor,
AllowListedViewerOrReviewer,
@ -726,7 +726,9 @@ class AddonAuthorViewSet(
),
]
)
return super().check_object_permissions(request, addon)
return super().check_permissions(request) and super().check_object_permissions(
request, addon
)
def check_object_permissions(self, request, obj):
# check_permissions will have been executed by this point, enforcing that add-on
@ -743,12 +745,60 @@ class AddonAuthorViewSet(
return self.get_addon_object().addonuser_set.all().order_by('position')
def perform_destroy(self, instance):
serializer = self.get_serializer(instance)
serializer.validate_role(value=None)
serializer.validate_listed(value=None)
if isinstance(instance, AddonUser):
serializer = self.get_serializer(instance)
serializer.validate_role(value=None)
serializer.validate_listed(value=None)
return super().perform_destroy(instance)
class AddonPendingAuthorViewSet(CreateModelMixin, AddonAuthorViewSet):
serializer_class = AddonPendingAuthorSerializer
permission_classes = [APIGatePermission('addon-submission-api'), IsAuthenticated]
def check_permissions(self, request):
# pending_confirm needs to be confirmable without being an addon author already
if self.action in ('confirm', 'decline'):
# .get_addon_object is cached so we just need to call without restricted
# permission_classes first and it'll be used for subsequent calls too.
self.get_addon_object(permission_classes=())
return super().check_permissions(request)
def get_queryset(self):
return self.get_addon_object().addonuserpendingconfirmation_set.all()
def get_object_for_request(self, request):
try:
return self.get_queryset().get(user=request.user)
except AddonUserPendingConfirmation.DoesNotExist:
raise exceptions.PermissionDenied()
@action(detail=False, methods=('post',))
def confirm(self, request, *args, **kwargs):
pending = self.get_object_for_request(request)
max_position = super().get_queryset().aggregate(max=Max('position'))['max'] or 0
AddonUser.unfiltered.update_or_create(
addon=pending.addon,
user=pending.user,
defaults={
'role': pending.role,
'listed': pending.listed,
'position': max_position + 1,
},
)
# The invitation is now obsolete.
pending.delete()
return Response()
@action(detail=False, methods=('post',))
def decline(self, request, *args, **kwargs):
self.get_object_for_request(request).delete()
return Response()
class AddonSearchView(ListAPIView):
authentication_classes = []
filter_backends = [

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

@ -811,8 +811,7 @@ class EmailUserRestriction(RestrictionAbstractBaseModel, NormalizeEmailMixin):
)
error_message = _(
'The email address used for your account is not '
'allowed for add-on submission.'
'The email address used for your account is not allowed for add-on submission.'
)
class Meta: