projects:gpgAuth
*** OUTDATED ***
The following django authentication example is outdated and does not conform to the current gpgAuth protocol. An updated version is in the works and should be complete in the next few days.
******
This method uses the django.contrib.auth authenticate and login functions, but this can be changed to a custom authentication backend. I am working on a django authentication backend for gpgAuth, it is not finished yet.
Requirements:
(These requirements and this Django implementation is based Linux and Apache, but the Django code should be the same for any platform Django supports)
Apache
Python
Django
GnuPG
python module gnupginterface
Setup:
If you are using the django.contrib.auth module for your user objects, it will need to be extended to provide a place to store the users public key fingerprint and the value of the authentication token.
To extend the user object, add this line to your settings.py file for your project:
AUTH_PROFILE_MODULE = 'userprofile.UserProfile'
Then create the app:
python manage.py startapp userprofile
This will create the blank views.py and models.py. Edit the models.py file to include:
from django.db import models from django.contrib.auth.models import User class UserProfile(models.Model): user = models.ForeignKey(User, unique=True)
fingerprint = models.TextField(null=True,blank=True)
gpgauth_token = models.TextField(null=True,blank=True)
class Meta:
verbose_name = ('User Profile')
verbose_name_plural = ('User Profiles')
def __str__( self ):
return "%s - Profile Object" % ( self.user.username )
If you want these objects to be accessible within the built-in django admin site, create an admin.py file in the 'userprofile' app directory and add the following lines:
from example.userprofile.models import UserProfile
from django.contrib import admin
admin.site.register(UserProfile)
Make sure the following is part of your INSTALLED_APPS context in your settings.py file:
INSTALLED_APPS = (
'django.contrib.auth',
),
Add these lines to your settings.py file (or change them if they already exist) - this will override where users are pointed when told to login using the @login_require decorator):
LOGIN_URL = '/login'
LOGOUT_URL = '/logout'
Now edit or create the URLs for the project by adding these lines to the urls.py file (the admin line is optional, this just creates the URLs for the built-in django admin site.):
urlpatterns = patterns('',
(r'^admin/(.*)', admin.site.root),
(r'^login/$', 'example.views.login_view'),
(r'^login/(?P<stage>\d+)', 'example.views.login_view'),
(r'^logout/$', 'example.views.logout_view'),
)
Now, within the views.py file of your main project, add the following views:
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponseRedirect
from django.contrib.auth.decorators import login_required
from django.contrib.auth import authenticate, login, logout
from example.gpgauth import gpgauthBackend
from django.contrib.auth.models import User
from cgi import parse_qs
def login_view(request, stage=u'1'):
if stage == u'1':
try:
user = request.user
if user.is_authenticated:
logout(request)
except:
pass
return render_to_response('login.html', { 'stage1' : True }, context_instance=RequestContext(request))
if stage == u'2':
try:
user = User.objects.get(username=request.POST['username'])
except User.DoesNotExist:
user = None
if request.POST['username'] and user:
if request.POST['password']:
''' Perform legacy authentication '''
user = None
user = authenticate(username=request.POST['username'], password=request.POST['password'])
if user is not None:
if user.is_active:
''' User is authenticated, call the login function '''
login(request, user)
return render_to_response('login.html', { 'stage3' : True, 'auth_method':'password', 'error' : None }, context_instance=RequestContext(request))
else:
''' Account is not active '''
return render_to_response('login.html', { 'stage1' : True, 'auth_method':'password', 'error' : True, 'error_message' : 'account disabled' }, context_instance=RequestContext(request))
else:
''' Username or password were incorrect '''
return render_to_response('login.html', { 'stage1' : True, 'auth_method':'password', 'error' : True, 'error_message' : 'invalid login' }, context_instance=RequestContext(request))
elif request.POST.has_key('gpg_auth:server_token') and len(request.POST['gpg_auth:server_token']) and user.userprofile_set.values() and user.userprofile_set.values()[0].has_key('fingerprint'):
''' Perform gpg authentication '''
server_token = parse_qs( request.raw_post_data )['gpg_auth:server_token'][0]
result = gpgauthBackend().authenticate( username=request.POST['username'], password=request.POST['password'], server_token=server_token )
request.session['auth_user'] = request.POST['username']
return render_to_response('login.html', { 'stage2' : True, 'decrypted_server_token' : result['decrypted_server_token'], 'encrypted_user_token' : result['encrypted_user_token'], 'auth_user':result['user'], 'error' : result['error'], 'error_message' : result['error_message'] }, context_instance=RequestContext(request))
else:
''' An error has occurred '''
if not user.userprofile_set.values() or not user.userprofile_set.values()[0].has_key('fingerprint'):
error = "This account is not configured for gpgAuth login"
if not request.POST.has_key('gpg_auth:server_token') or not len(request.POST['gpg_auth:server_token']):
error = "No token provided by the client."
return render_to_response('login.html', { 'stage1' : True, 'error' : True, 'error_message' : error }, context_instance=RequestContext(request))
else:
''' Invalid username or password '''
return render_to_response('login.html', { 'stage1' : True, 'error' : True, 'error_message' : 'Invalid username or password' }, context_instance=RequestContext(request))
if stage == u'3':
error = None
error_message = None
username = request.session.get( "auth_user" )
try:
user = User.objects.get(username=username)
if user.userprofile_set.values() and user.userprofile_set.values()[0].has_key('gpgauth_token'):
user_token = user.get_profile().gpgauth_token
if user_token == request.POST['user_response_token']:
''' the tokens match, lets login.. '''
user.backend = 'example.gpgauth.gpgauthBackend'
user.get_profile().gpgauth_token = None
user.get_profile().save()
login(request, user)
else:
user_token = "No token provided"
error = True
error_message = user_token
except User.DoesNotExist:
user = None
error = True
error_message = "invalid user"
return render_to_response('login.html', { 'stage3' : True, 'auth_method':'gpgAuth', 'user_token' : user_token, 'user_response_token' : request.POST['user_response_token'], 'error' : error, 'error_message' : error_message }, context_instance=RequestContext(request))
def logout_view(request):
logout(request)
return render_to_response( 'login.html', { 'stage1' : True }, context_instance=RequestContext(request) )
And finally, create a gpgauth.py file in the root of your project and add the following code to the file (NOTE: the string should be random data consisting of a-Z and 0-9 (alphanumeric) of any length. The example crypto_token is static, it never changes, it is only an example....)
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth import authenticate as legacy_authenticate
import GnuPGInterface
class gpgauthBackend:
"""
Authenticate using GnuPG/PGP via the gpgauth protocol (see http://www.gpgauth.org).
Django gpgAuth integration module v0.1
go to http://www.gpgauth.org for the latest version.
"""
def authenticate(self, username=None, password=None, server_token=None, user_token=None):
gnupg = self.MyGnuPG()
global gnupg
auth_user = None
decrypted_server_token = None
encrypted_user_token = None
user = User.objects.get(username=username)
if user.userprofile_set.values() and user.userprofile_set.values()[0].has_key('fingerprint') and user.get_profile().fingerprint and server_token:
user_fp = user.get_profile().fingerprint
user.get_profile().gpgauth_token = None
user.get_profile().save()
decrypted_server_token = gnupg.decrypt_string( server_token )
crypto_token = "qkEbiHSLzHfWHNvnKgPWMPdDrKxbczfjyFcGTtZCDKq"
user_token = "gpgauthv1.2.1|%s|%s|gpgauthv1.2.1" % ( len(crypto_token), crypto_token )
encrypted_user_token = gnupg.encrypt_and_sign_string( user_token, [user_fp.replace(" ", "" )] )
user.get_profile().gpgauth_token = user_token
user.get_profile().save()
auth_method = "gpgAuth"
return { 'auth_method' : auth_method, 'user' : user, 'decrypted_server_token' : decrypted_server_token, 'encrypted_user_token' : encrypted_user_token, 'error' : None, 'error_message' : None }
else:
return { 'auth_method' : 'gpgAuth', 'user' : user, 'error' : True, 'error_message' : 'no authentication key on file for user', 'encrypted_user_token' : encrypted_user_token, 'decrypted_server_token' : decrypted_server_token }
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
class MyGnuPG(GnuPGInterface.GnuPG):
def __init__(self):
gnupg = GnuPGInterface.GnuPG.__init__(self)
self.setup_my_options()
def setup_my_options(self):
self.options.armor = 1
self.options.meta_interactive = 0
self.options.extra_args.append('--no-secmem-warning')
self.options.default_key = 'example.com'
self.options.homedir = '/var/www/example.com/.gnupg'
def decrypt_string( self, string ):
gnupg.passphrase = ''
proc = gnupg.run( ['--decrypt', '-a'], create_fhs=['stdin', 'stdout','stderr'] )
proc.handles['stdin'].write(str(string))
proc.handles['stdin'].close()
output = proc.handles['stdout'].read()
proc.handles['stdout'].close()
error = proc.handles['stderr'].read()
error = proc.handles['stderr'].close()
proc.wait()
return output
def encrypt_and_sign_string(self, string, recipients):
gnupg.options.recipients = recipients # a list!
proc = gnupg.run(['--sign', '--encrypt'], create_fhs=['stdin', 'stdout'])
proc.handles['stdin'].write(string)
proc.handles['stdin'].close()
output = proc.handles['stdout'].read()
proc.handles['stdout'].close()
proc.wait()
return output
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Now, once you have added the users fingerprint to the database, and imported the users public_key into the GnuPG Keystore, that user should be able to login using gpgAuth, or the legacy password authentication, to disable the password method, just call the set_unusable_password on the given user object, and then call user_object.save(), then only gpgAuth login will work for the given user.