Commits

Roman Barczyński committed 7061080

initial commit

Comments (0)

Files changed (12)

+== Basic Installation ==
+
+ 1. Copy openid_provider into your project directory.
+
+ 2. Add 'openid_provider' to INSTALLED_APPS,
+    openid_provider requre at least:
+    django.contrib.auth and django.contrib.sessions
+
+ 3. Add openid_provider/urls.py to your urlpatterns, e.g.:
+    urlpatterns = patterns('',
+	  ...
+	  url(r'^openid/', include('openid_provider.urls')),
+	  ...
+	)
+
+ 4. Run python manage.py syncdb to create required tables
+    to your database.
+
+
+== Features ==
+
+  1. Automatic redirect to login page for unauthorized users.
+
+  2. Semi-automated creation of OpenID identifiers (leave OpenID field empty).
+
+  3. Decision page for adding trust_root to one's OpenID trusted sites.
+
+- "one time" access decision (without storing trust_root in database),
+- trusted_roots management,
+- sreg support.

openid_provider/__init__.py

Empty file added.

openid_provider/admin.py

+# -*- coding: utf-8 -*-
+# vim: set ts=4 sw=4 : */
+
+from django.contrib import admin
+from django import forms
+
+from models import *
+
+class TrustedRootInline(admin.TabularInline):
+	model = TrustedRoot
+
+class OpenIDAdmin(admin.ModelAdmin):
+	list_display = ['user', 'openid']
+	inlines = [TrustedRootInline, ]
+admin.site.register(OpenID, OpenIDAdmin)

openid_provider/models.py

+# -*- coding: utf-8 -*-
+# vim: set ts=4 sw=4 : */
+
+from django.contrib.auth.models import User
+from django.utils.translation import ugettext as _
+from django.db import models
+
+class OpenID(models.Model):
+	user = models.ForeignKey(User)
+	openid = models.CharField(max_length=200, blank=True, unique=True)
+
+	class Meta:
+		verbose_name = _('OpenID')
+		verbose_name_plural = _('OpenIDs')
+
+	def __unicode__(self):
+		return u"%s|%s" % (self.user.username, self.openid)
+
+	def save(self, *args, **kwargs):
+		if self.openid in ['', u'', None]:
+			from hashlib import sha1
+			import random, base64
+			sha = sha1()
+			sha.update(unicode(self.user.username).encode('utf-8'))
+			sha.update(str(random.random()))
+			value = str(base64.b64encode(sha.digest()))
+			value = value.replace('/', '').replace('+', '').replace('=', '')
+			self.openid = value
+		super(OpenID, self).save(*args, **kwargs)
+
+class TrustedRoot(models.Model):
+	openid = models.ForeignKey(OpenID)
+	trust_root = models.CharField(max_length=200)
+
+	def __unicode__(self):
+		return unicode(self.trust_root)

openid_provider/templates/openid_provider/decide.html

+{% extends "base.html" %}
+
+{% block content %}
+{% ifequal trust_root_valid "Valid" %}
+  <!-- Trust root has been validated by OpenID 2 mechanism. -->
+  <p>The site <tt>{{ trust_root|escape }}</tt> has requested verification
+  of your OpenID.</p>
+{% endifequal %}
+{% ifequal trust_root_valid "Invalid" %}
+<div class="error">
+  <p>This request claims to be from {{ trust_root|escape }} but I have
+  determined that <em>it is a pack of lies</em>.  Beware, if you release
+  information to them, they are likely to do unconscionable things with it,
+  being the lying liars that they are.</p>
+  <p>Please tell the <em>real</em> {{ trust_root|escape }} that someone is
+  trying to abuse your trust in their good name.</p>
+</div>
+{% endifequal %}
+{% ifequal trust_root_valid "Unreachable" %}
+  <p>The site <tt>{{ trust_root|escape }}</tt> has requested verification
+  of your OpenID.  I have failed to reach it and thus cannot vouch for its
+  authenticity.  Perhaps it is on your local network.</p>
+{% endifequal %}
+{% ifequal trust_root_valid "DISCOVERY_FAILED" %}
+  <p>The site <tt>{{ trust_root|escape }}</tt> has requested verification
+  of your OpenID.  However, <tt>{{ trust_root|escape }}</tt> does not 
+  implement OpenID 2.0's relying party verification mechanism.  Please use
+  extra caution in deciding whether to release information to this party,
+  and ask <tt>{{ trust_root|escape }}</tt> to implement relying party
+  verification for your future transactions.</p>
+{% endifequal %}
+
+<!-- trust_root_valid is {{ trust_root_valid }} -->
+
+<form method="post" action="{% url openid-provider-decide %}">
+Verify your identity to the relying party?
+<br/>
+<input type="hidden" name="decide_page" value="True" />
+<input type="submit" value="Yes (Allow)" name="allow" />
+<input type="submit" value="No (Cancel)" name="cancel" />
+</form>
+{% endblock %}

