dynamicweb/utils/middleware.py

159 lines
6.1 KiB
Python

import json
import re
from django.conf import settings
from django.http import HttpResponse
from django.urls import set_urlconf
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
from urllib import parse as urlparse
from cms.utils.apphook_reload import reload_urlconf
class CMSMultiSiteMiddleware(MiddlewareMixin):
"""
Picks the urlconf for the requested host (MULTISITE_CMS_URLS, with
MULTISITE_CMS_ALIASES and MULTISITE_CMS_FALLBACK). Derived from
djangocms-multisite (BSD, Copyright (c) 2013, Iacopo Spalletti), whose
ungleich fork is no longer installed.
"""
def process_request(self, request):
urls = getattr(settings, 'MULTISITE_CMS_URLS', {})
aliases = getattr(settings, 'MULTISITE_CMS_ALIASES', {})
fallback = getattr(settings, 'MULTISITE_CMS_FALLBACK', '')
host = urlparse.urlparse(
request.build_absolute_uri()).hostname.split(':')[0]
urlconf = urls.get(host)
if not urlconf:
for domain, hosts in aliases.items():
if host in hosts and domain in urls:
urlconf = urls[domain]
break
if not urlconf and fallback in urls:
urlconf = urls[fallback]
if urlconf:
request.urlconf = urlconf
# for code that does not know about the request (get_absolute_url())
set_urlconf(urlconf)
reload_urlconf()
def process_response(self, request, response):
patch_vary_headers(response, ('Host',))
# back to the default urlconf
set_urlconf(None)
return response
class MultipleProxyMiddleware(MiddlewareMixin):
FORWARDED_FOR_FIELDS = [
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED_HOST',
'HTTP_X_FORWARDED_SERVER',
]
def process_request(self, request):
"""
Rewrites the proxy headers so that only the most
recent proxy is used.
"""
for field in self.FORWARDED_FOR_FIELDS:
if field in request.META:
if ',' in request.META[field]:
parts = request.META[field].split(',')
request.META[field] = parts[-1].strip()
class DevSiteSwitcherMiddleware(MiddlewareMixin):
"""
Dev/test only (enabled from settings/local.py, i.e. DEBUG=True).
The project picks a site from the request's Host header. To test every
site from one server without DNS or /etc/hosts entries, the site is
chosen, in this order, from:
1. the host: "<domain>.<anything>", e.g. digitalglarus.ch.localhost:8000
or digitalglarus.ch.203.0.113.5.sslip.io:8000. Every site is then its
own browser origin, so tabs and cookies never interfere. Preferred.
2. ?site=<domain>, remembered in a cookie ("?site=" clears it). Redirects
keep the parameter so the choice survives them.
3. the cookie set by 2.
/dev-sites/ lists every configured site as a link.
"""
COOKIE = 'dev_site'
def domains(self):
try:
found = [d for d in json.loads(settings.UNGLEICH_SITE_CONFIGS)
if ':' not in d]
except ValueError:
found = []
return sorted(found, key=len, reverse=True)
def site_from_host(self, request):
host = request.get_host().split(':')[0].lower()
for domain in self.domains():
if host == domain or host.startswith(domain + '.'):
return domain
return ''
def process_request(self, request):
if request.path == '/dev-sites/':
return self.sites_page(request)
site = self.site_from_host(request)
if not site:
if 'site' in request.GET:
site = request.GET['site'].strip()
else:
site = request.COOKIES.get(self.COOKIE, '')
request.META['DEV_SITE_SWITCH'] = site
if site:
request.META['HTTP_HOST'] = site
request.META.pop('HTTP_X_FORWARDED_HOST', None)
def process_response(self, request, response):
site = request.META.get('DEV_SITE_SWITCH', '')
# dev only: never let a browser reuse a page or redirect from another
# site, and show which site the server picked (see browser dev tools)
response['Cache-Control'] = 'no-store'
response['X-Dev-Site'] = site or '(default)'
if 'site' not in request.GET:
return response
if site:
response.set_cookie(self.COOKIE, site)
location = response.get('Location', '')
if response.status_code in (301, 302) and location.startswith('/') \
and 'site=' not in location:
response['Location'] = location + (
'&' if '?' in location else '?') + 'site=' + site
else:
response.delete_cookie(self.COOKIE)
return response
def sites_page(self, request):
"""A page listing every configured site as a link, on any host"""
host, _, port = request.get_host().partition(':')
port = ':' + port if port else ''
# opened on a site's own address (e.g. blog.ungleich.ch.localhost):
# drop that site's prefix so links are not nested
for domain in self.domains():
if host.lower().startswith(domain + '.'):
host = host[len(domain) + 1:]
break
if re.match(r'^\d+\.\d+\.\d+\.\d+$', host):
# an IP has no subdomains; sslip.io resolves <anything>.<ip>.sslip.io
base = host + '.sslip.io'
else:
base = host
items = ''.join(
'<li><a href="http://{0}.{1}{2}/">{0}</a></li>'.format(
d, base, port) for d in sorted(self.domains()))
return HttpResponse(
'<!doctype html><title>Test sites</title>'
'<h1>Test sites</h1><p>Each site opens on its own address, so '
'you can open several at once.</p><ul>{}</ul>'
'<p>Fallback without wildcard names: '
'<code>/?site=&lt;domain&gt;</code> (<a href="/?site=">clear</a>)'
'</p>'.format(items))