Allow hop-by-hop headers in Django proxy middleware - python

I need to implement a HTTP proxy in Django and my Google safari led me to a project called django-webproxy.
Although no longer maintained, it's quite simple. Most of the logic relies on a simple proxy Middleware class that intercepts all requests to the Django WSGI server and handles it.
If the Middleware returns any data, the WSGI server simply passes it back to the client but if it returns nothing, Django simply handles the request by passing to the other Middleware.
Everything works fine, pretty much, but I need to implement proxy authentication which mean i have to send a 407 status code to the client with a Proxy-Authenticate header. This sin't allowed by Django as it is a hop-by-hop header and Django throws an exception. How can i hack/force/kludge Django into allowing me to send hop-by-hop headers?
FYI, ihe code for the middleware class can be found here.

from django.core.servers import basehttp
del basehttp._hop_headers['proxy-authenticate']
del basehttp._hop_headers['proxy-authorization']
This worked for me.

django.core.servers.basehttp._hop_headers is no longer with us present in the basehttp module (since Django 1.10).
I know 2 ways to change it:
Start your server like so:
$ python -O ./manage.py runserver --noreload
Change wsgiref.util._hoppish:
import wsgiref.util
wsgiref.util._hoppish = {
'connection': 1, 'keep-alive':1,
'te':1, 'trailers':1, 'transfer-encoding':1,
'upgrade':1
}.__contains__

Related

Using https as standard with django project

I am learning django and trying to complete my first webapp.
I am using shopify api & boilder plate (starter code) and am having an issue with the final step of auth.
Specifically, the redirect URL -- it's using HTTP:// when it should NOT and I don't know how to change it..
#in my view
def authenticate(request):
shop = request.GET.get('shop')
print('shop:', shop)
if shop:
scope = settings.SHOPIFY_API_SCOPE
redirect_uri = request.build_absolute_uri(reverse('shopify_app_finalize')) #try this with new store url?
print('redirect url', redirect_uri) # this equals http://myherokuapp.com/login/finalize/
permission_url = shopify.Session(shop.strip()).create_permission_url(scope, redirect_uri)
return redirect(permission_url)
return redirect(_return_address(request))
Which is a problem because my app uses the Embedded Shopify SDK which causes this error to occur at the point of this request
Refused to frame 'http://my.herokuapp.com/' because it violates the following Content Security Policy directive: "child-src 'self' https://* shopify-pos://*". Note that 'frame-src' was not explicitly set, so 'child-src' is used as a fallback.
How do i change the URL to use HTTPS?
Thank you so much in advance. Please let me know if I can share any other details but my code is practically identical to that starter code
This is what the Django doc says about build_absolute_uri:
Mixing HTTP and HTTPS on the same site is discouraged, therefore
build_absolute_uri() will always generate an absolute URI with the
same scheme the current request has. If you need to redirect users to
HTTPS, it’s best to let your Web server redirect all HTTP traffic to
HTTPS.
So you can do two things:
Make sure your site runs entirely on HTTPS (preferred option): Setup your web server to use HTTPS, see the Heroku documentation on how to do this. Django will automatically use HTTPS for request.build_absolute_uri if the incoming request is on HTTPS.
I'm not sure what gets passed in the shop parameter but if it contains personal data I'd suggest to use HTTPS anyway.
Create the URL yourself:
url = "https://{host}{path}".format(
host = request.get_host(),
path = reverse('shopify_app_finalize'))
But you will still need to configure your server to accept incoming HTTPS requests.

how do i get the hostname of a django application

I have a django application running using gunicorn and frontended by nginx. I am trying to get the full hostname of the application because I need to redirect out of the application and need to pass the external application my url so it can pass control back to me. How do I find my hostname/application name. I tried request.get_host(), but it gave me 127.0.0.1:8000
You can access it from the request object's META attribute.
request.META['HTTP_HOST']

Force Django to use HTTPS URLs when reversing

I am running my Django application in Cloud9 for development purposes using the usual ./manage.py runserver. But to the outside world, the app is accessible via a https:// URL.
The problem is that when I use the URL reverse function, the URLs that come back start with http:// (at least some of the time). When I try redirecting to one of those URLs, I get an error like this one in the console:
Mixed Content: The page at 'https://apps.facebook.com/xxxx/'
was loaded over HTTPS, but requested an insecure form action
'http://xxxx.c9users.io/facebook_app/gift_shop/'.
This request has been blocked; the content must be served over HTTPS.
My question: is there a way to force reverse to generate HTTPS URLs instead of HTTP?
Here is a snippet of code, which has problems with redirection from HTTPS URLs to HTTP ones:
class IndexRedirectView(RedirectView, CSRFExemptMixin):
permanent = False
def get_redirect_url(self, *args, **kwargs):
if self.request.user.visit_count >= 5:
return reverse('gift-shop')
if len(BaseGiftableInstance.objects.filter(giving_user=self.request.user)) > 0:
# has won something
return reverse('gift-shop')
return reverse('spinner')
If you are on Django 1.8 or greater, you can force SSL with the setting SECURE_SSL_REDIRECT = True - see this answer on a similar question
You should check out django-sslify, you just install it and add it to your MIDDLEWARE_CLASSES.

Google App Engine inter module communication authorization (python)