openid_provider/templates/openid_provider/error.html

+{% extends "base.html" %}
+
+{% block content %}
+<h1>{{ title }}</h1>
+{{ msg }}
+{% endblock %}

openid_provider/templates/openid_provider/identity.html

+{% extends "base.html" %}
+
+{% block extrahead %}{{ block.super }}
+<link rel="openid.server" href="{{ host }}{% url openid-provider-root %}">
+<link rel="openid2.provider" href="{{ host }}{% url openid-provider-root %}">
+{% endblock %}
+
+{% block content %}
+This is the identity page for the OpenID that this server serves.
+{% endblock %}

openid_provider/templates/openid_provider/server.html

+{% extends "base.html" %}
+
+{% block extrahead %}{{ block.super }}
+<meta http-equiv="x-xrds-location" content="{{ host }}{% url openid-provider-xrds %}">
+{% endblock %}
+
+{% block content %}
+This is an OpenID server.
+{% endblock %}

openid_provider/templates/openid_provider/xrds.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)">
+	<XRD>
+		<Service priority="0">{% for uri in types %}
+			<Type>{{ uri|escape }}</Type>
+		{% endfor %}{% for endpoint in endpoints %}
+			<URI>{{ host }}{{ endpoint }}</URI>
+		{% endfor %}</Service>
+	</XRD>
+</xrds:XRDS>

openid_provider/urls.py

+# -*- coding: utf-8 -*-
+# vim: set ts=4 sw=4 : */
+
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('openid_provider.views',
+	url(r'^$', 'openid_server', name='openid-provider-root'),
+	url(r'^id/(.*)/$', 'openid_identity', name='openid-provider-identity'),
+	url(r'^decide/$', 'openid_decide', name='openid-provider-decide'),
+	url(r'^xrds/$', 'openid_xrds', name='openid-provider-xrds'),
+)

openid_provider/views.py

