Derek Stegelman

Proxies and Django CAS: Friends Working Together

Django CAS and Proxies have never over well in my experience. This week after another application deployed using the Proxy/CAS I got fed up with how we were handling it. “There must be a better solution.” I thought. This does not have to suck this much. The before code is some snippets from inside the K-State django CAS library that we have modified and released to handle for situations when get_host() may be incorrect, ie, proxies.

The Before

def _service_url(request, redirect_to=None, gateway=False):
"""Generates application service URL for CAS"""

protocol = ('http://', 'https://')[request.is_secure()]
if settings.CAS_IGNORE_HOST:
host = settings.CAS_ACTUAL_HOST
host = get_host(request)
prefix = (('http://', 'https://')[request.is_secure()] + host)
service = protocol + host + request.path
if redirect_to:
if '?' in service:
service += '&'
service += '?'
if gateway:
service += urlencode({REDIRECT_FIELD_NAME: redirect_to, 'gatewayed': 'true'})
service += urlencode({REDIRECT_FIELD_NAME: redirect_to})
return service

def _redirect_url(request):
"""Redirects to referring page, or CAS_REDIRECT_URL if no referrer is

next = request.GET.get(REDIRECT_FIELD_NAME)
if not next:
next = settings.CAS_REDIRECT_URL
next = request.META.get('HTTP_REFERER', settings.CAS_REDIRECT_URL)
if settings.CAS_IGNORE_HOST:
host = settings.CAS_ACTUAL_HOST
host = get_host(request)
prefix = (('http://', 'https://')[request.is_secure()] + host)
if next.startswith(prefix):
next = next[len(prefix):]
return next

In general this hack sucks because we are changing the library to fit the need of a seperate system. This worked for a while until we started needing to use @login_required decorators and other pieces of code that relied upon Django’s redirecting. We discovered that Django uses get_host in a whole lot of other places besides in our small CAS library. Therefore I sat down to fix the real problem, the proxied apps HTTP HEADERS.

This turned out to be way easier and more obvious than I thought. Simply re-write the headers using some middleware and inject the middleware early enough that Django picks up these new headers.

class ProxyMiddleware(object):

# Middleware used to "fake" the django app that it lives at the Proxy Domain
def process_request(self, request):
request.META['HTTP_HOST'] = getattr(settings, 'PROXY_DOMAIN', PROXY_DOMAIN)

Just put this middleware in front of Django’s Common middleware class and you should be all good. Don’t foreget to set the proxied domain and if you want a default, just in case you forget to do that.