htaccess on heroku for django app - django

The title pretty much sums up my question. I would like to to password protect some files in my django app that lives on heroku.
If I can't use htaccess does anyone have suggestions on what else I could use?
Thanks.

As #mipadi said, you can't use htaccess on Heroku, but you can create a middleware for that:
from django.conf import settings
from django.http import HttpResponse
from django.utils.translation import ugettext as _
def basic_challenge(realm=None):
if realm is None:
realm = getattr(settings, 'WWW_AUTHENTICATION_REALM', _('Restricted Access'))
# TODO: Make a nice template for a 401 message?
response = HttpResponse(_('Authorization Required'), mimetype="text/plain")
response['WWW-Authenticate'] = 'Basic realm="%s"' % (realm)
response.status_code = 401
return response
def basic_authenticate(authentication):
# Taken from paste.auth
(authmeth, auth) = authentication.split(' ',1)
if 'basic' != authmeth.lower():
return None
auth = auth.strip().decode('base64')
username, password = auth.split(':',1)
AUTHENTICATION_USERNAME = getattr(settings, 'BASIC_WWW_AUTHENTICATION_USERNAME')
AUTHENTICATION_PASSWORD = getattr(settings, 'BASIC_WWW_AUTHENTICATION_PASSWORD')
return username == AUTHENTICATION_USERNAME and password == AUTHENTICATION_PASSWORD
class BasicAuthenticationMiddleware(object):
def process_request(self, request):
if not getattr(settings, 'BASIC_WWW_AUTHENTICATION', False):
return
if 'HTTP_AUTHORIZATION' not in request.META:
return basic_challenge()
authenticated = basic_authenticate(request.META['HTTP_AUTHORIZATION'])
if authenticated:
return
return basic_challenge()
Then you need to define in settings.py:
BASIC_WWW_AUTHENTICATION_USERNAME = "your user"
BASIC_WWW_AUTHENTICATION_PASSWORD = "your pass"
BASIC_WWW_AUTHENTICATION = True

I was able to use .htaccess files on heroku with the cedar stack.
Procfile needs to specify a script for the web nodes:
web: sh www/conf/web-boot.sh
The conf/web-boot.sh slipstreams the include of a apache configuration file, e.g.:
A conf/httpd/default.conf then can allow override, as you know it from apache.
You can then just use .htaccess files. The whole process is documented in detail in my blog post PHP on Heroku again of which one part is about the apache configuration. The step in 2. including your own httpd configuration basically is:
sed -i 's/Listen 80/Listen '$PORT'/' /app/apache/conf/httpd.conf
sed -i 's/^DocumentRoot/# DocumentRoot/' /app/apache/conf/httpd.conf
sed -i 's/^ServerLimit 1/ServerLimit 8/' /app/apache/conf/httpd.conf
sed -i 's/^MaxClients 1/MaxClients 8/' /app/apache/conf/httpd.conf
for var in `env|cut -f1 -d=`; do
echo "PassEnv $var" >> /app/apache/conf/httpd.conf;
done
echo "Include /app/www/conf/httpd/*.conf" >> /app/apache/conf/httpd.conf
touch /app/apache/logs/error_log
touch /app/apache/logs/access_log
tail -F /app/apache/logs/error_log &
tail -F /app/apache/logs/access_log &
export LD_LIBRARY_PATH=/app/php/ext
export PHP_INI_SCAN_DIR=/app/www
echo "Launching apache"
exec /app/apache/bin/httpd -DNO_DETACH
I hope this is helpful. I used this for .htaccess and for changing the webroot.

You can't use .htaccess, because Heroku apps aren't served with Apache. You can use Django authentication, though.
Or you can serve the files from another server that is using Apache.

Related

POST Request to Heroku in Python - 403 Forbidden

I'm learning web scraping and building a simple web app at the moment, and I decided to practice scraping a schedule of classes. Here's a code snippet I'm having trouble with in my application, using Python 2.7.4, Flask, Heroku, BeautifulSoup4, and Requests.
import requests
from bs4 import BeautifulSoup as Soup
url = "https://telebears.berkeley.edu/enrollment-osoc/osc"
code = "26187"
values = dict(_InField1 = "RESTRIC", _InField2 = code, _InField3 = "13D2")
html = requests.post(url, params=values)
soup = Soup(html.content, from_encoding="utf-8")
sp = soup.find_all("div", {"class" : "layout-div"})[2]
print sp.text
This works great locally. It gives me back the string "Computer Science 61A P 001 LEC:" as expected. However, when I tried to run it on Heroku (using heroku run bash and then run python), I got back an error,403 Forbidden.
Am I missing some settings on Heroku? At first I thought it's the school settings, but then I was wondering why it works locally without any trouble... Any explanation/suggestion would be really appreciated! Thank you in advance.
I was having a similar issue, request was working locally but getting blocked on Heroku. It looks like the issue is that some websites block requests coming from Heroku (which on on AWS Servers). To get around this you can send your requests via a proxy server.
There are a bunch of different add-ons in heroku to achieve this, I went with fixie which has a reasonably sized free tier.
To install:
heroku addons:create fixie:tricycle
Then import into your local environment so you can try locally:
heroku config -s | grep FIXIE_URL >> .env
then in your python file you just add a couple of lines:
import os
import requests
from bs4 import BeautifulSoup as Soup
proxyDict = {
"http" : os.environ.get('FIXIE_URL', ''),
"https" : os.environ.get('FIXIE_URL', '')
}
url = "https://telebears.berkeley.edu/enrollment-osoc/osc"
code = "26187"
values = dict(_InField1 = "RESTRIC", _InField2 = code, _InField3 = "13D2")
html = requests.post(url, params=values, proxies=proxyDict)
soup = Soup(html.content, from_encoding="utf-8")
sp = soup.find_all("div", {"class" : "layout-div"})[2]
print sp.text
Docs for Fixie are here:
https://devcenter.heroku.com/articles/fixie

