[Skip to Content]
contact CTIcontact
gpgAuth : Server Implementations : Django

*** 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.

    (These requirements and this Django implementation is based Linux and Apache, but the Django code should be the same for any platform Django supports)
    python module gnupginterface


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


Make sure the following is part of your INSTALLED_APPS context in your settings.py file:


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':
            user = request.user
            if user.is_authenticated:
        return render_to_response('login.html', { 'stage1' : True }, context_instance=RequestContext(request))
    if stage == u'2':
            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))
                        ''' 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))
                    ''' 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))
                ''' 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))
            ''' 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" )
            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
                    login(request, user)
                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):
    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
            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
            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 }
            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):
                return User.objects.get(pk=user_id)
            except User.DoesNotExist:
                return None

    class MyGnuPG(GnuPGInterface.GnuPG):
        def __init__(self):
            gnupg = GnuPGInterface.GnuPG.__init__(self)

        def setup_my_options(self):
            self.options.armor = 1
            self.options.meta_interactive = 0
            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'] )
            output = proc.handles['stdout'].read()
            error = proc.handles['stderr'].read()
            error = proc.handles['stderr'].close()
            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'])
            output = proc.handles['stdout'].read()
            return output

    def get_user(self, user_id):
            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.


<< 2.2 : PHP
3 : Client Implementations >>
mod_python  gpgAuth Enabled