mirror of
https://github.com/postgres/pgweb.git
synced 2025-08-03 15:38:59 +00:00
154 lines
5.4 KiB
Python
154 lines
5.4 KiB
Python
#
|
|
# Django module to support postgresql.org community authentication 2.0
|
|
#
|
|
# The main location for this module is the pgweb git repository hosted
|
|
# on git.postgresql.org - look there for updates.
|
|
#
|
|
# To integrate with django, you need the following:
|
|
# * Make sure the view "login" from this module is used for login
|
|
# * Map an url somwehere (typicall /auth_receive/) to the auth_receive
|
|
# view.
|
|
# * In settings.py, set AUTHENTICATION_BACKENDS to point to the class
|
|
# AuthBackend in this module.
|
|
# * (And of course, register for a crypto key with the main authentication
|
|
# provider website)
|
|
# * If the application uses the django admin interface, the login screen
|
|
# has to be replaced with something similar to login.html in this
|
|
# directory (adjust urls, and name it admin/login.html in any template
|
|
# directory that's processed before the default django.contrib.admin)
|
|
#
|
|
|
|
from django.http import HttpResponseRedirect
|
|
from django.contrib.auth.models import User
|
|
from django.contrib.auth.backends import ModelBackend
|
|
from django.contrib.auth import login as django_login
|
|
from django.contrib.auth import logout as django_logout
|
|
from django.conf import settings
|
|
|
|
import base64
|
|
import urlparse
|
|
import urllib
|
|
from Crypto.Cipher import AES
|
|
from Crypto.Hash import SHA
|
|
from Crypto import Random
|
|
import time
|
|
|
|
class AuthBackend(ModelBackend):
|
|
# We declare a fake backend that always fails direct authentication -
|
|
# since we should never be using direct authentication in the first place!
|
|
def authenticate(self, username=None, password=None):
|
|
raise Exception("Direct authentication not supported")
|
|
|
|
|
|
####
|
|
# Two regular django views to interact with the login system
|
|
####
|
|
|
|
# Handle login requests by sending them off to the main site
|
|
def login(request):
|
|
if request.GET.has_key('next'):
|
|
# Put together an url-encoded dict of parameters we're getting back,
|
|
# including a small nonce at the beginning to make sure it doesn't
|
|
# encrypt the same way every time.
|
|
s = "t=%s&%s" % (int(time.time()), urllib.urlencode({'r': request.GET['next']}))
|
|
# Now encrypt it
|
|
r = Random.new()
|
|
iv = r.read(16)
|
|
encryptor = AES.new(SHA.new(settings.SECRET_KEY).digest()[:16], AES.MODE_CBC, iv)
|
|
cipher = encryptor.encrypt(s + ' ' * (16-(len(s) % 16))) # pad to 16 bytes
|
|
|
|
return HttpResponseRedirect("%s?d=%s$%s" % (
|
|
settings.PGAUTH_REDIRECT,
|
|
base64.b64encode(iv, "-_"),
|
|
base64.b64encode(cipher, "-_"),
|
|
))
|
|
else:
|
|
return HttpResponseRedirect(settings.PGAUTH_REDIRECT)
|
|
|
|
# Handle logout requests by logging out of this site and then
|
|
# redirecting to log out from the main site as well.
|
|
def logout(request):
|
|
if request.user.is_authenticated():
|
|
django_logout(request)
|
|
return HttpResponseRedirect("%slogout/" % settings.PGAUTH_REDIRECT)
|
|
|
|
# Receive an authentication response from the main website and try
|
|
# to log the user in.
|
|
def auth_receive(request):
|
|
if request.GET.has_key('s') and request.GET['s'] == "logout":
|
|
# This was a logout request
|
|
return HttpResponseRedirect('/')
|
|
|
|
if not request.GET.has_key('i'):
|
|
raise Exception("Missing IV")
|
|
if not request.GET.has_key('d'):
|
|
raise Exception("Missing data!")
|
|
|
|
# Set up an AES object and decrypt the data we received
|
|
decryptor = AES.new(base64.b64decode(settings.PGAUTH_KEY),
|
|
AES.MODE_CBC,
|
|
base64.b64decode(str(request.GET['i']), "-_"))
|
|
s = decryptor.decrypt(base64.b64decode(str(request.GET['d']), "-_")).rstrip(' ')
|
|
|
|
# Now un-urlencode it
|
|
try:
|
|
data = urlparse.parse_qs(s, strict_parsing=True)
|
|
except ValueError, e:
|
|
raise Exception("Invalid encrypted data received.")
|
|
|
|
# Check the timestamp in the authentication
|
|
if (int(data['t'][0]) < time.time() - 10):
|
|
raise Exception("Authentication token too old.")
|
|
|
|
# Update the user record (if any)
|
|
try:
|
|
user = User.objects.get(username=data['u'][0])
|
|
# User found, let's see if any important fields have changed
|
|
changed = False
|
|
if user.first_name != data['f'][0]:
|
|
user.first_name = data['f'][0]
|
|
changed = True
|
|
if user.last_name != data['l'][0]:
|
|
user.last_name = data['l'][0]
|
|
changed = True
|
|
if user.email != data['e'][0]:
|
|
user.email = data['e'][0]
|
|
changed= True
|
|
if changed:
|
|
user.save()
|
|
except User.DoesNotExist, e:
|
|
# User not found, create it!
|
|
user = User(username=data['u'][0],
|
|
first_name=data['f'][0],
|
|
last_name=data['l'][0],
|
|
email=data['e'][0],
|
|
password='setbypluginnotasha1',
|
|
)
|
|
user.save()
|
|
|
|
# Ok, we have a proper user record. Now tell django that
|
|
# we're authenticated so it persists it in the session. Before
|
|
# we do that, we have to annotate it with the backend information.
|
|
user.backend = "%s.%s" % (AuthBackend.__module__, AuthBackend.__name__)
|
|
django_login(request, user)
|
|
|
|
# Finally, check of we have a data package that tells us where to
|
|
# redirect the user.
|
|
if data.has_key('d'):
|
|
(ivs, datas) = data['d'][0].split('$')
|
|
decryptor = AES.new(SHA.new(settings.SECRET_KEY).digest()[:16],
|
|
AES.MODE_CBC,
|
|
base64.b64decode(ivs, "-_"))
|
|
s = decryptor.decrypt(base64.b64decode(datas, "-_")).rstrip(' ')
|
|
try:
|
|
rdata = urlparse.parse_qs(s, strict_parsing=True)
|
|
except ValueError:
|
|
raise Exception("Invalid encrypted data received.")
|
|
if rdata.has_key('r'):
|
|
# Redirect address
|
|
return HttpResponseRedirect(rdata['r'][0])
|
|
# No redirect specified, see if we have it in our settings
|
|
if hasattr(settings, 'PGAUTH_REDIRECT_SUCCESS'):
|
|
return HttpResponseRedirect(settings.PGAUTH_REDIRECT_SUCCESS)
|
|
raise Exception("Authentication successful, but don't know where to redirect!")
|