Skip to content
This repository has been archived by the owner on Aug 26, 2022. It is now read-only.

bug 1401246: convert to new-style middleware #4841

Merged
merged 2 commits into from Jun 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions kuma/core/i18n.py
Expand Up @@ -193,3 +193,10 @@ def get_language_from_request(request):

def get_language_mapping():
return apps.get_app_config('core').language_mapping


def activate_language_from_request(request):
"""Activate the language, based on the request."""
language = get_language_from_request(request)
translation.activate(language)
request.LANGUAGE_CODE = language
97 changes: 54 additions & 43 deletions kuma/core/middleware.py
Expand Up @@ -3,12 +3,11 @@

from django.conf import settings
from django.contrib.sessions.middleware import SessionMiddleware
from django.core.exceptions import MiddlewareNotUsed
from django.core.urlresolvers import get_script_prefix, resolve, Resolver404
from django.http import (HttpResponseForbidden,
HttpResponsePermanentRedirect,
HttpResponseRedirect)
from django.utils import translation
from django.utils.deprecation import MiddlewareMixin
from django.utils.encoding import iri_to_uri, smart_str
from django.utils.six.moves.urllib.parse import urlsplit
from whitenoise.middleware import WhiteNoiseMiddleware
Expand All @@ -17,27 +16,34 @@
mindtouch_to_kuma_url)

from .decorators import add_shared_cache_control
from .i18n import (get_kuma_languages,
from .i18n import (activate_language_from_request,
get_kuma_languages,
get_language,
get_language_from_path,
get_language_from_request)
from .utils import is_untrusted, urlparams
from .views import handler403


class LangSelectorMiddleware(MiddlewareMixin):
class MiddlewareBase(object):

def __init__(self, get_response):
self.get_response = get_response
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like having this defined once.



class LangSelectorMiddleware(MiddlewareBase):
"""
Redirect requests with a ?lang= parameter.

This should appear higher than LocaleMiddleware in the middleware list.
"""

def process_request(self, request):
def __call__(self, request):
"""Redirect if ?lang query parameter is valid."""
query_lang = request.GET.get('lang')
if not (query_lang and query_lang in get_kuma_languages()):
# Invalid language requested, don't redirect
return
# Invalid or no language requested, so don't redirect.
return self.get_response(request)

# Check if the requested language is already embedded in URL
language = get_language_from_request(request)
Expand All @@ -61,16 +67,18 @@ def process_request(self, request):
return response


class LocaleStandardizerMiddleware(MiddlewareMixin):
class LocaleStandardizerMiddleware(MiddlewareBase):
"""
Convert 404s with legacy locales to redirects.

This should appear higher than LocaleMiddleware in the middleware list.
"""

def process_response(self, request, response):
def __call__(self, request):
"""Convert 404s into redirects to language-specific URLs."""

response = self.get_response(request)

if response.status_code != 404:
return response

Expand Down Expand Up @@ -104,12 +112,12 @@ def process_response(self, request, response):
redirect_response = HttpResponseRedirect(fixed_path)
add_shared_cache_control(redirect_response)
return redirect_response
else:
# No language fixup found, return the 404
return response

# No language fixup found, return the 404
return response


class LocaleMiddleware(MiddlewareMixin):
class LocaleMiddleware(MiddlewareBase):
"""
This is a very simple middleware that parses a request
and decides what translation object to install in the current
Expand All @@ -126,16 +134,14 @@ class LocaleMiddleware(MiddlewareMixin):
* Don't include "Vary: Accept-Language" header
* Add caching headers to locale redirects
"""
response_redirect_class = HttpResponseRedirect

def process_request(self, request):
"""Activate the language, based on the request."""
language = get_language_from_request(request)
translation.activate(language)
request.LANGUAGE_CODE = language
def __call__(self, request):
# Activate the language, and add LANGUAGE_CODE to the request.
activate_language_from_request(request)

def process_response(self, request, response):
"""Add Content-Language, convert some 404s to locale redirects."""
response = self.get_response(request)

# Add Content-Language, convert some 404s to locale redirects.
language = get_language()
language_from_path = get_language_from_path(request.path_info)
if response.status_code == 404 and not language_from_path:
Expand All @@ -152,7 +158,7 @@ def process_response(self, request, response):
'%s%s/' % (script_prefix, language),
1
)
redirect = self.response_redirect_class(language_path)
redirect = HttpResponseRedirect(language_path)
add_shared_cache_control(redirect)
return redirect

