fix_location_header in Django causes wrong redirec

2019-09-10 08:05发布

问题:

I am trying to send an oauth request from another application to Django. Django is hosted on 8000 port and the application on 8080. Here is the URL called from the application:

http://localhost:8000/o/authorize/?client_id=MM8i27ROetg3OQv60tDcJwp7JKXi28FqkuQgetyG&response_type=token&redirect_url=http://localhost:8080/auth/add_token

This will get redirected to a wrong location:

http://192.168.56.101:8000/o/authorize/accounts/login/?next=/o/authorize/%3Fclient_id%3DMM8i27ROetg3OQv60tDcJwp7JKXi28FqkuQgetyG%26response_type%3Dtoken%26redirect_url%3Dhttp%3A//192.168.56.101%3A8080/auth/add_token

when I expect it to be without the prefix /o/authorize which is is generated by WSGI handler using HTTP header called PATH_INFO:

http://192.168.56.101:8000/accounts/login/?next=/o/authorize/%3Fclient_id%3DMM8i27ROetg3OQv60tDcJwp7JKXi28FqkuQgetyG%26response_type%3Dtoken%26redirect_url%3Dhttp%3A//192.168.56.101%3A8080/auth/add_token

I have following routes in urls.py and they work properly if it is not for the weird redirection I am facing.

url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
# OAuth User Info
url(r'^o/api/get_userinfo/$', oauth2.GetuserInfoView.as_view(), name="get-userinfo"),

In settings.py I have:

LOGIN_URL = "/accounts/login"

Now here are the lines of Django code that I find problematic.

django/core/handlers/base.py:220

        # Apply response middleware, regardless of the response
        for middleware_method in self._response_middleware:
            response = middleware_method(request, response)
            # Complain if the response middleware returned None (a common error).
            if response is None:
                raise ValueError(
                    "%s.process_response didn't return an "
                    "HttpResponse object. It returned None instead."
                    % (middleware_method.__self__.__class__.__name__))
        response = self.apply_response_fixes(request, response)
                   # ^^^------------this will lead to fix_location_header being called.

django/http/utils.py

def fix_location_header(request, response):
    """
    Ensures that we always use an absolute URI in any location header in the
    response. This is required by RFC 2616, section 14.30.

    Code constructing response objects is free to insert relative paths, as
    this function converts them to absolute paths.
    """
    if 'Location' in response:
        response['Location'] = request.build_absolute_uri(response['Location'])
    return response

django/request.py

def build_absolute_uri(self, location=None):
    """
    Builds an absolute URI from the location and the variables available in
    this request. If no ``location`` is specified, the absolute URI is
    built on ``request.get_full_path()``. Anyway, if the location is
    absolute, it is simply converted to an RFC 3987 compliant URI and
    returned and if location is relative or is scheme-relative (i.e.,
    ``//example.com/``), it is urljoined to a base URL constructed from the
    request variables.
    """
    if location is None:
        # Make it an absolute url (but schemeless and domainless) for the
        # edge case that the path starts with '//'.
        location = '//%s' % self.get_full_path()
    bits = urlsplit(location)
    if not (bits.scheme and bits.netloc):
        current_uri = '{scheme}://{host}{path}'.format(scheme=self.scheme,
                                                       host=self.get_host(),
                                                       path=self.path)
        # ^^^---The location URL forming logic that I find problematic.
        # Join the constructed URL with the provided location, which will
        # allow the provided ``location`` to apply query strings to the
        # base path as well as override the host, if it begins with //
        location = urljoin(current_uri, location)
    return iri_to_uri(location)

I included the comments from the source file so the reader could know there are use cases for them.

So what is the proper way of getting around apply_reponse_fix? 1) inherit and rewrite the function 2) modify HTTP header PATH_INFO... somehow 3) change the logic for Location header in response so that it is a full URI with http:// prefix so that bits.scheme is not None. None of them seem like a proper fix for me, so I am wondering if I overlooked a convention. Any insight will be very helpful.

回答1:

This has been already addressed and fixed later version of Django 1.8

fix_location_header is removed: https://code.djangoproject.com/ticket/23960

Just upgrading to the latest version made it go away:

pip install "django>=1.8,<1.9"