Commits

David Wolever committed 17a953f

Initial commit.

Comments (0)

Files changed (14)

+syntax: glob
+*.pyc
+*.class
+*.o
+.DS_Store
+*.sw[op]

__init__.py

Empty file added.

libs/__init__.py

Empty file added.

libs/django_openidconsumer/__init__.py

Empty file added.

libs/django_openidconsumer/license.txt

+Copyright (c) 2009, Simon Willison
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

libs/django_openidconsumer/middleware.py

+class OpenIDMiddleware(object):
+    """
+    Populate request.openid and request.openids with their openid. This comes 
+    eithen from their cookie or from their session, depending on the presence 
+    of OPENID_USE_SESSIONS.
+    """
+    def process_request(self, request):
+        request.openids = request.session.get('openids', [])
+        if request.openids:
+            request.openid = request.openids[-1] # Last authenticated OpenID
+        else:
+            request.openid = None

libs/django_openidconsumer/models.py

+from django.db import models
+
+class Nonce(models.Model):
+    nonce = models.CharField(maxlength=8)
+    expires = models.IntegerField()
+    def __str__(self):
+        return "Nonce: %s" % self.nonce
+
+class Association(models.Model):
+    server_url = models.TextField(maxlength=2047)
+    handle = models.CharField(maxlength=255)
+    secret = models.TextField(maxlength=255) # Stored base64 encoded
+    issued = models.IntegerField()
+    lifetime = models.IntegerField()
+    assoc_type = models.TextField(maxlength=64)
+    def __str__(self):
+        return "Association: %s, %s" % (self.server_url, self.handle)

libs/django_openidconsumer/templates/openid_failure.html

+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+  "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <title>OpenID failed</title>
+</head>
+<body>
+<h1>OpenID failed</h1>
+
+<p>{{ message|escape }}</p>
+
+</body>
+</html>

libs/django_openidconsumer/templates/openid_signin.html

+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+  "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<title>Sign in with your OpenID</title>
+<style type="text/css">
+input.openid {
+  background: url({{ logo }}) no-repeat; 
+  background-position: 0 50%;
+  padding-left: 16px;
+}
+</style>
+</head>
+<body>
+<h1>Sign in with your OpenID</h1>
+
+<form action="{{ action }}" method="post">
+<p><input class="openid" type="text" name="openid_url"> <input type="submit" value="Sign in"></p>
+</form>
+
+</body>
+</html>

libs/django_openidconsumer/util.py

+from openid.store.interface import OpenIDStore
+from openid.association import Association as OIDAssociation
+from yadis import xri
+
+import time, base64, md5
+
+from django.conf import settings
+from models import Association, Nonce
+
+class OpenID:
+    def __init__(self, openid, issued, attrs=None, sreg=None):
+        self.openid = openid
+        self.issued = issued
+        self.attrs = attrs or {}
+        self.sreg = sreg or {}
+        self.is_iname = (xri.identifierScheme(openid) == 'XRI')
+    
+    def __repr__(self):
+        return '<OpenID: %s>' % self.openid
+    
+    def __str__(self):
+        return self.openid
+
+class DjangoOpenIDStore(OpenIDStore):
+    def __init__(self):
+        self.max_nonce_age = 6 * 60 * 60 # Six hours
+    
+    def storeAssociation(self, server_url, association):
+        assoc = Association(
+            server_url = server_url,
+            handle = association.handle,
+            secret = base64.encodestring(association.secret),
+            issued = association.issued,
+            lifetime = association.issued,
+            assoc_type = association.assoc_type
+        )
+        assoc.save()
+    
+    def getAssociation(self, server_url, handle=None):
+        assocs = []
+        if handle is not None:
+            assocs = Association.objects.filter(
+                server_url = server_url, handle = handle
+            )
+        else:
+            assocs = Association.objects.filter(
+                server_url = server_url
+            )
+        if not assocs:
+            return None
+        associations = []
+        for assoc in assocs:
+            association = OIDAssociation(
+                assoc.handle, base64.decodestring(assoc.secret), assoc.issued,
+                assoc.lifetime, assoc.assoc_type
+            )
+            if association.getExpiresIn() == 0:
+                self.removeAssociation(server_url, assoc.handle)
+            else:
+                associations.append((association.issued, association))
+        if not associations:
+            return None
+        return associations[-1][1]
+    
+    def removeAssociation(self, server_url, handle):
+        assocs = list(Association.objects.filter(
+            server_url = server_url, handle = handle
+        ))
+        assocs_exist = len(assocs) > 0
+        for assoc in assocs:
+            assoc.delete()
+        return assocs_exist
+    
+    def storeNonce(self, nonce):
+        nonce, created = Nonce.objects.get_or_create(
+            nonce = nonce, defaults={'expires': int(time.time())}
+        )
+    
+    def useNonce(self, nonce):
+        try:
+            nonce = Nonce.objects.get(nonce = nonce)
+        except Nonce.DoesNotExist:
+            return 0
+        
+        # Now check nonce has not expired
+        nonce_age = int(time.time()) - nonce.expires
+        if nonce_age > self.max_nonce_age:
+            present = 0
+        else:
+            present = 1
+        nonce.delete()
+        return present
+    
+    def getAuthKey(self):
+        # Use first AUTH_KEY_LEN characters of md5 hash of SECRET_KEY
+        return md5.new(settings.SECRET_KEY).hexdigest()[:self.AUTH_KEY_LEN]
+    
+    def isDumb(self):
+        return False
+
+def from_openid_response(openid_response):
+    issued = int(time.time())
+    return OpenID(
+        openid_response.identity_url, issued, openid_response.signed_args, 
+        openid_response.extensionResponse('sreg')
+    )

