Skip to content

Commit

Permalink
ensure HTTP_HOST is correctly defined
Browse files Browse the repository at this point in the history
  • Loading branch information
ludvigalden committed Mar 9, 2024
1 parent 2e1900f commit 236b86d
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 23 deletions.
52 changes: 52 additions & 0 deletions src/admin/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.http import HttpResponseRedirect, HttpResponse
from django.http import HttpRequest
from django.utils.deprecation import MiddlewareMixin
import re


class RedirectAdminMiddleware(MiddlewareMixin):
Expand All @@ -17,3 +18,54 @@ def process_request(self, request: HttpRequest) -> HttpResponse:
path = request.path[7:] # Remove '/admin/'
return HttpResponseRedirect(f'{protocol}://{host}/{path}')
return None


class TrustedHostMiddleware(MiddlewareMixin):
"""
Middleware to dynamically set the HTTP_HOST from a set of HTTP headers,
ensuring it matches the allowed hosts in settings.ALLOWED_HOSTS.
"""

def __call__(self, request):
# Headers to check for the host value, in priority order.
# X-Forwarded-Host is typically set by proxies.
# HTTP_HOST is the standard header sent by clients.
# HTTP_ORIGIN and HTTP_REFERER can offer fallbacks.
headers_to_check = [
'HTTP_X_FORWARDED_HOST', 'HTTP_HOST', 'HTTP_ORIGIN', 'HTTP_REFERER'
]

for header in headers_to_check:
host_value = request.META.get(header, '')
# Extract the hostname, excluding protocol and path.
host_name = self.extract_hostname(host_value)
# If extracted host is allowed, set it as HTTP_HOST
if self.is_allowed_host(host_name):
request.META['HTTP_HOST'] = host_name
print("HOST", host_name, header)
break
return self.get_response(request)

def extract_hostname(self, value):
"""
Extracts hostname from a URL, ignoring protocol, port, and path.
"""
# Regex captures hostname from start of string or after protocol.
# It stops at port delimiter ":" or path delimiter "/".
match = re.search(r'^(?:https?://)?([^:/]+)', value)
return match.group(1) if match else ''

def is_allowed_host(self, host):
"""
Checks if a host matches any pattern in ALLOWED_HOSTS.
"""
# Loop through ALLOWED_HOSTS to see if the host matches any pattern.
# Patterns starting with '.' match any subdomain.
for allowed_host in settings.ALLOWED_HOSTS:
if allowed_host.startswith('.'):
domain = allowed_host[1:]
if host.endswith('.' + domain) or host == domain[1:]:
return True
elif host == allowed_host: # Exact match
return True
return False
7 changes: 5 additions & 2 deletions src/moore/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@
]

MIDDLEWARE = [
# Make sure the HTTP_HOST is set correctly – djang-hosts uses get_host()
# https://docs.djangoproject.com/en/5.0/ref/request-response/#django.http.HttpRequest.get_host
'admin.middleware.TrustedHostMiddleware',
# Subdomain for admin site. Needed by django_hosts
'django_hosts.middleware.HostsRequestMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
Expand All @@ -92,10 +95,10 @@
'django.middleware.locale.LocaleMiddleware',

'wagtail.contrib.redirects.middleware.RedirectMiddleware',
# Subdomain for admin site. Needed by django_hosts
'django_hosts.middleware.HostsResponseMiddleware',
# Redirect GET html at path "/admin/*" admin subdomain
'admin.middleware.RedirectAdminMiddleware',
# Subdomain for admin site. Needed by django_hosts
'django_hosts.middleware.HostsResponseMiddleware',
]

DATABASES = {
Expand Down
9 changes: 0 additions & 9 deletions src/moore/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from wagtail.admin import urls as wagtailadmin_urls

from .api import api_router
from .urls_utils import delete_urls

from members.views import member_check_api

Expand Down Expand Up @@ -53,11 +52,3 @@
settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT
)

# We remove the /admin redirect
# if running tests in order to make writing tests easier.
if settings.IS_RUNNING_TEST:
urlpatterns = delete_urls(
urlpatterns,
delete_name='wagtailadmin_redirect'
)
12 changes: 0 additions & 12 deletions src/moore/urls_utils.py

This file was deleted.

0 comments on commit 236b86d

Please sign in to comment.