Django ALLOWED_HOSTS to accept local IPs through Apache - django

I'm serving a Django app with Apache.
In Django's settings.py I have DEBUG = False, therefore I had to allow some hosts, like: ALLOWED_HOSTS = ['.dyndns.org', 'localhost']. This works fine, however I would like to have the server accessible on the local network via its internal IP address as well, like: 192.168.0.x, or 127.0.0.1, etc. How could I define 192.* or 127.* in ALLOWED_HOSTS, if I'd like to avoid opening up the access entirely by ALLOWED_HOSTS = ['*']?

Following the recommendation from #rnevius, and based on the guidelines from #AlvaroAV in how to setup custom middleware in django, I've managed to solve with this middleware:
from django.http import HttpResponseForbidden
class FilterHostMiddleware(object):
def process_request(self, request):
allowed_hosts = ['127.0.0.1', 'localhost'] # specify complete host names here
host = request.META.get('HTTP_HOST')
if host[len(host)-10:] == 'dyndns.org': # if the host ends with dyndns.org then add to the allowed hosts
allowed_hosts.append(host)
elif host[:7] == '192.168': # if the host starts with 192.168 then add to the allowed hosts
allowed_hosts.append(host)
if host not in allowed_hosts:
raise HttpResponseForbidden
return None
and setting ALLOWED_HOSTS = ['*'] in settings.py no longer opens up for all hosts in an uncontrolled way.
Thanks guys! :)