Fabric - Force password prompt on Production Deploy

Is it possible to force the user to enter their password when they deploy to production?
I was deploying to staging, but accidentally hit tab on the CL to production instead and almost made a huge mistake! Needless to say I will never use autocomplete for fab ... ever ever again.
UPDATE:
Below is what our fabfile essentially looks like. Each host, like application-staging or application-production, is saved in the ssh config.
from fabric import colors
from fabric.api import *
from fabric.contrib.project import *
import git
env.app = '{{ project_name }}'
env.dest = "/var/www/%(app)s" % env
env.use_ssh_config = True
def reload_processes():
sudo("kill -HUP `cat /tmp/%(app)s.pid`" % env)
def sync():
repo = git.Repo(".")
sha = repo.head.commit.hexsha
with cd(env.dest):
run("git fetch --all")
run("git checkout {} -f".format(sha))
if "production" in env.host_string:
with cd(env.dest):
run("compass compile")
with prefix(". /home/ubuntu/environments/%(app)s/bin/activate" % env):
run("%(dest)s/manage.py syncmedia" % env)
def deploy():
sync()
link_files()
reload_processes()
add_commit_sha()
def link_files():
print(colors.yellow("Linking settings."))
env.label = env.host_string.replace("%(app)s-", "")
with cd(env.dest):
sudo("rm -f local_settings.py")
sudo("ln -s conf/settings/%(label)s.py local_settings.py" % env)
sudo("rm -f conf/gunicorn/current.py")
sudo("ln -s %(label)s.py conf/gunicorn/current.py" % env)
sudo("rm -f celeryconfig.py")
sudo("ln -s conf/settings/celery/%(label)s.py celeryconfig.py" % env)
sudo("rm -f conf/supervisor/programs.ini" % env)
sudo("ln -s %(label)s.ini conf/supervisor/programs.ini" % env)
def reload_processes(reload_type="soft"):
print(colors.yellow("Reloading processes."))
env.label = env.host_string.replace("%(app)s-", "")
with cd(env.dest):
sudo("kill -HUP `cat /tmp/gunicorn.%(app)s.%(label)s.pid`" % env)
def add_commit_sha():
repo = git.Repo(".")
sha = repo.head.commit.hexsha
sed("{}/settings.py".format(env.dest), "^COMMIT_SHA = .*$", 'COMMIT_SHA = "{}"'.format(sha), backup="\"\"", use_sudo=True)
I use this pattern, where you set up the staging/prod configurations in their own tasks:
#task
def stage():
env.deployment_location = 'staging'
env.hosts = ['staging']
#task
def prod():
env.deployment_location = 'production'
env.hosts = ['prod1', 'prod2']
#task
def deploy():
require('deployment_location', used_for='deployment. \
You need to prefix the task with the location, i.e: fab stage deploy.')
confirm("""OK. We're about to deploy to:
Location: {env.deployment_location}
Is that cool?""".format(env=env))
# deployment tasks down here
In this case, you have to type fab prod deploy and say yes to the confirmation message in order to deploy to production.
Just typing fab deploy is an error, because the deployment_location env variable isn't set.
It doesn't prevent total idiocy, but it does prevent accidental typos and so far it's worked well.
I mean yeah. You could remove all of their ssh keys and make them use passwords every time. You could also use stdlib prompts to ask the user if they meant production. You can also have only certain users write to production using basic ACLs. There are any number of ways of slowing the deployment process down, it's mostly going to come down to what you and your devs prefer.

How to do a simple captive portal with django http redirects?

