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:
Родитель
11f4e19213
Коммит
f202823a69
|
@ -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',
|
||||
|
|
Загрузка…
Ссылка в новой задаче