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:
Mathieu Pillard 2020-10-12 19:19:50 +02:00 коммит произвёл GitHub
Родитель dfcde84625
Коммит 4e56d728ce
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 152 добавлений и 13 удалений

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

@ -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