--- /dev/null
+*.pyc
+.*.sw[po]
+local_settings.py
--- /dev/null
+Original Copyright goes to Henrik Lied (henriklied)
+Code borrowed from https://github.com/henriklied/django-twitter-oauth
--- /dev/null
+Copyright (c) 2010, Matías Aguirre
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. 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.
+
+ 3. Neither the name of this project nor the names of its contributors may be
+ used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+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.
--- /dev/null
+django-openid-auth - OpenID integration for django.contrib.auth
+Copyright (C) 2007 Simon Willison
+Copyright (C) 2008-2010 Canonical Ltd.
+
+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.
--- /dev/null
+Django Social Auth
+==================
+
+1. Description
+--------------
+Basically this is a take
--- /dev/null
+from django.http import HttpResponseRedirect, HttpResponse
+from django.contrib.auth import logout as auth_logout
+from django.contrib.auth.decorators import login_required
+from django.template import Template, Context, RequestContext
+
+
+def home(request):
+ return HttpResponse(Template(
+ """
+ <html>
+ <head>
+ <title>Social access</title>
+ </head>
+ <body>
+ <h1>Login using any of the following methods:</h1>
+ <div style="padding-left: 30px;">
+ <div>
+ <h2>Login using <a href="http://oauth.net/">OAuth</a> from:</h2>
+ <ul>
+ <li><a href="/login/twitter/">Twitter</a></li>
+ <li><a href="/login/facebook/">Facebook</a></li>
+ </ul>
+ </div>
+ <div>
+ <h2>Login using <a href="http://openid.net/">OpenId</a> from:</h2>
+ <ul>
+ <li><a href="/login/google/">Google</a></li>
+ <li><a href="/login/yahoo/">Yahoo</a></li>
+ <li>
+ <form action="/login/openid/" method="post">{% csrf_token %}
+ <label for="openid_identifier">Other provider:</label>
+ <input id="openid_identifier" type="text" value="" name="openid_identifier" />
+ <input type="submit" />
+ </form>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </body>
+ </html>
+ """).render(Context(RequestContext(request))),
+ content_type='text/html;charset=UTF-8')
+
+@login_required
+def done(request):
+ user = request.user
+ return HttpResponse(Template(
+ """
+ <html>
+ <head>
+ <title>Logged in</title>
+ <style>th{text-align: left;}</style>
+ </head>
+ <body>
+ <h1>Logged in!</h1>
+ <table>
+ <tr><th>Id:</th> <td>{{ user.id }}</td></tr>
+ <tr><th>Username:</th> <td>{{ user.username }}</td></tr>
+ <tr><th>Email:</th> <td>{{ user.email|default:"Not provided" }}</td></tr>
+ <tr><th>First name:</th> <td>{{ user.first_name|default:"Not provided" }}</td></tr>
+ <tr><th>Last name:</th> <td>{{ user.last_name|default:"Not provided" }}</td></tr>
+ </table>
+ <p><a href="/logout/">Logout</a></p>
+ </body>
+ </html>
+ """).render(Context({'user':user})),
+ content_type='text/html;charset=UTF-8')
+
+def logout(request):
+ auth_logout(request)
+ return HttpResponseRedirect('/')
--- /dev/null
+TWITTER_CONSUMER_KEY = ''
+TWITTER_CONSUMER_SECRET = ''
+FACEBOOK_APP_ID = ''
+FACEBOOK_API_SECRET = ''
+SOCIAL_AUTH_CREATE_USERS = True
+SOCIAL_AUTH_FORCE_RANDOM_USERNAME = False
+SOCIAL_AUTH_DEFAULT_USERNAME = 'socialauth_user'
--- /dev/null
+#!/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)
--- /dev/null
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+ 'NAME': 'test.db', # Or path to database file if using sqlite3.
+ 'USER': '', # Not used with sqlite3.
+ 'PASSWORD': '', # Not used with sqlite3.
+ 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
+ '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.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# 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
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = 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 = 't2eo^kd%k+-##ml3@_x__$j0(ps4p0q6eg*c4ttp9d2n(t!iol'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+ROOT_URLCONF = 'example.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',
+ 'django.contrib.messages',
+ # Uncomment the next line to enable the admin:
+ # 'django.contrib.admin',
+ 'social_auth',
+)
+
+AUTHENTICATION_BACKENDS = (
+ 'social_auth.backends.TwitterOAuthBackend',
+ 'social_auth.backends.FacebookOAuthBackend',
+ 'social_auth.backends.OpenIDBackend',
+ 'django.contrib.auth.backends.ModelBackend',
+)
+
+try:
+ from local_settings import *
+except:
+ pass
--- /dev/null
+../social_auth/
\ No newline at end of file
--- /dev/null
+from django.conf.urls.defaults import *
+
+# Uncomment the next two lines to enable the admin:
+# from django.contrib import admin
+# admin.autodiscover()
+
+urlpatterns = patterns('',
+ (r'^', include('social_auth.urls', namespace='social')),
+
+ # Uncomment the next line to enable the admin:
+ # (r'^admin/', include(admin.site.urls)),
+)
--- /dev/null
+from django.contrib import admin
+
+from .models import UserSocialAuth, Nonce, Association
+
+
+class UserSocialAuthOption(admin.ModelAdmin):
+ """Social Auth user options"""
+ list_display = ('id', 'user', 'provider')
+ search_fields = ('user__name',)
+ list_filter = ('provider',)
+ raw_id_fields = ('user',)
+
+
+class NonceOption(admin.ModelAdmin):
+ """Nonce options"""
+ list_display = ('id', 'server_url', 'timestamp', 'salt')
+
+
+class AssociationOption(admin.ModelAdmin):
+ """Association options"""
+ list_display = ('id', 'server_url', 'assoc_type')
+ list_filter = ('assoc_type',)
+
+
+admin.site.register(UserSocialAuth, UserSocialAuthOption)
+admin.site.register(Nonce, NonceOption)
+admin.site.register(Association, AssociationOption)
--- /dev/null
+from openid.extensions import ax, sreg
+
+from .base import SocialAuthBackend
+from .openid_auth import OLD_AX_ATTRS, AX_SCHEMA_ATTRS
+
+
+class OAuthBackend(SocialAuthBackend):
+ """OAuth authentication backend base class"""
+ name = 'oauth'
+
+ def get_user_id(self, details, response):
+ "OAuth providers return an unique user id in response"""
+ return response['id']
+
+
+class TwitterOAuthBackend(OAuthBackend):
+ """Twitter OAuth authentication backend"""
+ name = 'twitter'
+
+ def authenticate(self, **kwargs):
+ if kwargs.pop('twitter', False):
+ return super(TwitterOAuthBackend, self).authenticate(**kwargs)
+
+ def get_user_details(self, response):
+ return {'email': '', # not supplied
+ 'username': response['screen_name'],
+ 'fullname': response['name'],
+ 'firstname': response['name'],
+ 'lastname': ''}
+
+
+class FacebookOAuthBackend(OAuthBackend):
+ """Facebook OAuth authentication backend"""
+ name = 'facebook'
+
+ def authenticate(self, **kwargs):
+ if kwargs.pop('facebook', False):
+ return super(FacebookOAuthBackend, self).authenticate(**kwargs)
+
+ def get_user_details(self, response):
+ return {'email': response.get('email', ''),
+ 'username': response['name'],
+ 'fullname': response['name'],
+ 'firstname': response.get('first_name', ''),
+ 'lastname': response.get('last_name', '')}
+
+
+class OpenIDBackend(SocialAuthBackend):
+ """Generic OpenID authentication backend"""
+ name = 'openid'
+
+ def authenticate(self, **kwargs):
+ """Authenticate the user based on an OpenID response."""
+ if kwargs.pop('openid', False):
+ return super(OpenIDBackend, self).authenticate(**kwargs)
+
+ def get_user_id(self, details, response):
+ return response.identity_url
+
+ def get_user_details(self, response):
+ values = {'email': None,
+ 'username': None,
+ 'fullname': None,
+ 'firstname': None,
+ 'lastname': None}
+
+ resp = sreg.SRegResponse.fromSuccessResponse(response)
+ if resp:
+ values.update({'email': resp.get('email'),
+ 'fullname': resp.get('fullname'),
+ 'username': resp.get('nickname')})
+
+ # Use Attribute Exchange attributes if provided
+ resp = ax.FetchResponse.fromSuccessResponse(response)
+ if resp:
+ values.update((alias.replace('old_', ''), resp.getSingle(src))
+ for src, alias in OLD_AX_ATTRS + AX_SCHEMA_ATTRS)
+
+ fullname = values.get('fullname', '')
+ firstname = values.get('firstname', '')
+ lastname = values.get('lastname', '')
+
+ if not fullname and firstname and lastname:
+ fullname = firstname + ' ' + lastname
+ elif fullname:
+ try: # Try to split name for django user storage
+ firstname, lastname = fullname.rsplit(' ', 1)
+ except ValueError:
+ lastname = fullname
+
+ values.update({'fullname': fullname,
+ 'firstname': firstname,
+ 'lastname': lastname,
+ 'username': values.get('username') or \
+ (firstname.title() + lastname.title())})
+ return values
--- /dev/null
+import os
+import md5
+
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.auth.backends import ModelBackend
+
+from .models import UserSocialAuth
+
+
+class BaseAuth(object):
+ """Base authentication class, new authenticators should subclass
+ and implement needed methods"""
+ def __init__(self, request, redirect):
+ self.request = request
+ self.redirect = redirect
+
+ def auth_url(self):
+ """Must return redirect URL to auth provider"""
+ raise NotImplementedError, 'Implement in subclass'
+
+ def auth_html(self):
+ """Must return login HTML content returned by provider"""
+ raise NotImplementedError, 'Implement in subclass'
+
+ def auth_complete(self):
+ """Completes loging process, must return user instance"""
+ raise NotImplementedError, 'Implement in subclass'
+
+ @property
+ def uses_redirect(self):
+ """Return True if this provider uses redirect url method,
+ otherwise return false."""
+ return True
+
+
+class SocialAuthBackend(ModelBackend):
+ """A django.contrib.auth backend that authenticates the user based on
+ a authentication provider response"""
+ name = '' # provider name, it's stored in database
+
+ def authenticate(self, **kwargs):
+ """Authenticate the user based on an OAuth response."""
+ # Require that the OAuth response be passed in as a keyword
+ # argument, to make sure we don't match the username/password
+ # calling conventions of authenticate.
+ response = kwargs.get('response')
+ if response is None:
+ return None
+
+ details = self.get_user_details(response)
+ uid = self.get_user_id(details, response)
+ try:
+ oauth_user = UserSocialAuth.objects.select_related('user')\
+ .get(provider=self.name,
+ uid=uid)
+ except UserSocialAuth.DoesNotExist:
+ if not getattr(settings, 'SOCIAL_AUTH_CREATE_USERS', False):
+ return None
+ user = self.create_user(response, details)
+ else:
+ user = oauth_user.user
+ self.update_user_details(user, details)
+ return user
+
+ def get_username(self, details):
+ def get_random_username():
+ return md5.md5(str(os.urandom(10))).hexdigest()[:30]
+
+ if getattr(settings, 'SOCIAL_AUTH_FORCE_RANDOM_USERNAME', False):
+ username = get_random_username()
+ else:
+ username = details.get('username') or \
+ getattr(settings, 'SOCIAL_AUTH_DEFAULT_USERNAME', '') or \
+ get_random_username()
+
+ name, idx = username, 2
+ while True:
+ try:
+ User.objects.get(username=name)
+ name = username + str(idx)
+ idx += 1
+ except User.DoesNotExist:
+ username = name
+ break
+ return username
+
+ def create_user(self, response, details):
+ username = self.get_username(details)
+ user = User.objects.create_user(username, details.get('email', ''))
+ self.update_user_details(user, details)
+ self.associate_auth(user, response, details)
+ return user
+
+ def associate_auth(self, user, response, details):
+ """Associate an OAuth with a user account."""
+ # Check to see if this OAuth has already been claimed.
+ uid = self.get_user_id(details, response)
+ try:
+ user_oauth = UserSocialAuth.objects.select_related('user')\
+ .get(provider=self.name,
+ uid=uid)
+ except UserSocialAuth.DoesNotExist:
+ user_oauth = UserSocialAuth.objects.create(user=user, uid=uid,
+ provider=self.name)
+ else:
+ if user_oauth.user != user:
+ raise ValueError, 'The identity has already been claimed'
+ return user_oauth
+
+ def update_user_details(self, user, details):
+ first_name = details.get('firstname') or user.first_name
+ last_name = details.get('lastname') or user.last_name
+ email = details.get('email') or user.email
+ if (user.first_name, user.last_name, user.email) != (first_name, last_name, email):
+ user.first_name = first_name
+ user.last_name = last_name
+ user.email = email
+ user.save()
+
+ def get_user_id(self, details, response):
+ """Must return a unique ID from values returned on details"""
+ raise NotImplementedError, 'Implement in subclass'
+
+ def get_user_details(self, response):
+ """Must return user details in a know internal struct:
+ {'email': <user email if any>,
+ 'username': <username if any>,
+ 'fullname': <user full name if any>,
+ 'firstname': <user first name if any>,
+ 'lastname': <user last name if any>}
+ """
+ raise NotImplementedError, 'Implement in subclass'
--- /dev/null
+# Twitter configuration
+TWITTER_SERVER = 'api.twitter.com'
+REQUEST_TOKEN_URL = 'https://%s/oauth/request_token' % TWITTER_SERVER
+ACCESS_TOKEN_URL = 'https://%s/oauth/access_token' % TWITTER_SERVER
+AUTHORIZATION_URL = 'http://%s/oauth/authorize' % TWITTER_SERVER
+TWITTER_CHECK_AUTH = 'https://twitter.com/account/verify_credentials.json'
+UNAUTHORIZED_TOKEN_NAME = 'twitter_unauthorized_token'
+
+# Facebook configuration
+FACEBOOK_SERVER = 'graph.facebook.com'
+AUTHORIZATION_URL = 'https://%s/oauth/authorize' % FACEBOOK_SERVER
+ACCESS_TOKEN_URL = 'https://%s/oauth/access_token' % FACEBOOK_SERVER
+FACEBOOK_CHECK_AUTH = 'https://%s/me' % FACEBOOK_SERVER
+
+# OpenID configuration
+OLD_AX_ATTRS = [
+ ('http://schema.openid.net/contact/email', 'old_email'),
+ ('http://schema.openid.net/namePerson', 'old_fullname'),
+ ('http://schema.openid.net/namePerson/friendly', 'old_nickname')
+]
+AX_SCHEMA_ATTRS = [
+ # Request both the full name and first/last components since some
+ # providers offer one but not the other.
+ ('http://axschema.org/contact/email', 'email'),
+ ('http://axschema.org/namePerson', 'fullname'),
+ ('http://axschema.org/namePerson/first', 'firstname'),
+ ('http://axschema.org/namePerson/last', 'lastname'),
+ ('http://axschema.org/namePerson/friendly', 'nickname'),
+]
+AX_ATTRS = AX_SCHEMA_ATTRS + OLD_AX_ATTRS
+SREG_ATTR = ['email', 'fullname', 'nickname']
+OPENID_ID_FIELD = 'openid_identifier'
+SESSION_NAME = 'openid'
+OPENID_GOOGLE_URL = 'https://www.google.com/accounts/o8/id'
+OPENID_YAHOO_URL = 'http://yahoo.com'
--- /dev/null
+from django.db import models
+from django.contrib.auth.models import User
+
+
+class UserSocialAuth(models.Model):
+ user = models.ForeignKey(User)
+ provider = models.CharField(max_length=32)
+ uid = models.CharField(max_length=2048)
+
+ class Meta:
+ unique_together = ('provider', 'uid')
+
+
+class Nonce(models.Model):
+ server_url = models.CharField(max_length=2047)
+ timestamp = models.IntegerField()
+ salt = models.CharField(max_length=40)
+
+
+class Association(models.Model):
+ server_url = models.TextField(max_length=2047)
+ handle = models.CharField(max_length=255)
+ secret = models.TextField(max_length=255) # Stored base64 encoded
+ issued = models.IntegerField()
+ lifetime = models.IntegerField()
+ assoc_type = models.TextField(max_length=64)
--- /dev/null
+import cgi
+import urllib
+import time
+import random
+import urlparse
+import hmac
+import binascii
+
+VERSION = '1.0' # Hi Blaine!
+HTTP_METHOD = 'GET'
+SIGNATURE_METHOD = 'PLAINTEXT'
+
+# Generic exception class
+class OAuthError(RuntimeError):
+ def __init__(self, message='OAuth error occured.'):
+ self.message = message
+
+# optional WWW-Authenticate header (401 error)
+def build_authenticate_header(realm=''):
+ return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
+
+# url escape
+def escape(s):
+ # escape '/' too
+ return urllib.quote(s, safe='~')
+
+# util function: current timestamp
+# seconds since epoch (UTC)
+def generate_timestamp():
+ return int(time.time())
+
+# util function: nonce
+# pseudorandom number
+def generate_nonce(length=8):
+ return ''.join([str(random.randint(0, 9)) for i in range(length)])
+
+# OAuthConsumer is a data type that represents the identity of the Consumer
+# via its shared secret with the Service Provider.
+class OAuthConsumer(object):
+ key = None
+ secret = None
+
+ def __init__(self, key, secret):
+ self.key = key
+ self.secret = secret
+
+# OAuthToken is a data type that represents an End User via either an access
+# or request token.
+class OAuthToken(object):
+ # access tokens and request tokens
+ key = None
+ secret = None
+
+ '''
+ key = the token
+ secret = the token secret
+ '''
+ def __init__(self, key, secret):
+ self.key = key
+ self.secret = secret
+
+ def to_string(self):
+ return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret})
+
+ # return a token from something like:
+ # oauth_token_secret=digg&oauth_token=digg
+ def from_string(s):
+ params = cgi.parse_qs(s, keep_blank_values=False)
+ key = params['oauth_token'][0]
+ secret = params['oauth_token_secret'][0]
+ return OAuthToken(key, secret)
+ from_string = staticmethod(from_string)
+
+ def __str__(self):
+ return self.to_string()
+
+# OAuthRequest represents the request and can be serialized
+class OAuthRequest(object):
+ '''
+ OAuth parameters:
+ - oauth_consumer_key
+ - oauth_token
+ - oauth_signature_method
+ - oauth_signature
+ - oauth_timestamp
+ - oauth_nonce
+ - oauth_version
+ ... any additional parameters, as defined by the Service Provider.
+ '''
+ parameters = None # oauth parameters
+ http_method = HTTP_METHOD
+ http_url = None
+ version = VERSION
+
+ def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None):
+ self.http_method = http_method
+ self.http_url = http_url
+ self.parameters = parameters or {}
+
+ def set_parameter(self, parameter, value):
+ self.parameters[parameter] = value
+
+ def get_parameter(self, parameter):
+ try:
+ return self.parameters[parameter]
+ except:
+ raise OAuthError('Parameter not found: %s' % parameter)
+
+ def _get_timestamp_nonce(self):
+ return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce')
+
+ # get any non-oauth parameters
+ def get_nonoauth_parameters(self):
+ parameters = {}
+ for k, v in self.parameters.iteritems():
+ # ignore oauth parameters
+ if k.find('oauth_') < 0:
+ parameters[k] = v
+ return parameters
+
+ # serialize as a header for an HTTPAuth request
+ def to_header(self, realm=''):
+ auth_header = 'OAuth realm="%s"' % realm
+ # add the oauth parameters
+ if self.parameters:
+ for k, v in self.parameters.iteritems():
+ if k[:6] == 'oauth_':
+ auth_header += ', %s="%s"' % (k, escape(str(v)))
+ return {'Authorization': auth_header}
+
+ # serialize as post data for a POST request
+ def to_postdata(self):
+ return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems()])
+
+ # serialize as a url for a GET request
+ def to_url(self):
+ return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata())
+
+ # return a string that consists of all the parameters that need to be signed
+ def get_normalized_parameters(self):
+ params = self.parameters
+ try:
+ # exclude the signature if it exists
+ del params['oauth_signature']
+ except:
+ pass
+ key_values = params.items()
+ # sort lexicographically, first after key, then after value
+ key_values.sort()
+ # combine key value pairs in string and escape
+ return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in key_values])
+
+ # just uppercases the http method
+ def get_normalized_http_method(self):
+ return self.http_method.upper()
+
+ # parses the url and rebuilds it to be scheme://host/path
+ def get_normalized_http_url(self):
+ parts = urlparse.urlparse(self.http_url)
+ url_string = '%s://%s%s' % (parts[0], parts[1], parts[2]) # scheme, netloc, path
+ return url_string
+
+ # set the signature parameter to the result of build_signature
+ def sign_request(self, signature_method, consumer, token):
+ # set the signature method
+ self.set_parameter('oauth_signature_method', signature_method.get_name())
+ # set the signature
+ self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token))
+
+ def build_signature(self, signature_method, consumer, token):
+ # call the build signature method within the signature method
+ return signature_method.build_signature(self, consumer, token)
+
+ def from_request(http_method, http_url, headers=None, parameters=None, query_string=None):
+ # combine multiple parameter sources
+ if parameters is None:
+ parameters = {}
+
+ # headers
+ if headers and 'Authorization' in headers:
+ auth_header = headers['Authorization']
+ # check that the authorization header is OAuth
+ if auth_header.index('OAuth') > -1:
+ try:
+ # get the parameters from the header
+ header_params = OAuthRequest._split_header(auth_header)
+ parameters.update(header_params)
+ except:
+ raise OAuthError('Unable to parse OAuth parameters from Authorization header.')
+
+ # GET or POST query string
+ if query_string:
+ query_params = OAuthRequest._split_url_string(query_string)
+ parameters.update(query_params)
+
+ # URL parameters
+ param_str = urlparse.urlparse(http_url)[4] # query
+ url_params = OAuthRequest._split_url_string(param_str)
+ parameters.update(url_params)
+
+ if parameters:
+ return OAuthRequest(http_method, http_url, parameters)
+
+ return None
+ from_request = staticmethod(from_request)
+
+ def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
+ if not parameters:
+ parameters = {}
+
+ defaults = {
+ 'oauth_consumer_key': oauth_consumer.key,
+ 'oauth_timestamp': generate_timestamp(),
+ 'oauth_nonce': generate_nonce(),
+ 'oauth_version': OAuthRequest.version,
+ }
+
+ defaults.update(parameters)
+ parameters = defaults
+
+ if token:
+ parameters['oauth_token'] = token.key
+
+ return OAuthRequest(http_method, http_url, parameters)
+ from_consumer_and_token = staticmethod(from_consumer_and_token)
+
+ def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
+ if not parameters:
+ parameters = {}
+
+ parameters['oauth_token'] = token.key
+
+ if callback:
+ parameters['oauth_callback'] = callback
+
+ return OAuthRequest(http_method, http_url, parameters)
+ from_token_and_callback = staticmethod(from_token_and_callback)
+
+ # util function: turn Authorization: header into parameters, has to do some unescaping
+ def _split_header(header):
+ params = {}
+ parts = header.split(',')
+ for param in parts:
+ # ignore realm parameter
+ if param.find('OAuth realm') > -1:
+ continue
+ # remove whitespace
+ param = param.strip()
+ # split key-value
+ param_parts = param.split('=', 1)
+ # remove quotes and unescape the value
+ params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
+ return params
+ _split_header = staticmethod(_split_header)
+
+ # util function: turn url string into parameters, has to do some unescaping
+ def _split_url_string(param_str):
+ parameters = cgi.parse_qs(param_str, keep_blank_values=False)
+ for k, v in parameters.iteritems():
+ parameters[k] = urllib.unquote(v[0])
+ return parameters
+ _split_url_string = staticmethod(_split_url_string)
+
+# OAuthServer is a worker to check a requests validity against a data store
+class OAuthServer(object):
+ timestamp_threshold = 300 # in seconds, five minutes
+ version = VERSION
+ signature_methods = None
+ data_store = None
+
+ def __init__(self, data_store=None, signature_methods=None):
+ self.data_store = data_store
+ self.signature_methods = signature_methods or {}
+
+ def set_data_store(self, oauth_data_store):
+ self.data_store = data_store
+
+ def get_data_store(self):
+ return self.data_store
+
+ def add_signature_method(self, signature_method):
+ self.signature_methods[signature_method.get_name()] = signature_method
+ return self.signature_methods
+
+ # process a request_token request
+ # returns the request token on success
+ def fetch_request_token(self, oauth_request):
+ try:
+ # get the request token for authorization
+ token = self._get_token(oauth_request, 'request')
+ except OAuthError:
+ # no token required for the initial token request
+ version = self._get_version(oauth_request)
+ consumer = self._get_consumer(oauth_request)
+ self._check_signature(oauth_request, consumer, None)
+ # fetch a new token
+ token = self.data_store.fetch_request_token(consumer)
+ return token
+
+ # process an access_token request
+ # returns the access token on success
+ def fetch_access_token(self, oauth_request):
+ version = self._get_version(oauth_request)
+ consumer = self._get_consumer(oauth_request)
+ # get the request token
+ token = self._get_token(oauth_request, 'request')
+ self._check_signature(oauth_request, consumer, token)
+ new_token = self.data_store.fetch_access_token(consumer, token)
+ return new_token
+
+ # verify an api call, checks all the parameters
+ def verify_request(self, oauth_request):
+ # -> consumer and token
+ version = self._get_version(oauth_request)
+ consumer = self._get_consumer(oauth_request)
+ # get the access token
+ token = self._get_token(oauth_request, 'access')
+ self._check_signature(oauth_request, consumer, token)
+ parameters = oauth_request.get_nonoauth_parameters()
+ return consumer, token, parameters
+
+ # authorize a request token
+ def authorize_token(self, token, user):
+ return self.data_store.authorize_request_token(token, user)
+
+ # get the callback url
+ def get_callback(self, oauth_request):
+ return oauth_request.get_parameter('oauth_callback')
+
+ # optional support for the authenticate header
+ def build_authenticate_header(self, realm=''):
+ return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
+
+ # verify the correct version request for this server
+ def _get_version(self, oauth_request):
+ try:
+ version = oauth_request.get_parameter('oauth_version')
+ except:
+ version = VERSION
+ if version and version != self.version:
+ raise OAuthError('OAuth version %s not supported.' % str(version))
+ return version
+
+ # figure out the signature with some defaults
+ def _get_signature_method(self, oauth_request):
+ try:
+ signature_method = oauth_request.get_parameter('oauth_signature_method')
+ except:
+ signature_method = SIGNATURE_METHOD
+ try:
+ # get the signature method object
+ signature_method = self.signature_methods[signature_method]
+ except:
+ signature_method_names = ', '.join(self.signature_methods.keys())
+ raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names))
+
+ return signature_method
+
+ def _get_consumer(self, oauth_request):
+ consumer_key = oauth_request.get_parameter('oauth_consumer_key')
+ if not consumer_key:
+ raise OAuthError('Invalid consumer key.')
+ consumer = self.data_store.lookup_consumer(consumer_key)
+ if not consumer:
+ raise OAuthError('Invalid consumer.')
+ return consumer
+
+ # try to find the token for the provided request token key
+ def _get_token(self, oauth_request, token_type='access'):
+ token_field = oauth_request.get_parameter('oauth_token')
+ token = self.data_store.lookup_token(token_type, token_field)
+ if not token:
+ raise OAuthError('Invalid %s token: %s' % (token_type, token_field))
+ return token
+
+ def _check_signature(self, oauth_request, consumer, token):
+ timestamp, nonce = oauth_request._get_timestamp_nonce()
+ self._check_timestamp(timestamp)
+ self._check_nonce(consumer, token, nonce)
+ signature_method = self._get_signature_method(oauth_request)
+ try:
+ signature = oauth_request.get_parameter('oauth_signature')
+ except:
+ raise OAuthError('Missing signature.')
+ # validate the signature
+ valid_sig = signature_method.check_signature(oauth_request, consumer, token, signature)
+ if not valid_sig:
+ key, base = signature_method.build_signature_base_string(oauth_request, consumer, token)
+ raise OAuthError('Invalid signature. Expected signature base string: %s' % base)
+ built = signature_method.build_signature(oauth_request, consumer, token)
+
+ def _check_timestamp(self, timestamp):
+ # verify that timestamp is recentish
+ timestamp = int(timestamp)
+ now = int(time.time())
+ lapsed = now - timestamp
+ if lapsed > self.timestamp_threshold:
+ raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold))
+
+ def _check_nonce(self, consumer, token, nonce):
+ # verify that the nonce is uniqueish
+ nonce = self.data_store.lookup_nonce(consumer, token, nonce)
+ if nonce:
+ raise OAuthError('Nonce already used: %s' % str(nonce))
+
+# OAuthClient is a worker to attempt to execute a request
+class OAuthClient(object):
+ consumer = None
+ token = None
+
+ def __init__(self, oauth_consumer, oauth_token):
+ self.consumer = oauth_consumer
+ self.token = oauth_token
+
+ def get_consumer(self):
+ return self.consumer
+
+ def get_token(self):
+ return self.token
+
+ def fetch_request_token(self, oauth_request):
+ # -> OAuthToken
+ raise NotImplementedError
+
+ def fetch_access_token(self, oauth_request):
+ # -> OAuthToken
+ raise NotImplementedError
+
+ def access_resource(self, oauth_request):
+ # -> some protected resource
+ raise NotImplementedError
+
+# OAuthDataStore is a database abstraction used to lookup consumers and tokens
+class OAuthDataStore(object):
+
+ def lookup_consumer(self, key):
+ # -> OAuthConsumer
+ raise NotImplementedError
+
+ def lookup_token(self, oauth_consumer, token_type, token_token):
+ # -> OAuthToken
+ raise NotImplementedError
+
+ def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp):
+ # -> OAuthToken
+ raise NotImplementedError
+
+ def fetch_request_token(self, oauth_consumer):
+ # -> OAuthToken
+ raise NotImplementedError
+
+ def fetch_access_token(self, oauth_consumer, oauth_token):
+ # -> OAuthToken
+ raise NotImplementedError
+
+ def authorize_request_token(self, oauth_token, user):
+ # -> OAuthToken
+ raise NotImplementedError
+
+# OAuthSignatureMethod is a strategy class that implements a signature method
+class OAuthSignatureMethod(object):
+ def get_name(self):
+ # -> str
+ raise NotImplementedError
+
+ def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token):
+ # -> str key, str raw
+ raise NotImplementedError
+
+ def build_signature(self, oauth_request, oauth_consumer, oauth_token):
+ # -> str
+ raise NotImplementedError
+
+ def check_signature(self, oauth_request, consumer, token, signature):
+ built = self.build_signature(oauth_request, consumer, token)
+ return built == signature
+
+class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod):
+
+ def get_name(self):
+ return 'HMAC-SHA1'
+
+ def build_signature_base_string(self, oauth_request, consumer, token):
+ sig = (
+ escape(oauth_request.get_normalized_http_method()),
+ escape(oauth_request.get_normalized_http_url()),
+ escape(oauth_request.get_normalized_parameters()),
+ )
+
+ key = '%s&' % escape(consumer.secret)
+ if token:
+ key += escape(token.secret)
+ raw = '&'.join(sig)
+ return key, raw
+
+ def build_signature(self, oauth_request, consumer, token):
+ # build the base signature string
+ key, raw = self.build_signature_base_string(oauth_request, consumer, token)
+
+ # hmac object
+ try:
+ import hashlib # 2.5
+ hashed = hmac.new(key, raw, hashlib.sha1)
+ except:
+ import sha # deprecated
+ hashed = hmac.new(key, raw, sha)
+
+ # calculate the digest base 64
+ return binascii.b2a_base64(hashed.digest())[:-1]
+
+class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod):
+
+ def get_name(self):
+ return 'PLAINTEXT'
+
+ def build_signature_base_string(self, oauth_request, consumer, token):
+ # concatenate the consumer key and secret
+ sig = escape(consumer.secret) + '&'
+ if token:
+ sig = sig + escape(token.secret)
+ return sig
+
+ def build_signature(self, oauth_request, consumer, token):
+ return self.build_signature_base_string(oauth_request, consumer, token)
--- /dev/null
+import time
+import base64
+
+from openid.association import Association as OIDAssociation
+from openid.store.interface import OpenIDStore
+from openid.store.nonce import SKEW
+
+from .models import Association, Nonce
+
+
+class DjangoOpenIDStore(OpenIDStore):
+ def __init__(self):
+ self.max_nonce_age = 6 * 60 * 60 # Six hours
+
+ def storeAssociation(self, server_url, association):
+ args = {'server_url': server_url, 'handle': association.handle}
+ try:
+ assoc = Association.objects.get(**args)
+ except Association.DoesNotExist:
+ assoc = Association(**args)
+ assoc.secret = base64.encodestring(association.secret)
+ assoc.issued = association.issued
+ assoc.lifetime = association.lifetime
+ assoc.assoc_type = association.assoc_type
+ assoc.save()
+
+ def getAssociation(self, server_url, handle=None):
+ args = {'server_url': server_url}
+ if handle is not None:
+ args['handle'] = handle
+
+ associations, expired = [], []
+ for assoc in Association.objects.filter(**args):
+ association = OIDAssociation(assoc.handle,
+ base64.decodestring(assoc.secret),
+ assoc.issued,
+ assoc.lifetime,
+ assoc.assoc_type)
+ if association.getExpiresIn() == 0:
+ expired.append(assoc.id)
+ else:
+ associations.append(association)
+
+ if expired: # clear expired associations
+ Association.objects.filter(pk__in=expired).delete()
+
+ if associations:
+ associations.sort(key=lambda x: x.issued, reverse=True)
+ return associations[0]
+
+ def useNonce(self, server_url, timestamp, salt):
+ if abs(timestamp - time.time()) > SKEW:
+ return False
+ nonce, created = Nonce.objects.get_or_create(server_url=server_url,
+ timestamp=timestamp,
+ salt=salt)
+ return created
--- /dev/null
+from django.conf.urls.defaults import patterns, url
+
+from .views import home, done, logout, auth, complete
+
+
+urlpatterns = patterns('',
+ url(r'^login/(?P<backend>[^/]+)/$', auth, name='begin'),
+ url(r'^complete/(?P<backend>[^/]+)/$', complete, name='complete'),
+
+ # demo urls
+ url(r'^$', home, name='home'),
+ url(r'^done/$', done, name='done'),
+ url(r'^logout/$', logout, name='logout'),
+)
--- /dev/null
+from django.conf import settings
+from django.http import HttpResponseRedirect, HttpResponse, \
+ HttpResponseServerError
+from django.core.urlresolvers import reverse
+from django.contrib.auth import login, logout as auth_logout, REDIRECT_FIELD_NAME
+from django.contrib.auth.decorators import login_required
+
+from .twitter import TwitterOAuth
+from .facebook import FacebookOAuth
+from .openid_auth import OpenIDAuth, GoogleAuth, YahooAuth
+
+
+BACKENDS = {
+ 'twitter': TwitterOAuth,
+ 'facebook': FacebookOAuth,
+ 'google': GoogleAuth,
+ 'yahoo': YahooAuth,
+ 'openid': OpenIDAuth,
+}
+
+
+def auth(request, backend):
+ if backend not in BACKENDS:
+ return HttpResponseServerError('Incorrect authentication service')
+ request.session[REDIRECT_FIELD_NAME] = request.GET.get(REDIRECT_FIELD_NAME,
+ settings.LOGIN_REDIRECT_URL)
+ redirect = reverse('social:complete', args=(backend,))
+ backend = BACKENDS[backend](request, redirect)
+ if backend.uses_redirect:
+ return HttpResponseRedirect(backend.auth_url())
+ else:
+ return HttpResponse(backend.auth_html(),
+ content_type='text/html;charset=UTF-8')
+
+
+def complete(request, backend):
+ if backend not in BACKENDS:
+ return HttpResponseServerError('Incorrect authentication service')
+ backend = BACKENDS[backend](request, request.path)
+ user = backend.auth_complete()
+ if user and user.is_active:
+ login(request, user)
+ return HttpResponseRedirect(request.session.pop(REDIRECT_FIELD_NAME,
+ settings.LOGIN_REDIRECT_URL))
+
+
+def home(request):
+ return HttpResponse(
+ """
+ <div>
+ <h2>OAuth</h2>
+ <a href="/login/twitter/">twitter</a>
+ <a href="/login/facebook/">facebook</a>
+ </div>
+
+ <div>
+ <h2>OPenID</h2>
+ <a href="/login/google/">google</a>
+ <a href="/login/yahoo/">yahoo</a>
+ <form action="/login/openid/" method="post">
+ provider: <input type="text" value="" name="openid_identifier" />
+ <input type="submit" />
+ </form>
+ </div>
+ <br />
+ <a href="/logout">logout</a>
+ """, content_type='text/html;charset=UTF-8')
+
+@login_required
+def done(request):
+ user = request.user
+ return HttpResponse('%s / %s / %s' % (user.id, user.username, user.first_name))
+
+def logout(request):
+ auth_logout(request)
+ return HttpResponse('logged out')