]> git.parisson.com Git - django-social-auth.git/commitdiff
Commiting missing files
authorMatías Aguirre <matiasaguirre@gmail.com>
Mon, 8 Nov 2010 10:08:13 +0000 (08:08 -0200)
committerMatías Aguirre <matiasaguirre@gmail.com>
Mon, 8 Nov 2010 10:08:13 +0000 (08:08 -0200)
.gitignore
README.rst
example/settings.py
example/urls.py
social_auth/auth.py [new file with mode: 0644]
social_auth/backends.py
social_auth/conf.py
social_auth/models.py
social_auth/urls.py
social_auth/views.py

index 5e2a46fa8bbefa31a81ad08dfb2aa3f4e056f3ed..f62485b8e7a1748855f9df00f953f19d0067e789 100644 (file)
@@ -1,3 +1,4 @@
 *.pyc
 .*.sw[po]
+test.db
 local_settings.py
index 289c41999f1daeaf8b6b963b4bd94a2c38528fe3..e396432e79c941768d3be79206ac228f28d519eb 100644 (file)
@@ -3,4 +3,52 @@ Django Social Auth
 
 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.
index 68f62f1cc43007fd025c00c2d98ffd6d56e0fc4a..69cbe16c8a6ef34f44493a5249d8e32185f37521 100644 (file)
@@ -87,14 +87,13 @@ INSTALLED_APPS = (
     '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',
 )
index 610e798cd195eef87812aac674ab664354494535..150a67665a4d0b4d4fe2ca8de977f500834dbf39 100644 (file)
@@ -1,12 +1,15 @@
 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)),
 )
diff --git a/social_auth/auth.py b/social_auth/auth.py
new file mode 100644 (file)
index 0000000..d5c73eb
--- /dev/null
@@ -0,0 +1,218 @@
+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
index 9511a05179d6c6434d032125ac7ddcbdc14caee5..21634d40fbcf33a207eef123ecb47b3570ea98a1 100644 (file)
@@ -1,25 +1,23 @@
 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
@@ -29,13 +27,13 @@ class TwitterOAuthBackend(OAuthBackend):
                 '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', ''),
@@ -58,17 +56,16 @@ class OpenIDBackend(SocialAuthBackend):
         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)
@@ -76,9 +73,9 @@ class OpenIDBackend(SocialAuthBackend):
             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
index ef13cb77e19288bac1ed05e3abd0d3e5f0c6a4f5..c1515de63fada119815771b9546797af2ec35127 100644 (file)
@@ -1,16 +1,16 @@
 # 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 = [
index 21baecb52527c57e0569a57530f5539ad675173e..106e60b01cff138e02b17258cec35da387ca6c2d 100644 (file)
@@ -3,6 +3,7 @@ from django.contrib.auth.models import User
 
 
 class UserSocialAuth(models.Model):
+    """Social Auth association model"""
     user = models.ForeignKey(User)
     provider = models.CharField(max_length=32)
     uid = models.CharField(max_length=2048)
index 1572f42a87c2d466c227966c150ca2b2b8a2f1dc..a7e131240680155e791995a160d8d3d6d5037519 100644 (file)
@@ -1,14 +1,9 @@
 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'), 
 )
index 1a3cc7b4eca01de31fc6657004a36af27b5ea3a9..6cf0e0062ec088ea474a99faadec3858815b6480 100644 (file)
@@ -2,20 +2,17 @@ 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 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,
 }
 
 
@@ -42,35 +39,3 @@ def complete(request, backend):
         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')