Expand All @@ -162,15 +168,17 @@ def process_response(self, request, response):
# instead. And a long comment.
if 'Content-Language' not in response: # pragma: no cover
response['Content-Language'] = language

return response


class Forbidden403Middleware(MiddlewareMixin):
class Forbidden403Middleware(MiddlewareBase):
"""
Renders a 403.html page if response.status_code == 403.
"""

def process_response(self, request, response):
def __call__(self, request):
response = self.get_response(request)
if isinstance(response, HttpResponseForbidden):
return handler403(request)
# If not 403, return response unmodified
Expand All @@ -193,7 +201,7 @@ def is_valid_path(request, path):
return False


class SlashMiddleware(MiddlewareMixin):
class SlashMiddleware(MiddlewareBase):
"""
Middleware that adds or removes a trailing slash if there was a 404.

Expand All @@ -208,7 +216,8 @@ class SlashMiddleware(MiddlewareMixin):
makes it so that Django's is_valid_url returns True for all URLs.
"""

def process_response(self, request, response):
def __call__(self, request):
response = self.get_response(request)
path = request.path_info
if response.status_code == 404 and not is_valid_path(request, path):
new_path = None
Expand Down Expand Up @@ -242,14 +251,14 @@ def safe_query_string(request):
request.META['QUERY_STRING'] = qs


class SetRemoteAddrFromForwardedFor(MiddlewareMixin):
class SetRemoteAddrFromForwardedFor(MiddlewareBase):
"""
Middleware that sets REMOTE_ADDR based on HTTP_X_FORWARDED_FOR, if the
latter is set. This is useful if you're sitting behind a reverse proxy that
causes each request's REMOTE_ADDR to be set to 127.0.0.1.
"""

def process_request(self, request):
def __call__(self, request):
try:
forwarded_for = request.META['HTTP_X_FORWARDED_FOR']
except KeyError:
Expand All @@ -260,6 +269,8 @@ def process_request(self, request):
forwarded_for = forwarded_for.split(',')[0].strip()
request.META['REMOTE_ADDR'] = forwarded_for

return self.get_response(request)


class ForceAnonymousSessionMiddleware(SessionMiddleware):

Expand All @@ -276,37 +287,37 @@ def process_response(self, request, response):
return response


class RestrictedEndpointsMiddleware(MiddlewareMixin):
class RestrictedEndpointsMiddleware(MiddlewareBase):
"""Restricts the accessible endpoints based on the host."""

def process_request(self, request):
"""
Restricts the accessible endpoints based on the host.
"""
if settings.ENABLE_RESTRICTIONS_BY_HOST and is_untrusted(request):
def __init__(self, get_response):
if not settings.ENABLE_RESTRICTIONS_BY_HOST:
raise MiddlewareNotUsed
super(RestrictedEndpointsMiddleware, self).__init__(get_response)

def __call__(self, request):
if is_untrusted(request):
request.urlconf = 'kuma.urls_untrusted'
return self.get_response(request)


class RestrictedWhiteNoiseMiddleware(WhiteNoiseMiddleware):
"""Restricts the use of WhiteNoiseMiddleware based on the host."""

def process_request(self, request):
"""
Restricts the use of WhiteNoiseMiddleware based on the host.
"""
if settings.ENABLE_RESTRICTIONS_BY_HOST and is_untrusted(request):
return None
return super(RestrictedWhiteNoiseMiddleware, self).process_request(
request
)


class LegacyDomainRedirectsMiddleware(MiddlewareMixin):
class LegacyDomainRedirectsMiddleware(MiddlewareBase):
"""Permanently redirects all requests from legacy domains."""

def process_request(self, request):
"""
Permanently redirects all requests from legacy domains.
"""
def __call__(self, request):
if request.get_host() in settings.LEGACY_HOSTS:
return HttpResponsePermanentRedirect(
urljoin(settings.SITE_URL, request.get_full_path())
)
return None
return self.get_response(request)
8 changes: 5 additions & 3 deletions kuma/core/tests/test_middleware.py
@@ -1,4 +1,5 @@
import pytest
from django.core.exceptions import MiddlewareNotUsed
from django.test import RequestFactory
from mock import MagicMock, patch