+# -*- coding: utf-8 -*-
+# vim: set ts=4 sw=4 fdm=indent : */
+# some code from http://www.djangosnippets.org/snippets/310/ by simon
+
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.core.urlresolvers import reverse
+from django.core.exceptions import ObjectDoesNotExist
+from django.template import RequestContext
+
+from models import *
+from openid.server.server import Server
+
+import datetime
+
+def get_base_uri(req):
+	name = req.META['HTTP_HOST']
+	try: name = name[:name.index(':')]
+	except: pass
+
+	try: port = int(req.META['SERVER_PORT'])
+	except: port = 80
+
+	proto = req.META['SERVER_PROTOCOL']
+	if 'HTTPS' in proto:
+		proto = 'https'
+	else:
+		proto = 'http'
+	
+	if port in [80, 443] or not port:
+		port = ''
+	else:
+		port = ':%s' % port
+	
+	return '%s://%s%s' % (proto, name, port)
+
+def django_response(webresponse):
+	"Convert a webresponse from the OpenID library in to a Django HttpResponse"
+	response = HttpResponse(webresponse.body)
+	response.status_code = webresponse.code
+	for key, value in webresponse.headers.items():
+		response[key] = value
+	return response
+
+def openid_server(req):
+	"""
+	This view is the actual OpenID server - running at the URL pointed to by 
+	the <link rel="openid.server"> tag. 
+	"""
+	host = get_base_uri(req)
+	try:
+		from django_openid_auth.store import DjangoOpenIDStore
+		store = DjangoOpenIDStore()
+	except:
+		OPENID_FILESTORE = '/tmp/openid-filestore'
+		from openid.store.filestore import FileOpenIDStore
+		store = FileOpenIDStore(OPENID_FILESTORE)
+		
+	server = Server(store, op_endpoint="%s%s" % (host, reverse('openid-provider-root')))
+
+	# Clear AuthorizationInfo session var, if it is set
+	if req.session.get('AuthorizationInfo', None):
+		del req.session['AuthorizationInfo']
+
+	querydict = dict(req.REQUEST.items())
+	orequest = server.decodeRequest(querydict)
+	if not orequest:
+		orequest = req.session.get('OPENID_REQUEST', None)
+		if not orequest:
+			# not request, render info page:
+			return render_to_response('openid_provider/server.html',
+				{'host': host,},
+				context_instance=RequestContext(req))
+		else:
+			# remove session stored data:
+			del req.session['OPENID_REQUEST']
+
+	if orequest.mode in ("checkid_immediate", "checkid_setup"):
+
+		if not req.user.is_authenticated():
+			return landing_page(req, orequest)
+
+		openid = openid_is_authorized(req, orequest.identity, orequest.trust_root)
+
+		if openid is not None:
+			oresponse = orequest.answer(True, identity="%s%s" % (host, reverse('openid-provider-identity', args=[openid.openid])))
+		elif orequest.immediate:
+			raise Exception('checkid_immediate mode not supported')
+		else:
+			req.session['OPENID_REQUEST'] = orequest
+			return HttpResponseRedirect(reverse('openid-provider-decide'))
+	else:
+		oresponse = server.handleRequest(orequest)
+	webresponse = server.encodeResponse(oresponse)
+	return django_response(webresponse)
+
+def openid_xrds(req):
+	from openid.yadis.constants import YADIS_CONTENT_TYPE
+	from openid.consumer.discover import OPENID_IDP_2_0_TYPE
+
+	response = render_to_response('openid_provider/xrds.xml',
+			{
+				'host': get_base_uri(req),
+				'types': [OPENID_IDP_2_0_TYPE],
+				'endpoints': [reverse('openid-provider-root')]
+			},
+			context_instance=RequestContext(req))
+	response['Content-Type'] = YADIS_CONTENT_TYPE
+	return response
+
+def openid_identity(req, *args):
+	return render_to_response('openid_provider/identity.html',
+		{'host': get_base_uri(req),},
+		context_instance=RequestContext(req))
+
+def openid_decide(req):
+	"""
+	The page that asks the user if they really want to sign in to the site, and
+	lets them add the consumer to their trusted whitelist.
+	# If user is logged in, ask if they want to trust this trust_root
+	# If they are NOT logged in, show the landing page
+	"""
+	from openid.server.trustroot import verifyReturnTo
+	from openid.yadis.discover import DiscoveryFailure
+	from openid.fetchers import HTTPFetchingError
+
+	orequest = req.session.get('OPENID_REQUEST')
+
+	if not req.user.is_authenticated():
+		return landing_page(req, orequest)
+	try:
+		openid = req.user.openid_set.all()[0]
+	except IndexError, ObjectDoesNotExist:
+		return error_page(req, "You are signed in but you don't have OpenID here!")
+
+	if req.method == 'POST' and req.POST.get('decide_page', False):
+		allowed = 'allow' in req.POST
+		openid.trustedroot_set.create(trust_root=orequest.trust_root)
+		return HttpResponseRedirect(reverse('openid-provider-root'))
+
+	# verify return_to of trust_root
+	try:
+		trust_root_valid = verifyReturnTo(orequest.trust_root, orequest.return_to ) and "Valid" or "Invalid"
+	except HTTPFetchingError:
+		trust_root_valid = "Unreachable"
+	except DiscoveryFailure:
+		trust_root_valid = "DISCOVERY_FAILED"
+
+	return render_to_response('openid_provider/decide.html', {
+			'title': 'Trust this site?',
+			'trust_root': orequest.trust_root,
+			'trust_root_valid': trust_root_valid,
+			'identity': orequest.identity,
+		},
+		context_instance=RequestContext(req))
+
+def error_page(req, msg):
+	return render_to_response('error.html', {
+		'title': 'Error',
+		'msg': msg,
+		},
+		context_instance=RequestContext(req))
+
+def landing_page(req, orequest):
+	"""
+	The page shown when the user attempts to sign in somewhere using OpenID 
+	but is not authenticated with the site. For idproxy.net, a message telling
+	them to log in manually is displayed.
+	"""
+	from django.contrib.auth import REDIRECT_FIELD_NAME
+	from django.conf import settings
+
+	req.session['OPENID_REQUEST'] = orequest
+	login_url = settings.LOGIN_URL
+	path = req.get_full_path()
+
+	return HttpResponseRedirect('%s?%s=%s' % (
+		login_url, REDIRECT_FIELD_NAME, path
+		))
+
+import urlparse
+def openid_is_authorized(req, identity_url, trust_root):
+	"""
+	Check that they own the given identity URL, and that the trust_root is 
+	in their whitelist of trusted sites.
+	"""
+	if not req.user.is_authenticated():
+		return None
+
+	try:
+		openid = req.user.openid_set.all()[0]
+	except IndexError, ObjectDoesNotExist:
+		return None
+
+	if openid.trustedroot_set.filter(trust_root=trust_root).count() < 1:
+		return None
+
+	return openid
+
+"""
+def openid_cancel(request):
+	"Called when the user cancels a pending OpenID auth request"
+	if request.session.get('AuthorizationInfo', None):
+		del request.session['AuthorizationInfo']
+	return HttpResponseRedirect(request.GET.get('n', '/'))
+"""
+