libs/django_openidconsumer/views.py

+from django.http import HttpResponse, HttpResponseRedirect, get_host
+from django.shortcuts import render_to_response as render
+from django.template import RequestContext
+from django.conf import settings
+
+import md5, re, time, urllib
+from openid.consumer.consumer import Consumer, \
+    SUCCESS, CANCEL, FAILURE, SETUP_NEEDED
+from openid.consumer.discover import DiscoveryFailure
+from yadis import xri
+
+from util import OpenID, DjangoOpenIDStore, from_openid_response
+
+from django.utils.html import escape
+
+def get_url_host(request):
+    if request.is_secure():
+        protocol = 'https'
+    else:
+        protocol = 'http'
+    host = escape(get_host(request))
+    return '%s://%s' % (protocol, host)
+
+def get_full_url(request):
+    if request.is_secure():
+        protocol = 'https'
+    else:
+        protocol = 'http'
+    host = escape(request.META['HTTP_HOST'])
+    return get_url_host(request) + request.get_full_path()
+
+next_url_re = re.compile('^/[-\w/]+$')
+
+def is_valid_next_url(next):
+    # When we allow this:
+    #   /openid/?next=/welcome/
+    # For security reasons we want to restrict the next= bit to being a local 
+    # path, not a complete URL.
+    return bool(next_url_re.match(next))
+
+def begin(request, sreg=None, extension_args=None, redirect_to=None, 
+        on_failure=None):
+    
+    on_failure = on_failure or default_on_failure
+    
+    if request.GET.get('logo'):
+        # Makes for a better demo
+        return logo(request)
+    
+    extension_args = extension_args or {}
+    if sreg:
+        extension_args['sreg.optional'] = sreg
+    trust_root = getattr(
+        settings, 'OPENID_TRUST_ROOT', get_url_host(request) + '/'
+    )
+    redirect_to = redirect_to or getattr(
+        settings, 'OPENID_REDIRECT_TO',
+        # If not explicitly set, assume current URL with complete/ appended
+        get_full_url(request).split('?')[0] + 'complete/'
+    )
+    # In case they were lazy...
+    if not redirect_to.startswith('http://'):
+        redirect_to =  get_url_host(request) + redirect_to
+    
+    if request.GET.get('next') and is_valid_next_url(request.GET['next']):
+        if '?' in redirect_to:
+            join = '&'
+        else:
+            join = '?'
+        redirect_to += join + urllib.urlencode({
+            'next': request.GET['next']
+        })
+    
+    user_url = request.POST.get('openid_url', None)
+    if not user_url:
+        request_path = request.path
+        if request.GET.get('next'):
+            request_path += '?' + urllib.urlencode({
+                'next': request.GET['next']
+            })
+        
+        return render('openid_signin.html', {
+            'action': request_path,
+            'logo': request.path + '?logo=1',
+        })
+    
+    if xri.identifierScheme(user_url) == 'XRI' and getattr(
+        settings, 'OPENID_DISALLOW_INAMES', False
+        ):
+        return on_failure(request, 'i-names are not supported')
+    
+    consumer = Consumer(request.session, DjangoOpenIDStore())
+    try:
+        auth_request = consumer.begin(user_url)
+    except DiscoveryFailure:
+        return on_failure(request, "The OpenID was invalid")
+    
+    # Add extension args (for things like simple registration)
+    for name, value in extension_args.items():
+        namespace, key = name.split('.', 1)
+        auth_request.addExtensionArg(namespace, key, value)
+    
+    redirect_url = auth_request.redirectURL(trust_root, redirect_to)
+    return HttpResponseRedirect(redirect_url)
+
+def complete(request, on_success=None, on_failure=None):
+    on_success = on_success or default_on_success
+    on_failure = on_failure or default_on_failure
+    
+    consumer = Consumer(request.session, DjangoOpenIDStore())
+    openid_response = consumer.complete(dict(request.GET.items()))
+    
+    if openid_response.status == SUCCESS:
+        return on_success(request, openid_response.identity_url, openid_response)
+    elif openid_response.status == CANCEL:
+        return on_failure(request, 'The request was cancelled')
+    elif openid_response.status == FAILURE:
+        return on_failure(request, openid_response.message)
+    elif openid_response.status == SETUP_NEEDED:
+        return on_failure(request, 'Setup needed')
+    else:
+        assert False, "Bad openid status: %s" % openid_response.status
+
+def default_on_success(request, identity_url, openid_response):
+    if 'openids' not in request.session.keys():
+        request.session['openids'] = []
+    
+    # Eliminate any duplicates
+    request.session['openids'] = [
+        o for o in request.session['openids'] if o.openid != identity_url
+    ]
+    request.session['openids'].append(from_openid_response(openid_response))
+    
+    next = request.GET.get('next', '').strip()
+    if not next or not is_valid_next_url(next):
+        next = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+    
+    return HttpResponseRedirect(next)
+
+def default_on_failure(request, message):
+    return render('openid_failure.html', {
+        'message': message
+    })
+
+def signout(request):
+    request.session['openids'] = []
+    next = request.GET.get('next', '/')
+    if not is_valid_next_url(next):
+        next = '/'
+    return HttpResponseRedirect(next)
+
+def logo(request):
+    return HttpResponse(
+        OPENID_LOGO_BASE_64.decode('base64'), mimetype='image/gif'
+    )
+
+# Logo from http://openid.net/login-bg.gif
+# Embedded here for convenience; you should serve this as a static file
+OPENID_LOGO_BASE_64 = """
+R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d
+3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA
+AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg
+EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD
+Fzk0lpcjIQA7
+"""
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+    import settings # Assumed to be in the same directory.
+except ImportError:
+    import sys
+    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+    sys.exit(1)
+
+if __name__ == "__main__":
+    execute_manager(settings)
+# Django settings for urlqueue project.
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+    # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = ''           # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+DATABASE_NAME = ''             # Or path to database file if using sqlite3.
+DATABASE_USER = ''             # Not used with sqlite3.
+DATABASE_PASSWORD = ''         # Not used with sqlite3.
+DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = ''
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'tou@knr8o%&bphk#6-n)m2n1&m!^g!6e+w6_hfsl8p$(^j#cz8'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.load_template_source',
+    'django.template.loaders.app_directories.load_template_source',
+#     'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+)
+
+ROOT_URLCONF = 'urlqueue.urls'
+
+TEMPLATE_DIRS = (
+    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+    # Always use forward slashes, even on Windows.
+    # Don't forget to use absolute paths, not relative paths.
+)
+
+INSTALLED_APPS = (
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+)
+from django.conf.urls.defaults import *
+
+# Uncomment the next two lines to enable the admin:
+# from django.contrib import admin
+# admin.autodiscover()
+
+urlpatterns = patterns('',
+    # Example:
+    # (r'^urlqueue/', include('urlqueue.foo.urls')),
+
+    # Uncomment the admin/doc line below and add 'django.contrib.admindocs' 
+    # to INSTALLED_APPS to enable admin documentation:
+    # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
+
+    # Uncomment the next line to enable the admin:
+    # (r'^admin/(.*)', admin.site.root),
+)