Expand Down Expand Up @@ -81,10 +82,11 @@ def test_restricted_endpoints_middleware(rf, settings):
middleware(request)
assert not hasattr(request, 'urlconf')


def test_restricted_endpoints_middleware_when_disabled(settings):
settings.ENABLE_RESTRICTIONS_BY_HOST = False
request = rf.get('/foo', HTTP_HOST='demos')
middleware(request)
assert not hasattr(request, 'urlconf')
with pytest.raises(MiddlewareNotUsed):
RestrictedEndpointsMiddleware(lambda req: None)


def test_restricted_whitenoise_middleware(rf, settings):
Expand Down
4 changes: 2 additions & 2 deletions kuma/search/tests/__init__.py
Expand Up @@ -5,7 +5,7 @@
from elasticsearch_dsl.connections import connections
from rest_framework.test import APIRequestFactory

from kuma.core.middleware import LocaleMiddleware
from kuma.core.i18n import activate_language_from_request
from kuma.users.tests import UserTestCase
from kuma.wiki.search import WikiDocumentType

Expand Down Expand Up @@ -75,5 +75,5 @@ def teardown_indexes(self):
def get_request(self, *args, **kwargs):
request = factory.get(*args, **kwargs)
# setting request.LANGUAGE_CODE correctly
LocaleMiddleware().process_request(request)
activate_language_from_request(request)
return request
26 changes: 17 additions & 9 deletions kuma/wiki/middleware.py
@@ -1,20 +1,24 @@
from django.conf import settings
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.utils.deprecation import MiddlewareMixin
from django.views.decorators.cache import never_cache

from kuma.core.i18n import get_kuma_languages
from kuma.core.middleware import MiddlewareBase
from kuma.core.utils import urlparams

from .exceptions import ReadOnlyException
from .jobs import DocumentZoneURLRemapsJob


class ReadOnlyMiddleware(MiddlewareMixin):
class ReadOnlyMiddleware(MiddlewareBase):
"""
Renders a 403.html page with a flag for a specific message.
"""

def __call__(self, request):
return self.get_response(request)

def process_exception(self, request, exception):
if isinstance(exception, ReadOnlyException):
context = {'reason': exception.args[0]}
Expand All @@ -23,24 +27,25 @@ def process_exception(self, request, exception):
return None


class DocumentZoneMiddleware(MiddlewareMixin):
class DocumentZoneMiddleware(MiddlewareBase):
"""
For document zones with specified URL roots, this middleware modifies the
incoming path_info to point at the internal wiki path
"""
def process_request(self, request):

def __call__(self, request):
# https://bugzil.la/1189222
# Don't redirect POST $subscribe requests to GET zone url
if (request.method == 'POST' and
('$subscribe' in request.path or '$files' in request.path)):
return None
return self.get_response(request)

# Skip slugs that don't have locales, and won't be in a zone
path = request.path_info
request_slug = path.lstrip('/')
if any(request_slug.startswith(slug)
for slug in settings.LANGUAGE_URL_IGNORED_PATHS):
return None
return self.get_response(request)

# Convert the request path to zamboni/amo style
maybe_lang = request_slug.split(u'/')[0]
Expand Down Expand Up @@ -68,17 +73,20 @@ def process_request(self, request):

elif path == u'/docs{}'.format(new_path):
# Is this a request for a DocumentZone, but with /docs/ wedged
# in the url path between the language code and the zone's url_root?
# in the url path between the language code and the zone's
# url_root?
new_path = u'/{}{}'.format(request.LANGUAGE_CODE, new_path)
query = request.GET.copy()
new_path = urlparams(new_path, query_dict=query)

return HttpResponseRedirect(new_path)

elif path.startswith(new_path):
# Is this a request for the relocated wiki path? If so, rewrite
# the path as a request for the proper wiki view.
# Is this a request for the relocated wiki path? If so,
# rewrite the path as a request for the proper wiki view.
request.path_info = request.path_info.replace(new_path,
original_path,
1)
break

return self.get_response(request)