*.pyc
.*.sw[po]
+test.db
local_settings.py
1. Description
--------------
-Basically this is a take
+Basically this is a code merge between:
+ * Django-Socialauth (https://github.com/uswaretech/Django-Socialauth)
+ * django-twitter-oauth (https://github.com/henriklied/django-twitter-oauth)
+the app supplies two views that controls OAuth and OpenID loggin/registration.
+
+2. OAuth
+-----------
+Twitter and Facebook Oauth mechanims for authentication/registration,
+corresponding apps and settings must be filled:
+
+ - Twitter
+ * Register a new app at http://twitter.com/apps/new
+ * Be sure to mark the "Yes, use Twitter for login" checkbox
+ * Fill "Cosumer Key" and "Consumer Secret" values in settings
+ TWITTER_CONSUMER_KEY
+ TWITTER_CONSUMER_SECRET
+ Twitter demands a redirect url configuration and will force the user
+ to that address when redirecting, I suggest to setup something like
+ http://myvirtualapp.com and then configuring myvirtualapp.com in
+ /etc/hosts file, the port will be missing but works well for testing.
+
+ - Facebook
+ * Register a new app at http://developers.facebook.com/setup/
+ * Fill "App Id" and "App Secret" values in settings:
+ FACEBOOK_APP_ID
+ FACEBOOK_API_SECRET
+
+3. OpenId
+---------
+Yahoo and Google OpenId providers are supported, also custom providers
+like myopenid.com are supported if provider url is specified by a POST
+parameter (openid_identifier).
+
+4. Bugs
+-------
+Several, maybe, please report :-)
+
+5. Copyrights
+-------------
+Base work is copyrighted by:
+
+Oauth base code:
+ Original Copyright goes to Henrik Lied (henriklied)
+ Code borrowed from https://github.com/henriklied/django-twitter-oauth
+
+OpenId base code:
+ django-openid-auth - OpenID integration for django.contrib.auth
+ Copyright (C) 2007 Simon Willison
+ Copyright (C) 2008-2010 Canonical Ltd.
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
- # Uncomment the next line to enable the admin:
- # 'django.contrib.admin',
+ 'django.contrib.admin',
'social_auth',
)
AUTHENTICATION_BACKENDS = (
- 'social_auth.backends.TwitterOAuthBackend',
- 'social_auth.backends.FacebookOAuthBackend',
+ 'social_auth.backends.TwitterBackend',
+ 'social_auth.backends.FacebookBackend',
'social_auth.backends.OpenIDBackend',
'django.contrib.auth.backends.ModelBackend',
)
from django.conf.urls.defaults import *
+from django.contrib import admin
-# Uncomment the next two lines to enable the admin:
-# from django.contrib import admin
-# admin.autodiscover()
+from app.views import home, done, logout
-urlpatterns = patterns('',
- (r'^', include('social_auth.urls', namespace='social')),
- # Uncomment the next line to enable the admin:
- # (r'^admin/', include(admin.site.urls)),
+admin.autodiscover()
+
+urlpatterns = patterns('',
+ url(r'^$', home, name='home'),
+ url(r'^done/$', done, name='done'),
+ url(r'^logout/$', logout, name='logout'),
+ url(r'', include('social_auth.urls', namespace='social')),
+ url(r'^admin/', include(admin.site.urls)),
)
--- /dev/null
+import cgi
+import urllib
+import httplib
+from openid.consumer.consumer import Consumer, SUCCESS, CANCEL, FAILURE
+from openid.consumer.discover import DiscoveryFailure
+from openid.extensions import sreg, ax
+
+from django.conf import settings
+from django.utils import simplejson
+from django.contrib.auth import authenticate
+
+from .base import BaseAuth
+from .store import DjangoOpenIDStore
+from .oauth import OAuthConsumer, OAuthToken, OAuthRequest, \
+ OAuthSignatureMethod_HMAC_SHA1
+from .conf import AX_ATTRS, SREG_ATTR, OPENID_ID_FIELD, SESSION_NAME, \
+ OPENID_GOOGLE_URL, OPENID_YAHOO_URL, TWITTER_SERVER, \
+ TWITTER_REQUEST_TOKEN_URL, TWITTER_ACCESS_TOKEN_URL, \
+ TWITTER_AUTHORIZATION_URL, TWITTER_CHECK_AUTH, \
+ TWITTER_UNAUTHORIZED_TOKEN_NAME, FACEBOOK_CHECK_AUTH, \
+ FACEBOOK_AUTHORIZATION_URL, FACEBOOK_ACCESS_TOKEN_URL
+
+
+class OpenIdAuth(BaseAuth):
+ def auth_url(self):
+ openid_request = self.setup_request()
+ # Construct completion URL, including page we should redirect to
+ return_to = self.request.build_absolute_uri(self.redirect)
+ trust_root = getattr(settings, 'OPENID_TRUST_ROOT',
+ self.request.build_absolute_uri('/'))
+ return openid_request.redirectURL(trust_root, return_to)
+
+ def auth_html(self):
+ openid_request = self.setup_request()
+ return_to = self.request.build_absolute_uri(self.redirect)
+ trust_root = getattr(settings, 'OPENID_TRUST_ROOT',
+ self.request.build_absolute_uri('/'))
+ form_tag = {'id': 'openid_message'}
+ return openid_request.htmlMarkup(trust_root, return_to,
+ form_tag_attrs=form_tag)
+
+ def auth_complete(self):
+ response = self.consumer().complete(dict(self.request.REQUEST.items()),
+ self.request.build_absolute_uri())
+ if not response:
+ raise ValueError, 'This is an OpenID relying party endpoint'
+ elif response.status == SUCCESS:
+ return authenticate(response=response, openid=True)
+ elif response.status == FAILURE:
+ raise ValueError, 'OpenID authentication failed: %s' % response.message
+ elif response.status == CANCEL:
+ raise ValueError, 'Authentication cancelled'
+ else:
+ raise ValueError, 'Unknown OpenID response type: %r' % response.status
+
+ def setup_request(self):
+ openid_request = self.openid_request()
+ # Request some user details. If the provider advertises support
+ # for attribute exchange, use that.
+ if openid_request.endpoint.supportsType(ax.AXMessage.ns_uri):
+ fetch_request = ax.FetchRequest()
+ # Mark all attributes as required, since Google ignores optional ones
+ for attr, alias in AX_ATTRS:
+ fetch_request.add(ax.AttrInfo(attr, alias=alias, required=True))
+ else:
+ fetch_request = sreg.SRegRequest(optional=SREG_ATTR)
+ openid_request.addExtension(fetch_request)
+
+ return openid_request
+
+ def consumer(self):
+ """Create an OpenID Consumer object for the given Django request."""
+ return Consumer(self.request.session.setdefault(SESSION_NAME, {}),
+ DjangoOpenIDStore())
+
+ @property
+ def uses_redirect(self):
+ if not hasattr(self, '_uses_redirect'):
+ setattr(self, '_uses_redirect', self.openid_request().shouldSendRedirect())
+ return getattr(self, '_uses_redirect', True)
+
+ def openid_request(self):
+ if not hasattr(self, '_openid_request'):
+ openid_url = self.openid_url()
+ try:
+ openid_request = self.consumer().begin(openid_url)
+ except DiscoveryFailure, e:
+ raise ValueError, 'OpenID discovery error: %s' % e
+ else:
+ setattr(self, '_openid_request', openid_request)
+ return getattr(self, '_openid_request', None)
+
+ def openid_url(self):
+ if self.request.method != 'POST' or OPENID_ID_FIELD not in self.request.POST:
+ raise ValueError, 'Missing openid identifier'
+ return self.request.POST[OPENID_ID_FIELD]
+
+
+class GoogleAuth(OpenIdAuth):
+ """Google OpenID authentication"""
+ def openid_url(self):
+ return OPENID_GOOGLE_URL
+
+
+class YahooAuth(OpenIdAuth):
+ """Yahoo OpenID authentication"""
+ def openid_url(self):
+ return OPENID_YAHOO_URL
+
+
+class TwitterAuth(BaseAuth):
+ """Twitter OAuth authentication mechanism"""
+ def auth_url(self):
+ """Returns redirect url"""
+ token = self.unauthorized_token()
+ self.request.session[TWITTER_UNAUTHORIZED_TOKEN_NAME] = token.to_string()
+ return self.oauth_request(token, TWITTER_AUTHORIZATION_URL).to_url()
+
+ def auth_complete(self):
+ """Returns user, might be logged in"""
+ unauthed_token = self.request.session.get(TWITTER_UNAUTHORIZED_TOKEN_NAME)
+ if not unauthed_token:
+ raise ValueError, 'Missing unauthorized token'
+
+ token = OAuthToken.from_string(unauthed_token)
+ if token.key != self.request.GET.get('oauth_token', 'no-token'):
+ raise ValueError, 'Incorrect tokens'
+ access_token = self.access_token(token)
+ data = self.user_data(access_token)
+ if data is not None:
+ data['access_token'] = access_token.to_string()
+ return authenticate(response=data, twitter=True)
+
+ def unauthorized_token(self):
+ request = self.oauth_request(token=None, url=TWITTER_REQUEST_TOKEN_URL)
+ response = self.fetch_response(request)
+ return OAuthToken.from_string(response)
+
+ def access_token(self, token):
+ request = self.oauth_request(token, TWITTER_ACCESS_TOKEN_URL)
+ return OAuthToken.from_string(self.fetch_response(request))
+
+ def user_data(self, access_token):
+ request = self.oauth_request(access_token, TWITTER_CHECK_AUTH)
+ json = self.fetch_response(request)
+ try:
+ return simplejson.loads(json)
+ except simplejson.JSONDecodeError:
+ return None
+
+ def oauth_request(self, token, url):
+ request = OAuthRequest.from_consumer_and_token(self.consumer, token=token, http_url=url)
+ request.sign_request(OAuthSignatureMethod_HMAC_SHA1(), self.consumer, token)
+ return request
+
+ def fetch_response(self, request):
+ self.connection.request(request.http_method, request.to_url())
+ response = self.connection.getresponse()
+ return response.read()
+
+ @property
+ def connection(self):
+ conn = getattr(self, '_connection', None)
+ if conn is None:
+ conn = httplib.HTTPSConnection(TWITTER_SERVER)
+ setattr(self, '_connection', conn)
+ return conn
+
+ @property
+ def consumer(self):
+ cons = getattr(self, '_consumer', None)
+ if cons is None:
+ cons = OAuthConsumer(settings.TWITTER_CONSUMER_KEY,
+ settings.TWITTER_CONSUMER_SECRET)
+ setattr(self, '_consumer', cons)
+ return cons
+
+
+class FacebookAuth(BaseAuth):
+ def __init__(self, request, redirect):
+ super(FacebookAuth, self).__init__(request, redirect)
+ self.redirect_uri = self.request.build_absolute_uri(self.redirect)
+ if settings.DEBUG:
+ self.redirect_uri = self.redirect_uri.replace(':8000', '')
+
+ def auth_url(self):
+ """Returns redirect url"""
+ args = {'client_id': settings.FACEBOOK_APP_ID,
+ 'redirect_uri': self.redirect_uri}
+ return FACEBOOK_AUTHORIZATION_URL + '?' + urllib.urlencode(args)
+
+ def auth_complete(self):
+ """Returns user, might be logged in"""
+ if 'code' in self.request.GET:
+ args = {'client_id': settings.FACEBOOK_APP_ID,
+ 'redirect_uri': self.redirect_uri,
+ 'client_secret': settings.FACEBOOK_API_SECRET,
+ 'code': self.request.GET['code']}
+ url = FACEBOOK_ACCESS_TOKEN_URL + '?' + urllib.urlencode(args)
+ response = cgi.parse_qs(urllib.urlopen(url).read())
+
+ access_token = response['access_token'][0]
+ data = self.user_data(access_token)
+ if data is not None:
+ if 'error' in data:
+ raise ValueError, 'Authentication error'
+ data['access_token'] = access_token
+ return authenticate(response=data, facebook=True)
+ else:
+ raise ValueError, 'Authentication error'
+
+ def user_data(self, access_token):
+ params = {'access_token': access_token}
+ url = FACEBOOK_CHECK_AUTH + '?' + urllib.urlencode(params)
+ try:
+ return simplejson.load(urllib.urlopen(url))
+ except simplejson.JSONDecodeError:
+ return None
from openid.extensions import ax, sreg
from .base import SocialAuthBackend
-from .openid_auth import OLD_AX_ATTRS, AX_SCHEMA_ATTRS
+from .conf 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):
+class TwitterBackend(OAuthBackend):
"""Twitter OAuth authentication backend"""
name = 'twitter'
def authenticate(self, **kwargs):
if kwargs.pop('twitter', False):
- return super(TwitterOAuthBackend, self).authenticate(**kwargs)
+ return super(TwitterBackend, self).authenticate(**kwargs)
def get_user_details(self, response):
return {'email': '', # not supplied
'lastname': ''}
-class FacebookOAuthBackend(OAuthBackend):
+class FacebookBackend(OAuthBackend):
"""Facebook OAuth authentication backend"""
name = 'facebook'
def authenticate(self, **kwargs):
if kwargs.pop('facebook', False):
- return super(FacebookOAuthBackend, self).authenticate(**kwargs)
+ return super(FacebookBackend, self).authenticate(**kwargs)
def get_user_details(self, response):
return {'email': response.get('email', ''),
return response.identity_url
def get_user_details(self, response):
- values = {'email': None,
- 'username': None,
- 'fullname': None,
- 'firstname': None,
- 'lastname': None}
+ values = {'email': '',
+ 'username': '',
+ 'fullname': '',
+ 'firstname': '',
+ 'lastname': ''}
resp = sreg.SRegResponse.fromSuccessResponse(response)
if resp:
- values.update({'email': resp.get('email'),
- 'fullname': resp.get('fullname'),
- 'username': resp.get('nickname')})
+ values.update((name, resp.get(name) or values.get(name) or '')
+ for name in ('email', 'fullname', 'nickname'))
# Use Attribute Exchange attributes if provided
resp = ax.FetchResponse.fromSuccessResponse(response)
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', '')
+ fullname = values.get('fullname') or ''
+ firstname = values.get('firstname') or ''
+ lastname = values.get('lastname') or ''
if not fullname and firstname and lastname:
fullname = firstname + ' ' + lastname
# 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'
+TWITTER_SERVER = 'api.twitter.com'
+TWITTER_REQUEST_TOKEN_URL = 'https://%s/oauth/request_token' % TWITTER_SERVER
+TWITTER_ACCESS_TOKEN_URL = 'https://%s/oauth/access_token' % TWITTER_SERVER
+TWITTER_AUTHORIZATION_URL = 'http://%s/oauth/authorize' % TWITTER_SERVER
+TWITTER_CHECK_AUTH = 'https://twitter.com/account/verify_credentials.json'
+TWITTER_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
+FACEBOOK_SERVER = 'graph.facebook.com'
+FACEBOOK_AUTHORIZATION_URL = 'https://%s/oauth/authorize' % FACEBOOK_SERVER
+FACEBOOK_ACCESS_TOKEN_URL = 'https://%s/oauth/access_token' % FACEBOOK_SERVER
+FACEBOOK_CHECK_AUTH = 'https://%s/me' % FACEBOOK_SERVER
# OpenID configuration
OLD_AX_ATTRS = [
class UserSocialAuth(models.Model):
+ """Social Auth association model"""
user = models.ForeignKey(User)
provider = models.CharField(max_length=32)
uid = models.CharField(max_length=2048)
from django.conf.urls.defaults import patterns, url
-from .views import home, done, logout, auth, complete
+from .views import 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'),
)
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 django.contrib.auth import login, REDIRECT_FIELD_NAME
-from .twitter import TwitterOAuth
-from .facebook import FacebookOAuth
-from .openid_auth import OpenIDAuth, GoogleAuth, YahooAuth
+from .auth import TwitterAuth, FacebookAuth, OpenIdAuth, GoogleAuth, YahooAuth
BACKENDS = {
- 'twitter': TwitterOAuth,
- 'facebook': FacebookOAuth,
+ 'twitter': TwitterAuth,
+ 'facebook': FacebookAuth,
'google': GoogleAuth,
'yahoo': YahooAuth,
- 'openid': OpenIDAuth,
+ 'openid': OpenIdAuth,
}
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')