For those wondering what this should be in Django 2.0.dev (In line with #Zorgmorduk's answer)
You need to make the object callable: django middleware docs
Create a folder named middleware in yourproject/yourapp/
Create an empty file __init__.py inside yourproject/yourapp/middleware folder.
Create another file, in this case filter_host_middleware.py
Add this code inside filter_host_middleware.py:
from django.http import HttpResponseForbidden
class FilterHostMiddleware(object):
def __init__(self, process_request):
self.process_request = process_request
def __call__(self, request):
response = self.process_request(request)
return response
def process_request(self, request):`
# use the same process_request definition as in #Zorgmorduk's answer
add yourapp.middleware.filter_host_middleware.FilterHostMiddleware to your MIDDLEWARE in yourproject's settings.py; additionally change ALLOWED_HOSTS=['*']
You are all set!

Related

Set up different CORS rules based on the endpoint in Django

I'm trying to figure out a way to have different CORS rules based on the backend endpoint frontend would hit.
So I can have
/api endpoint with a CORS domain whitelist and
/public-api without a CORS domain whitelist.
This is needed because I have both internal endpoints I use for my own frontend, and a public JS widget that can be installed in any 3rd party domain.
I've looked at django-cors-headers library, but it's regex configuration
CORS_ORIGIN_REGEX_WHITELIST = []
works to let requests FROM a list of domains through.
In my case, I need to a way to have a regex (or another method) to let requests TO my endpoints through or not.
django-cors-headers allows you to specify a custom handler function that will check if the request should be allowed. In your case you can use something like this:
# myapp/handlers.py
from corsheaders.signals import check_request_enabled
def cors_allow_particular_urls(sender, request, **kwargs):
return request.path.startswith('/public-api/')
check_request_enabled.connect(cors_allow_mysites)
handlers.py needs to be loaded in app config:
# myapp/__init__.py
default_app_config = 'myapp.apps.MyAppConfig'
# myapp/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'myapp'
def ready(self):
# Makes sure all signal handlers are connected
from myapp import handlers # noqa
More info here: https://github.com/adamchainz/django-cors-headers#signals
If you can club your private URLs under a separate prefix (ex: /private/<something>) you can use CORS_URLS_REGEX=r'^/private/.*$'
You can read more about it here:
https://github.com/adamchainz/django-cors-headers#cors_urls_regex

It is possbile to have ALLOWED_HOSTS different configuration for some urls?

I want to have ALLOWED_HOSTS=['*'] for some urls but for the rest I want it to be ALLOWED_HOSTS=[".example.com"].
For csrf we have #csrf_exempt
For cors we have the signal check_request_enabled
But for ALLOWED_HOSTS?
A way to go would be to write a middleware, that will check the request url and set ALLOWED HOSTS. You'll need to add this middleware at the top of MIDDLEWARES section in settings file. Try something like below:
from django.conf import settings
def simple_middleware(get_response):
def middleware(request):
# Code to be executed for each request before
# the view (and later middleware) are called.
if request.META['PATH_INFO'] == "your_logic":
settings.ALLOWED_HOSTS = ["*"]
else:
settings.ALLOWED_HOSTS = ['example.com']
response = get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
return middleware

Why is my hosted Django app showing my username in the URL?

I've written a Django app which I'm trying to get set up on shared web hosting (A2). It's working, except that when I go to:
http://example.com/terms/
the URL changes in the browser bar to:
http://example.com/home/myusername/myappfolder/myappname/terms/
showing the full path to where my app is on disk.
This doesn't happen with static files - e.g. http://example.com.com/static/image.png works normally.
The app is running in a virtual environment. I'm using python 3.6.8 and Django 2.1.4.
I followed these instructions to set up my app, which include setting up this passenger.wsgi file, that looks like this:
import myapp.wsgi
SCRIPT_NAME = '/home/username/myapp'
class PassengerPathInfoFix(object):
"""
Sets PATH_INFO from REQUEST_URI because Passenger doesn't provide it.
"""
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
from urllib.parse import unquote
environ['SCRIPT_NAME'] = SCRIPT_NAME
request_uri = unquote(environ['REQUEST_URI'])
script_name = unquote(environ.get('SCRIPT_NAME', ''))
offset = request_uri.startswith(script_name) and len(environ['SCRIPT_NAME']) or 0
environ['PATH_INFO'] = request_uri[offset:].split('?', 1)[0]
return self.app(environ, start_response)
application = myapp.wsgi.application
application = PassengerPathInfoFix(application)
I'd be grateful for any pointers as to where to look to solve this.
Got it working!
In my modified passenger_wsgi.py, I changed the line
SCRIPT_NAME = os.getcwd()
to
SCRIPT_NAME = ''
One thing I should point out is that the absolute path was getting inserted on redirects - so if I visited
http://example.com/terms
it would redirect to
http://example.com/terms/
and insert the path in the URL.
As you're debugging I recommend disabling the cache, as that threw me for several loops when changes I made didn't seem to take effect.
Thanks to this question for getting me on the right track.

Django hostname middleware gets cached

I created a Django project to manage two separate sites that share some backend code. Both of the sites are inside separate apps. Each app has its own models.py, views.py, templates etc...
To be able to react differently to different hostnames, I created an URLconf middleware:
class HostnameBasedUrlconfMiddleware(object):
"""This middleware parses the hostname from the request, and selects the
urlconf accordingly.
To set a custom urlconf according to the current hostname, add an URLCONF
dictionary to your settings.py file.
URLCONF = {
'example.com': 'urls_example',
'example.dev': 'urls_dev',
'admin.example.dev': 'apps.admin.urls'
}
If the hostname is not found in the URLCONF dictionary, the default
ROOT_URLCONF setting will be used.
"""
def process_request(self, request):
# Decide which urlconf to use. Fallback is to use the ROOT_URLCONF
# as defined in the settings.py file.
try:
hostname = request.META['HTTP_HOST']
request.urlconf = settings.URLCONF[hostname]
except (KeyError, AttributeError):
pass
return None
This seemed to work at first, but then I became aware that some kind of caching must be happening.
When starting the server and requesting site A, it would show up. If I then request site B, site A shows up. Sometimes (but not always), after several reloads, site B would finally show up. After restarting the server and requesting site B, it would show up, but now site A would show site B content.
This happened with the builtin devserver as well as with gunicorn.
I tried to request the site with curl to avoid browser caching, no difference.
I also suspected it could be some kind of template name collision, but all templates are inside a uniquely named subfolder inside their respective template folders.
I don't have memcached installed and I'm not using any caching middleware.
What could be the problem? Is there some internal automatic caching going on?
Here is the code in question that substitutes in the urlconf (for 1.3 at least):
django.core.handlers.base:
class BaseHandler(object):
[...snip...]
def get_response(self, request):
"Returns an HttpResponse object for the given HttpRequest"
from django.core import exceptions, urlresolvers
from django.conf import settings
try:
# Setup default url resolver for this thread, this code is outside
# the try/except so we don't get a spurious "unbound local
# variable" exception in the event an exception is raised before
# resolver is set
urlconf = settings.ROOT_URLCONF
urlresolvers.set_urlconf(urlconf)
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
try:
response = None
# Apply request middleware
for middleware_method in self._request_middleware:
response = middleware_method(request)
if response:
break
if response is None:
if hasattr(request, "urlconf"):
# Reset url resolver with a custom urlconf.
urlconf = request.urlconf
urlresolvers.set_urlconf(urlconf)
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
[...snip...]
So, it looks like it's just using the value directly from request.urlconf. And your middleware is setting the request value directly.
I'd install django-debug-toolbar to confirm whether or not the value for request.urlconf is a) being set or b) being changed along the way.
To make absolutely sure, why not change the code temporarily to something like:
request.urlconf = settings.URLCONF[hostname]
request.urlconf_set = datetime.datetime.now()
Then you can look at the values in the debug toolbar (or just output them in a template) to see what might be going on.
However, I would suggest instead of using middleware, that you simply set up different settings.py files for each domain. Then, in whatever web server you're using, set each one up to use its own .wsgi file, which points to its own settings file, like so:
settings_a.py:
from settings import *
ROOT_URLCONF = 'urls_a.py'
settings_b.py
from settings import *
ROOT_URLCONF = 'urls_b.py'

Page not found message using Django

If a user type a random url(http://testurl/cdsdfsd) for a site ,how to issue page not found.I there any changes settings.py or how to handle this..
The django tutorial and docs have sections you should read.
You need to override the default 404 view.
in your urlconf:
handler404 = 'mysite.views.my_custom_404_view'
By default, django is rendering a 404.html template. Crete this file anywhere where your templates are found. E.g. you can create templates directory in your django project root, then add it to the TEMPLATE_DIRS in settings.py:
import os
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
TEMPLATE_DIRS = (
os.path.join(BASE_DIR, 'templates/'),
)
another solution is to write your own middleware which will check if there was 404 response, and the to decide what to display. This is especially useful if you want to have a fallback solution (like static pages) or to play with the response and e.g. perform a search on the site and show possible options.
here is an example middleware from django.contrib.flatpages. it check if url is defined in the database, if so - returns this page if not, pass and return default response.
class FlatpageFallbackMiddleware(object):
def process_response(self, request, response):
if response.status_code != 404:
return response # No need to check for a flatpage for non-404 responses.
try:
return flatpage(request, request.path_info)
# Return the original response if any errors happened. Because this
# is a middleware, we can't assume the errors will be caught elsewhere.
except Http404:
return response
except:
if settings.DEBUG:
raise
return response