Add special URL to bypass authentication locally (#15702)
* Don't run generate_default_addons_for_frontend outside ui tests * Add special URL to bypass authentication locally * Always go through generate_default_addons_for_frontend but skip fxa if not needed * Add fake FxA authentication flow for local development * Revert "Add special URL to bypass authentication locally" * Tests for the view * Test fxa_login_url() * More testing * Remove leftover obsolete import
This commit is contained in:
Родитель
dfcde84625
Коммит
4e56d728ce
|
@ -127,6 +127,11 @@ FXA_OAUTH_HOST = 'https://oauth-stable.dev.lcip.org/v1'
|
|||
FXA_PROFILE_HOST = 'https://stable.dev.lcip.org/profile/v1'
|
||||
ALLOWED_FXA_CONFIGS = ['default', 'amo', 'local']
|
||||
|
||||
# When USE_FAKE_FXA_AUTH and settings.DEBUG are both True, we serve a fake
|
||||
# authentication page, bypassing FxA. To disable this behavior, set
|
||||
# USE_FAKE_FXA = False in your local settings.
|
||||
USE_FAKE_FXA_AUTH = True
|
||||
|
||||
# CSP report endpoint which returns a 204 from addons-nginx in local dev.
|
||||
CSP_REPORT_URI = '/csp-report'
|
||||
RESTRICTED_DOWNLOAD_CSP['REPORT_URI'] = CSP_REPORT_URI
|
||||
|
|
|
@ -17,6 +17,7 @@ from waffle.testutils import override_switch
|
|||
from olympia.accounts import utils
|
||||
from olympia.accounts.utils import process_fxa_event
|
||||
from olympia.amo.tests import TestCase, user_factory
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.users.models import UserProfile
|
||||
|
||||
|
||||
|
@ -208,6 +209,29 @@ def test_redirect_for_login(default_fxa_login_url):
|
|||
assert response['location'] == login_url
|
||||
|
||||
|
||||
@override_settings(DEBUG=True, USE_FAKE_FXA_AUTH=True)
|
||||
def test_fxa_login_url_when_faking_fxa_auth():
|
||||
path = '/en-US/addons/abp/?source=ddg'
|
||||
request = RequestFactory().get(path)
|
||||
request.session = {'fxa_state': 'myfxastate'}
|
||||
raw_url = utils.fxa_login_url(
|
||||
config=FXA_CONFIG['default'], state=request.session['fxa_state'],
|
||||
next_path=path, action='signin')
|
||||
url = urlparse(raw_url)
|
||||
assert url.scheme == ''
|
||||
assert url.netloc == ''
|
||||
assert url.path == reverse('fake-fxa-authorization')
|
||||
query = parse_qs(url.query)
|
||||
next_path = urlsafe_b64encode(path.encode('utf-8')).rstrip(b'=')
|
||||
assert query == {
|
||||
'action': ['signin'],
|
||||
'client_id': ['foo'],
|
||||
'scope': ['profile openid'],
|
||||
'state': ['myfxastate:{next_path}'.format(
|
||||
next_path=force_text(next_path))],
|
||||
}
|
||||
|
||||
|
||||
class TestProcessSqsQueue(TestCase):
|
||||
|
||||
@mock.patch('boto3._get_default_session')
|
||||
|
|
|
@ -159,6 +159,15 @@ class TestLoginStartView(TestCase):
|
|||
assert views.LoginStartView.ALLOWED_FXA_CONFIGS == (
|
||||
['default', 'amo', 'local'])
|
||||
|
||||
@override_settings(DEBUG=True, USE_FAKE_FXA_AUTH=True)
|
||||
def test_redirect_url_fake_fxa_auth(self):
|
||||
response = self.client.get(reverse_ns('accounts.login_start'))
|
||||
assert response.status_code == 302
|
||||
url = urlparse(response['location'])
|
||||
assert url.path == reverse('fake-fxa-authorization')
|
||||
query = parse_qs(url.query)
|
||||
assert query['state']
|
||||
|
||||
|
||||
class TestLoginUserAndRegisterUser(TestCase):
|
||||
|
||||
|
@ -723,6 +732,25 @@ class TestWithUser(TestCase):
|
|||
addon_factory(users=[self.user])
|
||||
self._test_should_continue_without_redirect_for_two_factor_auth()
|
||||
|
||||
@override_settings(DEBUG=True, USE_FAKE_FXA_AUTH=True)
|
||||
def test_fake_fxa_auth(self):
|
||||
self.user = user_factory()
|
||||
self.find_user.return_value = self.user
|
||||
self.request.data = {
|
||||
'code': 'foo',
|
||||
'fake_fxa_email': self.user.email,
|
||||
'state': 'some-blob:{next_path}'.format(
|
||||
next_path=force_text(base64.urlsafe_b64encode(b'/a/path/?'))),
|
||||
}
|
||||
args, kwargs = self.fn(self.request)
|
||||
assert args == (self, self.request)
|
||||
assert kwargs['user'] == self.user
|
||||
assert kwargs['identity']['email'] == self.user.email
|
||||
assert kwargs['identity']['uid'].startswith('fake_fxa_id-')
|
||||
assert len(kwargs['identity']['uid']) == 44 # 32 random chars + prefix
|
||||
assert kwargs['next_path'] == '/a/path/?'
|
||||
assert self.fxa_identify.call_count == 0
|
||||
|
||||
|
||||
@override_settings(FXA_CONFIG={
|
||||
'foo': {'FOO': 123},
|
||||
|
|
|
@ -14,6 +14,8 @@ import boto3
|
|||
|
||||
from olympia.accounts.tasks import (
|
||||
delete_user_event, primary_email_change_event)
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.amo.utils import use_fake_fxa
|
||||
from olympia.core.logger import getLogger
|
||||
|
||||
|
||||
|
@ -71,8 +73,12 @@ def fxa_login_url(config, state, next_path=None, action=None,
|
|||
if id_token:
|
||||
query['prompt'] = 'none'
|
||||
query['id_token_hint'] = id_token
|
||||
return '{host}/authorization?{query}'.format(
|
||||
host=settings.FXA_OAUTH_HOST, query=urlencode(query))
|
||||
if use_fake_fxa():
|
||||
base_url = reverse('fake-fxa-authorization')
|
||||
else:
|
||||
base_url = '{host}/authorization'.format(host=settings.FXA_OAUTH_HOST)
|
||||
return '{base_url}?{query}'.format(
|
||||
base_url=base_url, query=urlencode(query))
|
||||
|
||||
|
||||
def default_fxa_register_url(request):
|
||||
|
|
|
@ -46,7 +46,7 @@ from olympia.access import acl
|
|||
from olympia.access.models import GroupUser
|
||||
from olympia.amo import messages
|
||||
from olympia.amo.decorators import use_primary_db
|
||||
from olympia.amo.utils import fetch_subscribed_newsletters
|
||||
from olympia.amo.utils import fetch_subscribed_newsletters, use_fake_fxa
|
||||
from olympia.api.authentication import (
|
||||
JWTKeyAuthentication, UnsubscribeTokenAuthentication,
|
||||
WebTokenAuthentication)
|
||||
|
@ -261,8 +261,19 @@ def with_user(format):
|
|||
response, request.user)
|
||||
return response
|
||||
try:
|
||||
identity, id_token = verify.fxa_identify(
|
||||
data['code'], config=fxa_config)
|
||||
if use_fake_fxa() and 'fake_fxa_email' in data:
|
||||
# Bypassing real authentication, we take the email provided
|
||||
# and generate a random fxa id.
|
||||
identity = {
|
||||
'email': data['fake_fxa_email'],
|
||||
'uid': 'fake_fxa_id-%s' % force_text(
|
||||
binascii.b2a_hex(os.urandom(16))
|
||||
)
|
||||
}
|
||||
id_token = identity['email']
|
||||
else:
|
||||
identity, id_token = verify.fxa_identify(
|
||||
data['code'], config=fxa_config)
|
||||
except verify.IdentificationError:
|
||||
log.info('Profile not found. Code: {}'.format(data['code']))
|
||||
return render_error(
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2> {{ _('Register or Log in') }} </h2>
|
||||
<form id="fake_fxa_authorization" method="get" action="{{ url('auth:accounts.authenticate') }}">
|
||||
{% for key, value in request.GET.items() %}
|
||||
<input type="hidden" name="{{ key }}" value="{{ value }}" />
|
||||
{% endfor %}
|
||||
<input type="hidden" name="code" value="fakecode" />
|
||||
<label for="id_email">{{ _('Email:') }}</label> <input type="email" name="fake_fxa_email" id="id_email" />
|
||||
<input type="submit" value="{{ _('OK') }}" />
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -410,6 +410,37 @@ class TestRobots(TestCase):
|
|||
assert 'Allow: {}'.format(url) in response.content.decode('utf-8')
|
||||
|
||||
|
||||
def test_fake_fxa_authorization_correct_values_passed():
|
||||
with override_settings(DEBUG=True): # USE_FAKE_FXA_AUTH is already True
|
||||
url = reverse('fake-fxa-authorization')
|
||||
response = test.Client().get(url, {'state': 'foobar'})
|
||||
assert response.status_code == 200
|
||||
doc = pq(response.content)
|
||||
form = doc('#fake_fxa_authorization')[0]
|
||||
assert form.attrib['action'] == reverse('auth:accounts.authenticate')
|
||||
elm = doc('#fake_fxa_authorization input[name=code]')[0]
|
||||
assert elm.attrib['value'] == 'fakecode'
|
||||
elm = doc('#fake_fxa_authorization input[name=state]')[0]
|
||||
assert elm.attrib['value'] == 'foobar'
|
||||
elm = doc('#fake_fxa_authorization input[name=fake_fxa_email]')
|
||||
assert elm # No value yet, should just be present.
|
||||
|
||||
|
||||
def test_fake_fxa_authorization_deactivated():
|
||||
url = reverse('fake-fxa-authorization')
|
||||
with override_settings(DEBUG=False, USE_FAKE_FXA_AUTH=False):
|
||||
response = test.Client().get(url)
|
||||
assert response.status_code == 404
|
||||
|
||||
with override_settings(DEBUG=False, USE_FAKE_FXA_AUTH=True):
|
||||
response = test.Client().get(url)
|
||||
assert response.status_code == 404
|
||||
|
||||
with override_settings(DEBUG=True, USE_FAKE_FXA_AUTH=False):
|
||||
response = test.Client().get(url)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
class TestAtomicRequests(WithDynamicEndpointsAndTransactions):
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
@ -28,5 +28,6 @@ urlpatterns = [
|
|||
re_path(r'^opensearch\.xml$', render_xml,
|
||||
{'template': 'amo/opensearch.xml'},
|
||||
name='amo.opensearch'),
|
||||
|
||||
re_path(r'^fake-fxa-authorization/$', views.fake_fxa_authorization,
|
||||
name='fake-fxa-authorization')
|
||||
]
|
||||
|
|
|
@ -1032,6 +1032,12 @@ def extract_colors_from_image(path):
|
|||
return colors
|
||||
|
||||
|
||||
def use_fake_fxa():
|
||||
"""Return whether or not to use a fake FxA server for authentication.
|
||||
Should always return False in production"""
|
||||
return settings.DEBUG and settings.USE_FAKE_FXA_AUTH
|
||||
|
||||
|
||||
class AMOJSONEncoder(JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Translation):
|
||||
|
|
|
@ -7,7 +7,7 @@ from django import http
|
|||
from django.conf import settings
|
||||
from django.core.exceptions import ViewDoesNotExist
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.http import Http404, HttpResponse, JsonResponse
|
||||
from django.views.decorators.cache import never_cache
|
||||
|
||||
from django_statsd.clients import statsd
|
||||
|
@ -16,7 +16,7 @@ from rest_framework.response import Response
|
|||
from rest_framework.views import APIView
|
||||
|
||||
from olympia import amo
|
||||
from olympia.amo.utils import render
|
||||
from olympia.amo.utils import render, use_fake_fxa
|
||||
from olympia.api.exceptions import base_500_data
|
||||
from olympia.api.serializers import SiteStatusSerializer
|
||||
|
||||
|
@ -152,6 +152,13 @@ def frontend_view(*args, **kwargs):
|
|||
frontend_view.is_frontend_view = True
|
||||
|
||||
|
||||
def fake_fxa_authorization(request):
|
||||
"""Fake authentication page to bypass FxA in local development envs."""
|
||||
if not use_fake_fxa():
|
||||
raise Http404()
|
||||
return render(request, 'amo/fake_fxa_authorization.html')
|
||||
|
||||
|
||||
class SiteStatusView(APIView):
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
|
|
@ -43,11 +43,17 @@ class GenerateAddonsSerializer(serializers.Serializer):
|
|||
count = serializers.IntegerField(default=10)
|
||||
|
||||
def __init__(self):
|
||||
self.fxa_email = os.environ.get(
|
||||
'UITEST_FXA_EMAIL', 'uitest-%s@restmail.net' % uuid.uuid4())
|
||||
self.fxa_password = os.environ.get(
|
||||
'UITEST_FXA_PASSWORD', 'uitester')
|
||||
self.fxa_id = self._create_fxa_user()
|
||||
self.fxa_email = os.environ.get('UITEST_FXA_EMAIL')
|
||||
self.fxa_password = os.environ.get('UITEST_FXA_PASSWORD', 'uitester')
|
||||
if self.fxa_email:
|
||||
self.fxa_id = self._create_fxa_user()
|
||||
else:
|
||||
# If UITEST_FXA_EMAIL was empty/absent, skip creating the fxa
|
||||
# account: we won't need to log in with that user through FxA.
|
||||
log.info('UITEST_FXA_EMAIL is empty, skipping FxA user creation')
|
||||
# We still need those to be set for the content to be created.
|
||||
self.fxa_id = None
|
||||
self.fxa_email = 'uitest-%s@restmail.net' % uuid.uuid4()
|
||||
self.user = self._create_addon_user()
|
||||
|
||||
def _create_fxa_user(self):
|
||||
|
|
|
@ -1836,6 +1836,7 @@ FXA_OAUTH_HOST = 'https://oauth.accounts.firefox.com/v1'
|
|||
FXA_PROFILE_HOST = 'https://profile.accounts.firefox.com/v1'
|
||||
DEFAULT_FXA_CONFIG_NAME = 'default'
|
||||
ALLOWED_FXA_CONFIGS = ['default']
|
||||
USE_FAKE_FXA_AUTH = False # Should only be True for local development envs.
|
||||
|
||||
# List all jobs that should be callable with cron here.
|
||||
# syntax is: job_and_method_name: full.package.path
|
||||
|
|
Загрузка…
Ссылка в новой задаче