To get to the point where I could even write parts of the authenticator, I needed to teach myself how to write authenticators. So, here are two authenticators. Both are a little odd, but I think the code is short enough that it won’t be confusing.
If it gets confusing because I’m using a couple libraries – study the libraries. If you’re not already familiar with subprocess and requests, plan to spend a few more hours to learn those great libraries. It’s worth the effort.
The two authenticators are auth_command and auth_proxy_http. Auth_command executes an external command, and allows the login if it passes. Auth_proxy_http makes a request to another URL; the URL must be set up to accept an HTTP-Authorization username and password. If it returns a status code of 200 to 299, it’s considered authorized.
To make auth_command, you use
./manage.py startapp auth_command
Then, in that directory, create this command in the file “command”:
#! /usr/bin/python
"""
Usage: command username password
Returns 0 on success, 1 on failure.
"""
import sys
passwd = {
'paul': 'password',
}
try:
username = sys.argv[1]
password = sys.argv[2]
except:
sys.exit(1)
try:
if passwd[username] == password:
sys.exit(0)
else:
sys.exit(1)
except Exception as e:
sys.exit(1)
This is a simple unix command that checks the passwd dictionary to look up a password.
Then, make a file, auth.py:
from django.contrib.auth.models import User
import subprocess, inspect, os
class AuthCommandBackend(object):
def authenticate(self, username=None, password=None):
try:
command = os.path.dirname(inspect.getfile(
inspect.currentframe()))
command = '%s/command' % (command,)
# print 'running command %s' % (command,)
# run the command
exitcode = subprocess.call(
[command, username, password])
if exitcode == 1:
return None
except:
return None
user, created = User.objects.get_or_create(username=username)
if (created):
user.set_password(password)
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
It checks that the command accepts the username and password. If it doesn’t it returns None. Otherwise, it proceeds to try and get a Django user with the same username, creating it if it doesn’t exist.
It also sets the password. This means that future logins are performed against the User, not this other command. If you want the command to always verify the password, don’t set a password on the created model. Then, the login for the User will always fail, and fall back to using the command. (Think about this a bit before deciding what to do.)
To use this authentication app, you add the auth module to the AUTHENTICATION_BACKENDS in the settings:
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # default
'auth_command.auth.AuthCommandBackend',
)
Once this is done, you can try to log into the comment section with the username “paul” and the password “password”.
Auth_proxy_http is a similar authentication, but uses the network.
It uses the Requests library, so install it with:
pip install requests
Do a
./manage.py startapp auth_proxy_http
then create auth.py:
from django.contrib.auth.models import User
import requests
class AuthProxyHttpBackend(object):
url = 'http://localhost/protected/'
def authenticate(self, username=None, password=None):
try:
r = requests.get(self.url, auth=(username, password))
if r.status_code < 200 or r.status_code >= 300:
return None
except:
return None
user, created = User.objects.get_or_create(username=username)
if (created):
user.set_password(password)
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Set the URL to whatever you want. We should really check a global for a URL value, and then add some notes about overriding this class to specify a different URL.
The code is similar to the command authenticator, except it checks http://localhost/protected/, which, presumably, is protected with a password in the HTTP-Auth style. This is the classic .htaccess file technique.
You would create a file in the website’s /protected/ directory called .htaccess, with this content:
AuthType Basic
AuthName "restricted area"
AuthUserFile /home/johnk/Sites/riceball/htpasswd
require valid-user
Then, you create the file specified in AuthUserFile, like this:
htpasswd -c /home/johnk/Sites/riceball/htpasswd test
Then enter the password.
Then, add to your settings:
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # default
'auth_command.auth.AuthProxyHttpBackend',
)
Now, logins to your Django app will be checked against that URL. If you add someone to the htpasswd, they’ll also gain access to the Django site.