drop all support for internal WebTokenAuthentication (#18902)

* drop all support for internal WebTokenAuthentication

* don't need to set AUTHENTICATION_BACKENDS because using default
This commit is contained in:
Andrew Williamson 2022-02-28 12:18:37 +00:00 коммит произвёл GitHub
Родитель 11f4e19213
Коммит f202823a69
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
36 изменённых файлов: 143 добавлений и 613 удалений

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

@ -155,7 +155,7 @@ Other :ref:`editable values <account-edit-request>` can be set at the same time.
curl "https://addons.mozilla.org/api/v5/accounts/account/12345/"
-g -XPATCH --form "picture_upload=@photo.png"
-H "Authorization: Bearer <token>"
-H "Authorization: JWT <token>"
:param user-id: The numeric user id.
:form picture_upload: The user's picture to upload.
@ -345,7 +345,7 @@ sessions.
.. sourcecode:: bash
curl "https://addons.mozilla.org/api/v5/accounts/session/"
-H "Authorization: Bearer <jwt-token>" -X DELETE
-H "Authorization: Session <sessionid>" -X DELETE
**Response:**

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

@ -153,7 +153,7 @@ Other :ref:`editable values <v3-account-edit-request>` can be set at the same ti
curl "https://addons.mozilla.org/api/v3/accounts/account/12345/"
-g -XPATCH --form "picture_upload=@photo.png"
-H "Authorization: Bearer <token>"
-H "Authorization: JWT <token>"
:param user-id: The numeric user id.
:form picture_upload: The user's picture to upload.
@ -321,7 +321,7 @@ sessions.
.. sourcecode:: bash
curl "https://addons.mozilla.org/api/v3/accounts/session/"
-H "Authorization: Bearer <jwt-token>" -X DELETE
-H "Authorization: Session <sessionid>" -X DELETE
**Response:**

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

@ -17,31 +17,28 @@ are looking for how to authenticate with the API from an external client, using
your API keys, read the :ref:`documentation for external authentication
<v3-api-auth>` instead.
When using this authentication mechanism, the server is responsible for
creating an API Token when the user logs in, and sends it back in
the response. The clients must then include that token as an ``Authorization``
header on requests that need authentication. The clients never generate JWTs
themselves.
When using this authentication mechanism, the server creates a session and stores the
session id in the ``sessionid` cookie when the user logs in.
The client must then include that session id in an ``Authorization`` header on requests
that need authentication.
The clients never generate tokens or sessions themselves.
Fetching the token
Creating a session
==================
A fresh token, valid for 30 days, is automatically generated and added to the
responses of the following endpoint:
A session, valid for 30 days, is automatically generated when a log in via Firefox Accounts
has completed, and the user is redirected back to the following endpoint:
* ``/api/v3/accounts/authenticate/``
* ``/api/auth/authenticate-callback/``
The token is available in two forms:
* For the endpoint mentioned above, as a property called ``token``.
* For all endpoints, as a cookie called ``frontend_auth_token``. This cookie
expires after 30 days and is set as ``HttpOnly``.
The session id is then available in a cookie called ``sessionid``. This cookie expires
after 30 days and is set as ``HttpOnly``.
Creating an Authorization header
================================
When making an authenticated API request, put your generated API Token into an
HTTP Authorization header prefixed with ``Bearer``, like this::
When making an authenticated API request, put the session id from the cookie into an
HTTP Authorization header prefixed with ``Session``, like this::
Authorization: Bearer eyJhdXRoX2hhc2giOiJiY2E0MTZkN2RiMGU3NjFmYTA2NDE4MjAzZWU1NTMwOTM4OGZhNzcxIiwidXNlcl9pZCI6MTIzNDV9:1cqe2Q:cPMlmz8ejIkutD-gNo3EWU8IfL8
Authorization: Session 1234567890

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

@ -153,7 +153,7 @@ Other :ref:`editable values <v4-account-edit-request>` can be set at the same ti
curl "https://addons.mozilla.org/api/v4/accounts/account/12345/"
-g -XPATCH --form "picture_upload=@photo.png"
-H "Authorization: Bearer <token>"
-H "Authorization: JWT <token>"
:param user-id: The numeric user id.
:form picture_upload: The user's picture to upload.
@ -343,7 +343,7 @@ sessions.
.. sourcecode:: bash
curl "https://addons.mozilla.org/api/v4/accounts/session/"
-H "Authorization: Bearer <jwt-token>" -X DELETE
-H "Authorization: Session <sessionid>" -X DELETE
**Response:**

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

@ -10,31 +10,28 @@ are looking for how to authenticate with the API from an external client, using
your API keys, read the :ref:`documentation for external authentication
<v4-api-auth>` instead.
When using this authentication mechanism, the server is responsible for
creating an API Token when the user logs in, and sends it back in
the response. The clients must then include that token as an ``Authorization``
header on requests that need authentication. The clients never generate JWTs
themselves.
When using this authentication mechanism, the server creates a session and stores the
session id in the ``sessionid` cookie when the user logs in.
The client must then include that session id in an ``Authorization`` header on requests
that need authentication.
The clients never generate tokens or sessions themselves.
Fetching the token
Creating a session
==================
A fresh token, valid for 30 days, is automatically generated and added to the
responses of the following endpoint:
A session, valid for 30 days, is automatically generated when a log in via Firefox Accounts
has completed, and the user is redirected back to the following endpoint:
* ``/api/auth/authenticate-callback/``
The token is available in two forms:
* For the endpoint mentioned above, as a property called ``token``.
* For all endpoints, as a cookie called ``frontend_auth_token``. This cookie
expires after 30 days and is set as ``HttpOnly``.
The session id is then available in a cookie called ``sessionid``. This cookie expires
after 30 days and is set as ``HttpOnly``.
Creating an Authorization header
================================
When making an authenticated API request, put your generated API Token into an
HTTP Authorization header prefixed with ``Bearer``, like this::
When making an authenticated API request, put the session id from the cookie into an
HTTP Authorization header prefixed with ``Session``, like this::
Authorization: Bearer eyJhdXRoX2hhc2giOiJiY2E0MTZkN2RiMGU3NjFmYTA2NDE4MjAzZWU1NTMwOTM4OGZhNzcxIiwidXNlcl9pZCI6MTIzNDV9:1cqe2Q:cPMlmz8ejIkutD-gNo3EWU8IfL8
Authorization: Session 1234567890

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

@ -6,7 +6,7 @@ from unittest import mock
from olympia import amo
from olympia.abuse.models import AbuseReport
from olympia.amo.tests import (
APITestClientWebToken,
APITestClientSessionID,
TestCase,
addon_factory,
get_random_ip,
@ -16,7 +16,7 @@ from olympia.amo.tests import (
class AddonAbuseViewSetTestBase:
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.url = reverse_ns('abusereportaddon-list')
@ -404,7 +404,7 @@ class TestAddonAbuseViewSetLoggedIn(AddonAbuseViewSetTestBase, TestCase):
class UserAbuseViewSetTestBase:
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.url = reverse_ns('abusereportuser-list')

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

@ -32,7 +32,7 @@ from olympia.accounts.views import FxaNotificationView
from olympia.activity.models import ActivityLog
from olympia.amo.templatetags.jinja_helpers import absolutify
from olympia.amo.tests import (
APITestClientWebToken,
APITestClientSessionID,
InitializeSessionMixin,
TestCase,
WithDynamicEndpoints,
@ -43,7 +43,6 @@ from olympia.amo.tests import (
user_factory,
)
from olympia.amo.tests.test_helpers import get_uploaded_file
from olympia.api.authentication import WebTokenAuthentication
from olympia.api.tests.utils import APIKeyAuthTestMixin
from olympia.users.models import UserNotification, UserProfile
from olympia.users.notifications import (
@ -488,8 +487,7 @@ class TestWithUser(TestCase):
assert not self.find_user.called
assert not self.fxa_identify.called
@mock.patch.object(views, 'generate_api_token')
def test_logged_in_disallows_login(self, generate_api_token_mock):
def test_logged_in_disallows_login(self):
self.request.data = {
'code': 'foo',
'state': 'some-blob:{}'.format(
@ -499,37 +497,10 @@ class TestWithUser(TestCase):
self.user = UserProfile()
self.request.user = self.user
assert self.user.is_authenticated
self.request.COOKIES = {views.API_TOKEN_COOKIE: 'foobar'}
response = self.fn(self.request)
self.assertRedirects(response, '/next', fetch_redirect_response=False)
assert not response.cookies
assert not self.find_user.called
assert generate_api_token_mock.call_count == 0
@override_settings(SESSION_COOKIE_SECURE=True, SESSION_COOKIE_DOMAIN='example.com')
@mock.patch.object(views, 'generate_api_token', lambda u: 'fake-api-token')
def test_already_logged_in_add_api_token_cookie_if_missing(self):
self.request.data = {
'code': 'foo',
'state': 'some-blob:{}'.format(
force_str(base64.urlsafe_b64encode(b'/next'))
),
}
self.user = UserProfile()
self.request.user = self.user
assert self.user.is_authenticated
self.request.COOKIES = {}
response = self.fn(self.request)
self.assertRedirects(response, '/next', fetch_redirect_response=False)
assert not self.find_user.called
cookie = response.cookies.get(views.API_TOKEN_COOKIE)
assert len(response.cookies) == 1
assert cookie.value == 'fake-api-token'
assert cookie['domain'] == settings.SESSION_COOKIE_DOMAIN
assert cookie['max-age'] == settings.SESSION_COOKIE_AGE
assert cookie['secure'] == settings.SESSION_COOKIE_SECURE
assert cookie['httponly'] == settings.SESSION_COOKIE_HTTPONLY
assert cookie['samesite'] == settings.SESSION_COOKIE_SAMESITE
def test_state_does_not_match(self):
identity = {'uid': '1234', 'email': 'hey@yo.it'}
@ -849,9 +820,6 @@ class TestAuthenticateView(TestCase, InitializeSessionMixin):
assert self.login_user.called
assert not self.register_user.called
self.reregister_user.assert_called_with(user)
token = response.cookies['frontend_auth_token'].value
verify = WebTokenAuthentication().authenticate_token(token)
assert verify[0] == UserProfile.objects.get(fxa_id='10')
def test_success_deleted_account_reregisters_with_force_2fa_waffle(self):
self.create_switch('2fa-for-developers', active=True)
@ -877,9 +845,6 @@ class TestAuthenticateView(TestCase, InitializeSessionMixin):
assert self.login_user.called
self.register_user.assert_called_with(identity)
assert not self.reregister_user.called
token = response.cookies['frontend_auth_token'].value
verify = WebTokenAuthentication().authenticate_token(token)
assert verify[0] == UserProfile.objects.get(username='foo')
def test_success_no_account_registers_with_force_2fa_waffle(self):
self.create_switch('2fa-for-developers', active=True)
@ -1058,9 +1023,6 @@ class TestAuthenticateView(TestCase, InitializeSessionMixin):
response['Cache-Control']
== 'max-age=0, no-cache, no-store, must-revalidate, private'
)
token = response.cookies['frontend_auth_token'].value
verify = WebTokenAuthentication().authenticate_token(token)
assert verify[0] == user
self.login_user.assert_called_with(
views.AuthenticateView, mock.ANY, user, identity, self.token_data
)
@ -1257,7 +1219,7 @@ class TestAuthenticateViewV3(TestAuthenticateView):
class TestAccountViewSet(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory()
@ -1384,7 +1346,7 @@ class TestProfileViewWithJWT(APIKeyAuthTestMixin, TestCase):
class TestAccountViewSetUpdate(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
update_data = {
'display_name': 'Bob Loblaw',
'biography': 'You don`t need double talk; you need Bob Loblaw',
@ -1569,7 +1531,7 @@ class TestAccountViewSetUpdate(TestCase):
class TestAccountViewSetDelete(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory()
@ -1581,16 +1543,10 @@ class TestAccountViewSetDelete(TestCase):
# Also add api token and session cookies: they should be cleared when
# the user deletes their own account.
self.client.cookies[settings.SESSION_COOKIE_NAME] = 'something'
self.client.cookies[views.API_TOKEN_COOKIE] = 'somethingelse'
# Also add cookies that should be kept.
self.client.cookies['dontremoveme'] = 'keepme'
response = self.client.delete(self.url)
assert response.status_code == 204
assert response.cookies[views.API_TOKEN_COOKIE].value == ''
assert (
response.cookies[views.API_TOKEN_COOKIE].get('samesite')
== settings.SESSION_COOKIE_SAMESITE
)
assert response.cookies[settings.SESSION_COOKIE_NAME].value == ''
assert (
response.cookies[settings.SESSION_COOKIE_NAME].get('samesite')
@ -1598,7 +1554,6 @@ class TestAccountViewSetDelete(TestCase):
)
assert response['Cache-Control'] == 's-maxage=0'
assert 'dontremoveme' not in response.cookies
assert self.client.cookies[views.API_TOKEN_COOKIE].value == ''
assert self.client.cookies[settings.SESSION_COOKIE_NAME].value == ''
assert self.client.cookies['dontremoveme'].value == 'keepme'
assert self.user.reload().deleted
@ -1618,16 +1573,12 @@ class TestAccountViewSetDelete(TestCase):
def test_admin_delete(self):
self.grant_permission(self.user, 'Users:Edit')
self.client.login_api(self.user)
# Also add api token and session cookies: they should be *not* cleared
# when the admin deletes someone else's account.
self.client.cookies[views.API_TOKEN_COOKIE] = 'something'
random_user = user_factory()
url = reverse_ns('account-detail', kwargs={'pk': random_user.pk})
response = self.client.delete(url)
assert response.status_code == 204
assert random_user.reload().deleted
assert views.API_TOKEN_COOKIE not in response.cookies
assert self.client.cookies[views.API_TOKEN_COOKIE].value == 'something'
alog = ActivityLog.objects.get(user=self.user, action=amo.LOG.USER_DELETED.id)
assert alog.arguments == [random_user]
@ -1636,17 +1587,10 @@ class TestAccountViewSetDelete(TestCase):
addon = addon_factory(users=[self.user])
assert self.user.is_developer and self.user.is_addon_developer
# Also add api token and session cookies: they should be *not* cleared
# when the account has not been deleted.
self.client.cookies[views.API_TOKEN_COOKIE] = 'something'
response = self.client.delete(self.url)
assert response.status_code == 204
assert self.user.reload().deleted
assert addon.reload().is_deleted
# Account was deleted so the cookies should have been cleared
assert response.cookies[views.API_TOKEN_COOKIE].value == ''
assert self.client.cookies[views.API_TOKEN_COOKIE].value == ''
def test_theme_developers_can_delete(self):
self.client.login_api(self.user)
@ -1827,7 +1771,7 @@ class TestSessionView(TestCase):
'olympia.accounts.views.verify.fxa_identify',
lambda code, config: (identity, self.token_data),
):
response = self.client.get(
self.client.get(
'{url}?code={code}&state={state}'.format(
url=reverse_ns(
'accounts.authenticate', api_version=self.api_version
@ -1836,22 +1780,17 @@ class TestSessionView(TestCase):
code='thecode',
)
)
token = response.cookies[views.API_TOKEN_COOKIE].value
assert token
verify = WebTokenAuthentication().authenticate_token(token)
assert verify[0] == user
assert self.client.session['_auth_user_id'] == str(user.id)
return token
def test_delete_when_authenticated(self):
user = user_factory(fxa_id='123123412')
token = self.login_user(user)
authorization = f'Bearer {token}'
self.login_user(user)
authorization = f'Session {self.client.session.session_key}'
assert user.auth_id
response = self.client.delete(
reverse_ns('accounts.session'), HTTP_AUTHORIZATION=authorization
)
assert not response.cookies[views.API_TOKEN_COOKIE].value
assert response.status_code == 200, response.content
assert not self.client.session.get('_auth_user_id')
user.reload()
assert not user.auth_id # Cleared at logout.
@ -1862,21 +1801,22 @@ class TestSessionView(TestCase):
def test_cors_headers_are_exposed(self):
user = user_factory(fxa_id='123123412')
token = self.login_user(user)
authorization = f'Bearer {token}'
self.login_user(user)
authorization = f'Session {self.client.session.session_key}'
origin = 'http://example.org'
response = self.client.delete(
reverse_ns('accounts.session'),
HTTP_AUTHORIZATION=authorization,
HTTP_ORIGIN=origin,
)
assert response.status_code == 200, response.content
assert response['Access-Control-Allow-Origin'] == origin
assert response['Access-Control-Allow-Credentials'] == 'true'
def test_delete_omits_cors_headers_when_there_is_no_origin(self):
user = user_factory(fxa_id='123123412')
token = self.login_user(user)
authorization = f'Bearer {token}'
self.login_user(user)
authorization = f'Session {self.client.session.session_key}'
response = self.client.delete(
reverse_ns('accounts.session'),
HTTP_AUTHORIZATION=authorization,
@ -1912,7 +1852,7 @@ class TestSessionViewV3(TestSessionView):
class TestAccountNotificationViewSetList(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory()
@ -2070,7 +2010,7 @@ class TestAccountNotificationViewSetList(TestCase):
class TestAccountNotificationViewSetUpdate(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory()
@ -2210,7 +2150,7 @@ class TestAccountNotificationViewSetUpdate(TestCase):
class TestAccountNotificationUnsubscribe(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory()

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

@ -9,7 +9,6 @@ from urllib.parse import quote_plus
from django.conf import settings
from django.contrib.auth import login, logout
from django.contrib.auth.signals import user_logged_in
from django.core import signing
from django.db.models import Q
from django.http import Http404, HttpResponseRedirect
from django.urls import reverse
@ -62,7 +61,6 @@ from olympia.api.authentication import (
JWTKeyAuthentication,
SessionIDAuthentication,
UnsubscribeTokenAuthentication,
WebTokenAuthentication,
)
from olympia.api.permissions import AnyOf, ByHttpMethod, GroupPermission
from olympia.users.models import UserNotification, UserProfile
@ -102,13 +100,6 @@ LOGIN_ERROR_MESSAGES = {
ERROR_STATE_MISMATCH: _('You could not be logged in. Please try again.'),
}
# Name of the cookie that contains the auth token for the API. It used to be
# "api_auth_token" but we had to change it because it wasn't set on the right
# domain, and we couldn't clear both the old and new versions at the same time,
# since sending multiple Set-Cookie headers with the same name is not allowed
# by the spec, even if they have a distinct domain attribute.
API_TOKEN_COOKIE = 'frontend_auth_token'
def safe_redirect(request, url, action):
if not is_safe_url(url, request):
@ -254,17 +245,7 @@ def with_user(f):
)
return safe_redirect(request, next_path, ERROR_STATE_MISMATCH)
elif request.user.is_authenticated:
response = safe_redirect(request, next_path, ERROR_AUTHENTICATED)
# If the api token cookie is missing but we're still
# authenticated using the session, add it back.
if API_TOKEN_COOKIE not in request.COOKIES:
log.info(
'User %s was already authenticated but did not '
'have an API token cookie, adding one.',
request.user.pk,
)
response = add_api_token_to_response(response, request.user)
return response
return safe_redirect(request, next_path, ERROR_AUTHENTICATED)
try:
if use_fake_fxa() and 'fake_fxa_email' in data:
# Bypassing real authentication, we take the email provided
@ -337,33 +318,6 @@ def with_user(f):
return inner
def generate_api_token(user):
"""Generate a new API token for a given user."""
data = {
'auth_hash': user.get_session_auth_hash(),
'user_id': user.pk,
}
return signing.dumps(data, salt=WebTokenAuthentication.salt)
def add_api_token_to_response(response, user):
"""Generate API token and add it in a session cookie named API_TOKEN_COOKIE."""
token = generate_api_token(user)
# Include the API token in a session cookie, so that it is
# available for universal frontend apps.
response.set_cookie(
API_TOKEN_COOKIE,
token,
domain=settings.SESSION_COOKIE_DOMAIN,
max_age=settings.SESSION_COOKIE_AGE,
secure=settings.SESSION_COOKIE_SECURE,
httponly=settings.SESSION_COOKIE_HTTPONLY,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
return response
class FxAConfigMixin:
DEFAULT_FXA_CONFIG_NAME = settings.DEFAULT_FXA_CONFIG_NAME
ALLOWED_FXA_CONFIGS = settings.ALLOWED_FXA_CONFIGS
@ -433,9 +387,7 @@ class AuthenticateView(FxAConfigMixin, APIView):
action = 'login'
login_user(self.__class__, request, user, identity, token_data)
response = safe_redirect(request, next_path, action)
add_api_token_to_response(response, user)
return response
return safe_redirect(request, next_path, action)
def logout_user(request, response):
@ -444,11 +396,6 @@ def logout_user(request, response):
# generated during the next login.
request.user.update(auth_id=None)
logout(request)
response.delete_cookie(
API_TOKEN_COOKIE,
domain=settings.SESSION_COOKIE_DOMAIN,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
# This view is not covered by the CORS middleware, see:
@ -593,7 +540,6 @@ class AccountViewSet(
class ProfileView(APIView):
authentication_classes = [
JWTKeyAuthentication,
WebTokenAuthentication,
SessionIDAuthentication,
]
permission_classes = [IsAuthenticated]

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

@ -12,7 +12,7 @@ from olympia.activity.views import EmailCreationPermission, inbound_email
from olympia.addons.models import AddonUser, AddonRegionalRestrictions
from olympia.addons.utils import generate_addon_guid
from olympia.amo.tests import (
APITestClientWebToken,
APITestClientSessionID,
TestCase,
addon_factory,
req_factory_factory,
@ -217,7 +217,7 @@ class ReviewNotesViewSetDetailMixin(LogMixin):
class TestReviewNotesViewSetDetail(ReviewNotesViewSetDetailMixin, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()
@ -258,7 +258,7 @@ class TestReviewNotesViewSetDetail(ReviewNotesViewSetDetailMixin, TestCase):
class TestReviewNotesViewSetList(ReviewNotesViewSetDetailMixin, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()
@ -337,7 +337,7 @@ class TestReviewNotesViewSetList(ReviewNotesViewSetDetailMixin, TestCase):
class TestReviewNotesViewSetCreate(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()

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

@ -29,7 +29,6 @@ from olympia import amo
from olympia.activity.models import ActivityLog
from olympia.addons.models import AddonCategory, AddonReviewerFlags, DeniedSlug
from olympia.amo.tests import (
APITestClientWebToken,
ESTestCase,
APITestClientJWT,
APITestClientSessionID,
@ -80,7 +79,7 @@ from ..views import (
class TestStatus(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
fixtures = ['base/addon_3615']
def setUp(self):
@ -497,7 +496,7 @@ class AddonAndVersionViewSetDetailMixin:
class TestAddonViewSetDetail(AddonAndVersionViewSetDetailMixin, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()
@ -711,7 +710,7 @@ class TestAddonViewSetDetail(AddonAndVersionViewSetDetailMixin, TestCase):
class TestAddonViewSetCreate(UploadMixin, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()
@ -1186,12 +1185,8 @@ class TestAddonViewSetCreateJWTAuth(TestAddonViewSetCreate):
client_class = APITestClientJWT
class TestAddonViewSetCreateSessionIDAuth(TestAddonViewSetCreate):
client_class = APITestClientSessionID
class TestAddonViewSetUpdate(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()
@ -1536,7 +1531,7 @@ class TestAddonViewSetUpdateJWTAuth(TestAddonViewSetUpdate):
class TestVersionViewSetDetail(AddonAndVersionViewSetDetailMixin, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()
@ -1906,7 +1901,7 @@ class SubmitSourceMixin:
class TestVersionViewSetCreate(UploadMixin, SubmitSourceMixin, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
@classmethod
def setUpTestData(cls):
@ -2373,7 +2368,7 @@ class TestVersionViewSetCreateJWTAuth(TestVersionViewSetCreate):
class TestVersionViewSetUpdate(UploadMixin, SubmitSourceMixin, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
@classmethod
def setUpTestData(cls):
@ -2790,7 +2785,7 @@ class TestVersionViewSetUpdateJWTAuth(TestVersionViewSetUpdate):
class TestVersionViewSetList(AddonAndVersionViewSetDetailMixin, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()
@ -3122,7 +3117,7 @@ class TestVersionViewSetList(AddonAndVersionViewSetDetailMixin, TestCase):
class TestAddonViewSetEulaPolicy(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()
@ -3159,7 +3154,7 @@ class TestAddonViewSetEulaPolicy(TestCase):
class TestAddonSearchView(ESTestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
fixtures = ['base/users']
@ -4126,7 +4121,7 @@ class TestAddonSearchView(ESTestCase):
class TestAddonAutoCompleteSearchView(ESTestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
fixtures = ['base/users']
@ -4326,7 +4321,7 @@ class TestAddonAutoCompleteSearchView(ESTestCase):
class TestAddonFeaturedView(ESTestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
fixtures = ['base/users']
@ -4391,7 +4386,7 @@ class TestAddonFeaturedView(ESTestCase):
class TestStaticCategoryView(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()
@ -4465,7 +4460,7 @@ class TestStaticCategoryView(TestCase):
class TestLanguageToolsView(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()
@ -4806,7 +4801,7 @@ class TestLanguageToolsView(TestCase):
class TestReplacementAddonView(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def test_basic(self):
# Add a single addon replacement
@ -4848,7 +4843,7 @@ class TestCompatOverrideView(TestCase):
But now we don't have any CompatOverrides we just return an empty response.
"""
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def test_response(self):
response = self.client.get(
@ -4862,7 +4857,7 @@ class TestCompatOverrideView(TestCase):
class TestAddonRecommendationView(ESTestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
fixtures = ['base/users']

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

@ -31,7 +31,6 @@ from olympia.amo.urlresolvers import get_outgoing_url
from olympia.api.authentication import (
JWTKeyAuthentication,
SessionIDAuthentication,
WebTokenAuthentication,
)
from olympia.api.exceptions import UnavailableForLegalReasons
from olympia.api.pagination import ESPageNumberPagination
@ -212,7 +211,6 @@ class AddonViewSet(
]
authentication_classes = [
JWTKeyAuthentication,
WebTokenAuthentication,
SessionIDAuthentication,
]
georestriction_classes = [
@ -378,7 +376,6 @@ class AddonVersionViewSet(
permission_classes = []
authentication_classes = [
JWTKeyAuthentication,
WebTokenAuthentication,
SessionIDAuthentication,
]
throttle_classes = addon_submission_throttles

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

@ -18,7 +18,6 @@ from django.conf import settings
from django.contrib import auth
from django.contrib.auth.models import AnonymousUser
from django.contrib.messages.storage.fallback import FallbackStorage
from django.core import signing
from django.core.management import call_command
from django.db.models.signals import post_save
from django.http import HttpRequest, SimpleCookie
@ -37,7 +36,6 @@ from rest_framework.test import APIClient, APIRequestFactory
from waffle.models import Flag, Sample, Switch
from olympia import amo
from olympia.api.authentication import WebTokenAuthentication
from olympia.amo import search as amo_search
from olympia.access.models import Group, GroupUser
from olympia.accounts.utils import fxa_login_url
@ -336,36 +334,6 @@ class TestClient(Client):
raise AttributeError
class APITestClientWebToken(APIClient):
def generate_api_token(self, user, **payload_overrides):
"""
Creates a jwt token for this user.
"""
data = {
'auth_hash': user.get_session_auth_hash(),
'user_id': user.pk,
}
data.update(payload_overrides)
token = signing.dumps(data, salt=WebTokenAuthentication.salt)
return token
def login_api(self, user):
"""
Creates a jwt token for this user as if they just logged in. This token
will be sent in an Authorization header with all future requests for
this client.
"""
prefix = WebTokenAuthentication.auth_header_prefix
token = self.generate_api_token(user)
self.defaults['HTTP_AUTHORIZATION'] = f'{prefix} {token}'
def logout_api(self):
"""
Removes the Authorization header from future requests.
"""
self.defaults.pop('HTTP_AUTHORIZATION', None)
class APITestClientSessionID(APIClient):
def create_session(self, user, **overrides):
"""

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

@ -25,7 +25,7 @@ from olympia.access.models import Group, GroupUser
from olympia.addons.models import Addon, AddonUser, get_random_slug
from olympia.amo.sitemap import get_sitemap_path
from olympia.amo.tests import (
APITestClientWebToken,
APITestClientSessionID,
TestCase,
WithDynamicEndpointsAndTransactions,
check_links,
@ -543,7 +543,7 @@ class TestVersion(TestCase):
class TestSiteStatusAPI(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()

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

@ -1,8 +1,5 @@
from django.conf import settings
from django.contrib.auth import get_user
from django.contrib.auth.signals import user_logged_in
from django.core import signing
from django.utils.crypto import constant_time_compare
from django.utils.encoding import force_str, smart_str
from django.utils.translation import gettext
@ -27,137 +24,6 @@ from olympia.users.utils import UnsubscribeCode
log = olympia.core.logger.getLogger('z.api.authentication')
class WebTokenAuthentication(BaseAuthentication):
"""
DRF authentication class for our internal auth API tokens (i.e. not
external clients using API keys - see JWTKeyAuthentication for that).
Clients should authenticate by passing the token key in the "Authorization"
HTTP header, prepended with the "Bearer" prefix. For example:
Authorization: Bearer eyJhbGciOiAiSFMyNTYiLCAidHlwIj
"""
www_authenticate_realm = 'Access to addons.mozilla.org internal API'
auth_header_prefix = 'Bearer'
salt = 'olympia.api.auth'
def authenticate_header(self, request):
"""
Return a string to be used as the value of the `WWW-Authenticate`
header in a `401 Unauthenticated` response, or `None` if the
authentication scheme should return `403 Permission Denied` responses.
"""
return '{} realm="{}"'.format(
self.auth_header_prefix, self.www_authenticate_realm
)
def get_token_value(self, request):
auth_header = get_authorization_header(request).split()
expected_header_prefix = self.auth_header_prefix.upper()
if not auth_header or (
smart_str(auth_header[0].upper()) != expected_header_prefix
):
return None
if len(auth_header) == 1:
msg = {
'detail': gettext(
'Invalid Authorization header. No credentials provided.'
),
'code': 'ERROR_INVALID_HEADER',
}
raise exceptions.AuthenticationFailed(msg)
elif len(auth_header) > 2:
msg = {
'detail': gettext(
'Invalid Authorization header. Credentials '
'string should not contain spaces.'
),
'code': 'ERROR_INVALID_HEADER',
}
raise exceptions.AuthenticationFailed(msg)
return auth_header[1]
def authenticate(self, request):
"""
Returns a two-tuple of `User` and token if a valid token has been
supplied. Otherwise returns `None`.
Raises AuthenticationFailed if a token was specified but it's invalid
in some way (expired signature, invalid token, etc.)
"""
token = self.get_token_value(request)
if token is None:
# No token specified, skip this authentication method.
return None
# Proceed.
return self.authenticate_token(token)
def authenticate_token(self, token):
try:
payload = signing.loads(
force_str(token),
salt=self.salt,
max_age=settings.SESSION_COOKIE_AGE or None,
)
except signing.SignatureExpired:
msg = {
'detail': gettext('Signature has expired.'),
'code': 'ERROR_SIGNATURE_EXPIRED',
}
raise exceptions.AuthenticationFailed(msg)
except signing.BadSignature:
msg = {
'detail': gettext('Error decoding signature.'),
'code': 'ERROR_DECODING_SIGNATURE',
}
raise exceptions.AuthenticationFailed(msg)
# We have a valid token, try to find the corresponding user.
user = self.authenticate_credentials(payload)
return (user, token)
def authenticate_credentials(self, payload):
"""
Return a non-deleted user that matches the payload's user id.
Mimic what our UserAndAddrMiddleware and django's get_user() do when
authenticating, because otherwise that behaviour would be missing in
the API since API auth happens after the middleware process request
phase.
"""
if 'user_id' not in payload:
log.info(f'No user_id in token payload {payload}')
raise exceptions.AuthenticationFailed()
try:
user = UserProfile.objects.filter(deleted=False).get(pk=payload['user_id'])
except UserProfile.DoesNotExist:
log.info(f'User not found from token payload {payload}')
raise exceptions.AuthenticationFailed()
# Check get_session_auth_hash like django's get_user() does.
session_auth_hash = user.get_session_auth_hash()
payload_auth_hash = payload.get('auth_hash', '')
if not constant_time_compare(payload_auth_hash, session_auth_hash):
log.info(
'User tried to authenticate with invalid auth hash in'
'payload {}'.format(payload)
)
msg = {
'detail': gettext('Auth hash mismatch. Session is likely expired.'),
'code': 'ERROR_AUTHENTICATION_EXPIRED',
}
raise exceptions.AuthenticationFailed(msg)
# Set user in thread like UserAndAddrMiddleware does.
core.set_user(user)
return user
class SessionIDAuthentication(BaseAuthentication):
"""
DRF authentication class using the session id for django sessions (i.e. not

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

@ -6,10 +6,8 @@ from datetime import datetime, timedelta
from unittest import mock
from django.conf import settings
from django.core import signing
from django.test import RequestFactory
from django.urls import reverse
from django.utils.encoding import force_bytes
import jwt
@ -23,8 +21,8 @@ from olympia import core
from olympia.accounts.verify import IdentificationError
from olympia.amo.templatetags.jinja_helpers import absolutify
from olympia.amo.tests import (
APITestClientWebToken,
APITestClientSessionID,
APITestClientJWT,
TestCase,
WithDynamicEndpoints,
user_factory,
@ -32,7 +30,6 @@ from olympia.amo.tests import (
from olympia.api.authentication import (
JWTKeyAuthentication,
SessionIDAuthentication,
WebTokenAuthentication,
)
from olympia.api.tests import JWTAuthKeyTester
@ -54,7 +51,7 @@ class JWTKeyAuthTestView(APIView):
class TestJWTKeyAuthentication(JWTAuthKeyTester, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientJWT
def setUp(self):
super().setUp()
@ -181,7 +178,7 @@ class TestJWTKeyAuthentication(JWTAuthKeyTester, TestCase):
class TestJWTKeyAuthProtectedView(WithDynamicEndpoints, JWTAuthKeyTester, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientJWT
def setUp(self):
super().setUp()
@ -220,167 +217,6 @@ class TestJWTKeyAuthProtectedView(WithDynamicEndpoints, JWTAuthKeyTester, TestCa
assert res.status_code == 401, res.content
class TestWebTokenAuthentication(TestCase):
client_class = APITestClientWebToken
def setUp(self):
super().setUp()
self.auth = WebTokenAuthentication()
self.factory = RequestFactory()
self.user = user_factory(read_dev_agreement=datetime.now())
def _authenticate(self, token):
url = absolutify('/api/v4/whatever/')
prefix = WebTokenAuthentication.auth_header_prefix
request = self.factory.post(
url,
HTTP_HOST='testserver',
HTTP_AUTHORIZATION=f'{prefix} {token}',
)
return self.auth.authenticate(request)
def test_success(self):
token = self.client.generate_api_token(self.user)
user, _ = self._authenticate(token)
assert user == self.user
def test_authenticate_header(self):
request = self.factory.post('/api/v4/whatever/')
assert (
self.auth.authenticate_header(request)
== 'Bearer realm="Access to addons.mozilla.org internal API"'
)
def test_wrong_header_only_prefix(self):
request = self.factory.post(
'/api/v4/whatever/',
HTTP_AUTHORIZATION=WebTokenAuthentication.auth_header_prefix,
)
with self.assertRaises(AuthenticationFailed) as exp:
self.auth.authenticate(request)
assert exp.exception.detail['code'] == 'ERROR_INVALID_HEADER'
assert exp.exception.detail['detail'] == (
'Invalid Authorization header. No credentials provided.'
)
def test_wrong_header_too_many_spaces(self):
request = self.factory.post(
'/api/v4/whatever/',
HTTP_AUTHORIZATION='{} foo bar'.format(
WebTokenAuthentication.auth_header_prefix
),
)
with self.assertRaises(AuthenticationFailed) as exp:
self.auth.authenticate(request)
assert exp.exception.detail['code'] == 'ERROR_INVALID_HEADER'
assert exp.exception.detail['detail'] == (
'Invalid Authorization header. '
'Credentials string should not contain spaces.'
)
def test_no_token(self):
request = self.factory.post('/api/v4/whatever/')
self.auth.authenticate(request) is None
def test_expired_token(self):
old_date = datetime.now() - timedelta(seconds=settings.SESSION_COOKIE_AGE + 1)
with freeze_time(old_date):
token = self.client.generate_api_token(self.user)
with self.assertRaises(AuthenticationFailed) as exp:
self._authenticate(token)
assert exp.exception.detail['code'] == 'ERROR_SIGNATURE_EXPIRED'
assert exp.exception.detail['detail'] == 'Signature has expired.'
def test_still_valid_token(self):
not_so_old_date = datetime.now() - timedelta(
seconds=settings.SESSION_COOKIE_AGE - 30
)
with freeze_time(not_so_old_date):
token = self.client.generate_api_token(self.user)
assert self._authenticate(token)[0] == self.user
def test_bad_token(self):
token = 'garbage'
with self.assertRaises(AuthenticationFailed) as exp:
self._authenticate(token)
assert exp.exception.detail['code'] == 'ERROR_DECODING_SIGNATURE'
assert exp.exception.detail['detail'] == 'Error decoding signature.'
def test_user_id_is_none(self):
token = self.client.generate_api_token(self.user, user_id=None)
with self.assertRaises(AuthenticationFailed) as exp:
self._authenticate(token)
assert 'code' not in exp.exception.detail
def test_no_user_id_in_payload(self):
data = {
'auth_hash': self.user.get_session_auth_hash(),
}
token = signing.dumps(data, salt=WebTokenAuthentication.salt)
with self.assertRaises(AuthenticationFailed) as exp:
self._authenticate(token)
assert 'code' not in exp.exception.detail
def test_no_auth_hash_in_payload(self):
data = {
'user_id': self.user.pk,
}
token = signing.dumps(data, salt=WebTokenAuthentication.salt)
with self.assertRaises(AuthenticationFailed) as exp:
self._authenticate(token)
assert exp.exception.detail['code'] == 'ERROR_AUTHENTICATION_EXPIRED'
assert (
exp.exception.detail['detail']
== 'Auth hash mismatch. Session is likely expired.'
)
def test_user_deleted(self):
self.user.delete()
token = self.client.generate_api_token(self.user)
with self.assertRaises(AuthenticationFailed) as exp:
self._authenticate(token)
assert 'code' not in exp.exception.detail
def test_invalid_user_not_found(self):
token = self.client.generate_api_token(self.user, user_id=-1)
with self.assertRaises(AuthenticationFailed) as exp:
self._authenticate(token)
assert 'code' not in exp.exception.detail
def test_invalid_user_other_user(self):
user2 = user_factory(read_dev_agreement=datetime.now())
token = self.client.generate_api_token(self.user, user_id=user2.pk)
with self.assertRaises(AuthenticationFailed) as exp:
self._authenticate(token)
assert exp.exception.detail['code'] == 'ERROR_AUTHENTICATION_EXPIRED'
assert (
exp.exception.detail['detail']
== 'Auth hash mismatch. Session is likely expired.'
)
def test_wrong_auth_id(self):
token = self.client.generate_api_token(self.user)
self.user.update(auth_id=self.user.auth_id + 42)
with self.assertRaises(AuthenticationFailed) as exp:
self._authenticate(token)
assert exp.exception.detail['code'] == 'ERROR_AUTHENTICATION_EXPIRED'
assert (
exp.exception.detail['detail']
== 'Auth hash mismatch. Session is likely expired.'
)
def test_make_sure_token_is_decodable(self):
token = self.client.generate_api_token(self.user)
# A token is really a string containing the json dict,
# a timestamp and a signature, separated by ':'. The base64 encoding
# lacks padding, which is why we need to use signing.b64_decode() which
# handles that for us.
data = json.loads(signing.b64_decode(force_bytes(token.split(':')[0])))
assert data['user_id'] == self.user.pk
assert data['auth_hash'] == self.user.get_session_auth_hash()
class TestSessionIDAuthentication(TestCase):
client_class = APITestClientSessionID

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

@ -11,7 +11,7 @@ from rest_framework.views import APIView
from olympia import amo
from olympia.access.models import GroupUser
from olympia.amo.tests import (
APITestClientWebToken,
APITestClientSessionID,
TestCase,
WithDynamicEndpoints,
addon_factory,
@ -47,7 +47,7 @@ def myview(*args, **kwargs):
class TestGroupPermissionOnView(WithDynamicEndpoints):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
# Note: be careful when testing, under the hood we're using a method that
# relies on UserProfile.groups_list, which is cached on the UserProfile

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

@ -12,7 +12,7 @@ from rest_framework.response import Response
from olympia import amo
from olympia.amo.tests import (
APITestClientWebToken,
APITestClientSessionID,
TestCase,
addon_factory,
collection_factory,
@ -24,7 +24,7 @@ from olympia.bandwagon.models import Collection, CollectionAddon
class TestCollectionViewSetList(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory()
@ -114,7 +114,7 @@ class TestCollectionViewSetList(TestCase):
class TestCollectionViewSetDetail(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory()
@ -310,7 +310,7 @@ class TestCollectionViewSetDetail(TestCase):
class CollectionViewSetDataMixin:
client_class = APITestClientWebToken
client_class = APITestClientSessionID
data = {
'name': {'fr': 'lé $túff', 'en-US': '$tuff'},
'description': {'fr': 'Un dis une dát', 'en-US': 'dis n dat'},
@ -642,7 +642,7 @@ class TestCollectionViewSetPatch(CollectionViewSetDataMixin, TestCase):
class TestCollectionViewSetDelete(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory()
@ -776,7 +776,7 @@ class CollectionAddonViewSetMixin:
class TestCollectionAddonViewSetList(CollectionAddonViewSetMixin, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory()
@ -1209,7 +1209,7 @@ class TestCollectionAddonViewSetList(CollectionAddonViewSetMixin, TestCase):
class TestCollectionAddonViewSetDetail(CollectionAddonViewSetMixin, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory()
@ -1247,7 +1247,7 @@ class TestCollectionAddonViewSetDetail(CollectionAddonViewSetMixin, TestCase):
class TestCollectionAddonViewSetCreate(CollectionAddonViewSetMixin, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory()
@ -1355,7 +1355,7 @@ class TestCollectionAddonViewSetCreate(CollectionAddonViewSetMixin, TestCase):
class TestCollectionAddonViewSetPatch(CollectionAddonViewSetMixin, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory()
@ -1417,7 +1417,7 @@ class TestCollectionAddonViewSetPatch(CollectionAddonViewSetMixin, TestCase):
class TestCollectionAddonViewSetDelete(CollectionAddonViewSetMixin, TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
self.user = user_factory()

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

@ -110,7 +110,7 @@
<tr>
<td colspan="4" id="{{ version.id }}-review-history" class="review-history hidden"
data-api-url="{{ drf_url('version-reviewnotes-list', addon.id, version.id) }}"
data-token="{{ token }}">
data-session-id="{{ session_id }}">
<div class="history-container">
<div class="review-entry-loading">{{ _('Loading Review History...') }}</div>
<div class="review-entry-failure hidden">{{ _('We had a problem retrieving review notes') }}</div>
@ -126,7 +126,7 @@
{% if latest_version_in_channel_including_disabled == version %}
<div class="dev-review-reply">
<form class="dev-review-reply-form" action="{{ drf_url('version-reviewnotes-list', addon.id, version.id) }}"
data-token="{{ token }}" data-history="#{{ version.id }}-review-history" data-no-csrf>
data-session-id="{{ session_id }}" data-history="#{{ version.id }}-review-history" data-no-csrf>
<textarea name="comments" placeholder="{{ _('Leave a reply') }}"></textarea>
<button type="submit" class="submit" >{{ _('Reply') }}</button>
</form>

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

@ -20,7 +20,6 @@ from pyquery import PyQuery as pq
from waffle.testutils import override_switch
from olympia import amo, core
from olympia.accounts.views import API_TOKEN_COOKIE
from olympia.activity.models import GENERIC_USER_NAME, ActivityLog
from olympia.addons.models import Addon, AddonCategory, AddonUser
from olympia.amo.templatetags.jinja_helpers import (
@ -2029,16 +2028,12 @@ class TestLogout(UserViewBase):
def test_session_cookie_deleted_on_logout(self):
self.client.login(email='jbalogh@mozilla.com')
self.client.cookies[API_TOKEN_COOKIE] = 'some.token.value'
response = self.client.get(reverse('devhub.logout'))
cookie = response.cookies[settings.SESSION_COOKIE_NAME]
cookie_date_string = 'Thu, 01 Jan 1970 00:00:00 GMT'
assert cookie.value == ''
# in django2.1+ changed to django.utils.http.http_date from cookie_date
assert cookie['expires'].replace('-', ' ') == cookie_date_string
jwt_cookie = response.cookies[API_TOKEN_COOKIE]
assert jwt_cookie.value == ''
assert jwt_cookie['expires'].replace('-', ' ') == cookie_date_string
class TestStatsLinksInManageMySubmissionsPage(TestCase):

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

@ -13,7 +13,6 @@ from pyquery import PyQuery as pq
from waffle.testutils import override_switch
from olympia import amo
from olympia.accounts.views import API_TOKEN_COOKIE
from olympia.activity.models import ActivityLog
from olympia.activity.utils import ACTIVITY_MAIL_GROUP
from olympia.addons.models import Addon, AddonReviewerFlags
@ -609,7 +608,6 @@ class TestVersion(TestCase):
assert buttons.length == 0
def test_version_history(self):
self.client.cookies[API_TOKEN_COOKIE] = 'magicbeans'
v1 = self.version
v2, _ = self._extra_version_and_file(amo.STATUS_AWAITING_REVIEW)
@ -631,7 +629,9 @@ class TestVersion(TestCase):
# Test review history
review_history_td = doc('#%s-review-history' % v1.id)[0]
assert review_history_td.attrib['data-token'] == 'magicbeans'
assert review_history_td.attrib['data-session-id'] == (
self.client.session.session_key
)
api_url = absolutify(
reverse_ns(
'version-reviewnotes-list', args=[self.addon.id, self.version.id]
@ -648,7 +648,7 @@ class TestVersion(TestCase):
assert doc('.dev-review-reply-form').length == 1
review_form = doc('.dev-review-reply-form')[0]
review_form.attrib['action'] == api_url
review_form.attrib['data-token'] == 'magicbeans'
review_form.attrib['data-session-id'] == self.client.session.session_key
review_form.attrib['data-history'] == '#%s-review-history' % v2.id
def test_version_history_mixed_channels(self):

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

@ -30,7 +30,7 @@ import olympia.core.logger
from olympia import amo
from olympia.access import acl
from olympia.accounts.utils import redirect_for_login
from olympia.accounts.views import API_TOKEN_COOKIE, logout_user
from olympia.accounts.views import logout_user
from olympia.activity.models import ActivityLog, VersionLog
from olympia.activity.utils import log_and_notify
from olympia.addons.models import (
@ -1333,9 +1333,12 @@ def version_list(request, addon_id, addon):
versions = amo_utils.paginate(request, qs)
is_admin = acl.action_allowed(request, amo.permissions.REVIEWS_ADMIN)
token = request.COOKIES.get(API_TOKEN_COOKIE, None)
data = {'addon': addon, 'versions': versions, 'token': token, 'is_admin': is_admin}
data = {
'addon': addon,
'versions': versions,
'session_id': request.session.session_key,
'is_admin': is_admin,
}
return TemplateResponse(request, 'devhub/versions/list.html', context=data)

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

@ -9,7 +9,7 @@ from freezegun import freeze_time
from olympia import amo
from olympia.amo.tests import (
APITestClientWebToken,
APITestClientSessionID,
APITestClientJWT,
get_random_ip,
reverse_ns,
@ -62,7 +62,7 @@ class TestServeFileUpload(UploadMixin, TestCase):
class TestFileUploadViewSet(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()

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

@ -15,7 +15,6 @@ from olympia.amo.utils import HttpResponseXSendFile
from olympia.api.authentication import (
JWTKeyAuthentication,
SessionIDAuthentication,
WebTokenAuthentication,
)
from olympia.api.permissions import AllowOwner, APIGatePermission
from olympia.api.throttling import file_upload_throttles
@ -65,7 +64,6 @@ class FileUploadViewSet(CreateModelMixin, ReadOnlyModelViewSet):
]
authentication_classes = [
JWTKeyAuthentication,
WebTokenAuthentication,
SessionIDAuthentication,
]
lookup_field = 'uuid'

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

@ -1432,7 +1432,6 @@ REST_FRAMEWORK = {
# We can't use the default because we don't use django timezone support.
'DATETIME_FORMAT': '%Y-%m-%dT%H:%M:%SZ',
'DEFAULT_AUTHENTICATION_CLASSES': (
'olympia.api.authentication.WebTokenAuthentication',
'olympia.api.authentication.SessionIDAuthentication',
),
'DEFAULT_PAGINATION_CLASS': ('olympia.api.pagination.CustomPageNumberPagination'),

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

@ -14,7 +14,7 @@ from olympia import amo
from olympia.activity.models import ActivityLog
from olympia.addons.utils import generate_addon_guid
from olympia.amo.tests import (
APITestClientWebToken,
APITestClientSessionID,
TestCase,
addon_factory,
get_random_ip,
@ -33,7 +33,7 @@ locmem_cache['default'][
class TestRatingViewSetGet(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
list_url_name = 'rating-list'
detail_url_name = 'rating-detail'
@ -1182,7 +1182,7 @@ class TestRatingViewSetGet(TestCase):
class TestRatingViewSetDelete(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
detail_url_name = 'rating-detail'
def setUp(self):
@ -1333,7 +1333,7 @@ class TestRatingViewSetDelete(TestCase):
class TestRatingViewSetEdit(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
detail_url_name = 'rating-detail'
def setUp(self):
@ -1583,7 +1583,7 @@ class TestRatingViewSetEdit(TestCase):
class TestRatingViewSetPost(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
list_url_name = 'rating-list'
detail_url_name = 'rating-detail'
abuse_report_url_name = 'abusereportaddon-list'
@ -2254,7 +2254,7 @@ class TestRatingViewSetPost(TestCase):
class TestRatingViewSetFlag(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
flag_url_name = 'rating-flag'
def setUp(self):
@ -2453,7 +2453,7 @@ class TestRatingViewSetFlag(TestCase):
class TestRatingViewSetReply(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
reply_url_name = 'rating-reply'
def setUp(self):

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

@ -217,7 +217,7 @@
{% endif %}
{% if not addon.is_deleted or is_admin %}
<form class="more-actions" id="extra-review-actions" data-api-token="{{ api_token }}">
<form class="more-actions" id="extra-review-actions" data-session-id="{{ session_id }}">
<p><strong>{{ _('More Actions') }}</strong></p>
<div class="more-actions-inner">
<ul>

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

@ -26,7 +26,6 @@ from olympia import amo, core, ratings
from olympia.abuse.models import AbuseReport
from olympia.access import acl
from olympia.access.models import Group, GroupUser
from olympia.accounts.views import API_TOKEN_COOKIE
from olympia.accounts.serializers import BaseUserSerializer
from olympia.activity.models import ActivityLog, DraftComment
from olympia.addons.models import (
@ -42,7 +41,7 @@ from olympia.amo.templatetags.jinja_helpers import (
format_datetime,
)
from olympia.amo.tests import (
APITestClientWebToken,
APITestClientSessionID,
TestCase,
addon_factory,
check_links,
@ -3597,12 +3596,11 @@ class TestReview(ReviewBase):
def test_extra_actions_token(self):
self.login_as_reviewer()
self.client.cookies[API_TOKEN_COOKIE] = 'youdidntsaythemagicword'
response = self.client.get(self.url)
assert response.status_code == 200
doc = pq(response.content)
token = doc('#extra-review-actions').attr('data-api-token')
assert token == 'youdidntsaythemagicword'
token = doc('#extra-review-actions').attr('data-session-id')
assert token == self.client.session.session_key
def test_extra_actions_not_for_reviewers(self):
AddonReviewerFlags.objects.create(
@ -6374,7 +6372,7 @@ class TestPolicyView(ReviewerTest):
class TestAddonReviewerViewSet(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()
@ -6819,7 +6817,7 @@ class TestAddonReviewerViewSet(TestCase):
class TestAddonReviewerViewSetJsonValidation(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
fixtures = ['devhub/addon-validation-1']
def setUp(self):
@ -6967,7 +6965,7 @@ class AddonReviewerViewSetPermissionMixin:
class TestReviewAddonVersionViewSetDetail(
TestCase, AddonReviewerViewSetPermissionMixin
):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
__test__ = True
def setUp(self):
@ -7185,7 +7183,7 @@ class TestReviewAddonVersionViewSetDetail(
class TestReviewAddonVersionViewSetList(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()
@ -7314,7 +7312,7 @@ class TestReviewAddonVersionViewSetList(TestCase):
class TestDraftCommentViewSet(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()
@ -7958,7 +7956,7 @@ class TestDraftCommentViewSet(TestCase):
class TestReviewAddonVersionCompareViewSet(
TestCase, AddonReviewerViewSetPermissionMixin
):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
__test__ = True
def setUp(self):
@ -8472,7 +8470,7 @@ class TestDownloadGitFileView(TestCase):
class TestCannedResponseViewSet(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()

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

@ -39,7 +39,6 @@ import olympia.core.logger
from olympia import amo
from olympia.abuse.models import AbuseReport
from olympia.access import acl
from olympia.accounts.views import API_TOKEN_COOKIE
from olympia.activity.models import ActivityLog, CommentLog, DraftComment
from olympia.addons.models import (
Addon,
@ -825,7 +824,6 @@ def review(request, addon, channel=None):
actions_reasons=actions_reasons,
addon=addon,
addons_sharing_same_guid=addons_sharing_same_guid,
api_token=request.COOKIES.get(API_TOKEN_COOKIE, None),
approvals_info=approvals_info,
auto_approval_info=auto_approval_info,
base_version=base_version,
@ -851,6 +849,7 @@ def review(request, addon, channel=None):
num_pages=num_pages,
pager=pager,
reports=reports,
session_id=request.session.session_key,
subscribed_listed=ReviewerSubscription.objects.filter(
user=request.user, addon=addon, channel=amo.RELEASE_CHANNEL_LISTED
).exists(),

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

@ -6,7 +6,7 @@ from django.urls.exceptions import NoReverseMatch
from olympia import amo
from olympia.activity.models import ActivityLog, VersionLog
from olympia.amo.tests import (
APITestClientWebToken,
APITestClientSessionID,
TestCase,
addon_factory,
reverse_ns,
@ -30,7 +30,7 @@ from olympia.scanners.serializers import ScannerResultSerializer
@pytest.mark.internal_routes_allowed
class TestScannerResultViewInternal(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()

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

@ -8,7 +8,6 @@ from olympia import amo
from olympia.api.authentication import (
JWTKeyAuthentication,
SessionIDAuthentication,
WebTokenAuthentication,
)
from olympia.constants.scanners import (
LABEL_BAD,
@ -25,7 +24,6 @@ from .serializers import ScannerResultSerializer
class ScannerResultView(ListAPIView):
authentication_classes = [
JWTKeyAuthentication,
WebTokenAuthentication,
SessionIDAuthentication,
]

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

@ -4,13 +4,13 @@ from django.conf import settings
from django.utils.encoding import force_str
from olympia import amo
from olympia.amo.tests import APITestClientWebToken, ESTestCase, reverse_ns
from olympia.amo.tests import APITestClientSessionID, ESTestCase, reverse_ns
from olympia.constants.promoted import LINE, RECOMMENDED, VERIFIED
from olympia.constants.search import SEARCH_LANGUAGE_TO_ANALYZER
class TestRankingScenarios(ESTestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def _check_scenario(self, query, expected, **kwargs):
def get_name_from_result(item, expected_lang):

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

@ -9,7 +9,7 @@ from rest_framework.test import APIRequestFactory
from olympia import amo
from olympia.amo.tests import (
addon_factory,
APITestClientWebToken,
APITestClientSessionID,
collection_factory,
ESTestCase,
reverse_ns,
@ -29,7 +29,7 @@ from olympia.users.models import UserProfile
class TestShelfViewSet(ESTestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
@classmethod
def setUpTestData(cls):
@ -308,7 +308,7 @@ class TestShelfViewSet(ESTestCase):
class TestEditorialShelfViewSet(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def test_basic(self):
url = reverse_ns('shelves-editorial-list', api_version='v5')

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

@ -1,10 +1,10 @@
from olympia.amo.tests import APITestClientWebToken, TestCase, reverse_ns
from olympia.amo.tests import APITestClientSessionID, TestCase, reverse_ns
from ..models import Tag
class TestTagListView(TestCase):
client_class = APITestClientWebToken
client_class = APITestClientSessionID
def setUp(self):
super().setUp()

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

@ -287,8 +287,6 @@ class UserProfile(OnChangeMixin, ModelBase, AbstractBaseUser):
)
return self.read_dev_agreement > settings.DEV_AGREEMENT_CHANGE_FALLBACK
backend = 'django.contrib.auth.backends.ModelBackend'
def get_session_auth_hash(self):
"""Return a hash used to invalidate sessions of users when necessary.

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

@ -611,7 +611,7 @@ function initVersions() {
if (replybox.length == 1) {
replybox[0].scrollIntoView(false);
}
var token = div.data('token');
var sessionId = div.data('session-id');
var container = div.children('.history-container');
container.children('.review-entry-loading').removeClass('hidden');
container.children('.review-entry-failure').addClass('hidden');
@ -650,7 +650,7 @@ function initVersions() {
url: api_url,
type: 'get',
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.setRequestHeader('Authorization', 'Session ' + sessionId);
},
complete: function (xhr) {
container.children('.review-entry-loading').addClass('hidden');
@ -700,8 +700,8 @@ function initVersions() {
data: $replyForm.serialize(),
beforeSend: function (xhr) {
submitButton.prop('disabled', true);
var token = $replyForm.data('token');
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
var sessionId = $replyForm.data('session-id');
xhr.setRequestHeader('Authorization', 'Session ' + sessionId);
},
success: function (json) {
var historyDiv = $($replyForm.data('history'));

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

@ -183,7 +183,7 @@ function initReviewActions() {
}
function callReviewersAPI(apiUrl, method, data, successCallback) {
var apiToken = $('form.more-actions').data('api-token');
var sessionId = $('form.more-actions').data('session-id');
if (data) {
data = JSON.stringify(data);
}
@ -192,7 +192,7 @@ function callReviewersAPI(apiUrl, method, data, successCallback) {
data: data,
type: method,
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + apiToken);
xhr.setRequestHeader('Authorization', 'Session ' + sessionId);
},
processData: false,
contentType: 'application/json',