I have a device running django and serving up an wireless access point. I would essentially like to create a captive portal of sorts where any url entered would go to my django web page.
To accomplish this so far I have created an iptables rule like so (for now):
iptables -t nat -A PREROUTING -d 0/0 -p tcp –dport 80 -j DNAT –to 192.168.50.1:8000
This works in a way, except that if I go to for example "google.com", I get google.com shown in the browser url, but my django web page displayed.
What I would like is for mydjangowebpage.com to be shown in the url instead. There seems to be a host of captive portal solutions that require dns servers, multiple iptables rules, radius servers, etc.
To me it seems like a simple solution would be to do that one rule I specified above, followed by some httpredirect command in django. I would like to know if my urls.py dispatcher is capable of checking the actual site url (i.e. the "google.com" part), and if it is NOT "mydjangowebpage.com" to do a httpredirect to "mydjangowebpage". Does that make sense?
I guess the urlpatterns looks for matches, It seems to accomplish this the pattern would need to look for NOT matches and look at the whole url, not just the stuff after the site name.
For example, something along the lines of:
urlpatterns = patterns('',
url(lambda x: not models.get_current_site(request) == 'mydjangowebpage.com', lambda x: HttpResponseRedirect("http://mydjangowebpage.com")),
....
Well I found a way to do it. I'll still leave it open in case there are better ways...
You can make use of a middleware filter. I found this one here to work (called hostname_middleware.py). I have copied it here for convenience:
from django.conf import settings
from django.utils.http import urlquote
from django import http
class EnforceHostnameMiddleware(object):
"""
Enforce the hostname per the ENFORCE_HOSTNAME setting in the project's settings
The ENFORCE_HOSTNAME can either be a single host or a list of acceptable hosts
"""
def process_request(self, request):
try:
if not settings.ENFORCE_HOSTNAME:
return None
except AttributeError, e:
return None
host = request.get_host()
# find the allowed host name(s)
allowed_hosts = settings.ENFORCE_HOSTNAME
if not isinstance(allowed_hosts, list):
allowed_hosts = [allowed_hosts]
if host in allowed_hosts:
return None
# redirect to the proper host name
new_url = [allowed_hosts[0], request.path]
new_url = "%s://%s%s" % (
request.is_secure() and 'https' or 'http',
new_url[0], urlquote(new_url[1]))
if request.GET:
new_url += '?' + request.META['QUERY_STRING']
return http.HttpResponsePermanentRedirect(new_url)
Then add in your settings.py file (customize to fit your needs):
ENFORCE_HOSTNAME=['192.168.50.1','mydjangowebpage.com']
And add the middleware to MIDDLEWARE_CLASSES:
'mydjangowebpage.hostname_middleware.EnforceHostnameMiddleware'
Now when I go to a url "facebook.com" or anything else, it will redirect the name to the first in the list. Yay! Easy captive portal!

nginx, django and x_requested_with: request.is_ajax() returns False

I've got this code in my Django view:
if request.is_ajax():
if request.method == 'POST':
data = json.loads(request.raw_post_data)
And I send this request, via CURL:
curl -v -i -H "Content-Type:application/json" -H "X_REQUESTED_WITH:XMLHttpRequest" -X POST -d '{"tweet_id":"189881044923719680", "question_id":"1", "choice_one":"T", "choice_two":"F", "choice_three":"F", "extra_passback":"common cold"}' http://ec[...].compute-1.amazonaws.com/question/answer/
This works when I hit my development Django server. In production I'm running nginx and gunicorn. And the curl statement doesn't work.
request.is_ajax() returns False (is_ajax looks for the x_requested_with header).
I think this must have to do with an NGINX configuration perhaps. But I'm not sure. What do I need to change?
Try to change the underscores in X_REQUESTED_WITH:XMLHttpRequest with dashes: X-REQUESTED-WITH:XMLHttpRequest.
I had the same problem with nginx as proxy deleted the X_REQUESTED_WITH field from the request. Per default nginx marks headers with underscores as invalid and ignores invalid headers.
You can use nginx directives to either allow underscores in headers with underscores_in_headers on; or don't ignore invalid header with ignore_invalid_headers off;.
In my concrete case I used python requests as client, django as server and nginx as proxy and solved my problem in which I renamed X_REQUESTED_WITH to X-REQUESTED-WITH. (Django automatically adds 'HTTP_' to the start of x-header names before making the header available in request.META.)
import requests
headers = {'X-REQUESTED-WITH':'XMLHttpRequest'}
r = requests.post(url, data=payload, headers=headers)

Django + Apache - file.open() Permission Denied

So I have the following problem:
On an ivent a javascript sends some text to the django server and there are two functions that should work:
views.py:
def log(request):
f = open('media/log.txt', 'r')
return HttpResponse(f, mimetype='text/plain')
def modelers(request):
mod_stat = request.POST['id']
time = datetime.datetime.now().strftime("%b %d %Y %H:%M:%S")
file=open('media/log.txt', 'a')
file.write(time)
file.write(' ')
file.write(mod_stat)
file.write('\n')
file.close()
return ErrorResponse()
so the user clicks on a button and the "modelers" function is getting the info and is trying to add a line to the log file. But it doesn't work!
The apache error.log says that
IOError: [Errno 13] Permission denied: 'media/log.txt', referer: ...
chmod 777 media doesn't help.. I know that I must config the apache somehow to let the django write files, but didn't find how :(
If not under apache it works great(so the url.py is OK), but I need to make it work with apache. The other part of the application also works fine but there are no operations with files.. until now..
Have you tried chmod 777 media/log.txt? Does ls -l media/ say rwxrwxrwx log.txt? If yes then try to specify absolute path to log.txt in f = open('...log.txt')