Citing Google App Engine inter module communication authorization the problem I have is that in the Docs (communication between modules) says:
You can configure any manual or basic scaling module to accept
requests from other modules in your app by restricting its handler to
only allow administrator accounts, specifying login: admin for the
appropriate handler in the module's configuration file. With this
restriction in place, any URLFetch from any other module in the app
will be automatically authenticated by App Engine, and any request
that is not from the application will be rejected.
And this is exactly the configuration I have for my module called "api1". In my app.yaml file I have:
# can accept requests from other modules.
# with login: admin and they are authenticated automatically.
- url: /.*
script: _go_app
login: admin
I'm trying now, from a different module in the same app, to make a service call as suggested in the doc using urfetch.fetch() method, and my implementation is:
from google.appengine.api import urlfetch, modules, app_identity
from rest_framework.response import Response, status
#api_view(['POST'])
def validate_email(request):
url = "http://%s/" % modules.get_hostname(module="api1")
payload = json.dumps({"SOME_KEY":"SOME_VALUE"})
appid = app_identity.get_application_id()
result = urlfetch.fetch(url + "emails/validate/document",
follow_redirects=False,
method=urlfetch.POST,
payload=payload,
headers={"Content-Type":"application/json")
return Response({
'status_code': result.status_code,
'content': result.content
}, status=status.HTTP_200_OK)
According to the documentation, having specified the follow_redirects=False, fetch() will automatically insert an header in my call (I've even tried to add it explicitly) with the "X-Appengine-Inbound-Appid" : MY-APP-ID.
Unfortunately I get as result of the fetch call a 302 redirect, if I follow it, it's a redirect to the authentication form. This occurs in Development server as well as in Production.
Can you please let me know how can I call my api1 service inside my validate_email method (belonging to a different module in the same app)?
Is there another way to authenticate the call since it seems the way suggested inside the documentation is not working?
Thank you
As written here this is a tracked issue now on google appengine public issue tracker. So everyone can go there to check for updates.
In the meanwhile I solved the issue removing the login: admin from the app.yaml and in the handler of my service I've checked manually for the existence of the header X-Appengine-Inbound-Appid and its value.

Proxying to another web service with Flask

I want to proxy requests made to my Flask app to another web service running locally on the machine. I'd rather use Flask for this than our higher-level nginx instance so that we can reuse our existing authentication system built into our app. The more we can keep this "single sign on" the better.
Is there an existing module or other code to do this? Trying to bridge the Flask app through to something like httplib or urllib is proving to be a pain.
I spent a good deal of time working on this same thing and eventually found a solution using the requests library that seems to work well. It even handles setting multiple cookies in one response, which took a bit of investigation to figure out. Here's the flask view function:
from dotenv import load_dotenv # pip package python-dotenv
import os
#
from flask import request, Response
import requests # pip package requests
load_dotenv()
API_HOST = os.environ.get('API_HOST'); assert API_HOST, 'Envvar API_HOST is required'
#api.route('/', defaults={'path': ''}) # ref. https://medium.com/#zwork101/making-a-flask-proxy-server-online-in-10-lines-of-code-44b8721bca6
#api.route('/<path>')
def redirect_to_API_HOST(path): #NOTE var :path will be unused as all path we need will be read from :request ie from flask import request
res = requests.request( # ref. https://stackoverflow.com/a/36601467/248616
method = request.method,
url = request.url.replace(request.host_url, f'{API_HOST}/'),
headers = {k:v for k,v in request.headers if k.lower() == 'host'},
data = request.get_data(),
cookies = request.cookies,
allow_redirects = False,
)
#region exlcude some keys in :res response
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection'] #NOTE we here exclude all "hop-by-hop headers" defined by RFC 2616 section 13.5.1 ref. https://www.rfc-editor.org/rfc/rfc2616#section-13.5.1
headers = [
(k,v) for k,v in res.raw.headers.items()
if k.lower() not in excluded_headers
]
#endregion exlcude some keys in :res response
response = Response(res.content, res.status_code, headers)
return response
Update April 2021: excluded_headers should probably include all "hop-by-hop headers" defined by RFC 2616 section 13.5.1.
I have an implementation of a proxy using httplib in a Werkzeug-based app (as in your case, I needed to use the webapp's authentication and authorization).
Although the Flask docs don't state how to access the HTTP headers, you can use request.headers (see Werkzeug documentation). If you don't need to modify the response, and the headers used by the proxied app are predictable, proxying is staightforward.
Note that if you don't need to modify the response, you should use the werkzeug.wsgi.wrap_file to wrap httplib's response stream. That allows passing of the open OS-level file descriptor to the HTTP server for optimal performance.
My original plan was for the public-facing URL to be something like http://www.example.com/admin/myapp proxying to http://myapp.internal.example.com/. Down that path leads madness.
Most webapps, particularly self-hosted ones, assume that they're going to be running at the root of a HTTP server and do things like reference other files by absolute path. To work around this, you have to rewrite URLs all over the place: Location headers and HTML, JavaScript, and CSS files.
I did write a Flask proxy blueprint which did this, and while it worked well enough for the one webapp I really wanted to proxy, it was not sustainable. It was a big mess of regular expressions.
In the end, I set up a new virtual host in nginx and used its own proxying. Since both were at the root of the host, URL rewriting was mostly unnecessary. (And what little was necessary, nginx's proxy module handled.) The webapp being proxied to does its own authentication which is good enough for now.

Categories